/* ============================================================================
   FERNDOWN JUNIOR CHESS — design tokens
   FJC-28. The single source of truth for colour, type, spacing, radius and
   shadow. Spec §8 repo tree places this at site/assets/css/tokens.css.

   ── PROVENANCE ─────────────────────────────────────────────────────────────
   Lifted from the two approved mockups, docs/html-mockups/*.html, which are
   GITIGNORED (they pair children's first names with photograph URLs). This
   file therefore has to stand on its own: a successor who never receives the
   mockups must still be able to read, verify and change the palette. Nothing
   here is copied from those files except values — no selectors tied to
   content, no page copy.

   Cross-check the colours with:  node tools/contrast-check.js --after

   ── VALUES ARE NOT VERBATIM, AND THAT WAS A DECISION ───────────────────────
   The mockups carry 36 distinct fixed font sizes, 16 radii, 29 rgba() values
   and 16 box-shadows. Much of that spread is accidental rather than designed:
   at the small end the type sizes ran 11.20, 11.52, 11.84, 12.16px — steps of
   a third of a pixel, which nobody can see and which no template could
   honour deliberately.

   Peter's decision on 27/07/2026 was to COLLAPSE values that differ by less
   than 1px and keep everything else exact. That takes type from 36 steps to
   17 and radii from 16 to 11, and the largest shift applied to any single
   value is under half a pixel. Deliberate distinctions survive; accidental
   ones do not. Each token below records what it absorbed, so the collapse can
   be audited or reversed rather than taken on trust.

   ── THE CONTRAST FIXES ARE ALREADY IN ──────────────────────────────────────
   FJC-26 measured this palette against WCAG 2.2 AA: 137 pairings, 23 failures.
   The corrected values are baked in here, NOT the mockups' originals. Where a
   token replaces a failing value the old one is named in the comment, so a
   reader who diffs against the mockups sees a reason rather than a discrepancy.

   ⚠️ --brass IS NOT A TEXT COLOUR ON LIGHT SURFACES, AT ANY SIZE. It reaches
   only 2.17:1 on buff and fails even the 3:1 large-text threshold, so setting
   bigger type does not rescue it. Use --brass-ink. This is the single easiest
   mistake to make with this palette.
   ============================================================================ */

/* ============================================================================
   WEBFONTS — SELF-HOSTED. FJC-29.
   ⚠️ NO @import, NO link to fonts.googleapis.com, NO reference to any host but
   this one. The mockups loaded these three from Google's CDN; production must
   not, because that discloses every visitor's IP to a third party on a site
   used by children (FR-D14), and ADR-003 already removed analytics for exactly
   that reason. Licences and provenance: assets/fonts/NOTICE.

   ⚠️ THESE @font-face RULES ARE WHAT MAKE --f-display/-body/-mono MEAN
   ANYTHING. Until 3 August 2026 the three families below were named in the
   stacks with NOTHING declaring them, so every visitor silently got system-ui
   and the design rendered in a different face on every platform. That is not a
   cosmetic footnote: it is why FJC-168's reflow check failed on CI and passed
   on Windows — the footer's min-content width is a function of the rendering
   font, and the rendering font was whatever the visitor happened to own.

   ⚠️⚠️ THESE FONTS COST 525ms OF LCP HEADROOM, AND THE NUMBER IS HERE SO THE
   NEXT PERSON DOES NOT HAVE TO REDISCOVER IT. Measured by the Performance gate
   on 3 August 2026, worst page of 21:

       before any webfont      LCP 1200-1260ms   margin ~740ms   CLS 0.000
       swap, opsz kept  90.0K  LCP 1935ms        margin   65ms   CLS 0.001
       swap, opsz pinned 59.1K LCP 1785ms        margin  215ms   CLS 0.001   <-- shipped
       optional, opsz pinned   LCP 1860ms        margin  140ms   CLS 0.000

   ⚠️ font-display is swap, and `optional` was TRIED AND REJECTED ON THE
   MEASUREMENT, not on taste. It made LCP WORSE — 1860ms against swap's 1785ms —
   because the fonts sit on the critical path either way and `optional` adds its
   own block period on top. It would also have cost first-time visitors the
   typeface entirely. Do not "optimise" by switching to it; that experiment has
   been run.

   ⚠️ CLS is 0.001 against FR-X13's 0.05 budget, so the swap costs essentially
   no layout shift and metric overrides (size-adjust, ascent-override) are not
   needed. If that ever changes, reach for those before reaching for optional.

   ⚠️⚠️ 215ms IS THE WHOLE REMAINING LCP MARGIN, AND TWO TICKETS WILL SPEND IT.
   The Performance gate's own closing note says these pages carry no photographs
   and no chess viewer, and that FJC-166's images and FJC-81's board will both
   move these numbers. Anything added to the critical path from here has 215ms
   to play with, not 740ms. If FJC-81 needs room, the first lever is this file:
   drop a preload, or narrow the subset range.
   ============================================================================ */

/* Variable, wght 600-800 — the only display weights the site renders.
   ⚠️ The opsz axis is PINNED at 32, and that is a performance decision reversed
   from the first attempt. Keeping the axis is typographically better — display
   type here is bimodal, an 18px cluster and 24-65px headings, exactly what
   optical sizing is for — but it costs 67.6 KB against 36.7 KB pinned, on a
   file that is preloaded and sits in front of the LCP element. That 30.9 KB was
   150ms of LCP. Restore the axis only if the LCP budget gains room. */
@font-face {
  font-family: 'Bricolage Grotesque';
  src: url('/assets/fonts/bricolage-grotesque-var.woff2') format('woff2-variations');
  font-weight: 600 800;
  font-style: normal;
  font-display: swap;
}

/* Variable, wght 400-700. Body copy — the most-rendered face on the site. */
@font-face {
  font-family: 'Hanken Grotesk';
  src: url('/assets/fonts/hanken-grotesk-var.woff2') format('woff2-variations');
  font-weight: 400 700;
  font-style: normal;
  font-display: swap;
}

/* Static. No variable axes upstream, so two files.
   ⚠️⚠️ SPACE MONO REGULAR IS PRELOADED AS OF FJC-199, AND IT WAS THE ONLY FACE
   THAT WAS NOT. Hanken and Bricolage were preloaded from the start; this one
   always arrived after first paint, so it always swapped in front of the reader.
   Nothing had noticed because no mono element had ever been near enough to a
   wrap boundary for the swap to change its LINE COUNT.
   ⭐⭐ Then FJC-199 put the breadcrumb on the reading rail, taking it from 379px
   to 349px at a 412px viewport — and measured, at 349px "HOME > LEARN CHESS >
   ENDGAMES" is ONE line in the system fallback and TWO in Space Mono, because
   Space Mono is the wider face. The crumb sits at the top of the page, so
   everything below it moved 58px when the font landed: CLS went 0.000 -> 0.136
   against FR-X13's 0.05, and the Performance category 98 -> 93.
   ⭐ Lighthouse named the cause itself — the `layout-shifts` audit reports
   "Web font loaded" against space-mono-regular.woff2 and space-mono-bold.woff2.
   ⛔ THE FIX IS THE FONT, NOT THE CRUMB. Narrowing one element was what exposed
   it, but every mono thing on this site is exposed the same way — the eyebrow,
   every label, every figcaption, the section-index heading, the PGN move text.
   Widening the crumb back would have restored the slack and left the trap set
   for the next element that lands near a boundary.
   ⭐ Measured on learn/endgames.html, the page that failed: CLS 0.136 -> 0.000,
   Performance 93 -> 98, and LCP 2177ms -> 2176ms — the preload costs nothing
   readable. 9.3 KB, against 59.1 KB already preloaded.
   ⚠️ The BOLD face is deliberately NOT preloaded. Lighthouse listed it too, but
   it is 9.4 KB in front of the LCP element for a face used only in small chips
   and table labels, and regular alone took the measured page to 0.000. If a bold
   mono element ever tips, measure before adding it — do not assume symmetry. */
@font-face {
  font-family: 'Space Mono';
  src: url('/assets/fonts/space-mono-regular.woff2') format('woff2');
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: 'Space Mono';
  src: url('/assets/fonts/space-mono-bold.woff2') format('woff2');
  font-weight: 700;
  font-style: normal;
  font-display: swap;
}

/* ⚠️ NO ITALIC FACE IS SHIPPED, AND THAT IS MEASURED RATHER THAN ASSUMED. The
   audit of all 21 pages found zero italic text nodes in any of the three
   families. A future <em> will get a synthesised oblique, which is acceptable
   and standard; shipping four more files against no current usage is not. If
   italics become common in posts, add the real faces — do not leave it to the
   synthesiser by default. */

:root {
  /* ── Brand greens ──────────────────────────────────────────────────────── */
  --pine: #204a36;              /* primary brand green; header, CTAs, links   */
  --pine-700: #193b2b;          /* deeper — gradient ends, mobile nav panel   */
  --pine-900: #102419;          /* darkest — facts strip, footer, PGN source  */
  --pine-tint: #e6efe9;         /* pale green panel on a light background     */

  /* ── Warm neutrals ─────────────────────────────────────────────────────── */
  --buff: #ebe2c9;              /* boxwood; also the board's light square     */
  --buff-lite: #f4eeda;         /* lighter warm — results foot, annotations   */
  --chalk: #f5f6f2;             /* page background, cool white                */
  --card: #ffffff;              /* raised surfaces                            */

  /* ⚠️ --card is written as `#fff` 25 times across the mockups. That single
     shorthand is a quarter of FJC-28's "no hard-coded colour" problem: it is
     not a different colour, it is this token spelled another way. */

  /* ── Ink ───────────────────────────────────────────────────────────────── */
  --ink: #1b241e;               /* primary text                               */
  --ink-soft: #4a544c;          /* secondary text                             */
  --ink-prose: #28322b;         /* article body copy — was an untokened literal */
  --ink-panel: #33413a;         /* callout and annotation copy — ditto        */

  /* ── Brass ─────────────────────────────────────────────────────────────── */
  --brass: #c6912f;             /* BACKGROUNDS AND DECORATION ONLY            */
  --brass-lite: #dcb259;        /* brighter gold — text on dark surfaces      */
  --brass-ink: #7d5c1e;         /* FJC-26 [A]. The text-safe gold. 4.75:1 on
                                   buff, its worst case; 6.14:1 on white.     */

  /* ── Lines and boundaries ──────────────────────────────────────────────── */
  --line: #e2e4dc;              /* decorative hairline — card edges, dividers */
  --line-strong: #77877a;       /* FJC-26 [B]. Where the border IS the control
                                   boundary and 1.4.11 applies at 3:1. Derived
                                   by lightening --ink-soft to the compliant
                                   edge, NOT by darkening --line, whose
                                   near-neutral hue is noise and swung olive
                                   when held.
                                   FJC-26 named five users. Live today: .tag,
                                   .postnav a and .pagin (FJC-34). `.share a`
                                   is gone — FJC-33 dropped the share row.
                                   `.controls button` and `.pgn-toggle` belong
                                   to the viewer in epic FJC-4 and do not exist
                                   yet. The measurement is unaffected; only the
                                   list of what it covers has moved.          */

  /* ── Focus ─────────────────────────────────────────────────────────────── */
  --focus-light: #9f7426;       /* FJC-26 [F]. Rings on light surfaces; brass
                                   manages only 2.17–2.80:1 there. WCAG 2.2
                                   2.4.13 is AAA, so this is above the AA bar
                                   rather than required by it.                */
  --focus-dark: #dcb259;        /* rings on pine and pine-900 — brass-lite    */

  /* ── Text on dark surfaces ─────────────────────────────────────────────── */
  --on-pine: #f4eeda;           /* default text on pine — buff-lite           */
  --on-pine-soft: #dbe5dd;      /* secondary copy on pine                     */
  --on-pine-lede: #e5ecdf;      /* hero lede                                  */
  --on-pine-meta: #cfdcd2;      /* post dates and similar metadata            */
  --on-pine-crumb: #c3d0c6;     /* breadcrumbs, footer body copy              */
  --on-pine-link: #cad6cc;      /* footer links                               */
  --on-pine-faint: #9fb0a4;     /* footer legal line — lowest on pine-900     */
  --on-pine-code: #d7e3d9;      /* PGN source text                            */
  --on-pine-fig: #e8efe7;       /* photo-placeholder filename                 */

  /* ── Board ─────────────────────────────────────────────────────────────── */
  --sq-light: #ebe2c9;          /* light square — same value as --buff        */
  --sq-dark: #4f7a5c;           /* dark square                                */
  --sq-frame: #1c4130;          /* board frame base                           */
  --sq-frame-lift: #2c5a41;     /* board frame gradient top                   */
  --coord-light: #55664c;       /* FJC-26 [D], was #5c6f52 at opacity .92.
                                   The opacity is dropped, not reduced.       */
  --coord-dark: #fcfbf8;        /* FJC-26 [E], was #e9e2cd at opacity .92.    */
  --sq-hl: #10251b;             /* FJC-26 [C]. The move highlight, as an inset
                                   ring. It MUST be dark: white is only
                                   1.35:1 against --sq-light, so no lightening
                                   of any hue can reach 3:1 against both
                                   squares. Replaces brass-lite at 42%, which
                                   measured 1.19:1 and was near-invisible —
                                   and ADR-005 makes this the mechanism by
                                   which a move is shown.                     */

  /* ── Pieces (ADR-006 renders these as Cburnett SVG, not glyphs) ────────── */
  --piece-light: #fbf7ec;
  --piece-dark: #141f17;

  /* ── Odds and ends that were untokened literals ────────────────────────── */
  --emblem-lo: #e3d3a4;         /* emblem / avatar radial-gradient outer stop */
  --foot-tint: #fcfcfa;         /* game-viewer footer, a hair off --card      */
  --alert: #b23b3b;             /* blunder marker in the move list            */
  --panel-edge-cool: #5b8d6d;   /* FJC-26 [J], was #cfe0d5 at 1.17:1          */
  --panel-edge-warm: #99813c;   /* FJC-26 [K], was #e6dcbf at 1.18:1          */

  /* ── Overlays and translucent borders ──────────────────────────────────── */
  /* Five of the seven rgba() bases in the mockups are tokens above, written
     out as decimal triplets — rgb(32,74,54) is --pine, rgb(16,36,25) is
     --pine-900. They are composed here rather than recomposed at each use, so
     there is no second copy of a colour to drift. */
  --brass-border: rgba(220, 178, 89, 0.70);   /* FJC-26 [G][H][M][N]. One value
                                   replacing four (.18/.40/.42/.55), all of
                                   which failed 1.4.11. 3.28:1 on pine.       */
  --brass-rule: rgba(220, 178, 89, 0.16);     /* purely decorative gridlines  */
  --white-wash: rgba(255, 255, 255, 0.05);    /* raised panel on pine         */
  --white-wash-hover: rgba(255, 255, 255, 0.06);
  --scrim-pine: rgba(32, 74, 54, 0.88);       /* over photographs             */
  --scrim-pine-deep: rgba(25, 59, 43, 0.91);

  /* ── Type ──────────────────────────────────────────────────────────────── */
  --f-display: 'Bricolage Grotesque', system-ui, sans-serif;
  --f-body: 'Hanken Grotesk', system-ui, sans-serif;
  --f-mono: 'Space Mono', ui-monospace, monospace;

  /* Fixed steps. rem is relative to the root, and the body's fluid size is set
     on <body> rather than <html>, so 1rem stays 16px throughout. The count in
     each comment is how many mockup declarations the step absorbed. */
  --fs-micro: 0.68rem;          /* 8 uses; absorbed 9.92 and 10.88px          */
  --fs-label: 0.72rem;          /* 27 uses — the dominant mono label size;
                                   absorbed 11.20, 11.52, 11.84 and 12.16px   */
  --fs-label-lg: 0.8rem;        /* 10 uses; absorbed 12.48–13.12px            */
  --fs-sm: 0.95rem;             /* 15 uses; absorbed 14.72–15.68px            */
  --fs-base: 1rem;              /* 6 uses; absorbed 16 and 16.8px             */
  --fs-prose: 1.12rem;          /* 9 uses; absorbed 17.28–18.24px             */
  --fs-md: 1.15rem;             /* 6 uses; absorbed 18.4–19.2px               */
  --fs-lg: 1.28rem;             /* 5 uses; absorbed 20–20.8px                 */
  --fs-xl: 1.35rem;             /* 3 uses                                     */
  --fs-2xl: 1.5rem;             /* 5 uses; absorbed 24 and 24.8px             */

  /* Display and decorative sizes. Single-use in the mockups, kept exact —
     collapsing a 51.2px number onto a 57.6px drop cap would be a design
     change, not a tidy-up. */
  --fs-display-sm: 1.6rem;
  --fs-glyph: 1.7rem;
  --fs-glyph-lg: 1.9rem;
  --fs-glyph-xl: 2.2rem;
  --fs-numeral: 3.2rem;
  --fs-dropcap: 3.6rem;
  --fs-knight: 4.5rem;

  /* Fluid steps, kept exactly as approved — a clamp() is a deliberate curve
     and there is nothing accidental to collapse. */
  --fs-body-fluid: clamp(1rem, 0.96rem + 0.2vw, 1.075rem);
  --fs-lede: clamp(1.05rem, 1rem + 0.35vw, 1.22rem);
  --fs-h2-prose: clamp(1.5rem, 1.3rem + 1vw, 2rem);
  --fs-h2-related: clamp(1.6rem, 1.4rem + 1vw, 2.2rem);
  --fs-h2: clamp(1.8rem, 1.4rem + 1.8vw, 2.75rem);
  /* ⚠️ Identical to --fs-h2 except at the top of the curve: 2.6rem against
     2.75rem, a 2.4px difference at full width. Above the 1px collapse
     tolerance, so it is NOT merged into --fs-h2 — the rule was to collapse
     only what cannot be seen, and 2.4px can be.
     Found late, and only because FJC-30 read the inline style= attributes:
     this value never appeared in either <style> block, so the extraction that
     built this file could not have seen it. If another stray turns up, it will
     be from the same blind spot. */
  --fs-h2-involve: clamp(1.8rem, 1.4rem + 1.8vw, 2.6rem);
  --fs-pull: clamp(1.35rem, 1.2rem + 1vw, 1.8rem);
  --fs-h1-article: clamp(2rem, 1.5rem + 3vw, 3.4rem);
  --fs-h1-hero: clamp(2.35rem, 1.6rem + 3.6vw, 4.05rem);

  --lh-tight: 1.05;             /* display headings                           */
  --lh-heading: 1.18;
  --lh-body: 1.62;
  --lh-prose: 1.72;
  --ls-tight: -0.02em;          /* display headings                           */
  --ls-mono: 0.12em;            /* uppercase mono labels                      */
  --ls-mono-wide: 0.22em;       /* the eyebrow                                */

  /* ── Layout and spacing ────────────────────────────────────────────────── */
  --wrap: 1120px;               /* content maximum                            */
  --measure: 680px;             /* article text column                        */
  --measure-wide: 940px;        /* lead figure, gallery, game viewer          */
  --gutter: 92%;                /* width:min(var(--wrap), var(--gutter))      */

  --sp-section: clamp(3.5rem, 3rem + 5vw, 6.25rem);
  --sp-section-sm: clamp(3rem, 2.5rem + 3vw, 4.5rem);
  --sp-hero-top: clamp(3rem, 2rem + 6vw, 5.5rem);
  --sp-hero-bottom: clamp(3.5rem, 3rem + 4vw, 5rem);
  --sp-masthead-top: clamp(2rem, 1.5rem + 3vw, 3.5rem);
  --sp-masthead-bottom: clamp(4.5rem, 4rem + 5vw, 7rem);
  --sp-grid-gap: clamp(2rem, 4vw, 4rem);

  /* ── Radii ─────────────────────────────────────────────────────────────── */
  --r-xs: 3px;                  /* absorbed 2px                               */
  --r-sm: 4px;
  --r-md: 6px;
  --r-lg: 8px;                  /* absorbed 9px                               */
  --r-xl: 10px;                 /* absorbed 11px                              */
  --r-2xl: 12px;
  --r-3xl: 14px;
  --r-4xl: 16px;                /* 12 uses — the default card radius          */
  --r-5xl: 18px;
  --r-6xl: 20px;
  --r-pill: 99px;
  --r-circle: 50%;

  /* ── Shadows ───────────────────────────────────────────────────────────── */
  /* The mockups carry 16 distinct box-shadows, but eight of them are one
     design intent at slightly different scales — the same soft downward lift
     in --pine-900 at .4 to .6 alpha. Collapsed to three steps. */
  --sh-card: 0 14px 30px -22px rgba(16, 36, 25, 0.40);
  --sh-lift: 0 18px 36px -26px rgba(16, 36, 25, 0.50);
  --sh-float: 0 26px 54px -30px rgba(16, 36, 25, 0.55);
  --sh-header: 0 10px 30px -14px rgba(0, 0, 0, 0.55);
  --sh-nav-panel: 0 20px 40px -18px rgba(0, 0, 0, 0.60);
  --sh-board: 0 2px 10px rgba(0, 0, 0, 0.35);
  --sh-board-frame: inset 0 0 0 1.5px var(--brass-border),
                    0 22px 44px -26px rgba(0, 0, 0, 0.60);
  --sh-emblem: inset 0 0 0 2px var(--brass), 0 3px 8px rgba(0, 0, 0, 0.30);

  /* ── Motion ────────────────────────────────────────────────────────────── */
  /* Every one of these is disabled wholesale under prefers-reduced-motion in
     the mockups. That rule belongs with the component CSS (FJC-30), not here,
     but the tokens must not be used in a way that assumes motion is safe. */
  --t-fast: 0.15s;
  --t-base: 0.18s;
  --t-slow: 0.3s;
  --ease: ease;
}
