/* ===========================================================================
   boards.css — the season standings band and the friends leaderboard strip.

   A COMPONENT SHEET, loaded after style.css so it can lean on the :root tokens
   defined there (--clr-*, --gutter, --font) exactly as visitbar.css and
   trialbanner.css already do. Nothing here redefines a token; everything here
   is scoped under .sendband, .sboard or .fstrip.

   It is a separate file rather than an addition to style.css for a second
   reason worth recording: style.css is where the home page's video work lives
   and it is edited often. Keeping a whole new feature's rules out of it means
   this change cannot collide with work in flight there, and the two can be
   reviewed apart.

   The site is dark-only — style.css declares color-scheme: dark on :root and
   there is no light theme to switch to — so these rules commit to one palette
   deliberately, and every colour is painted explicitly rather than inherited.
   =========================================================================== */

/* ---------------------------------------------------------------------------
   The band under the hero
   --------------------------------------------------------------------------- */
.sendband {
  padding-block: 5rem;
  background: var(--clr-bg);
}

/* The board is FIRST in the DOM. That is what gets the mobile ordering for
   free: stacked, it is already on top, with no `order` property anywhere and
   so nothing for a screen reader and a pair of eyes to disagree about. */
.sendband__inner {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: clamp(1.5rem, 4vw, 3rem);
  align-items: start;
}

/* One half missing — no clip this week, or no standings yet. */
.sendband__inner--solo { grid-template-columns: minmax(0, 1fr); }

@media (max-width: 980px) {
  .sendband__inner { grid-template-columns: minmax(0, 1fr); }
}

/* _member_of_week.html keeps its own <section class="motw py-20 bg-900"> and
   its own .container, because wrapping it rather than editing it is what left
   every decision in that partial where it was made. The cost is that it
   contributes a second lot of section padding, a second background and a
   second gutter inside a column that already has all three — so they are taken
   back off here, in the sheet that put it in a column.
   Specificity: .sendband .motw (0,2,0) beats .py-20 / .bg-900 (0,1,0). */
.sendband .motw {
  padding-block: 0;
  background: transparent;
}

.sendband .motw__inner {
  padding-inline: 0;
  /* Half a page is not two columns. Collapsing to one puts the clip above its
     own caption at every width, which is the arrangement style.css already
     switches to below 860px — so this is that layout, applied because the
     column is narrow rather than because the screen is. */
  grid-template-columns: minmax(0, 1fr);
  gap: 1.5rem;
  align-items: start;
}

/* ---------------------------------------------------------------------------
   Season standings
   --------------------------------------------------------------------------- */
.sboard {
  background: var(--clr-surface);
  border: 1px solid var(--clr-border);
  border-radius: 0.75rem;
  overflow: hidden;
}

.sboard__head {
  padding: 1.25rem 1.25rem 1rem;
  border-bottom: 1px solid var(--clr-border);
}

.sboard__eyebrow {
  margin: 0;
  font-size: 0.75rem;
  font-weight: 700;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--clr-teal-4);
}

.sboard__title {
  margin: 0.4rem 0 0;
  font-size: clamp(1.25rem, 2.4vw, 1.6rem);
  line-height: 1.15;
  color: var(--clr-text);
}

.sboard__when {
  margin: 0.4rem 0 0;
  font-size: 0.8125rem;
  color: var(--clr-muted);
}

.sboard__rows {
  list-style: none;
  margin: 0;
  padding: 0.25rem 0;
}

/* Five columns: rank, grade disc, name + line, weeks, points. minmax(0,1fr) on
   the third is what lets the line truncate instead of forcing the row wider
   than its column — a grid child's default min-width is auto, which ignores
   overflow settings and is the usual cause of a table that will not shrink. */
.sboard__row {
  display: grid;
  grid-template-columns: 1.5rem 1.5rem minmax(0, 1fr) auto auto;
  align-items: center;
  gap: 0.7rem;
  padding: 0.6rem 1.25rem;
  border-bottom: 1px solid rgb(55 65 81 / 0.5);
}

/* SIX when somebody on this board is flying a badge: the medallion takes a
   column of its own ahead of the rank. On the ROWS rather than on each row, so
   the whole board agrees — a per-row column would put the ranks on two
   different x depending on who happened to have picked a badge.

   2.5rem is the 40px coin exactly; the gap does the spacing. The row's left
   padding drops to .85rem to pay for most of the new column, which is what
   keeps the name and its line on the same measure they had before rather than
   handing the badge 3rem of the width the sentence was using. */
.sboard__rows--badged .sboard__row {
  grid-template-columns: 2.5rem 1.5rem 1.5rem minmax(0, 1fr) auto auto;
  padding-left: 0.85rem;
}

/* At phone width this row was ALREADY fighting before the badge arrived — the
   template's own note says 360px is where "🔥11" and "6/8" started touching, and
   at 360 the name wraps and the sentence truncates with or without a medallion.
   So the rule here is that the coin must not be paid for out of the name: the
   30px coin, the tighter gap and the narrower padding together give the name
   MORE room than it had with no badge column at all (measured in Chromium at
   360/390/412/430: 59.5 / 89.5 / 109.6 / 124.7px against a badge-less 54.3 /
   84.3 / 104.3 / 119.5). The row keeps its height at every width above 360.

   The padding comes off BOTH sides here, unlike the desktop rule. On a phone
   the board is edge-to-edge in a one-column band and 1.25rem of gutter inside a
   card that is already inset is width the sentence can use.

   560px rather than a round 600: below that the send band is already one column
   (see the 980px query), and 560 is where six columns stop fitting at 40px.
   The coin's own size lives in a second query further down this file — see the
   note there for why it cannot sit in this block. */
@media (max-width: 560px) {
  .sboard__rows--badged .sboard__row {
    grid-template-columns: 1.875rem 1.25rem 1.5rem minmax(0, 1fr) auto auto;
    gap: 0.45rem;
    padding-left: 0.55rem;
    padding-right: 0.55rem;
  }
}

.sboard__row:last-child { border-bottom: 0; }

.sboard__row.is-top {
  background: linear-gradient(90deg, rgb(20 184 166 / 0.09), transparent 70%);
}

.sboard__rank {
  font-size: 0.8125rem;
  font-variant-numeric: tabular-nums;
  text-align: right;
  color: var(--clr-muted);
}

.sboard__row.is-top .sboard__rank {
  color: var(--clr-teal-4);
  font-weight: 700;
}

/* The coloured disc under a problem, at type size. The colour comes from
   Route.GRADE_BANDS via an inline style — it is data, not design — and the
   inset ring is what keeps Black 7 visible on a dark panel and White 8 from
   glaring on it. */
.sboard__disc {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.5rem;
  height: 1.5rem;
  border-radius: 50%;
  font-size: 0.75rem;
  font-weight: 700;
  box-shadow: inset 0 0 0 1px rgb(243 244 246 / 0.28);
  flex: none;
}

.sboard__disc--sm {
  width: 1.125rem;
  height: 1.125rem;
  font-size: 0.625rem;
}

.sboard__main { min-width: 0; }

.sboard__name {
  display: block;
  font-size: 0.9375rem;
  font-weight: 600;
  color: var(--clr-text);
}

.sboard__line {
  display: block;
  font-size: 0.8125rem;
  color: var(--clr-muted);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* The half of the board that makes "most consistent" a fact rather than a
   claim. Without it the page asserts something the numbers do not show. */
.sboard__weeks {
  text-align: right;
  font-size: 0.6875rem;
  color: var(--clr-muted);
  line-height: 1.2;
}

.sboard__weeks b {
  display: block;
  font-size: 0.8125rem;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  color: var(--clr-muted-3);
}

.sboard__weeks.is-perfect b { color: var(--clr-teal-4); }

.sboard__pts {
  min-width: 3rem;
  text-align: right;
  font-size: 0.9375rem;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  color: var(--clr-text);
}

.sboard__pts span {
  display: block;
  font-size: 0.5625rem;
  font-weight: 500;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--clr-muted);
}

.sboard__perfect {
  padding: 0.85rem 1.25rem;
  border-top: 1px solid var(--clr-border);
  background: rgb(20 184 166 / 0.05);
}

.sboard__perfect-label {
  margin: 0;
  font-size: 0.6875rem;
  font-weight: 700;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--clr-teal-4);
}

.sboard__perfect ul {
  list-style: none;
  margin: 0.6rem 0 0;
  padding: 0;
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem;
}

.sboard__perfect li {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  padding: 0.2rem 0.6rem 0.2rem 0.3rem;
  border: 1px solid var(--clr-border);
  border-radius: 999px;
  background: var(--clr-bg-alt);
  font-size: 0.8125rem;
  color: var(--clr-muted-3);
}

.sboard__perfect-more {
  padding-inline: 0.6rem;
  color: var(--clr-muted);
}

/* ---------------------------------------------------------------------------
   Friends leaderboard strip
   --------------------------------------------------------------------------- */
.fstrip__head { margin-bottom: 2rem; }

.fstrip__eyebrow {
  margin: 0;
  font-size: 0.75rem;
  font-weight: 700;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--clr-teal-4);
}

.fstrip__head h2 {
  margin: 0.5rem 0 0;
  color: var(--clr-text);
}

.fstrip__sub {
  margin: 0.75rem 0 0;
  max-width: 52ch;
  color: var(--clr-muted);
}

.fstrip__grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 1rem;
}

@media (max-width: 900px) {
  .fstrip__grid { grid-template-columns: minmax(0, 1fr); }
}

.fcard {
  display: flex;
  flex-direction: column;
  gap: 0.85rem;
  padding: 1.1rem;
  background: var(--clr-surface);
  border: 1px solid var(--clr-border);
  border-radius: 0.75rem;
}

.fcard__head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 0.75rem;
}

.fcard__name {
  margin: 0;
  font-size: 1.0625rem;
  color: var(--clr-text);
}

.fcard__meta {
  margin: 0.2rem 0 0;
  font-size: 0.75rem;
  color: var(--clr-muted);
}

/* The game is named on every card, always: three formulas side by side only
   works if each one says which it is playing. One colour per game so the strip
   reads as three different contests rather than three copies of one. */
.fcard__game {
  flex: none;
  padding: 0.2rem 0.5rem;
  border-radius: 0.25rem;
  border: 1px solid;
  font-size: 0.625rem;
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  white-space: nowrap;
}

.fcard__game--level {
  color: var(--clr-teal-4);
  border-color: rgb(45 212 191 / 0.35);
  background: rgb(20 184 166 / 0.12);
}

.fcard__game--straight {
  color: var(--clr-orange-4);
  border-color: rgb(249 115 22 / 0.35);
  background: rgb(249 115 22 / 0.12);
}

.fcard__game--turnup {
  color: #d8b4fe;
  border-color: rgb(168 85 247 / 0.35);
  background: rgb(168 85 247 / 0.13);
}

.fcard__rows {
  list-style: none;
  margin: 0;
  padding: 0;
}

.fcard__rows li {
  display: grid;
  grid-template-columns: 1rem 1.125rem minmax(0, 1fr);
  align-items: center;
  gap: 0.55rem;
  padding: 0.35rem 0;
  border-bottom: 1px solid rgb(55 65 81 / 0.45);
  font-size: 0.875rem;
  color: var(--clr-muted-3);
}

.fcard__rows li:last-child { border-bottom: 0; }

.fcard__rows li.is-win {
  font-weight: 700;
  color: var(--clr-text);
}

.fcard__rank {
  font-size: 0.75rem;
  font-variant-numeric: tabular-nums;
  text-align: right;
  color: var(--clr-muted);
}

.fcard__rows li.is-win .fcard__rank { color: var(--clr-teal-4); }

.fcard__who {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.fcard__champ {
  margin: 0;
  padding-top: 0.7rem;
  border-top: 1px solid var(--clr-border);
  font-size: 0.75rem;
  color: var(--clr-muted);
}

/* ---- The flown badge, drawn ---------------------------------------------
   The badge a climber chose to fly (Member.display_badge, resolved through
   gym/emblems.py). It was an emoji on the name's baseline until 2026-08-30 and
   is now the real medallion in its own column, left of the rank —
   _season_board.html carries the argument for the move.

   SIZED BY TRANSFORM, NEVER BY --badge-size, which is the standing rule of
   badges.css: every rim, hexagon and orbit radius in that sheet is px against
   that token, so lowering it rebuilds the badge at a different shape instead of
   shrinking the one the member earned. So the token is PINNED here — 56px, the
   smallest size the sheet is tuned at, and pinned because badges.css raises it
   to 68px at 700px wide and 78px at 1080px for its tile grids, which on this
   row would grow the coin past the row it sits in as the window widened.
   --badge-orbit-r is NOT pinned alongside it here, and must not be: .bdg--coin
   now derives it from whatever --badge-size a caller pins. It was not always
   so, and the day it wasn't, "First in the book" flew its orbiting dot 11.5px
   outside a 40px coin, over the rank number.

   .72 of 56px is 40px, against a row that is about 61px tall (two text lines
   plus .6rem of padding each side): as close to the row's height as it can go
   while the row's own padding still reads as padding rather than as a coin
   touching the rules above and below it.

   TWO CLASSES, not one, and not because of the load order. badges.css declares
   --coin-scale: .62 on a bare .bdg--coin; index.html happens to load it before
   this sheet, so a single-class rule here would win TODAY and silently lose the
   day somebody reorders the two links — which is exactly how the portal's
   fly-set lost its size once already. .sboard__badge.bdg--coin outranks it
   wherever it loads, so the order stops being load-bearing. */
.sboard__badge.bdg--coin {
  --badge-size: 56px;
  --coin-scale: .72;
  justify-self: center;
}

/* Holds the column open on a row that flies nothing, so every rank sits on the
   same x. The whole column is absent when NOBODY on the board is flying one —
   see SeasonBoard.has_emblems — so this is never a gutter down an empty board. */
.sboard__badge--none { display: block; }

/* The phone half of the 560px query above, and it lives DOWN HERE rather than
   up there for a reason worth keeping: both rules are .sboard__badge.bdg--coin,
   so at equal specificity the later one in this file wins. Written beside the
   grid it belongs to, it lost to the base rule below it and the coin stayed
   40px on a 360px screen — a media query that read correctly and did nothing. */
@media (max-width: 560px) {
  .sboard__badge.bdg--coin { --coin-scale: .535; }  /* 30px */
}

/* The board and the line under it are one grid child — see _season_board.html
   for why a sibling would break the two-column band. */
.sboard-col {
  display: flex;
  flex-direction: column;
  gap: 0.85rem;
  min-width: 0;
}

/* How a member gets on the board, said under the board. Deliberately quiet:
   it is a caption on somebody else's leaderboard, not a banner, and the two
   informational variants must not look like a call to action. */
.sboard__invite {
  margin: 0;
  padding-inline: 0.25rem;
  font-size: 0.8125rem;
  line-height: 1.5;
  color: var(--clr-muted);
}

.sboard__invite a {
  color: var(--clr-teal-4);
  font-weight: 600;
}

.sboard__invite strong {
  color: var(--clr-muted-3);
  font-weight: 600;
}

/* The one variant that IS asking for something — a member who is not on the
   board yet. Given a panel so it reads as an offer rather than as small print,
   and left-barred in the accent so it is distinguishable at a glance from the
   two lines that merely explain. */
.sboard__invite--cta {
  padding: 0.7rem 0.9rem;
  border: 1px solid var(--clr-border);
  border-left: 3px solid var(--clr-teal);
  border-radius: 0.375rem;
  background: var(--clr-bg-alt);
  color: var(--clr-muted-3);
}

/* When this season runs. Above the week count and below the title, because the
   question a member has looking at a leaderboard is "when do I get a clean
   slate" — a week index answers how far through we are and not that. A season
   is a calendar quarter (gym/standings.py), so these are dates anybody can hold
   in their head. */
.sboard__season {
  margin: 0 0 0.15rem;
  font-size: 0.8125rem;
  font-weight: 600;
  color: var(--clr-teal-4);
  letter-spacing: 0.01em;
}
