/* ══════════════════════════════════════════════════════════════════════
   §ED440 · THE SHARED TOKEN LAYER — Phase A (commit 1)

   WHAT THIS IS. One file carrying the LANDING's design language
   (prototype/prototype-a2.css) into the app under the `--c-*` namespace
   ("c" for Culturily). It is the single source of truth for colour,
   elevation, radius, room, type floor and motion from here on.

   WHAT THIS IS NOT. It is not a restyle. Phase A ships ZERO visual
   change: nothing in the app reads a `--c-*` token yet. Every value
   below is inert until Phase B (briefing card) starts consuming it.
   That is deliberate and it is verifiable — see PROOF at the bottom.

   WHY A NEW NAMESPACE AND NOT AN OVERWRITE. The two sheets collide on
   names with DIFFERENT values. `--r-sm` is 10px on the landing and 6px
   in the app; `--r-md` is 18px vs 12px; `--r-lg` 26px vs 16px; `--r-xl`
   40px vs 24px. `--bg` is #F5F3EE vs #161616. Merging by name would
   have silently re-radiused and re-coloured all 31 pages the instant
   this file loaded. The namespace makes the migration explicit, one
   component at a time, with a diff you can read.

   HUE IS LOCKED AT 72°. Measured, not assumed:
     #CCFF00 (brand lime)       H = 72.00°  S 100%  L 50.0%
     #3D4D00 (landing olive)    H = 72.47°  S 100%  L 15.1%
     #586F00 (app light accent) H = 72.43°  S 100%  L 21.8%
   Three shades of ONE colour. Nothing below introduces a second accent
   hue, and dark mode is this same hue at a different lightness — never
   a second palette.

   THE NEUTRALS ARE WARM IN BOTH THEMES. Measured:
     landing light #F5F3EE  H 42.9°  S 25.9%
     landing dark  #100E0C  H 30.0°  S 14.3%
     app (retiring) #161616 / #F4F4F4  S 0% — achromatic graphite
   The paper feeling survives into dark. There is no #000 anywhere in
   this file and no achromatic ground.
   ══════════════════════════════════════════════════════════════════════ */


/* ══════════════════════════════════════════════════════════════════════
   1 · THEME-INVARIANT TOKENS
   Shape, room, measure, type floor, motion. These do not change between
   light and dark — only colour and elevation do.
   ══════════════════════════════════════════════════════════════════════ */
:root {

  /* ── SHAPE ────────────────────────────────────────────────────────
     Verbatim from the landing. Note these are LARGER than the app's
     current radii at every step; that softening is part of the
     identity, not an accident. */
  --c-r-sm:  10px;
  --c-r-md:  18px;
  --c-r-lg:  26px;
  --c-r-xl:  40px;
  --c-r-full: 9999px;

  /* ── ROOM — the hierarchy lever ───────────────────────────────────
     Verbatim from the landing. Space is a RANK, not a constant: the
     dominant beat on a screen gets --c-room-xl and nothing else does.
     This is orthogonal to the app's --sp-1..20 4px scale, which stays
     for component-internal padding. Room is for SECTION separation. */
  --c-room-xl: clamp(132px, 24vh, 280px);
  --c-room-lg: clamp(92px, 15vh, 176px);
  --c-room-md: clamp(60px, 10vh, 112px);
  --c-room-sm: clamp(34px, 5vh, 56px);
  --c-gutter:  clamp(20px, 5vw, 72px);

  /* ── MEASURE — reading columns, ranked ──────────────────────────── */
  --c-measure:        1080px;
  --c-measure-narrow:  820px;

  /* ── TYPE FLOOR (§ED306, carried) ─────────────────────────────────
     The audience skews middle-aged and older. 16px is the absolute
     floor for ANY string; 18px is the floor for reading prose. Both in
     rem so browser zoom and Dynamic Type actually move them.

     CONFLICT, STATED: the app's existing ladder goes BELOW this floor
     (--text-xs 0.75rem = 12px, --text-sm 0.875rem = 14px) and both are
     in live use. Phase A does not change them. Every component Phase B
     touches moves onto --c-t-micro or above. */
  --c-t-micro: 1rem;        /* 16px — labels, eyebrows, mono captions */
  --c-t-body:  1.125rem;    /* 18px — every reading string           */
  --c-lh-body: 1.7;

  /* ── TYPEFACES ────────────────────────────────────────────────────
     BLOCKED, NOT SHIPPED. The landing's display face is Nohemi. Nohemi
     lives at prototype/fonts/Nohemi-{Light,Regular,Medium}.woff2 and
     NOWHERE ELSE — assets/fonts/ has no copy — and netlify.toml:311
     force-404s /prototype/* in production. An app page pointing at
     /prototype/fonts/ would render Nohemi locally and fall back to
     -apple-system on the deploy: the worst possible failure, invisible
     to every local screenshot.

     So --c-f-display below resolves to the app's CURRENT display face
     (Fraunces, §ED430) and NOT to Nohemi. Moving the woff2 files into
     assets/fonts/ is asset work that belongs to §ED437 (ed437-a2-assets),
     which this pass is told to stay out of. When those files land under
     /assets/fonts/, this one declaration is the only edit needed. */
  --c-f-display: 'Fraunces', Georgia, 'Times New Roman', serif;
  --c-f-body:    'Hanken Grotesk', system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
  --c-f-mono:    ui-monospace, "SF Mono", SFMono-Regular, Menlo, "JetBrains Mono", Consolas, monospace;

  /* ── MOTION · DURATION BY ROLE ────────────────────────────────────
     Seven roles, each a named token. A component picks the token for
     what it IS doing, never a number. The default when a role is not
     obvious is --c-m-default.

       role            token            value   ceiling rule
       tap/press       --c-m-tap        100ms   80-120ms
       toggle/select   --c-m-toggle     130ms   100-150ms
       tab/section     --c-m-tab        180ms   150-200ms
       card expand     --c-m-expand     220ms   200-250ms
       page transition --c-m-page       320ms   280-350ms
       sheet present   --c-m-sheet      340ms   300-400ms
       celebration     --c-m-celebrate  640ms   500-800ms

     HARD CEILING: anything a reader does dozens of times per session
     caps at 200ms. That is --c-m-tap, --c-m-toggle and --c-m-tab. Only
     --c-m-celebrate is allowed past 400ms, and only once per day. */
  --c-m-tap:        100ms;
  --c-m-toggle:     130ms;
  --c-m-tab:        180ms;
  --c-m-expand:     220ms;
  --c-m-page:       320ms;
  --c-m-sheet:      340ms;
  --c-m-celebrate:  640ms;
  --c-m-default:    240ms;

  /* ── MOTION · EXIT DURATIONS (§ED440 item 6) ──────────────────────────
     Part 1 mechanic 3: exits run at ~70% of their entry. Symmetric timing
     feels sluggish because a thing LEAVING has nothing to be read — the
     reader has already moved on — while a thing ARRIVING has to be
     perceived. Phase C measured the app: there were no exit tokens at all
     and no consumer distinguished an entry from an exit, so every one of
     the seven was symmetric by construction.

     Each is its entry x 0.7, rounded to 5ms. They are literals, not
     calc(), because a calc() in a transition-duration slot cannot be read
     back by an audit as a resolved number and this pass verifies by
     computed style.

       entry              exit
       tap       100ms -> 70ms
       toggle    130ms -> 90ms
       tab       180ms -> 125ms
       expand    220ms -> 155ms
       page      320ms -> 225ms
       sheet     340ms -> 240ms
       default   240ms -> 170ms

     THERE IS NO --c-m-celebrate-out, DELIBERATELY. A celebration has no
     exit: it settles onto its end state and stays there. Adding one would
     invite something to animate the reader's own confirmed answer back
     off the screen. */
  --c-m-tap-out:     70ms;
  --c-m-toggle-out:  90ms;
  --c-m-tab-out:    125ms;
  --c-m-expand-out: 155ms;
  --c-m-page-out:   225ms;
  --c-m-sheet-out:  240ms;
  --c-m-default-out:170ms;

  /* ── MOTION · EASING BY ROLE ──────────────────────────────────────
     Four curves. Nothing may invent a fifth. */
  --c-e-standard: cubic-bezier(.4, 0, .2, 1);      /* presses, general  */
  --c-e-out:      cubic-bezier(0, 0, .2, 1);       /* arrivals          */
  --c-e-inout:    cubic-bezier(.4, 0, .2, 1);      /* swaps, tabs       */
  --c-e-spring:   cubic-bezier(.34, 1.56, .64, 1); /* CELEBRATION ONLY  */

  /* ── MOTION · AMPLITUDE ───────────────────────────────────────────
     Distances are tokens too, so "a small lift" means one thing. */
  --c-amp-sm:  4px;   /* hover lift, link nudge */
  --c-amp-md: 14px;   /* card entrance          */
  --c-amp-lg: 40px;   /* section reveal         */
  --c-stagger: 90ms;  /* one step between siblings */

  /* ── ICON ASSET · THE TICK ────────────────────────────────────────
     The landing's .p-opt-tick path (viewBox 24, stroke-width 2.4), inlined
     as a data-URI so it can be used as a CSS mask and therefore INHERIT the
     accent token rather than carrying a baked-in colour. Used by the answer
     celebration. Kept here, not in briefing.css, because a shared asset with
     a colour contract belongs to the token layer. */
  --c-tick: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2.4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M20 6 9 17l-5-5'/%3E%3C/svg%3E");

  /* ── MOTION · THE REDUCED-MOTION SWITCH ───────────────────────────
     See section 4. Components multiply their duration by this so the
     kill is inherited, never bolted on per-component. */
  --c-motion-ok: 1;
}


/* ══════════════════════════════════════════════════════════════════════
   2 · COLOUR + ELEVATION — LIGHT (the base)
   Warm paper. Every value verbatim from prototype-a2.css except the
   three marked RE-RULED, which failed WCAG AA and are fixed here.
   ══════════════════════════════════════════════════════════════════════ */
:root {
  /* Grounds — warm neutral, never graphite */
  --c-bg:        #F5F3EE;
  --c-bg-deep:   #EAE6DE;
  --c-surface:   #FFFDFA;
  --c-surface-2: #F0EDE6;

  /* Ink ladder. Measured on --c-bg:
       ink   16.87:1   ink-2  7.09:1   ink-3  5.22:1 */
  --c-ink:   #14120E;
  --c-ink-2: #56514A;
  /* RE-RULED from the landing's #8B857B, which measures 3.30:1 on --c-bg
     and 2.94:1 on --c-bg-deep — under AA, and under 3:1 it fails even
     the large-text exemption. The landing survives it because #8B857B
     only ever carries decorative strings there; in the app this tier
     carries timestamps, source lines and counts, which are body text.
     #6B6558 is the same warm hue, clears 4.65:1 on the worst ground. */
  --c-ink-3: #6B6558;

  /* Hairlines */
  --c-line:   rgba(20, 18, 14, .13);
  --c-line-2: rgba(20, 18, 14, .06);

  /* ── ACCENT — hue 72°, three tiers with three different jobs ──────
       --c-accent      the FILL. Full-brightness lime, buttons and bars.
       --c-accent-ink  the TEXT/GLYPH tier. Lime is unreadable as text
                       on paper, so text uses the olive at the same hue.
       --c-on-accent   the only readable ink ON the lime fill.
     Measured: olive on paper 8.38:1 · on-accent on lime 16.77:1 */
  --c-accent:      #CCFF00;
  --c-accent-ink:  #3D4D00;
  --c-on-accent:   #0A0C00;
  --c-accent-wash: rgba(61, 77, 0, .08);
  --c-accent-wash-strong: rgba(61, 77, 0, .14);
  --c-focus:       #14120E;

  /* ── THE BAND FILL (§ED440 F1 / item 5) ───────────────────────────
     The answer zone is the ONE licensed large-area lime surface
     (docs/DESIGN-INVARIANTS.md). It gets its OWN token rather than
     reading --c-accent, because a large FILL and a small BUTTON have
     different emission budgets and must be able to diverge in dark
     without dragging every lime button with them.
     LIGHT: the full brand lime, unchanged. */
  --c-band-fill:   #CCFF00;

  /* ── SEMANTIC ─────────────────────────────────────────────────────
     The landing models --danger ONLY; success/warning/info do not exist
     there, and the app's current set (#4ade80 / #fbbf24 / #60a5fa) is
     the cool SaaS ramp this pass is told to leave behind. Re-derived
     warm and AA-verified on all four light grounds. */
  --c-danger:  #B3241C;   /* landing value, 5.94:1 on bg */
  --c-success: #2A6135;   /* 5.90:1 worst ground */
  --c-warning: #7D5200;   /* 5.48:1 worst ground */
  --c-info:    #2A5599;   /* 5.90:1 worst ground */
  --c-danger-wash:  rgba(179, 36, 28, .10);
  --c-success-wash: rgba(42, 97, 53, .10);
  --c-warning-wash: rgba(125, 82, 0, .10);
  --c-info-wash:    rgba(42, 85, 153, .10);

  /* ── ELEVATION SCALE ──────────────────────────────────────────────
     Four levels. A surface picks a LEVEL, never a raw box-shadow.

       L0  page ground     flat, no border, no shadow
       L1  cards / rows    1px warm hairline + a 2px contact shadow
       L2  focused card    L1 + a 24px ambient lift, larger radius
       L3  sheets / modals stronger shadow + scrim, NO blur behind

     SHADOW COLOUR — DEPARTURE, STATED. The brief specifies
     rgba(23,24,26,·). That is a COOL near-neutral (it reads slightly
     blue against warm paper) and it is the graphite tint this pass is
     told to drop. The geometry and every alpha below are the brief's
     exactly; only the RGB is swapped to the landing's warm 20,18,14 so
     the shadow belongs to the same room as the paper. Say the word and
     it is a three-value edit back.

     BACKDROP BLUR is defined once, --c-blur-header, and is licensed for
     exactly one consumer: a sticky header over scrolling content.
     Nowhere else. There is no --c-blur-card and there will not be one. */
  --c-e0-shadow: none;
  --c-e0-border: none;

  --c-e1-border: 1px solid var(--c-line);
  --c-e1-shadow: 0 1px 2px rgba(20, 18, 14, .04);
  --c-e1-radius: var(--c-r-md);

  --c-e2-border: 1px solid var(--c-line);
  --c-e2-shadow: 0 1px 2px rgba(20, 18, 14, .04), 0 8px 24px rgba(20, 18, 14, .05);
  --c-e2-radius: var(--c-r-lg);

  --c-e3-border: 1px solid var(--c-line);
  --c-e3-shadow: 0 4px 12px rgba(20, 18, 14, .08), 0 30px 70px rgba(20, 18, 14, .16);
  --c-e3-radius: var(--c-r-lg);
  --c-e3-scrim:  rgba(20, 18, 14, .38);

  --c-blur-header: 18px;
  --c-glass-header: rgba(255, 253, 250, .72);
  --c-glass-header-line: rgba(255, 253, 250, .9);

  /* Skeleton — the shimmer ground for Phase B loading states. Warm. */
  --c-skeleton:       rgba(20, 18, 14, .07);
  --c-skeleton-sheen: rgba(255, 253, 250, .55);
}


/* ══════════════════════════════════════════════════════════════════════
   3 · COLOUR + ELEVATION — DARK
   THE SAME COLOURWAY AT INVERTED LIGHTNESS. Not a second palette.
   Hue stays 72° on the accent; the ground stays warm.

   THEME MECHANISM. The app switches on a CLASS (html.theme-light, set
   pre-paint by the head script in every page: web defaults light, the
   native shell defaults dark). The landing switches on an ATTRIBUTE
   (html[data-theme]). Both are honoured below so this file is correct
   whichever surface loads it, and the ORDER matters — the two selectors
   have equal specificity (0,1,1), so the light re-assertion must come
   last or html[data-theme="light"] would be caught by :not(.theme-light)
   and render dark.
   ══════════════════════════════════════════════════════════════════════ */
html:not(.theme-light),
html[data-theme="dark"] {
  /* NO `color-scheme` HERE, DELIBERATELY. It was in the first draft and
     it was wrong: `color-scheme` is declared NOWHERE in css/ today
     (verified), so adding it would change native scrollbar, form-control
     and autofill rendering on all 31 pages — a real visual change, which
     Phase A is not allowed to make. It belongs to whichever phase
     actually adopts this palette. */

  /* Warm dark, derived from the brand hue at very low lightness.
     #100E0C is H 30° S 14.3% — warm. Not #000, not graphite. */
  --c-bg:        #100E0C;
  --c-bg-deep:   #080706;
  --c-surface:   #191714;
  --c-surface-2: #211E1A;

  /* Ink ladder inverts. Measured on --c-bg:
       ink 17.24:1   ink-2 7.97:1   ink-3 5.33:1 */
  --c-ink:   #F5F2EC;
  --c-ink-2: #ACA69C;
  /* RE-RULED from the landing's #7A746A — 3.58:1 on --c-surface-2, under
     AA, the exact mirror of the light-theme failure above. #8C867B is
     the same warm hue and clears 4.59:1 on the worst ground. */
  --c-ink-3: #8C867B;

  --c-line:   rgba(255, 252, 245, .13);
  --c-line-2: rgba(255, 252, 245, .06);

  /* ── ACCENT — SAME HUE, DIALLED BACK ──────────────────────────────
     The landing sets --lime-ink to full #CCFF00 in dark. Measured on
     the warm dark grounds that is 16.39 / 15.22 / 14.12 / 17.13 : 1 —
     roughly four times the AA floor. At that separation lime stops
     reading as an accent and starts reading as a light source: it
     glares, it blooms on OLED, and it is the single loudest "developer
     tool" signal in the whole dark theme.

     #A8D400 is the same colour — H 72.45°, S 100% — at L 41.6% instead
     of 50%. Measured 11.08 / 10.29 / 9.55 / 11.62 : 1. Comfortably AAA,
     under the 12:1 line, and still unmistakably the brand lime.

     The FILL tier stays full #CCFF00. A lime button is a lit surface
     and is meant to be the brightest thing on the screen; the dial-back
     is a TEXT/GLYPH ruling only. --c-on-accent is unchanged, so the
     button's own contrast is untouched in both themes.

     Candidates measured, on --c-bg / --c-surface:
       #CCFF00  L 50.0%   16.39 / 15.22   as shipped — HOT
       #BFEF00  L 46.9%   14.27 / 13.26   still over 12
       #B4E000  L 43.9%   12.48 / 11.59   borderline
       #A8D400  L 41.6%   11.08 / 10.29   <- PROPOSED
       #9FC900  L 39.4%    9.93 /  9.22   starts reading olive-in-dark
     Overrule me with any row and it is a one-line change. */
  --c-accent:      #CCFF00;
  --c-accent-ink:  #A8D400;
  --c-on-accent:   #0A0C00;
  /* THE BAND FILL, DIALLED — Jake ruled P4b, §ED440 Phase C.
     MEASURED on the shipped dark band at 375: 305x124px, 33,375 pixels at
     exactly #CCFF00 (88.2% of the band), 19.7% of the answer-zone crop,
     16.19:1 on the ground. That is the same separation Phase A ruled
     "stops reading as an accent and starts reading as a light source" —
     at roughly four thousand times the pixel count of one glyph. It
     flared.
     #A8D400 is the SAME COLOUR (H 72.45, S 100%) at L 41.6 instead of
     50. Relative luminance 0.554 vs #CCFF00's 0.844 — 34% less emissive.
     THE LIP STAYS A FULL-BLEED LIP: only the lightness moves. The
     rejected alternative (P4) dissolved the fill into a low-alpha tint
     with a border, which de-flared by demoting the band to a bordered
     panel — softening the one element ruled untouchable.
     Ink on this fill stays --c-on-accent: 11.45:1, comfortably AAA. */
  --c-band-fill:   #A8D400;
  --c-accent-wash: rgba(168, 212, 0, .10);
  --c-accent-wash-strong: rgba(168, 212, 0, .18);
  --c-focus:       #A8D400;

  /* Semantic, inverted. AA-verified on all four dark grounds. */
  --c-danger:  #FF8A80;   /* landing value, 8.44:1 on bg */
  --c-success: #8ADCA4;   /* 10.16:1 worst ground */
  --c-warning: #EDC070;   /*  9.78:1 worst ground */
  --c-info:    #9AC2F0;   /*  8.97:1 worst ground */
  --c-danger-wash:  rgba(255, 138, 128, .12);
  --c-success-wash: rgba(138, 220, 164, .12);
  --c-warning-wash: rgba(237, 192, 112, .12);
  --c-info-wash:    rgba(154, 194, 240, .12);

  /* ELEVATION — in dark, a shadow alone cannot carry lift (black on
     near-black is invisible). The hairline does more of the work and
     the shadows deepen. Same four levels, same geometry, same radii. */
  --c-e1-shadow: 0 1px 2px rgba(0, 0, 0, .40);
  --c-e2-shadow: 0 1px 2px rgba(0, 0, 0, .40), 0 8px 24px rgba(0, 0, 0, .45);
  --c-e3-shadow: 0 6px 16px rgba(0, 0, 0, .55), 0 30px 70px rgba(0, 0, 0, .70);
  --c-e3-scrim:  rgba(6, 5, 4, .62);

  --c-glass-header: rgba(25, 23, 20, .72);
  --c-glass-header-line: rgba(255, 252, 245, .10);

  --c-skeleton:       rgba(255, 252, 245, .07);
  --c-skeleton-sheen: rgba(255, 252, 245, .10);
}

/* Light re-assertion. MUST stay last of the two — see the note above. */
html.theme-light,
html[data-theme="light"] {
  /* No `color-scheme` — see the note in the dark block. */

  --c-bg:        #F5F3EE;
  --c-bg-deep:   #EAE6DE;
  --c-surface:   #FFFDFA;
  --c-surface-2: #F0EDE6;

  --c-ink:   #14120E;
  --c-ink-2: #56514A;
  --c-ink-3: #6B6558;

  --c-line:   rgba(20, 18, 14, .13);
  --c-line-2: rgba(20, 18, 14, .06);

  --c-accent:      #CCFF00;
  --c-accent-ink:  #3D4D00;
  --c-on-accent:   #0A0C00;
  --c-band-fill:   #CCFF00;   /* light keeps the full fill — see the dark note */
  --c-accent-wash: rgba(61, 77, 0, .08);
  --c-accent-wash-strong: rgba(61, 77, 0, .14);
  --c-focus:       #14120E;

  --c-danger:  #B3241C;
  --c-success: #2A6135;
  --c-warning: #7D5200;
  --c-info:    #2A5599;
  --c-danger-wash:  rgba(179, 36, 28, .10);
  --c-success-wash: rgba(42, 97, 53, .10);
  --c-warning-wash: rgba(125, 82, 0, .10);
  --c-info-wash:    rgba(42, 85, 153, .10);

  --c-e1-shadow: 0 1px 2px rgba(20, 18, 14, .04);
  --c-e2-shadow: 0 1px 2px rgba(20, 18, 14, .04), 0 8px 24px rgba(20, 18, 14, .05);
  --c-e3-shadow: 0 4px 12px rgba(20, 18, 14, .08), 0 30px 70px rgba(20, 18, 14, .16);
  --c-e3-scrim:  rgba(20, 18, 14, .38);

  --c-glass-header: rgba(255, 253, 250, .72);
  --c-glass-header-line: rgba(255, 253, 250, .9);

  --c-skeleton:       rgba(20, 18, 14, .07);
  --c-skeleton-sheen: rgba(255, 253, 250, .55);
}


/* ══════════════════════════════════════════════════════════════════════
   4 · REDUCED MOTION — WCAG 2.1 SC 2.3.3
   Built into the token layer so every component Phase B and after
   builds INHERITS the kill for free. Nothing downstream re-declares a
   prefers-reduced-motion block; if it needs one, the token is wrong.

   WHAT IS DISABLED: transform-based motion, parallax, scroll-triggered
   animation, and every spring.
   WHAT SURVIVES: opacity fades, and anything whose end state conveys
   STATE rather than delight. Durations collapse toward zero but never
   to `none` — `none` would skip an animation's end state entirely and
   can leave content stuck invisible at opacity 0. This is a real bug
   class, already documented at css/global.css:1355 for .m-turn-in.

   NOTE ON THE EXISTING BLOCK. global.css:1390 already carries a hard
   universal kill (animation/transition-duration 0.01ms !important on
   *, plus zeroed delays). That block is CORRECT and is left alone —
   this section is not a replacement for it. What it adds is the two
   things a universal duration kill cannot do: null the transform
   AMPLITUDES so a reveal has nowhere to travel from, and give
   components a single multiplier (--c-motion-ok) they can read.
   ══════════════════════════════════════════════════════════════════════ */
@media (prefers-reduced-motion: reduce) {
  :root {
    /* Amplitudes go to zero — a reveal that travels 0px cannot be seen
       to travel even if some later sheet re-enables its duration. */
    --c-amp-sm: 0px;
    --c-amp-md: 0px;
    --c-amp-lg: 0px;
    --c-stagger: 0ms;

    /* The multiplier. A component writes
         transition-duration: calc(var(--c-m-expand) * var(--c-motion-ok));
       and inherits the kill with no media query of its own. */
    --c-motion-ok: 0.001;

    /* The spring flattens. A celebration under reduced motion still
       HAPPENS — it just arrives as a fade instead of an overshoot. */
    --c-e-spring: cubic-bezier(0, 0, .2, 1);
  }
  /* No `scroll-behavior: auto` here — global.css:1390's universal
     reduced-motion block already sets it `!important` on `*`, which
     matches html. A second copy would be dead weight and a drift
     surface, and it would break this file's one-line invariant below. */
}


/* ══════════════════════════════════════════════════════════════════════
   PROOF — WHY PHASE A IS A NO-OP ON SCREEN

   1. Every custom property declared here is prefixed --c-. Verified by
      execution: zero `--c-` references exist anywhere in css/ or js/
      outside this file, so nothing resolves any of them.
   2. THIS FILE DECLARES NOTHING BUT CUSTOM PROPERTIES. There is not one
      ordinary CSS declaration in it — no color-scheme, no
      scroll-behavior, no property that any element can render. A
      stylesheet that only defines unreferenced custom properties cannot
      change a pixel. That is the invariant, and it is greppable:
        grep -E '^\s+[a-z-]+\s*:' css/tokens.css | grep -v -- '--'
      must return NOTHING.
   3. Phase A's screenshot proof is a byte-identical PNG comparison of
      briefing.html with and without this file loaded, at two widths and
      both themes. Recorded in the §ED440 report.
   ══════════════════════════════════════════════════════════════════════ */


/* ══════════════════════════════════════════════════════════════════════
   5 · §ED440 PHASE E — GLASS, SPECULAR, GRAIN, LIGHT

   THE RULE THIS SECTION ENCODES, AND WHAT IT SUPERSEDES.
   Section 2 above says, verbatim: "there is no --c-blur-card and there
   will not be one." That sentence is now WRONG and it is superseded
   here rather than deleted, because its REASONING was right and still
   binds. What it was protecting against is a blurred CONTENT card — a
   fact, an answer zone, a band — and that is still refused.

   The replacement rule (Phase E, Part 3):

     GLASS BELONGS TO THE CHROME LAYER, NEVER THE CONTENT LAYER.

     CHROME  = anything FLOATING ABOVE content: the bottom nav, the
               sticky masthead, sheets, modals, the open culture
               selector, toasts, and the locked-state card.  -> glass
     CONTENT = the card, the fact, the answer zone, the band. -> OPAQUE

   Blurred chrome over sharp content is what makes iOS 26's material
   read as expensive. A blurred content card is a 2021 dribbble shot.
   So this section adds ONE glass recipe, and the thing it forbids is
   not "blur" — it is blur applied below the chrome plane.

   THE THIRD CASE, RULED SEPARATELY (Part 1 amendment): blur applied to
   WITHHELD content as a paywall device is authorised. That is not the
   reader's content being degraded; it is the shape of what they do not
   yet have. Blur on content the reader is SUPPOSED to read remains
   refused. See docs/DESIGN-INVARIANTS.md.

   THE SATURATE IS THE PART EVERYONE OMITS. A backdrop blur alone
   averages colour toward grey and reads as frosted plastic. Pushing
   saturation back up as the same filter pass is what makes glass look
   like a lit material rather than a translucent sheet. Values are
   tokens because they are TUNED PER THEME — see the measurement note
   on each below; light and dark do NOT ship at the same settings.
   ══════════════════════════════════════════════════════════════════════ */
:root {

  /* ── GLASS · GEOMETRY (theme-invariant) ───────────────────────────
     Radius and blur distance are shape, not colour, so they do not
     change between themes. Only the tint, the specular and the
     saturate do.

     --c-glass-blur is 20px per the Part 3 recipe. It is a token so the
     perf lever named in Part 3 ("over ~2ms difference, reduce blur
     radius first") is ONE edit and not a sweep. */
  --c-glass-blur:     20px;
  --c-glass-blur-sm:  14px;   /* toasts and small chrome — less area to fill */

  /* ── GLASS · SATURATE, TUNED PER THEME ────────────────────────────
     MEASURED, not guessed (scripts/audit-ed440-glass.js samples the
     composited pixel). The two themes need DIFFERENT values and the
     reason is the ground each one saturates:

       LIGHT ground #F5F3EE is S 25.9%. At the recipe's 180% that
       becomes S ~47% behind the glass — the paper turns visibly beige
       and reads as a STAIN under the nav rather than as a material.
       160% keeps the warmth and stops short of the cast.

       DARK ground #100E0C is S 14.3% and near-black; there is very
       little chroma to amplify, so 180% is safe and is what makes the
       warm dark read as warm at all through 20px of blur. Below ~170%
       the dark glass collapses toward the graphite this whole pass
       exists to remove. */
  --c-glass-sat: 160%;

  /* ── GLASS · THE FILTER, ASSEMBLED ────────────────────────────────
     One token so no consumer can ship blur without the saturate. Every
     glass surface writes `backdrop-filter: var(--c-glass-filter)` and
     the pairing is structurally guaranteed. */
  --c-glass-filter:    blur(var(--c-glass-blur))    saturate(var(--c-glass-sat));
  --c-glass-filter-sm: blur(var(--c-glass-blur-sm)) saturate(var(--c-glass-sat));

  /* ── THE ONE LIGHT SOURCE ─────────────────────────────────────────
     ABOVE AND SLIGHTLY IN FRONT. Every highlight and every shadow in
     the product agrees with this and there is no second light. One
     inconsistent element collapses the illusion — that is the whole
     difference between a lit interface and a decorated one.

     Consequences, stated so they are checkable:
       · highlights go on TOP edges          (--c-specular)
       · contact shadows go BELOW            (--c-e1/e2/e3-shadow, all +y)
       · the radial wash sits ABOVE the card (--c-light-wash, translate -22%)
       · no element carries a bottom highlight or a top shadow. */

  /* ── SPECULAR — THE 1px LIT TOP EDGE ──────────────────────────────
     Why this is the "3D pop" and not decoration: a real surface catches
     the light source at the edge that faces it. A 1px inner highlight
     on the top edge IS that catch, and it is the single cheapest signal
     that an element is a lit object rather than a drawn rectangle.

     THE LIGHT VALUE IS A DEPARTURE FROM THE BRIEF AND IT IS MEASURED.
     Part 3 specifies rgba(255,255,255,.18) light. On this palette that
     is INVISIBLE and would have shipped as a dead declaration: light
     glass composites to ~#FCFAF7, and a .18 white inset over it lands
     at ~#FCFBF8 — a delta of about 1/255, under the threshold of any
     display. The dark value (.10 over a near-black composite) reads
     fine and is kept exactly.

     So light gets its own settings, which is what Part 1 demands
     everywhere else too. Light's specular is near-opaque white, and it
     reads because the glass TINT below it is deliberately held off pure
     white (see --c-glass-chrome). Verified by pixel sample, not by eye. */
  --c-specular:       rgba(255, 255, 255, .90);
  /* The underside. A lit object has THICKNESS: the bottom inner edge
     falls away from the light and takes a warm dark line. Without it
     the element reads as a cut-out hole rather than a raised pane. */
  --c-specular-under: rgba(20, 18, 14, .05);
  /* Assembled, so a consumer cannot ship the highlight without the
     thickness and end up with a half-lit object. */
  --c-specular-edge: inset 0 1px 0 var(--c-specular),
                     inset 0 -1px 0 var(--c-specular-under);
  /* The weaker tier, for elements that are lit but are NOT the primary
     object on the screen — option rows, list items. Same light source,
     less catch. */
  --c-specular-soft:      rgba(255, 255, 255, .55);
  --c-specular-edge-soft: inset 0 1px 0 var(--c-specular-soft);

  /* ── GLASS · TINT (light) ─────────────────────────────────────────
     WARM, never neutral and never pure white. 66% alpha sits inside the
     Part 3 band (60-72%) and is chosen at the LOW end deliberately: it
     is what leaves the composite far enough below pure white for the
     specular above to have something to be brighter THAN. Push this to
     .72 and the specular stops reading; that coupling is why the two
     tokens are documented together. */
  --c-glass-chrome:      rgba(255, 253, 250, .66);
  --c-glass-chrome-line: rgba(255, 255, 255, .55);

  /* ── PAPER GRAIN (P1, adopted — GROUND ONLY) ──────────────────────
     Fractal noise as a data-URI. No image request, no runtime, no
     filter at paint time (the feTurbulence is rasterised once inside
     the SVG, it is NOT a live CSS filter — that distinction is the
     difference between free and a per-frame cost).

     P1 AS PROPOSED PUT GRAIN ON THE CARD TOO (.bf-daily::after). That
     half is REFUSED here: Part 3 rules grain to the ground only. Grain
     on a card fights the card's own material and, worse, grain on a
     chrome element sits UNDER the backdrop blur and turns to mud. The
     ground is the one surface with nothing above it to disturb. */
  --c-grain: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.82' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='140' height='140' filter='url(%23n)'/%3E%3C/svg%3E");
  --c-grain-opacity: .025;

  /* ── THE RADIAL LIGHT (P2, adopted verbatim) ──────────────────────
     ONE soft radial behind the primary card, brand hue, very low alpha.
     It is the visible consequence of the light source declared above —
     the card is lit from above-behind — and it is the reason the card
     has somewhere to sit rather than floating on a flat field.
     EXACTLY ONE ELEMENT IN THE APP carries this. */
  --c-light-wash: radial-gradient(50% 50% at 50% 50%,
                    rgba(204, 255, 0, .06) 0%,
                    rgba(204, 255, 0, .028) 45%,
                    rgba(204, 255, 0, 0) 72%);

  /* ── FOCUS RING (Part 5c) ─────────────────────────────────────────
     Designed, warm, on-palette, and NOT the browser default. Two parts:
     a solid ring in the ink colour and a soft wash outside it so the
     ring reads on both a light and a dark ground without changing
     colour. --c-focus already exists (section 2) and is the ring; these
     add the halo and the geometry so every consumer writes the same
     three lines. */
  --c-focus-width:  2px;
  --c-focus-offset: 3px;
  --c-focus-halo:   0 0 0 5px rgba(61, 77, 0, .18);
}


/* ── DARK OVERRIDES ──────────────────────────────────────────────────
   Same order as sections 2/3: dark first, light re-asserted after. */
html:not(.theme-light),
html[data-theme="dark"] {
  /* See the measurement note on --c-glass-sat above: dark needs MORE,
     because there is almost no chroma in #100E0C to bring back and
     under-saturating collapses it to graphite. */
  --c-glass-sat: 180%;

  /* The brief's dark specular, kept exactly — it reads as specified
     over a near-black composite. The underside goes to real black at a
     higher alpha: on a dark pane the "falling away" edge has further to
     fall before it is visible at all. */
  --c-specular:       rgba(255, 252, 245, .10);
  --c-specular-under: rgba(0, 0, 0, .22);
  --c-specular-soft:  rgba(255, 252, 245, .06);

  /* Warm dark glass. Alpha runs HIGHER than light (.72 vs .66) for the
     opposite reason: a dark translucent pane over a dark ground has
     very little luminance difference to work with, so it needs more of
     its own body to separate from what is behind it. */
  --c-glass-chrome:      rgba(25, 23, 20, .72);
  --c-glass-chrome-line: rgba(255, 252, 245, .10);

  /* Grain is LESS visible on a dark ground at the same alpha (the noise
     is mid-grey; against near-black it has more contrast to give, but
     the eye reads texture on paper more readily than on shadow).
     Measured up one step. */
  --c-grain-opacity: .03;

  /* Dark needs more wash to read at all — P2's own ruling, kept. */
  --c-light-wash: radial-gradient(50% 50% at 50% 50%,
                    rgba(204, 255, 0, .09) 0%,
                    rgba(204, 255, 0, .04) 45%,
                    rgba(204, 255, 0, 0) 72%);

  --c-focus-halo: 0 0 0 5px rgba(204, 255, 0, .22);
}

/* Light re-assertion. MUST stay last of the two — same reason as §3. */
html.theme-light,
html[data-theme="light"] {
  --c-glass-sat: 160%;

  --c-specular:       rgba(255, 255, 255, .90);
  --c-specular-under: rgba(20, 18, 14, .05);
  --c-specular-soft:  rgba(255, 255, 255, .55);

  --c-glass-chrome:      rgba(255, 253, 250, .66);
  --c-glass-chrome-line: rgba(255, 255, 255, .55);

  --c-grain-opacity: .025;

  --c-light-wash: radial-gradient(50% 50% at 50% 50%,
                    rgba(204, 255, 0, .06) 0%,
                    rgba(204, 255, 0, .028) 45%,
                    rgba(204, 255, 0, 0) 72%);

  --c-focus-halo: 0 0 0 5px rgba(61, 77, 0, .18);
}
