/* =========================================================================
   tokens.css — the one place every Kicksrv surface gets its values from.

   Three layers, borrowed from Material 3's token ARCHITECTURE and only that.
   M3's palette, its components and its tonal-elevation model are deliberately
   not adopted — see DESIGN.md §1 for why the identity stays ours.

     ref     raw values. The ONLY place in this repo a hex literal may appear.
             A skin overrides this block and nothing else.
     sys     semantic roles: what a value MEANS. Components use these, always.
     scale   radius / type / spacing steps, so "a bit rounder" stops being a
             free parameter every component answers on its own.

   Why this exists: the palette was copy-pasted into SEVEN :root blocks —
   climb.css, admin.css, and page-local <style> in auth.html, invite.html,
   landing.html, profile.html and site_home.html — carrying 23 / 21 / 15 / 15 /
   15 / 15 / 15 tokens respectively. Every shared name still agreed on its
   value, but coverage had drifted badly, and site_home.html had started writing
   `var(--divider,#16191f)` — inlining the value it was missing rather than
   fixing where it came from. That is where that road ends.

   static/bracket.css was already written against tokens only, with no :root and
   no hex of its own. It is the model this file generalises, and it needed no
   change at all.
   ========================================================================= */

/* -------------------------------------------------------------------------
   LAYER 0 — the faces themselves, served from this repo

   The three families used to come from fonts.googleapis.com, requested by a
   hand-written <link> in FIFTEEN templates. That cost a DNS lookup, a TLS
   handshake and a connection to somebody else's server before any text could
   render in the right face — and Google hands over one file per weight, so it
   was 333KB across twelve files.

   Self-hosting is what makes VARIABLE fonts possible, and that is the real
   win rather than the extra hop: one file carries the whole weight axis.
   Archivo goes from five files and 170KB to one file and 34KB; JetBrains Mono
   from three and 92KB to one and 39.5KB. Saira Condensed has no variable
   version published, so it stays two static cuts — 700 and 800, which are the
   only two anything uses.

   IT ALSO FIXES A BUG NOBODY HAD NOTICED. The stylesheets ask for Archivo 300
   and JetBrains Mono 600 and 800, and none of the three was ever requested
   from Google — so the browser was synthesising them, stretching a neighbouring
   weight into a shape the designer never drew. A variable font covers the axis,
   so those three render correctly for the first time.

   DECLARING A FAMILY COSTS NOTHING. A face is fetched when a rule MATCHES an
   element, not when it is declared — so every shell can load this one file and
   each page pulls only what it actually renders. That is why the per-shell font
   URLs are gone rather than merely trimmed: the browser does that sorting
   better than a hand-curated list, which had already drifted once.

   `latin-ext` is kept alongside `latin`. It is only fetched when a character
   needs it, and a club's member list is exactly where an accented name turns
   up.

   PROVENANCE AND LICENCE: static/fonts/README.md names the exact source URL
   for every file. All three families are SIL Open Font License 1.1, which
   permits redistribution and embedding on the condition that the licence
   travels with them — hence static/fonts/OFL-*.txt, which must not be deleted.
   ------------------------------------------------------------------------- */

@font-face {
  font-family: 'Archivo';
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url('fonts/archivo-var-100-900-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Archivo';
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url('fonts/archivo-var-100-900-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

@font-face {
  font-family: 'JetBrains Mono';
  font-style: normal;
  font-weight: 100 800;
  font-display: swap;
  src: url('fonts/jetbrainsmono-var-100-800-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'JetBrains Mono';
  font-style: normal;
  font-weight: 100 800;
  font-display: swap;
  src: url('fonts/jetbrainsmono-var-100-800-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

@font-face {
  font-family: 'Saira Condensed';
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url('fonts/sairacondensed-700-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Saira Condensed';
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url('fonts/sairacondensed-700-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'Saira Condensed';
  font-style: normal;
  font-weight: 800;
  font-display: swap;
  src: url('fonts/sairacondensed-800-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Saira Condensed';
  font-style: normal;
  font-weight: 800;
  font-display: swap;
  src: url('fonts/sairacondensed-800-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

/* Material Symbols, subset at the source to four codepoints — 1.7KB against
   the full axis's ~200KB. See static/fonts/README.md for the exact request.

   ALL FOUR ARE SPENT: expand_all and collapse_all on a section head,
   chevron_left on a page's back link, and keyboard_arrow_down on the Scout
   sheet's history fold — which is also its "Hide" chevron, rotated in CSS,
   because the subset has no up glyph. A fifth glyph means re-cutting this
   face (static/fonts/README.md) and adding it to the range below and to
   _icons.html; used without that, it renders as tofu.

   ⚠ APACHE-2.0, not SIL OFL like the other three families. The licence file
   beside it is named LICENSE-materialsymbols.txt and not OFL-*, and
   test_the_font_files_ship_with_their_licence carries the mapping, because
   naming an Apache file OFL-* would be a false statement about somebody else's
   terms rather than a tidy filename.

   font-display: BLOCK, and it is the one face here that does not swap. The
   glyphs are emitted as codepoints in the Private Use Area, so the fallback
   render is not a different typeface, it is a tofu box — and a control that
   flashes a rectangle is worse than one that appears a beat late. 1.7KB makes
   that beat very short.

   unicode-range pins it to the four: nothing else in the app may accidentally
   resolve through this face. */
@font-face {
  font-family: 'Material Symbols';
  font-style: normal;
  font-weight: 400;
  font-display: block;
  src: url('fonts/materialsymbols-var-subset.woff2') format('woff2');
  unicode-range: U+E313, U+E5CB, U+E944, U+E946;
}

/* -------------------------------------------------------------------------
   LAYER 1 — ref: raw values

   Named for what they ARE, never for what they are used for. Tone numbers are
   approximate lightness on a 0-100 scale, so a skin can keep every name while
   moving every value.
   ------------------------------------------------------------------------- */
:root {
  /* Neutral ramp, darkest first. Charcoal rather than the old near-black: the
     ramp starts at #0E0E10 and the card sits a full step above the page, which
     is what stops a dark UI reading as a void. */
  --ref-neutral-00: #0E0E10;
  --ref-neutral-05: #18181B;
  --ref-neutral-08: #1e1e22;
  --ref-neutral-12: #222227;
  --ref-neutral-16: #2A2A2F;
  --ref-neutral-20: #33333A;
  --ref-neutral-24: #3a3a40;
  --ref-neutral-30: #4b4b52;
  --ref-neutral-38: #5F5F68;
  --ref-neutral-46: #71717A;
  --ref-neutral-48: #7A7A83;
  --ref-neutral-56: #8B8B94;
  --ref-neutral-78: #C4C4CC;
  --ref-neutral-100: #ffffff;

  /* brand hues */
  /* The accent, from the Kicksrv wordmark. Renamed from --ref-green when the
     brand changed: a reference token is named for the colour it IS, and
     `--ref-green: #5EF7FD` would be a lie that every later reader has to
     decode. The SYS layer below (--acc) is what components name, so this
     rename touched four lines and nothing outside this file. */
  --ref-cyan:     #5EF7FD;
  --ref-cyan-ink: #04181A;   /* the only readable text on it */
  --ref-red:       #ff5a4d;
  /* WAS #ffb84d, retuned to the player-view artwork. Retuning rather than
     adding a fourth hue is deliberate: the artwork's orange is doing exactly
     the job --amb already names (caution / "there is a live challenge here"),
     and every --amb-* role below derives from it, so one value moves the whole
     set and no component or test changes. */
  --ref-amber:     #F39B2F;

  /* The player view's own surfaces. It stopped being a phone column of cards
     on the page and became a page of grouped data cards: a header strip, one
     row per fact, 2px gutters that let the page show through as hairlines. The
     row surface is one value, and it is not --surface — --surface is a card
     that sits ON the page, this is a row that sits IN a card. */
  --ref-panel:         #232929;
  /* --ref-cyan-soft #B0E9EB was here: the desaturated cyan the 2026-08 artwork
     used for the active nav pill and the "+N rungs" chip. 2026-08b took both —
     the pill inverted to dark slate with cyan ink (§5.1) and the chip became
     amber-or-grey by the §1.9 rule — so it had no consumers left. Deleted
     rather than kept as a documented-dead value: a ref token nothing points at
     is a colour the next person reaches for without knowing why it was
     abandoned. */
  /* The player view's two greys. The neutral ramp above is cool; these are
     the artwork's, which sit slightly green, and the difference is visible
     when a label and a value share a row. */
  --ref-grey-court:    #909795;
  --ref-grey-court-hi: #AAAAAA;

  /* --- 2026-08b: the artwork's second pass ------------------------------
     Measured out of design_handoff_kicksrv_artwork_2026-08b/artwork/*.svg,
     not estimated. The pass splits a job --acc had been doing twice: ORANGE
     ACTS, CYAN IDENTIFIES. See that bundle's README §1.

     The four dark surfaces, deepest first. The page moved DOWN and got a
     blue cast; the old page colour became the box a card's rows sit in, so
     the 2px gutters now show card ground rather than page. */
  --ref-navy-00:     #0D101F;   /* the player page ground */
  /* Measured out of the 2026-08 ADMIN artwork, not estimated — the same
     discipline as the block above. It is the navy ramp's second step, and it
     sits BELOW --ref-row rather than above it, which is the whole reason it
     exists: the admin card's header strip recesses under its rows where the
     player card's (--ref-panel, lighter than --ref-row) stands proud of them.
     Same component, inverted stack, and the drawings are deliberate about it.
     If the two ever want to agree, point --surface-card-head at --ref-panel
     and this value loses its consumer.

     NOT A color-mix, and the arithmetic is why. Mixing --ref-row into
     --ref-navy-00 to land on (21,27,39) wants 47% for R, 55% for G and 47%
     for B; a flat 50% gives #161A28, which is off by one in two channels. A
     mix with a hand-picked percentage is a literal wearing a derivation, and
     this file's own scale comment already says a formula with a free
     parameter is hand-picking with extra steps. A measured artwork surface
     belongs in LAYER 1. */
  --ref-navy-08:     #151B27;   /* the admin card's header strip */
  --ref-row:         #1E2430;   /* the row + footer surface inside a card */

  /* THE RESULT PAIR — a standings row you have beaten, and one you have lost
     to. Two new hues, and the reasons neither could be borrowed:

     --acc is cyan and means IDENTITY. On a ladder where your own row is
     already cyan, a cyan tint on other rows says "these are also you".

     --danger is red and means SOMETHING IS WRONG. Reusing it would style a
     match you lost as a validation error, which is the distinction DESIGN.md
     2 splits --neg from --danger to preserve.

     These are ROW SURFACES, never ink and never a fill behind text of their
     own, so they carry no --on-X and are correctly outside the HUES list that
     test_every_hue_has_a_full_role_set and the contrast test iterate. The only
     text over them is the row's own, which stays white on a 14% wash. */
  --ref-win:         #35C46B;   /* beaten them */
  --ref-fell:        #E5484D;   /* lost to them */

  /* The action hue. Two values because the fill and the inline text are not
     the same colour in the artwork: the fill is the darker one, and the
     lighter one is what reads as a link at 11-13px on a dark row.

     THE FILL IS NOT THE ARTWORK'S #F95E04, and this is the resolution of
     §3b / §11 item 1. The artwork draws every CTA white on #F95E04, which is
     3.17:1 and fails AA at the 13-14px the pills actually use. What shipped
     first kept that fill and used dark ink instead (5.8:1, resolution 2); the
     product owner looked at both and chose white ink, so the fill is the one
     that earns it.

     #CC4D03 is the artwork's own hue and saturation — 22 degrees, 0.968 —
     taken down in lightness until white clears AA at 4.54:1. It is NOT the
     handoff's suggested #D14A00, which the handoff claims measures 4.5:1 and
     actually measures 4.48 — close enough to read as passing in prose and not
     close enough to pass the test, which is the whole reason that test
     computes ratios instead of trusting sentences.

     --ref-orange-hi is UNCHANGED at the artwork's value: it is text rather
     than a fill, so it is not part of any --on-X pair, and it is also the last
     stop of --grad-name, which every player page's H1 wears. */
  --ref-orange:      #CC4D03;   /* primary action fill; white ink at 4.54:1 */
  --ref-orange-hi:   #FE793F;   /* inline/underlined orange, glyphs, gradient stop */

  /* Three cyans, because the artwork uses three and collapsing them to one
     loses the hierarchy they carry: a card's own name is brighter than a
     row's eyebrow label, and the nav's ink is a fourth thing again. */
  /* #98EDFF was --ref-cyan-title, "a card's own name". Deleted with
     --t-card-title when card heads went inert: cyan was marking BOTH "this is
     the card's name" and "this is tappable", so an inert heading read as a
     link. Card names are white now. Two cyans left, and they still differ —
     --ref-cyan-label is a live row's eyebrow, --ref-cyan-nav is the active
     nav's ink. */
  --ref-cyan-label:  #9AD9E6;   /* a live row's eyebrow label */
  --ref-cyan-nav:    #2EDDE3;   /* the active nav label's ink */
  --ref-slate-nav:   #4A5F60;   /* the active nav pill, at 89% */

  /* podium */
  --ref-gold:   #E8B923;
  --ref-silver: #AEB4BE;
  --ref-bronze: #C67C48;

  /* Type families. The fallbacks matter and were not consistent: five of the
     seven old copies fell back to generic sans / monospace rather than to
     Archivo and ui-monospace, which renders differently whenever the webfont is
     slow, blocked or absent. One definition, the better fallback. */
  --ref-family-sans: 'Archivo', system-ui, sans-serif;
  --ref-family-cond: 'Saira Condensed', 'Archivo', sans-serif;
  --ref-family-mono: 'JetBrains Mono', ui-monospace, monospace;
}

/* -------------------------------------------------------------------------
   LAYER 2 — sys: semantic roles

   These keep the names the app already uses. Renaming them to M3's
   primary/secondary/tertiary was considered and rejected: this product's roles
   are win / loss / challenge / caution, M3 offers no `success` or `warning`, and
   a LOSS here is neutral grey while an ERROR is red — so a single M3 role would
   have had to mean both `error` and `on-surface-variant`. M3's own answer to
   that is extended colors, which is the shape used below: every domain hue gets
   the same four slots, every time.

   `--surface-2` keeps its name rather than becoming `--surface-low`, because the
   relationship is not stable across palettes: it is LIGHTER than --surface here
   and was DARKER under the old one. It means "inset / chip surface", which is
   true either way.
   ------------------------------------------------------------------------- */
:root {
  /* --- surfaces --- */
  --screen:    var(--ref-neutral-00);   /* the page itself */
  --surface:   var(--ref-neutral-05);   /* cards, panels */
  --surface-2: var(--ref-neutral-12);   /* insets, wells, chips */
  --tile:      var(--ref-neutral-05);   /* stat tiles — same as surface */

  /* The player view's grouped-card surfaces. A --surface card is a thing ON
     the page with a border; a --surface-panel row is a thing IN a card, and
     the 2px of page between rows is the only divider it needs.

     The two variants are the row's states, and both are mixes rather than
     literals so a skin that moves --screen or --acc moves them with it:
       -dim  a finished event. Reads as over, not as disabled — the section
             head and the row label still say the word (see DESIGN.md §2).
       -win  the viewer's own row, and a won side of a bracket match. */
  /* 2026-08b §2 NARROWED --surface-panel to the HEADER STRIP. Everything else
     inside a card — every row, and the footer — is --surface-row, and the
     card itself is a --card-ground box with a hairline outline. Four
     surfaces where there were two:

       --screen-player   the page            #0D101F
         --card-ground   the card's box      #0E0E10  (and the 2px gutter)
           --surface-panel  the header strip #232929
           --surface-row    every other row  #1E2430

     --screen ITSELF DOES NOT MOVE. It is consumed by admin.css, climb.css,
     six page-local <style> blocks and five --on-* roles, none of which were
     redesigned; --screen-player is applied in player.css, which is loaded by
     _player_base.html and nothing else. */
  --screen-player:     var(--ref-navy-00);
  --card-ground:       var(--ref-neutral-00);
  /* The ADMIN grouped card's header strip, and the one place the two views'
     cards differ. The player card heads on --surface-panel, which is LIGHTER
     than the rows below it; the admin artwork heads on a value DARKER than
     them, so the title strip recedes and the rows carry the weight. Named for
     the slot rather than for the shade, so unifying the two later is a change
     of pointer here and nothing else. */
  --surface-card-head: var(--ref-navy-08);
  /* The artwork draws a 0.5px #AAAAAA stroke. 0.5px is not reliable across
     devices — this is the same rendered weight at a width that is. */
  --card-line:         color-mix(in srgb, var(--ref-grey-court-hi) 50%, transparent);

  --surface-panel:     var(--ref-panel);
  --surface-row:       var(--ref-row);
  /* The dim and win states, RENAMED from --surface-panel-*: in the new stack
     they apply to ROWS, not to the header strip, and a name that says `panel`
     while the value sits on a row is the drift this file exists to stop.

     The old names are still below, still consumed by player.css, and go in
     the commit that repoints those call sites. A rename split across two
     commits is a rename you can bisect; a rename that also moves twelve
     rendered colours in the same commit is not. */
  --surface-row-dim:   color-mix(in srgb, var(--screen-player) 45%, var(--surface-row));
  --surface-row-win:   color-mix(in srgb, var(--acc) 12%, var(--surface-row));
  /* YOUR OWN ROW ON THE STANDINGS, and it is WARM rather than cyan since the
     2026-08 frames. The rest of the player view says "this is you" in --acc,
     and that rule is untouched — the word "You" in the row's status cell is
     still --acc, and so is every other self-marker on every other screen.

     What changed is what the row's own ground has to do. The list is now
     centred on the reader: fifteen challengeable rows above their own, each
     carrying an --action pill, and a cyan wash under the one row with no pill
     read as a cooler version of the same family. A warm ground at a wash's
     strength separates the anchor from the fifteen offers above it without
     competing with any of them, which a tint of the accent could not.

     Deliberately weaker than --action-container (14% of the action hue over
     TRANSPARENT, which is the pill's fill): this sits under a whole row and
     has to stay quieter than the control inside it. */
  --surface-row-me:    color-mix(in srgb, var(--action) 13%, var(--surface-row));
  /* HOW THE LAST MEETING WENT — now a CHIP beside the record, not a wash
     behind the row and not the icon that replaced it. 2026-08c replaced
     `--surface-row-beaten` / `-lost` and their two -hi hover pairs with these,
     and the four old names are gone: a tint says something about the ROW, and
     this is a fact about the person in it. The 2026-08 frames then replaced
     the icons with words — "Prev: You won" — and these two came along, because
     the argument for the hue never depended on the shape carrying it.

     ⚠ NOT --acc AND NOT --neg, and both near misses are worth naming. --acc is
     "this is you" everywhere else in the player view, so a cyan chip beside
     ANOTHER player's name says the wrong thing outright. --neg is the muted
     grey that keeps a lost match from reading as a fault — right for your own
     downward trend, but it is not the same fact as "this specific opponent has
     your number", and a grey chip barely registers.

     --ref-win and --ref-fell were minted for this pair and their comments say
     so. The chip mixes them down for its ground and keeps them at full
     strength for its ink; the WORDS are the carrier either way, legible to a
     reader who sees neither hue, which the wash never was. */
  --mark-beaten: var(--ref-win);
  --mark-lost:   var(--ref-fell);
  --mark-beaten-container: color-mix(in srgb, var(--mark-beaten) 16%, transparent);
  --mark-lost-container:   color-mix(in srgb, var(--mark-lost) 16%, transparent);

  --surface-panel-dim: color-mix(in srgb, var(--screen) 45%, var(--surface-panel));
  --surface-panel-win: color-mix(in srgb, var(--acc) 12%, var(--surface-panel));

  /* --- outlines and dividers --- */
  --border:    var(--ref-neutral-16);   /* default hairline */
  --border-2:  var(--ref-neutral-20);   /* stronger / hover */
  --divider:   var(--ref-neutral-08);   /* row separators */
  --divider-2: var(--ref-neutral-12);   /* slightly stronger separator */

  /* --- text ramp, high to dim.
     All seven steps are kept. M3 offers roughly four neutral foreground roles;
     the extra resolution is used to rank three or four kinds of information
     inside one dense data row, and collapsing it would flatten those rows. --- */
  --t-hi:      var(--ref-neutral-100);
  --t-body:    var(--ref-neutral-78);
  --t-muted:   var(--ref-neutral-56);
  --t-muted-2: var(--ref-neutral-46);
  --t-dim:     var(--ref-neutral-38);
  --t-dim-2:   var(--ref-neutral-30);
  --t-dim-3:   var(--ref-neutral-24);

  /* The player view's two-step ramp, beside the seven above rather than
     inside them. A grouped row carries exactly two kinds of text — what the
     fact IS and what it SAYS — and borrowing --t-muted / --t-body for that
     would tie the row's contrast to decisions made for dense admin tables. */
  --t-court:    var(--ref-grey-court);      /* meta, labels, the as-of line */
  --t-court-hi: var(--ref-grey-court-hi);   /* the value in a row */

  /* Cyan AS TEXT — 2026-08b §1.1's other half. Once orange took the fills,
     what cyan is left saying is "this is a name / this is identity", and the
     artwork ranks that in two steps: a card's own title is brighter than a
     live row's eyebrow label. Named for the slot rather than the brightness,
     because the next artwork will move the values and not the slots. */
  --t-row-label:  var(--ref-cyan-label);

  /* --- domain hues, each an M3-style extended colour with four slots:
           --X            the colour itself
           --on-X         text/icon that sits ON it (a contrast promise)
           --X-wash       subtle tint for a whole row or card
           --X-container  stronger tint for a pill, badge or chip
           --X-line       border on anything tinted

     Three steps replace 40 improvised percentages across 85 call sites. The old
     values already clustered into exactly these three intents — 6-12% row
     washes, 14-22% chip fills, 26-55% borders — so this is a snap onto the
     nearest step rather than a new look. Within "border" the 26-to-55 spread was
     carrying no meaning at all; a tinted border is now one value.

     All three mix with `transparent` rather than with a surface, deliberately.
     `color-mix(C 10%, transparent)` composited over B resolves to
     0.1*C + 0.9*B — arithmetically identical to `color-mix(C 10%, B)` — so the
     transparent form is the same colour on a card AND correct on anything else,
     where the baked-in form would be subtly wrong. --- */

  /* win / positive / active — use sparingly, scarcity is the whole effect */
  --acc:           var(--ref-cyan);
  /* REQUIRED on any accent background (DESIGN.md). The accent is a LIGHT
     colour — white on it is unreadable, and always was: this promise survived
     the green-to-cyan brand change unchanged, which is the point of naming it
     rather than hardcoding a near-black at each call site. */
  --on-acc:        var(--ref-cyan-ink);
  --acc-wash:      color-mix(in srgb, var(--acc) 10%, transparent);
  --acc-container: color-mix(in srgb, var(--acc) 14%, transparent);
  --acc-line:      color-mix(in srgb, var(--acc) 40%, transparent);

  /* A LOST MATCH, a dropped rank, a downward trend. An outcome, not a fault, and
     therefore NEUTRAL GREY rather than red: losses are not punished. This is a
     product decision, not a palette detail — it is why --danger below had to be
     split out, so an invalid form stays red while a defeat does not. */
  --neg:           var(--ref-neutral-48);
  --on-neg:        var(--screen);   /* dark ink beats white on mid-grey */
  --neg-wash:      color-mix(in srgb, var(--neg) 10%, transparent);
  --neg-container: color-mix(in srgb, var(--neg) 14%, transparent);
  --neg-line:      color-mix(in srgb, var(--neg) 40%, transparent);

  /* SOMETHING IS WRONG, or is about to be destroyed: a validation error, a
     failed action, an overdue match, a delete control, bad ladder health. Stays
     red under every palette, because "your form is invalid" cannot be styled as
     encouragement. */
  --danger:        var(--ref-red);
  --on-danger:     var(--screen);
  --danger-wash:   color-mix(in srgb, var(--danger) 10%, transparent);
  --danger-container: color-mix(in srgb, var(--danger) 14%, transparent);
  --danger-line:   color-mix(in srgb, var(--danger) 40%, transparent);

  /* caution / lock / pending */
  --amb:           var(--ref-amber);
  --on-amb:        var(--screen);
  --amb-wash:      color-mix(in srgb, var(--amb) 10%, transparent);
  --amb-container: color-mix(in srgb, var(--amb) 14%, transparent);
  --amb-line:      color-mix(in srgb, var(--amb) 40%, transparent);

  /* Challenge. Unified with the accent rather than kept as a separate blue: one
     electric colour per screen is the whole premise, and a challenge IS the
     positive action. */
  --chal:           var(--ref-cyan);
  --on-chal:        var(--ref-cyan-ink);
  --chal-wash:      color-mix(in srgb, var(--chal) 10%, transparent);
  --chal-container: color-mix(in srgb, var(--chal) 14%, transparent);
  --chal-line:      color-mix(in srgb, var(--chal) 40%, transparent);

  /* THE PRIMARY ACTION — 2026-08b §1.1, and the reason this pass exists.
     --acc was carrying two jobs that pull apart: "the thing to press" and
     "this is a name". It keeps the second. Every FILLED control is this
     colour now; every cyan TEXT stays cyan. --amb still means caution and
     does not move again.

     A full role set, because test_every_hue_has_a_full_role_set iterates
     HUES and "action" is in it. --X without --on-X is a contrast promise
     nobody made.

     WHITE INK, which is the artwork's, on a fill dark enough to carry it —
     resolution 1 of the three the handoff offered (§3b / §11 item 1), chosen
     by the product owner after seeing it beside the alternatives.

     This replaces the dark ink that shipped first. That was resolution 2: it
     kept the artwork's exact #F95E04 and put --screen on it at 5.8:1, which
     passed and was legible but read heavier than the drawing. The trade runs
     the other way now — the ink is the artwork's and the fill moved instead.
     See --ref-orange for why #CC4D03 and not #D14A00. */
  --action:           var(--ref-orange);
  --on-action:        var(--t-hi);
  --action-wash:      color-mix(in srgb, var(--action) 10%, transparent);
  --action-container: color-mix(in srgb, var(--action) 14%, transparent);
  --action-line:      color-mix(in srgb, var(--action) 40%, transparent);

  /* The tertiary tier: orange as TEXT rather than as a fill — an underlined
     link, a 16px glyph, a stat cell's sub-caption when it goes somewhere.
     The lighter of the two oranges, because the fill value is too dark to
     read at 11px on a row. */
  --action-inline: var(--ref-orange-hi);

  /* The badge with no semantic colour at all — "archived", "n/a". Neutral so it
     reads as absence of state rather than as a state. */
  --neutral-container: color-mix(in srgb, var(--t-dim) 22%, transparent);

  /* Where you ARE — quieter than --acc on purpose. The accent marks the one
     thing per screen you want the eye to hit, and "the tab you are already on"
     is not that thing; a nav pill in full --acc competes with the CTA below it.

     ⚠ THE POLARITY FLIPPED IN 2026-08b §5.1. This was a LIGHT pill (#B0E9EB)
     with dark ink, and the comment here promised exactly that — "dark ink
     only, never white". The artwork makes it a DARK slate pill with cyan ink,
     which is a legitimate design change and the precise opposite of what was
     written. The sentence is rewritten with the value rather than left beside
     it, because a stale promise next to a live value is the drift this whole
     file exists to prevent.

     What survives is the RATIO, not the direction: #2EDDE3 on #4A5F60-at-89%
     over the player page measures ~4.9:1, and
     test_the_contrast_promises_are_measured_not_asserted composites and checks
     it rather than taking this paragraph's word for it. */
  --nav-active:    color-mix(in srgb, var(--ref-slate-nav) 89%, transparent);
  --on-nav-active: var(--ref-cyan-nav);

  /* ONE CHIP RULE, NO EXCEPTIONS — 2026-08b §1.9. Amber if the fact is about
     the viewer, grey if it is about anybody else. That replaces a chip colour
     chosen per component, which is how the same "+15" ended up meaning "your
     climb" on one screen and "their seed" on another. The reader learns one
     thing and it holds everywhere.

     --chip is kept as the generic, pointed at the "about you" fill: it is what
     shared.css and anything outside the player view still name, and repointing
     it is what takes --ref-cyan-soft's last consumer away. */
  --chip-you:   color-mix(in srgb, var(--amb) 40%, transparent);
  --chip-other: color-mix(in srgb, var(--ref-grey-court) 30%, transparent);
  --chip:       var(--chip-you);
  --on-chip:    var(--t-hi);

  /* THE ONE GRADIENT IN THE PRODUCT, and the second set of literals allowed
     outside the palette proper — the same exemption court.css's gold overview
     hero used to hold, and this replaces it.

     THE PAGE-TITLE TREATMENT, on every player page — 2026-08b §1.3.

     IT NO LONGER MEANS "THIS IS YOU". It was reserved for the signed-in
     player's own name, once per session-owner page, and it was scarce because
     scarcity was what made it mean that. The product owner retired both: it is
     now simply what a headline looks like at the top of a player page, on the
     player's own name in Home's greeting and on somebody else's name on their
     record page alike. Do not reintroduce identity reasoning here — the colour
     that says "you" is --acc, and it says it on the row and the label.

     So the failure to guard against flipped: it used to be "a second call site
     appeared" and it is now "a page lost it".

     Two call sites, and both are named in
     test_the_gradient_is_the_page_title_treatment: .phead-h1 on every player
     page, and .phead-name on Home's greeting — where the H1 itself opts OUT,
     so the gradient lands on the player's name and not on "Welcome back,".

     The stops moved with the pass: the old ones ended on --ref-amber, and
     these end on --ref-orange-hi, which is the action colour. */
  --grad-name: linear-gradient(90deg, #FFF4E7 0%, #E5A488 44%, #FE793F 93%);

  /* Podium. Only the top three ranks, and only on a leaderboard. */
  --gold:   var(--ref-gold);
  --silver: var(--ref-silver);
  --bronze: var(--ref-bronze);

  /* EVENT IDENTITY — the dot on each Feed card, level with its top line, so
     the A draw and the B draw read apart at a glance. Assigned by an event's
     position among the club's published tournaments
     (services/feed.EVENT_COLOURS), cycling.

     A DOT, NOT TEXT. These coloured the tournament name itself for one pass,
     and then sat beside a grey name in a header line; both made a label the
     loudest thing on the card. The card shows the dot alone now.

     THE NAME IS STILL THE WORD, just not on the card face. The filter chips
     pair each dot with the event's name as their key, and every card carries
     the name for screen readers, so nothing here is a colour that means
     something only to someone who can see it.

     Chosen AWAY from every hue that already means something on these pages:
     amber (you), orange (press this), green/red (beat/lost), gold/silver/bronze
     (podium). Cyan is the first because it already identifies things (§1.1).
     All four are light on --card-ground and --surface-row, well past 4.5:1. */
  --event-1: var(--ref-cyan);
  --event-2: #B69CFF;   /* violet */
  --event-3: #FF8FC8;   /* pink */
  --event-4: #7FB8FF;   /* sky */

  /* --- type families --- */
  --sans: var(--ref-family-sans);
  --cond: var(--ref-family-cond);
  --mono: var(--ref-family-mono);
}

/* -------------------------------------------------------------------------
   LAYER 3 — scales

   Declared here, applied progressively. Before this file the app used 12
   distinct border-radius values and 16+ distinct font sizes, including
   half-pixel steps (9.5 / 10.5 / 11.5 / 12.5 / 13.5px), while DESIGN.md
   described three radius bands. A component should pick a step, not a number.
   ------------------------------------------------------------------------- */
:root {
  /* Radius: 12 observed values collapse onto 5. Three of the five steps are the
     exact existing mode for their band (6px pills, 9px buttons, 12px rows,
     16px cards), so nothing moves more than 2px except the three stray 20px
     corners, which come down to 16. */
  --r-xs:   6px;    /* pills, badges, small chips  (was 4/5/6/7) */
  --r-s:    9px;    /* buttons, inputs            (was 8/9/10) */
  --r-m:   12px;    /* rows, tiles                (was 11/12/14) */
  --r-l:   16px;    /* cards, panels              (was 15/16/20) */
  --r-xl:  24px;    /* bottom sheet / modal — deliberately larger than a card */
  --r-full: 999px;  /* fully round */
  /* A sixth step, below --r-xs, and the one case where a smaller radius is not
     "a bit sharper" but a different object: a row INSIDE a grouped card, whose
     corners answer to the 2px gutter around it rather than to the card. --r-l
     still covers the card itself and the pill CTA — a 32px-tall pill with a
     16px radius is a pill. */
  --r-row:  4px;

  /* Type scale. Declared here, applied by the stylesheet rewrites rather than
     by a mechanical sweep: there are 16+ sizes today including half-pixel steps,
     and snapping a font size changes how text wraps. Each one gets chosen when
     its sheet is rewritten, not guessed at in bulk. */
  --fs-micro:    9px;   /* mono eyebrows, tiny tags */
  --fs-label:   11px;   /* labels, meta */
  --fs-body:    13px;   /* body copy, row text */
  --fs-lead:    15px;   /* emphasised row text */
  --fs-title:   18px;   /* panel and block titles */
  --fs-display: 30px;   /* page titles, big stats */
  /* The gradient page title at desktop width — 2026-08 admin artwork, which
     draws it at ~54px on a 1440 artboard. A STEP rather than a number in
     admin.css, because §4 of DESIGN.md is that a component picks a step; and
     its own step rather than --fs-hero, because 60px is reserved for the one
     big FIGURE on a screen and a title is not a figure. The phone value stays
     --fs-display. */
  --fs-display-2: 54px; /* the page title, desktop */
  --fs-hero:    60px;   /* the one number per screen */

  /* spacing, 4px step */
  --sp-1:  4px;
  --sp-2:  8px;
  --sp-3: 12px;
  --sp-4: 16px;
  --sp-5: 22px;
  --sp-6: 28px;
  /* Not a spacing step: the gap between two rows of a grouped card, where the
     page itself shows through and reads as a hairline. Named apart from the
     4px scale because it is a divider that happens to be made of space, and
     snapping it to --sp-1 would quadruple it. */
  --gutter-hair: 2px;

  /* layout */
  --maxw: 600px;   /* app column; widened at >=880px by climb.css */
}
