/* AvoLingo — design tokens.
   Built to design-system.md. Three things govern this file:

   §1  The root font size is fluid and EVERY token is expressed in rem, so type,
       spacing, radii, container width and nav height all scale continuously.
   §3  Colour is one ramp; components never name a hex, they read a theme slot.
   §6  Elevation is an eight-rung ladder; every variant declares all eight rungs
       so a shadow change interpolates instead of snapping.

   Single deliberate addition outside the ramp: electric blue, rationed to
   live state, active navigation, focus rings, and one metric. */

/* ============================================================
   §1 — Fluid root
   ------------------------------------------------------------
   `rem` inside a root font-size resolves against the initial 16px, not
   against itself, so these are stable:
     1024 → 16.72px    1440 → 18.05px    1920 → 19.58px
   ============================================================ */

html { font-size: 1.25rem; }
@media screen and (max-width: 1920px) { html { font-size: calc(0.84rem + 0.32vw); } }
@media screen and (max-width: 768px)  { html { font-size: calc(0.87rem + 0.26vw); } }

:root {

  /* ==========================================================
     §3.1 — The ramp. Twelve families, steps 50·100·200·400·600·800·1000.
     ========================================================== */

  --color--white: #ffffff;
  --color--black: #000000;

  --color--grey-50:   rgba(0, 31, 31, 0.04);
  --color--grey-100:  rgba(0, 31, 31, 0.08);
  --color--grey-200:  #c4cccc;
  --color--grey-400:  #889494;
  --color--grey-600:  #6a7878;
  --color--grey-800:  #455252;
  --color--grey-1000: #001f1f;

  --color--sand-50:   #faf9f7;
  --color--sand-100:  #f7f6f2;
  --color--sand-200:  #ebe7e1;
  --color--sand-400:  #d4cec4;
  --color--sand-600:  #b2a495;
  --color--sand-800:  #89796c;
  --color--sand-1000: #675a51;

  --color--lime-50:   #f8fbe7;
  --color--lime-100:  #eff5ce;
  --color--lime-200:  #dbeb7a;
  --color--lime-400:  #b3cc18;
  --color--lime-600:  #99ad15;
  --color--lime-800:  #7b8f00;
  --color--lime-1000: #515c0b;

  --color--green-50:   #eafce9;
  --color--green-100:  #cdface;
  --color--green-200:  #a9eba9;
  --color--green-400:  #6bb85f;
  --color--green-600:  #4a8f3f;
  --color--green-800:  #366625;
  --color--green-1000: #2a4e1c;

  --color--teal-50:   #e6f9ee;
  --color--teal-100:  #c4f5db;
  --color--teal-200:  #8bf0ba;
  --color--teal-400:  #33b88c;
  --color--teal-600:  #1d8f69;
  --color--teal-800:  #046645;
  --color--teal-1000: #0f4d38;

  --color--turquoise-50:   #def6f3;
  --color--turquoise-100:  #b1f0e7;
  --color--turquoise-200:  #51e0cd;
  --color--turquoise-400:  #00a3a3;
  --color--turquoise-600:  #007a7a;
  --color--turquoise-800:  #006666;
  --color--turquoise-1000: #003d3d;

  --color--blue-50:   #e4f7f9;
  --color--blue-100:  #bff1f5;
  --color--blue-200:  #67e2eb;
  --color--blue-400:  #0794b8;
  --color--blue-600:  #066899;
  --color--blue-800:  #05537a;
  --color--blue-1000: #003152;

  --color--purple-50:   #f0f0ff;
  --color--purple-100:  #e1e1fa;
  --color--purple-200:  #b8b8ff;
  --color--purple-400:  #7070ff;
  --color--purple-600:  #624ee5;
  --color--purple-800:  #4533b8;
  --color--purple-1000: #261d66;

  --color--violet-50:   #f8f0ff;
  --color--violet-100:  #ecdcfa;
  --color--violet-200:  #d9adff;
  --color--violet-400:  #ad67eb;
  --color--violet-600:  #953fe0;
  --color--violet-800:  #652ea3;
  --color--violet-1000: #3d1866;

  --color--pink-50:   #fcf2fe;
  --color--pink-100:  #fde5ff;
  --color--pink-200:  #fab8ff;
  --color--pink-400:  #e063c7;
  --color--pink-600:  #d64da9;
  --color--pink-800:  #b8337a;
  --color--pink-1000: #7a2251;

  --color--red-50:   #fceff1;
  --color--red-100:  #ffe0e2;
  --color--red-200:  #ffbdc1;
  --color--red-400:  #f55861;
  --color--red-600:  #d63c46;
  --color--red-800:  #b8252f;
  --color--red-1000: #851b22;

  --color--yellow-50:   #fffae6;
  --color--yellow-100:  #fff3c2;
  --color--yellow-200:  #ffdd99;
  --color--yellow-400:  #ffc247;
  --color--yellow-600:  #e09f2d;
  --color--yellow-800:  #cc8418;
  --color--yellow-1000: #a36a14;

  /* The one colour outside the ramp. Rationed — see bar.md §7. */
  --color--accent: #0a4cff;

  /* Brand green (set 2026-09-03) — a thirteenth family, built on the same
     50·100·200·400·600·800·1000 ladder as the rest of §3.1 so buttons can step
     within it instead of naming one-off hexes. Step 400 is the colour chosen by
     the client, #1ab878; every other step is that same hue (155.7°) relit. */
  --color--brand-50:   #ebf9f4;
  --color--brand-100:  #cef3e4;
  --color--brand-200:  #9ce8c9;
  --color--brand-400:  #1ab878;
  --color--brand-600:  #159662;
  --color--brand-800:  #0e744b;
  --color--brand-1000: #095033;

  /* ---- Refero's aliases into the ramp. Kept so nothing has to re-derive them,
          and so the frozen pages keep resolving. ---- */
  --forest-ink:   var(--color--grey-1000);
  --deep-forest:  var(--color--green-1000);
  --deep-teal:    var(--color--turquoise-1000);
  --meadow:       var(--color--green-100);
  --olive:        var(--color--lime-1000);
  --plum:         var(--color--pink-1000);
  --saffron:      var(--color--yellow-1000);
  --iris:         var(--color--violet-800);
  --electric:     var(--color--accent);

  --mint:         var(--color--blue-50);
  --lime:         var(--color--lime-50);
  --lavender:     var(--color--purple-100);
  --blush:        var(--color--pink-50);
  --buttercream:  var(--color--yellow-100);
  --sage-glow:    var(--color--lime-100);
  --petal:        var(--color--pink-100);
  --lilac-stroke: #dcc2eb;

  --parchment:   var(--color--sand-100);
  --paper-white: var(--color--white);
  --slate:       var(--color--grey-800);
  --lichen-gray: #5c7070;
  --stone:       var(--color--grey-600);
  --mist:        var(--color--grey-400);

  --hairline: var(--color--grey-100);

  /* ==========================================================
     §3.3 — Component theme slots.
     A component reads a slot; a section may re-theme its children by
     redefining the slot. No component names a hex.
     ========================================================== */

  --_theme---page--background:      var(--color--white);
  --_theme---page--wash:            var(--color--sand-50);
  --_theme---page--text:            var(--color--grey-1000);
  --_theme---page--text-muted:      var(--lichen-gray);

  --_theme---panels--background:    var(--color--sand-100);
  --_theme---panels--text:          var(--color--grey-1000);   /* client 2026-10-01: no brown (sand-1000) type */

  --_theme---cards--background:     var(--color--white);
  --_theme---cards--text:           var(--color--grey-800);
  --_theme---cards--heading:        var(--color--grey-1000);

  --_theme---pills--pill-background: var(--color--lime-100);
  --_theme---pills--pill-text:       var(--color--lime-1000);
  --_theme---pills--icon-background: var(--color--lime-200);
  --_theme---pills--pill-tag:        var(--color--lime-200);

  --_theme---tags--background:      var(--color--green-100);
  --_theme---tags--text:            var(--color--green-800);

  --_theme---icons--background:     var(--color--green-100);
  --_theme---icons--stroke:         var(--color--green-800);

  /* The moving gradient word (§8.4.1) — the three stops of its palindrome
     (1, 2, 3, 2, 1), measured verbatim from lattice.com/about: #00a3a3,
     #33b88c, #a9eba9, all already on the ramp. Stop 3 is below 3:1 on white
     by itself, so the slot is for display-size headings only. */
  --_theme---gradtext--stop-1:      var(--color--turquoise-400);
  --_theme---gradtext--stop-2:      var(--color--teal-400);
  --_theme---gradtext--stop-3:      var(--color--green-200);

  /* The per-clip play/pause control (§3.3), now used by #curriculum's looping
     tile. (The #needs scene-label scrim and blur that sat here went with the
     old portrait scene row; #needs is now the card carousel below.) */
  --_theme---scene-toggle--background: color-mix(in srgb, var(--color--white) 90%, transparent);
  --_theme---scene-toggle--icon:       var(--color--grey-1000);

  /* #needs card carousel (§3.3) — bar-needs-carousel.md. User direction
     2026-09-27 (after lattice.com/platform/performance, "Trusted by top
     performers"; the section stays on the white page): every card is a
     DIFFERENT pastel, and each card's inks come from its own family (§3.2) —
     the family's 50 as the fill, 1000 for the title and link, 800 for the
     body. The card CTA is Lattice's white pill. All pairs clear AA at their
     size (the 800s on their 50s are ≥ 6:1, the 1000s ≥ 10:1). */
  --_theme---needs--individual-surface: var(--color--green-50);
  --_theme---needs--individual-heading: var(--color--green-1000);
  --_theme---needs--individual-text:    var(--color--green-800);
  --_theme---needs--k12-surface:        var(--color--turquoise-50);
  --_theme---needs--k12-heading:        var(--color--turquoise-1000);
  --_theme---needs--k12-text:           var(--color--turquoise-800);
  --_theme---needs--university-surface: var(--color--blue-50);
  --_theme---needs--university-heading: var(--color--blue-1000);
  --_theme---needs--university-text:    var(--color--blue-800);
  --_theme---needs--corporate-surface:  var(--color--violet-50);
  --_theme---needs--corporate-heading:  var(--color--violet-1000);
  --_theme---needs--corporate-text:     var(--color--violet-800);
  --_theme---needs--button:        color-mix(in srgb, var(--color--white) 80%, transparent);
  --_theme---needs--button-text:   var(--color--grey-1000);
  --_theme---needs--dot:           var(--color--grey-400);
  --_theme---needs--dot-fill:      var(--color--brand-1000);
  /* The practice rows over each card's video (bar-needs-overlay.md): white
     live text straight on the footage. NO scrim (user ruling 2026-09-27):
     the rows are placed in each clip's face-free, darker band instead.
     Plain white glyphs, no text-shadow either (user ruling 2026-09-28: the
     shadow looked off). (The card has no outline any more; the 8% hairline
     token that sat here went with it.) */
  --_theme---needs--say-text:  var(--color--white);
  /* #belief, the conversation tile (user, 2026-09-28): the phone recording
     stands on Lattice's lavender-to-mint wash (the user's reference image,
     sampled top #dcdcfa -> bottom #b1f0e6, a straight vertical ramp), which
     is the house purple-100 -> turquoise-100 pair. */
  --_theme---belief--wash-top:    var(--color--purple-100);
  --_theme---belief--wash-bottom: var(--color--turquoise-100);
  /* solutions/business.html (css/business.css): the same lavender-to-mint
     wash for its product stages; .bz-stage--* variants re-theme it. */
  --_theme---biz--wash-top:    var(--color--purple-100);
  --_theme---biz--wash-bottom: var(--color--turquoise-100);

  /* Homepage hero carousel (§3.3) — the even darkening over each photograph,
     plus the neutrals its arrows and dots read.

     USER RULINGS, 2026-09-26. (1) The photographs are the originals with a
     local "Portra" film grade baked in (tone and colour only; not one pixel of
     content redrawn — assets/hero/candidates/film/styles.py; the pre-grade
     files are kept in assets/hero/_pre-portra/). (2) The darkening covers the
     full frame, in pure black. (3) It stays UNDER 50% on every slide.

     HOW THE NUMBERS WERE MEASURED. For each graded photograph: the band the
     glyphs can occupy (full width, y 4-96%, the union over every viewport),
     drawn at ~1241px, box-blurred 9x9 — the region one headline stroke sits
     on. White against the brightest such region, the brightest single pixel,
     and the band's mean:

       slide        brightest region   strength   region   pixel    mean
       individual   (see below)           40%     measured on the glyph areas
       k12          (248,239,227)         49%     4.26:1   4.08:1   9.14:1
       university   (255,255,241)         49%     3.87:1   3.85:1  11.64:1
       business     (255,249,234)         49%     4.00:1   3.93:1   8.67:1

     INDIVIDUAL, REPLACED 2026-09-26 (user pick: candidates/v8/i3-balcony,
     Paris balcony). Widened to the left with Nano Banana Pro so she sits in
     the right third and her phone clears the copy; her own pixels were then
     composited back from the original. 2026-09-27: widened again to the
     right and bottom so she could be enlarged ~8% and her head raised to the
     upper third without the phone moving into the copy
     (candidates/outpaint2/composite2.png, crop final2-16x7.png). Measured on the live glyph rects at 1440
     (title lines + body line, 3x3 blur): at 40% every title pixel clears 3:1
     and every body pixel clears 4.5:1; at 35%, 2.9% of body pixels dip.

     K12, REPLACED 2026-09-27 (user pick: candidates/k12intl/a-commons,
     international-school learning commons). Widened to the left with Nano
     Banana Pro (the original's dark left-edge strip was regenerated so no
     seam shows) and the three children composited back from the original
     (candidates/outpaint-k12b/composite3.png, crop final-16x7.png, eyes on
     the upper third line). The new left side is bright windows, so at 49%
     every title pixel clears 3:1 while ~5% of body-line pixels dip under
     4.5:1; clearing all of them would need more than the 50% ceiling.

     UNIVERSITY, REPLACED 2026-09-27 (user pick: candidates/v10/u3-nursing).
     Recomposed wide with Nano Banana Pro, hospital beds removed, her figure
     composited back from the original. User then chose the ORIGINAL tones —
     no relight, no colour grade (candidates/outpaint-uni/original-crop-600.png).
     That photo is bright, so it takes a wash; the user set it to 35%. At 35%
     ~9% of title pixels dip under 3:1 and ~7.5% of body-line pixels under
     4.5:1 where the copy crosses the sunlit windows (at 0%, 69% of title
     pixels failed; 40% cleared every title pixel).

     K12, REPLACED 2026-09-27 (user pick: candidates/k12intl/bench-s3,
     international-school corridor). Widened to the left with Nano Banana Pro
     under a strict keep-the-colour-grade prompt, then the original children
     composited back so their colours match the source exactly
     (candidates/outpaint-k12/k12-wide-keep.png, crop final-keep-16x7.png).
     2026-09-27: user asked for more saturation, less grey haze, a more
     natural-daylight look and less darkness — but NOT redder. The crop is
     white-balanced so the walls and floor read neutral grey, de-hazed
     (common black point), mid-tones lifted (gamma 0.90) and saturation
     raised 20% except on red/orange/skin hues (5%) (final-keep-graded.png).
     Wash 42%: every title pixel clears 3:1; 8% of body-line pixels dip
     under 4.5:1 (min 3.8) — accepted by the user's ruling. (A film/vintage
     grade was tried and rolled back at the user's request, 2026-09-27.)

     WHAT THE UNDER-50% RULING COSTS, stated so no one re-derives it: every
     headline clears WCAG's 3:1 for large text everywhere. Body copy clears
     4.5:1 on average by a wide margin, but where a line crosses the brightest
     patch of k12, university or business it dips to 3.9-4.3:1. Individual
     clears 4.5:1 even there. Reaching 4.5:1 at the peaks would need 51-54%.

     INK IS PLAIN WHITE for both the heading and the body (the grey family has
     no light step that works; a translucent white would let the ratio move
     with every pixel underneath). The darkening is pure black: grey-1000 is a
     teal-black and tinted the whole photograph green. */
  --_theme---hero-scrim--ink:        var(--color--black);
  --_theme---hero-scrim--individual: 40%;
  --_theme---hero-scrim--k12:        42%;
  --_theme---hero-scrim--university: 35%;
  --_theme---hero-scrim--business:   35%;  /* user set 35%, 2026-09-27 */
  --_theme---hero--heading:          var(--color--white);
  --_theme---hero--body:             var(--color--white);
  /* Arrows: white discs with the site's dark ink, 17.27:1 for the chevron.
     Dots: ONE colour for every dot (mechanism 5 — width marks the active one,
     never colour). grey-400 reads 3.13:1 on the white page, clearing the 3:1
     floor for a non-text control; grey-200 would have been 1.7:1 and fails. */
  --_theme---hero--control:          var(--color--white);
  --_theme---hero--control-icon:     var(--color--grey-1000);
  --_theme---hero--dot:              var(--color--grey-400);
  /* M4 (R4) — the bar that fills the ACTIVE dot over the dwell. It is the
     timer made visible, so it must read against the grey pill it runs inside:
     grey-1000 on grey-400 is 5.52:1. The reference's rgb(11,5,29) is a
     near-black with no ramp step of its own; grey-1000 is the ramp's darkest
     ink and the one this hero already uses for the scrim, so no colour is
     added. */
  --_theme---hero--dot-fill:         var(--color--grey-1000);
  /* Phone copy sits on the WHITE page below the photo, so it takes page inks.
     The body ink is grey-600 (#6a7878), the ramp step design-system.md names
     for muted body text — NOT --_theme---page--text-muted, which is Refero's
     off-ramp Lichen Gray #5c7070. The site-wide slot is left alone.
     grey-600 on white: 4.60:1. grey-1000 on white: 18.6:1. */
  --_theme---hero--phone-heading:    var(--color--grey-1000);
  --_theme---hero--phone-body:       var(--color--grey-600);
  /* FOCUS HALO. The site's focus ring is 2px accent at a 3px offset (§10.1).
     Over the photo that blue measured ~1.1:1 against the scrim. The hero's
     controls add a WHITE halo under and around it, so the blue ring is
     bounded by white on BOTH sides — accent on white is 6.02:1 — whatever
     the photograph is doing underneath. The accent itself is unchanged. */
  --_theme---hero--focus-halo:       var(--color--white);

  --_theme---quotes--background:    var(--color--sand-100);
  --_theme---quotes--text:          var(--color--grey-1000);   /* client 2026-10-01: no brown (sand-1000) type */

  --_theme---stats--background:     var(--color--purple-800);
  --_theme---stats--text:           var(--color--purple-100);

  --_theme---announce--background:  var(--color--green-50);
  --_theme---announce--text:        var(--color--green-1000);
  --_theme---announce--rule:        var(--color--green-800);

  --_theme---nav--background:       var(--color--white);
  --_theme---nav--link:             var(--color--grey-800);
  --_theme---nav--link-hover:       var(--color--grey-1000);
  --_theme---nav--surface:          var(--color--white);
  /* §9.2 — the dropdown callout is a WHITE card, not a sand tint. It reads as
     an object against the white dropdown by the shadow ladder's ring, not by
     a fill; white is not a tint, so its ink is the page ink. */
  --_theme---nav--callout:          var(--color--white);
  --_theme---nav--callout-text:     var(--color--grey-1000);

  /* §9.5 — AvoLingo's button pair.
     Lattice's mechanism is copied — a tinted ground carrying its own ink,
     inverted for secondary. Lattice's #046645 / #cdface must not appear on a
     button. Because these are slots, this is the only place the pair is written.

     Set to the brand-green family 2026-09-03, replacing the turquoise pair.

     PRIMARY — white ink on brand green, as the client specified.
       primary       #1ab878 on #ffffff → 2.57:1
       primary hover #159662 on #ffffff → 3.77:1
     Both are under WCAG AA (4.5:1 for text this size), and that is a known,
     accepted deviation, not an oversight: the client asked for this exact green
     with white type after the measurement was put to them. If AA is ever
     required here, move the ground down to brand-800 #0e744b, which carries
     white at 5.80:1 and needs no other change.

     SECONDARY — the inverse, and it does reach AA:
       secondary       brand-100 #cef3e4 ground / brand-1000 #095033 ink → 7.96:1
       secondary hover brand-200 #9ce8c9 ground / brand-1000 #095033 ink → 6.71:1

     .btn--ghost, .btn--light and .btn--outline-light stay neutral by design —
     white, grey and translucent-white sit against any hue and were not retuned. */
  --_theme---buttons--primary-background:         var(--color--brand-400);
  --_theme---buttons--primary-text:               var(--color--white);
  --_theme---buttons--primary-background-hover:   var(--color--brand-600);
  --_theme---buttons--primary-text-hover:         var(--color--white);
  --_theme---buttons--secondary-background:       var(--color--brand-100);
  --_theme---buttons--secondary-text:             var(--color--brand-1000);
  --_theme---buttons--secondary-background-hover: var(--color--brand-200);
  --_theme---buttons--secondary-text-hover:       var(--color--brand-1000);

  /* Hero demo — the speaking-room module is the blue family (§3.4). */
  --_theme---demo--stage:      var(--color--blue-50);
  --_theme---demo--stage-ink:  var(--color--blue-1000);
  --_theme---demo--surface:    var(--color--white);
  --_theme---demo--them:       var(--color--blue-50);
  --_theme---demo--them-ink:   var(--color--blue-1000);
  --_theme---demo--you:        var(--color--grey-1000);
  --_theme---demo--you-ink:    var(--color--white);
  --_theme---demo--fix:        var(--color--lime-50);
  --_theme---demo--fix-ink:    var(--color--lime-1000);
  --_theme---demo--save:       var(--color--green-50);
  --_theme---demo--save-ink:   var(--color--green-1000);
  /* The score meter was the last thing in the hero still painted with
     Lattice's teal-800 #046645. It now reads AvoLingo's own turquoise, so
     #046645 appears nowhere in the first viewport. */
  --_theme---demo--meter:      var(--color--turquoise-800);
  --_theme---demo--meter-track: var(--color--grey-100);

  /* ==========================================================
     §4 — Type. All rem; the px column in the spec is the value at 1440.
     ========================================================== */

  --font-sans: "DM Sans", "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif;

  --fs-jumbo:    6.4rem;
  --fs-headline: 4rem;
  --fs-h1:       3.2rem;
  --fs-h2:       2.6rem;
  --fs-h3:       2rem;
  --fs-h4:       1.6rem;
  --fs-h5:       1.2rem;
  --fs-h6:       1rem;

  --fs-body-3xl: 2rem;
  --fs-body-2xl: 1.6rem;
  --fs-body-xl:  1.4rem;
  --fs-body-l:   1.2rem;
  --fs-body-m:   1rem;
  --fs-body-s:   0.9rem;
  --fs-body-xs:  0.8rem;
  --fs-body-2xs: 0.7rem;
  --fs-label:    0.7rem;
  --fs-tag:      0.5rem;

  /* Aliases used by the existing markup and the frozen pages. */
  --fs-caption:     var(--fs-body-2xs);
  --fs-body-sm:     var(--fs-body-xs);
  --fs-body:        var(--fs-body-m);
  --fs-17:          var(--fs-body-s);
  --fs-18:          var(--fs-body-m);
  --fs-subheading:  var(--fs-body-l);
  --fs-heading-sm:  var(--fs-h5);
  --fs-heading:     var(--fs-h2);
  --fs-display:     var(--fs-headline);

  --lh-tight:   1.0;
  --lh-display: 0.9;
  --lh-heading: 1.2;
  --lh-snug:    1.1;   /* §4.1's h3 / h4 row */
  --lh-body:    1.3;
  --lh-prose:   1.4;

  /* §4.1's ladder is tuned for Latin and tops out at 1.4. Hangul body text needs
     more: syllable blocks are full-height, so there is no x-height for the
     leading to breathe into, and a three-line Korean paragraph at 1.3 sets
     visibly denser than the English in the same box. This is the BODY
     counterpart of the heading correction :lang(ko) .method__trigger already
     makes, and it is published here rather than written as a loose number in a
     component file. Latin body keeps §4.1's 1.3; only :lang(ko) reads this.
     Added 2026-09-17 — to be recorded in design-system.md §4 alongside §4.1.1. */
  --lh-ko-body: 1.7;

  /* Three tracking values only. */
  --kern-headline: -0.024em;
  --kern-heading:  -0.016em;
  --kern-tag:      0.08em;
  --tracking-tight: var(--kern-headline);
  --tracking-wide:  var(--kern-tag);

  /* ==========================================================
     §5 — Space and layout, in rem.
     ========================================================== */

  --sp-4:  0.4rem;
  --sp-8:  0.8rem;
  --sp-12: 1.2rem;
  --sp-16: 1.6rem;
  --sp-20: 2rem;
  --sp-24: 2.4rem;
  --sp-32: 3.2rem;
  --sp-40: 4rem;
  --sp-56: 5.6rem;
  --sp-64: 6.4rem;

  /* Legacy ladder, re-expressed on the same rem grid. */
  --space-1:  0.2rem;
  --space-2:  var(--sp-4);
  --space-3:  var(--sp-8);
  --space-4:  var(--sp-12);
  --space-5:  var(--sp-20);
  --space-6:  var(--sp-24);
  --space-7:  var(--sp-40);
  --space-8:  var(--sp-64);
  --space-9:  10.4rem;

  --max-width:   72rem;
  --gutter:      1.6rem;
  --section-gap: 4rem;
  --panel-pad:   5.6rem;
  --card-pad:    1.6rem;
  --nav-h:       3.6rem;
  --announce-h:  2.4rem;

  /* Homepage hero box — bar-hero.md "Re-measure 2" (2026-09-26, nine widths),
     which supersedes M1's height and width rule. The reference box is:
       width  = min(viewport - 2 x gutter, 1680px), centred once capped
       height = 560px below a 1536px viewport, 742px from 1536px up — a STEP
     Round 4 had read "560 fixed" from two samples and built a curve,
     max(560px, 31rem), which reached 606px at 1920 where the reference is 742.

     WHY THESE THREE ARE PX, stated rather than hidden: they are reference-scale
     box dimensions, not type-relative spacing, and no token lands near them.
     On the fluid root 1680px is 85.8rem at 1920 but 84rem at 2560, and 742px
     is 37.9rem at 1920 but 37.1rem at 2560 — a rem value would drift at
     exactly the widths the cap and the step exist to hold still. The nearest
     published lengths are nowhere close (--max-width 72rem is 1410px at 1920).
     They live here, with their derivation, as the old #needs label blur
     did, and site.css only names them.
     The 1536px breakpoint that switches between the two heights is the
     reference's, not one of §10.3's; it is scoped to the hero and written in
     site.css beside the rule it drives, recorded there as a deviation. */
  --hero-stage-h:      560px;
  --hero-stage-h-wide: 742px;
  --hero-stage-w:      1680px;

  /* THE STAGE EDGE, shared by the hero card and the site nav (user ruling
     2026-09-26: the logo and the nav's right-hand button line up with the
     card's edges, as on the reference). One expression, so the two can never
     disagree:
         edge = max(gutter, (100% - 1680px) / 2)
     — the gutter until the 1680px cap, then whatever centres a 1680px stage.
     `100%` is left unresolved on purpose: it resolves where the property is
     USED, against that element's containing block — the hero slide's grid
     column and the nav's <header>, both the full document width.
     The gutter is the reference's fixed 40px (20px on phones, overridden at
     the foot of this file). The nav reads this only on the homepage — see
     site.css's NAV ALIGNMENT note. */
  --stage-gutter: 40px;
  --stage-edge:   max(var(--stage-gutter), calc((100% - var(--hero-stage-w)) / 2));

  /* THE REFERENCE'S FIXED PX (coordinator ruling after round 4: "as close as
     possible"). RECORDED DEVIATIONS, all scoped to the homepage hero: the
     reference holds these at fixed px across every width, and the rem ladder
     drifts with the fluid root (--sp-24 was 43.3px at 1440 and 48px at 2560
     for a measured 40). Each is published here once so site.css only names it.
       --stage-gutter      40px   (above; 20px on phones, foot of this file)
       --hero-stage-top    33px   card top below the nav
       --hero-radius       48px   card radius, every width, phone included
       --hero-copy-w       600px  ONE shared measure for heading AND body
       --hero-copy-inset   40px   copy inset inside the card, all sides
       --hero-arrow        44px   arrow disc; 8px from the viewport edge
       --hero-pip          6px    dot; active pip 24px; 8px visual gap;
                                  pips 16px below the card
     COPY COLUMN FIT, measured with DM Sans's own metrics: at 600px every
     slide fits both cards in both languages at every width. The tightest is
     EN slide 4 at 2560 — five heading lines at 100px — 714.8px of the 742px
     card. The narrowest column that still fits everything is 560px. */
  --hero-stage-top:    33px;
  --hero-radius:       48px;
  --hero-copy-w:       600px;
  --hero-copy-inset:   40px;
  --hero-arrow:        44px;
  --hero-arrow-inset:  8px;
  --hero-pip:          6px;
  --hero-pip-active:   24px;
  --hero-pip-gap:      8px;
  --hero-dots-offset:  16px;
  /* The reference's arrow shadow, 0 2px 4px rgba(0,0,0,.1) — written through
     the ramp's own black rather than as a raw rgba. It is the one hand-written
     shadow in the hero, recorded as a deviation from §6's ladder at the
     user's request; --shadow--card-xsmall was the round-4 stand-in. */
  --_theme---hero--arrow-shadow: 0 2px 4px color-mix(in srgb, var(--color--black) 10%, transparent);

  /* Hero headline from 1536px up (user ruling 2026-09-26: "match the
     reference's jump", 84 -> 130px). RECORDED DEVIATION from §1, which forbids
     type-size breakpoints; scoped to this one heading.
     PX, and the cost of that stated: a px size does not follow a reader's
     browser font-size preference the way the rem ladder does. It is the
     ruled, reference-scale value. The nearest published rank, --fs-jumbo
     (6.4rem), lands at 117.5px at 1536 and 125.2px at 1913 — within 4% at
     1913 but 10% short at 1536 — so it was not taken; switching is one word.
     FIT, measured with DM Sans's metrics: 130px fits the 742px card on every
     slide in both languages ONLY because the copy column is 14ch of the
     heading's own size (site.css ties them). At the old 14ch-of-headline
     column EN slide 4 ran to 5 lines and 913-932px. Tied, the tallest slide
     is KO slide 4 at 654-685px; the largest size that would still fit every
     slide is ~153px. */
  /* USER RULING 2026-09-26: reduced from 130px to 100px ("지금 너무 커보여, 100정도로").
     At 130 the copy column (14ch of the heading) spread to ~1256px and the
     scrim, which is tied to it, covered 84-98% of the photo on wide screens.
     At 100 the column is ~966px and the photo keeps more of its own light. */
  --hero-title-wide: 100px;

  /* ==========================================================
     §7 — Radii. Nothing interactive is 0.
     ========================================================== */

  --radius--2xs:    0.1rem;
  --radius--xs:     0.2rem;
  --radius--s:      0.4rem;
  --radius--m:      0.8rem;
  --radius--l:      1.2rem;
  --radius--xl:     1.6rem;
  --radius--2xl:    2.4rem;
  --radius--3xl:    3.2rem;
  --radius--circle: 50%;

  --radius-button-nav:  0.64rem;
  --radius-button-hero: 0.72rem;
  /* §7 — tags are 1.0rem, which is pill-shaped at their size. Declared in rem,
     never `3em`, or the radius tracks the font-size instead of the scale. */
  --radius--tag:        1rem;

  /* Aliases used by the existing markup and the frozen pages. */
  --radius-sm:      var(--radius--s);
  --radius-input:   var(--radius--m);
  --radius-card:    var(--radius--l);
  --radius-feature: var(--radius--2xl);
  --radius-badge:   var(--radius--tag);
  --radius-button:  var(--radius-button-hero);
  --radius-full:    999px;

  /* ==========================================================
     §6 — The eight-rung shadow ladder.
     Rungs are forest-tinted (Refero). All rungs are grey-50 except rung 7,
     which is grey-100. Every variant declares all eight rungs — sizes are
     made by blanking upper rungs, never by writing a shorter stack — so a
     shadow change animates instead of snapping.
     ========================================================== */

  --rung: var(--color--grey-50);
  --rung-7: var(--color--grey-100);
  --rung-off: rgba(0, 31, 31, 0);

  --shadow--card-large:
    0 64px 64px -32px var(--rung),
    0 32px 32px -16px var(--rung),
    0 16px 16px -8px  var(--rung),
    0 12px 12px -6px  var(--rung),
    0 6px  6px  -3px  var(--rung),
    0 3px  3px  -1.5px var(--rung),
    0 1px  1px  -0.5px var(--rung-7),
    0 0    0    1px    var(--rung);

  --shadow--card:
    0 64px 64px -32px var(--rung-off),
    0 32px 32px -16px var(--rung-off),
    0 16px 16px -8px  var(--rung-off),
    0 12px 12px -6px  var(--rung),
    0 6px  6px  -3px  var(--rung),
    0 3px  3px  -1.5px var(--rung),
    0 1px  1px  -0.5px var(--rung-7),
    0 0    0    1px    var(--rung);

  --shadow--card-small:
    0 64px 64px -32px var(--rung-off),
    0 32px 32px -16px var(--rung-off),
    0 16px 16px -8px  var(--rung-off),
    0 12px 12px -6px  var(--rung-off),
    0 6px  6px  -3px  var(--rung),
    0 3px  3px  -1.5px var(--rung),
    0 1px  1px  -0.5px var(--rung-7),
    0 0    0    1px    var(--rung);

  --shadow--card-xsmall:
    0 64px 64px -32px var(--rung-off),
    0 32px 32px -16px var(--rung-off),
    0 16px 16px -8px  var(--rung-off),
    0 12px 12px -6px  var(--rung-off),
    0 6px  6px  -3px  var(--rung-off),
    0 3px  3px  -1.5px var(--rung),
    0 1px  1px  -0.5px var(--rung-7),
    0 0    0    1px    var(--rung);

  --shadow-none:
    0 64px 64px -32px var(--rung-off),
    0 32px 32px -16px var(--rung-off),
    0 16px 16px -8px  var(--rung-off),
    0 12px 12px -6px  var(--rung-off),
    0 6px  6px  -3px  var(--rung-off),
    0 3px  3px  -1.5px var(--rung-off),
    0 1px  1px  -0.5px var(--rung-off),
    0 0    0    1px    var(--rung-off);

  /* Aliases used by the existing markup and the frozen pages. */
  --shadow-md:   var(--shadow--card);
  --shadow-lift: var(--shadow--card-large);

  /* ==========================================================
     §8 — Motion. Four curves, 240–300ms; only ambient loops are slow.
     ========================================================== */

  --ease:       cubic-bezier(0.22, 0.61, 0.36, 1);   /* button */
  --ease-global: cubic-bezier(0.215, 0.61, 0.355, 1);
  --ease-nav:   cubic-bezier(0.165, 0.84, 0.44, 1);
  --ease-spring: linear(0, 0.402 7.4%, 0.711 15.3%, 0.929 23.7%, 1.008 28.2%, 1.067 33%, 1.099 36.9%, 1.12 41%, 1.13 45.4%, 1.13 50.1%, 1.111 58.5%, 1.019 83.2%, 1.004 91.3%, 1);

  --dur-fast: 240ms;
  --dur:      300ms;
  --dur-slow: 300ms;
  --transition: all 300ms var(--ease-global);

  /* How long the homepage hero carousel HOLDS a slide. This is a token, not a
     constant in js/main.js, so the dwell and the CSS that paints the carousel
     cannot drift apart; main.js reads it back off :root. 5.0s is the
     reference's measured dwell (advances landed 5035ms apart) and it is also
     the figure design-system.md §10.2 already names for a carousel, so nothing
     new is being introduced to the motion system.

     There is deliberately NO token for the MOVE. §8.1 publishes its durations
     PAIRED with a curve — 300ms with --ease-global, 240ms with --ease (the
     button curve) — so the carousel spends the published 300ms/--ease-global
     pair and adds nothing to the ladder. The reference's ~267ms sits between
     the two steps; taking 240 to get nearer it, as round 1 did, meant writing
     240 beside --ease-global, which is neither published pair. Cost of 300ms:
     the slide is still for 16.7x as long as it moves against the reference's
     18.9x, comfortably inside mechanism 2's "under ~400ms". */
  --dwell-carousel: 5000ms;

  /* #needs card carousel timing — bar-needs-carousel.md mechanism 6, read
     back off :root by js/main.js exactly as --dwell-carousel is.
     --needs-dwell 8000ms: the reference's measured autoplay interval.
     --needs-step-dur 550ms: the reference's measured step. A RECORDED
     DEVIATION from §8.1's 240-300ms band, and why: this move carries a whole
     card (1169px at 1440) across the stage. At 300ms that is ~3.9px per ms,
     which on the fast-start curve crosses half the stage in under 90ms and
     reads as a cut, not a slide; the reference spends 550ms for the same
     travel. The CURVE is still §8.1's (--ease-global), so the profile — fast
     start, long settle — is the system's own. Nothing else uses this token. */
  --needs-dwell:     8000ms;
  --needs-step-dur:  550ms;

  /* #belief roadmap card timing — bar-roadmap-card.md mechanisms 3-6, read
     by css/site.css only. A RECORDED EXCEPTION to §8.1's 240-300ms band
     (design-system.md §8.1, "the roadmap card"): the bar is monzo.com's
     "Save like a superhero" tile, sampled per frame on 2026-09-28, and these
     are its measured timings. Curves: the glass is linear (Monzo's glass
     fades at an even rate); the pill roll runs on --roadmap-roll-ease, a
     sine ease-in-out, because on §8.1's ease-out curves a roll moves almost
     entirely in its first ~150ms and reads as a flick; the radio dot and
     the photo stay on --ease-global.
     --roadmap-glass-dur   550ms: frosted glass -> solid white (Monzo 0.07-0.63s).
     --roadmap-hold        500ms: stillness before the change (Monzo ~0.63-1.2s).
     The radio swaps like a real one: in one frame the old dot is gone and
     the new one appears at 58%; only the new dot animates.
     --roadmap-dot-dur     350ms: the new dot grows from 58% to full.
     --roadmap-stagger     250ms: row 2's change after row 1's (Monzo 0.25s).
     --roadmap-roll-dur    600ms: row 2's pill roll (Monzo rows 0.35 / 0.42s);
       on the sine ease-in-out ~0.4s of it is visible travel.
     --roadmap-photo-dur  3600ms: the photo's push-in to 1.04, the tile's
       whole run (Monzo's footage runs 4.07s). Reduced motion and no JS:
       none of it runs, and the page shows the animated END frame. */
  --roadmap-glass-dur:   550ms;
  --roadmap-hold:        500ms;
  --roadmap-dot-dur:     350ms;
  --roadmap-stagger:     250ms;
  --roadmap-roll-dur:    600ms;
  --roadmap-photo-dur:   3600ms;
  --roadmap-roll-ease:   cubic-bezier(0.37, 0, 0.63, 1);

  /* #needs practice-line reveal — bar-needs-overlay.md mechanisms 3-5, read by
     css/site.css (and nowhere else). Each character rises over §8.1's Global
     pair (--dur on --ease-global); these three set only WHEN each one starts.
     --needs-say-stagger 35ms: per character slot (reference ~40ms). A row's
       value starts 4 slots after its label ends (the reference's value
       follows its label by ~0.6s).
     --needs-say-lead    750ms: card active and in view -> row 1 (ref ~0.9s;
       it also lets the 550ms track step settle first).
     --needs-say-row     1400ms: row to row (ref ~1.5s); four rows finish
       by ~6s, inside the 8s dwell, and never overlap. */
  --needs-say-stagger: 35ms;
  --needs-say-lead:    750ms;
  --needs-say-row:     1400ms;

  /* Homepage hero ENTRANCE — bar-hero.md Re-measure 3 (MEASURED from the
     user's recording of a fresh load, box edge traced frame by frame).
     Only the media layer (photo + scrim, with its rounded clip) moves; the
     copy never scales or moves. These are the only place the numbers live.
       --hero-enter-scale      0.20, uniform, about the card's centre
       --hero-enter-dur        750ms, the grow
       --hero-enter-ease       cubic-bezier(0.40, 0, 0.05, 1), fitted to the
                               traced frames (RMS 0.008): 6% by 100ms, 61% by
                               250ms, 94% by 500ms
       --hero-enter-opacity    0.3, the card's starting opacity
       --hero-enter-fade-dur   250ms — the card is fully opaque by 250ms, and
                               the copy fades on the SAME curve so white type
                               is never shown on the white page. Linear: the
                               recording gives the endpoints, not a curve, and
                               a 250ms fade that short does not show one.
     RECORDED DEVIATIONS from §8 (user-ruled, reference-measured): 750ms is
     outside §8's 240-300ms band, and the curve is not one of §8's four. They
     are a one-off entrance, never a state change. */
  --hero-enter-scale:    0.20;
  --hero-enter-dur:      750ms;
  --hero-enter-ease:     cubic-bezier(0.40, 0, 0.05, 1);
  --hero-enter-opacity:  0.3;
  --hero-enter-fade-dur: 250ms;
  /* THE COPY WAITS FOR THE CARD (round-5 fix). The copy is white and sits over
     the card; while the card is still growing its left edge has not reached
     the copy's first glyph (40px inside the card), so any visible copy would
     be white on the white page. Inverting the curve above: the card's left
     edge passes x = 40px at scale 1 - 80/W — 423ms for the 929px card at
     1024, 467ms at 1440, 492ms for the widest, 1680px card. (The top edge
     clears the tallest slide's first line earlier, at 355ms, so the horizontal
     edge governs.) The copy therefore holds at opacity 0 until 500ms and then
     fades in over --dur-fast, finishing with the card at ~740ms. */
  --hero-enter-copy-delay: 500ms;
  --hero-enter-copy-dur:   var(--dur-fast);

  /* ---- Gradients ---- */
  --halo-gradient: radial-gradient(circle at 30% 20%, var(--color--blue-100) 0%, var(--color--blue-50) 45%, var(--color--teal-50) 100%);
  --banner-gradient: linear-gradient(104deg, var(--color--violet-800) 0%, var(--color--purple-600) 46%, var(--color--purple-400) 100%);
}

/* Phones: the stage gutter is the reference's measured 20px (its box is x 20,
   350 wide at a 390 window). Fixed px for the same reason as the 40px above. */
@media (max-width: 640px) {
  :root { --stage-gutter: 20px; }   /* the reference's phone inset: x 20 at 390 */
}
