/* ===== Design tokens — TWO LAYERS =====
   Normalised from DESIGN_AUDIT.md (112 literal values across the sheet,
   most of them one-offs).

   LAYER 1 — PRIMITIVES. Raw values, named for what they LOOK like
     (--gray-900, --blue-500). No opinion about where they are used. Colours
     were computed in OKLCH and written here as hex: OKLCH is what makes the
     ladders perceptually even, hex is what every browser reads without a
     fallback. The OKLCH coordinates are in the comments so the ladder can be
     extended without guessing.

   LAYER 2 — SEMANTICS. Roles (--surface-page, --text-primary,
     --accent-default). Components read ONLY these. Every one points at a
     primitive through var(), so a theme swaps meanings, never raw numbers.
     Themes live here and nowhere else: [data-theme="dark"] re-points the
     roles, it does not redefine any primitive.

   LAYER 3 — LEGACY BRIDGE at the bottom. The component sheets still use the
     old token names (--c-bg, --fs-300, --sp-3 …) and are deliberately NOT
     being touched in this pass, so those names are kept and re-pointed at
     the semantic layer. That way this change is one file and one revert.
     The bridge is temporary; it goes away when the components are migrated.

   ---------------------------------------------------------------------
   SYSTEM RULE — two hue families, one set of lightness steps.
   The light theme is warm (H ≈ 90, cream paper) and the dark theme is cool
   (H ≈ 286, slate ink). This is a rule, not a leftover: the site's light
   mode has always read as paper rather than as white, and forcing both
   themes onto one hue would cost that. What the two families DO share is
   the lightness ladder — identical L stops in both — so a role that is
   "one step down from the page" is the same perceptual step in either theme.
   Chroma is deliberately tiny (.008 warm / .010 cool): these are neutrals
   that lean, not colours.
   --------------------------------------------------------------------- */

/* =====================================================================
   LAYER 1 — PRIMITIVES
   ===================================================================== */
:root {
  /* --- neutral ladder, warm family — OKLCH H 90, C .008 --------------
     Steps: 100 96 72 45 37 32 26 20 12 %L. The gaps are uneven on purpose:
     they sit where the design actually needs stops (page, text, chrome),
     not on a mathematical curve nothing lands on. */
  --warm-000: #ffffff;   /* L 100 % — C forced to 0: white has no hue      */
  --warm-050: #f4f2ec;   /* L  96 % — the page                             */
  --warm-300: #a6a49f;   /* L  72 %                                        */
  --warm-500: #575550;   /* L  45 % — secondary text on light              */
  --warm-600: #41403b;   /* L  37 %                                        */
  --warm-700: #34332e;   /* L  32 %                                        */
  --warm-800: #252420;   /* L  26 %                                        */
  --warm-900: #171612;   /* L  20 % — primary text on light                */
  --warm-950: #070603;   /* L  12 %                                        */

  /* --- neutral ladder, cool family — OKLCH H 286, C .010 -------------
     Same nine L stops as the warm family. */
  --cool-000: #ffffff;   /* L 100 % */
  --cool-050: #f1f1f8;   /* L  96 % — primary text on dark */
  --cool-200: #c3c3ca;   /* L  82 % — see the note on --text-secondary below */
  --cool-300: #a4a4ab;   /* L  72 % */
  --cool-500: #55555b;   /* L  45 % */
  --cool-600: #3f3f45;   /* L  37 % */
  --cool-700: #323238;   /* L  32 % */
  --cool-800: #232329;   /* L  26 % */
  --cool-900: #15151a;   /* L  20 % */
  --cool-950: #050509;   /* L  12 % — the darkest page */

  /* --- background tints (the visitor-pickable shades) ----------------
     One rule: same L, same C, hue is the only variable. Light options sit
     at L 95 % / C .025, dark options take an L stop from the ladder at
     C .030 — high enough to read as a tint, low enough to stay a surface. */
  --violet-050: #f1ebfd;   /* L 95 % C .025 H 301 */
  --azure-050:  #e5efff;   /* L 95 % C .025 H 262 */

  --indigo-800: #232232;   /* L 26 % C .030 H 288 */
  --indigo-700: #323142;   /* L 32 % C .030 H 288 — surface = bg + 6 % L */

  /* EXCEPTION — noir keeps C .010, not the .030 the other tints share.
     It is the "clean black" option and that is its whole job; pulling it up
     to the shared chroma turns it violet and it stops being the neutral
     choice in the set. At C .010 it lands exactly on the cool neutral
     ladder, which is why it has no primitives of its own: noir IS the
     ladder (--cool-900 / --cool-800). Kept as an alias for readability. */
  --noir-900: var(--cool-900);
  --noir-800: var(--cool-800);

  /* --- accents ------------------------------------------------------
     Both normalised to L 70 % / C .18 so they carry the same visual weight;
     only hue separates them. */
  --coral-500: #fa6b52;   /* L 70 % C .18 H  32 */
  --blue-500:  #6997ff;   /* L 70 % C .18 H 266 — on dark backgrounds */
  --blue-600:  #527df1;   /* L 62 % C .18 H 266 — on light backgrounds */

  /* EXCEPTION — the logo's sun is NOT on the accent ladder.
     Its lightness is semantic: the sun is the light source in the mark, and
     at the accents' L 70 % it stops reading as emitted light and starts
     reading as an orange dot. Left at its measured L 84.5 % / C .139 / H 77.
     Decorative graphic, never a text background — see the contrast note in
     DESIGN_AUDIT.md. */
  --sun-400: #ffbf58;

  /* --- alpha ladder --------------------------------------------------
     Four steps replacing fourteen hand-picked opacities. Shadow alphas are
     NOT on this ladder — they are baked into the three elevation tokens
     below, where they are part of a ramp rather than independent values. */
  --black-a05: rgba(0, 0, 0, .05);
  --black-a10: rgba(0, 0, 0, .10);
  --black-a20: rgba(0, 0, 0, .20);
  --black-a45: rgba(0, 0, 0, .45);
  --white-a10: rgba(255, 255, 255, .10);
  --white-a20: rgba(255, 255, 255, .20);

  /* --- type scale — modular, 16px base, ratio 1.25 -------------------
     12.8 · 16 · 20 · 25 · 31.25 · 48.83 */
  /* One step BELOW xs, and not an exception: 12.8 / 1.25 = 10.24, so this is
     the ladder continued, not a value picked to fit something. It exists
     because dense labelling — a count in a pill, an index before a category
     name — sits under the smallest reading size and had nowhere to go. */
  --text-2xs: 0.64rem;     /* 10.24px */
  --text-xs:  0.8rem;      /* 12.8px */
  --text-md:  1rem;        /* 16px   — base */
  --text-lg:  1.25rem;     /* 20px   */
  --text-xl:  1.5625rem;   /* 25px   */
  --text-2xl: clamp(1.953rem, 6vw, 3.052rem);   /* 31.25 → 48.83px */

  /* EXCEPTION — off the modular scale, on purpose.
     14px sits between the scale's 12.8 and 16 and exists because the ratio
     is too coarse for dense UI: at 12.8 the nav tabs read as fine print, at
     16 the five of them stop fitting a 320px screen. Intended consumers:
     the nav tabs and the footer contact pills. */
  --text-sm: 0.875rem;     /* 14px */

  /* EXCEPTION — compositional size, does not obey the scale.
     The Hero headline is sized to the viewport and to the shape of the
     three lines, not to a type ramp: it is the page's largest graphic
     element, and the nearest scale step (76.3px) makes the third line
     stop balancing the first. Left at its measured 44 → 88px. */
  --text-display: clamp(2.75rem, 10vw, 5.5rem);   /* 44 → 88px */

  /* --- weight — three steps, where there were none -------------------
     Weights were written as bare numbers in the component sheets (card.css
     has a literal 600). Three is the whole range the site draws on, and
     naming them is what lets a component say which ROLE a weight plays
     rather than restating the number. */
  --weight-regular:  400;
  --weight-medium:   500;
  --weight-semibold: 600;

  /* --- tracking — two steps, where there were none --------------------
     In `em`, not px, so a step holds its proportion at whatever size it is
     used: small type needs the letters opened up and the amount is a share
     of the size, not a constant. caps is for uppercase labels, which need
     the most; micro is for the small numerals beside them. */
  --tracking-caps:  0.07em;
  --tracking-micro: 0.05em;

  /* --- leading — three roles, was seven near-identical numbers ------- */
  --leading-tight:   1.2;   /* display and headings */
  --leading-normal:  1.4;   /* UI rows, labels */
  --leading-relaxed: 1.6;   /* prose */

  /* --- spacing — every step a multiple of 4 at the default base ------
     In rem, not px: the type scale is already rem-based, and a user who
     raises the browser's base font size was getting text that grew while
     the padding around it did not. The two axes now scale together. The
     4px grid still holds at base 16; above it the whole grid scales. */
  --space-1:  0.25rem;   /*  4px at base 16 */
  --space-2:  0.5rem;    /*  8px */
  --space-3:  0.75rem;   /* 12px */
  --space-4:  1rem;      /* 16px */
  --space-5:  1.25rem;   /* 20px */
  --space-6:  1.5rem;    /* 24px */
  --space-8:  2rem;      /* 32px */
  --space-10: 2.5rem;    /* 40px */
  --space-12: 3rem;      /* 48px */
  --space-20: 5rem;      /* 80px */
  --space-24: 6rem;      /* 96px */

  /* --- radius — four steps ------------------------------------------
     `50%` stays literal in the components that need a circle: that is a
     shape, not a step on this scale. */
  --radius-sm:   8px;
  --radius-md:  16px;
  --radius-lg:  24px;
  --radius-full: 999px;

  /* --- motion — curves are tokenised, durations are not (yet) --------
     Inventoried in MOTION_AUDIT.md. Two curves cover everything on the site:

     --ease-out is the house curve, on 19 of 27 transitions: anything that
       appears, dims, tints or recolours.
     --ease-overshoot passes the target and comes back — the 1.4 control
       point is what takes it past 100 %. Reserved for things that TRAVEL:
       the nav highlight sliding between tabs, the sun rising out of the
       cloud. It was written longhand in two files before this pass.

     A third curve, cubic-bezier(.25, .8, .35, 1), used once on the logo
     wordmark's colour fade, was folded into --ease-out: over 350 ms on a
     colour change the two are not tellable apart.

     Durations are still literals in three files — see MOTION_AUDIT.md for
     the per-value breakdown; collapsing them is a separate decision. */
  --ease-out:       cubic-bezier(0.16, 1, 0.3, 1);
  --ease-overshoot: cubic-bezier(.34, 1.4, .5, 1);
  --dur-fast:  180ms;
  --dur:       320ms;
  --dur-slow:  600ms;
  --theme-dur: 420ms;

  /* EXCEPTION — a named duration, not a step on the scale.
     The Hero headline's word drop is a composed scene: 500ms is what the
     four overlapping steps were timed against, and the stagger between them
     is derived from it (500 × (1 − 0.85) = 75ms), not set independently.
     Moving it to --dur-slow would stretch the whole scene by ~150ms. It is
     compositional, like --text-display, and does not obey the scale.
     The stagger stays a derived number in hero.css — giving each delay its
     own token would cut the tie to the duration it is computed from. */
  --dur-hero-drop: 500ms;

  /* --- motion roles --------------------------------------------------
     Each role is a duration + curve pair, usable straight inside a
     transition: `transition: color var(--motion-hover)`. Components read
     these, not the primitives above.

     --motion-hover     pointer feedback and colour change
     --motion-enter     something appearing
     --motion-exit      something leaving
     --motion-emphasis  travel with an overshoot — the site's signature move

     TWO OF THE FOUR CURRENTLY HAVE NO CONSUMER, and that is reported rather
     than papered over:
     • exit — every appearing element on the site uses ONE symmetric
       transition for both directions, so nothing declares a separate
       leaving state yet. The role exists for when one does.
     • emphasis — its only two candidates are the nav capsule and the logo
       sun, and both are hand-tuned exceptions deliberately kept on their
       own literals (see MOTION_AUDIT.md). The pair below is the value a
       new consumer should start from, not a description of existing code.

     Theme cross-fades are NOT one of these roles: they already have their
     own semantic duration, --theme-dur, and they answer to a state change
     rather than to an interaction. */
  --motion-hover:    var(--dur-fast) var(--ease-out);
  --motion-enter:    var(--dur) var(--ease-out);
  --motion-exit:     var(--dur-fast) var(--ease-out);
  --motion-emphasis: var(--dur) var(--ease-overshoot);

  /* --- typefaces ----------------------------------------------------- */
  --font-display: "Georgia", "Times New Roman", serif;
  --font-body:    system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  /* A third family, and the only web font the site loads (self-hosted, see
     the @font-face block in work/iconography/iconography.css). It is here
     rather than scoped to its one consumer because a typeface is a role, not
     a local value — and because the reason it exists generalises: dense,
     numeric, tightly-tracked interface text needs metrics the system stack
     does not guarantee across platforms. */
  --font-ui:      "Inter", system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
}

/* =====================================================================
   LAYER 2 — SEMANTICS
   Roles only. Components read these; primitives are an implementation
   detail they never touch.
   ===================================================================== */
:root {
  --surface-page:    var(--warm-050);
  --surface-raised:  var(--warm-000);
  --text-primary:    var(--warm-900);
  --text-secondary:  var(--warm-500);
  --border-subtle:   var(--black-a10);
  --grid-dot:        var(--black-a10);
  --overlay-scrim:   var(--black-a45);

  --accent-default:   var(--coral-500);
  /* Theme-dependent on purpose. The headline blue has to clear 3:1 (large
     text) against whatever page it sits on, and one lightness cannot do that
     on both a L 95 % page and a L 20 % one. Measured: blue-500 reaches only
     2.41–2.43:1 on the three light tints; blue-600 clears at 3.24:1 worst
     case. Dark keeps blue-500, which measures 3.76–6.47:1 there. */
  --accent-highlight: var(--blue-600);
  --brand-sun:        var(--sun-400);
  --brand-cloud:      var(--blue-500);

  /* Elevation — three levels, replacing ten hand-written shadows.
     Tinted rather than black: over the tinted page a neutral-black shadow
     desaturates whatever it falls on and reads as grey grime. Alphas are
     lower and blurs wider than a black scale would need — a coloured shadow
     reads heavier at equal opacity. */
  --shadow-tint: 74, 60, 122;      /* muted indigo, as R,G,B for rgba() */
  --elev-1: 0 1px 2px  rgba(var(--shadow-tint), .05), 0 3px 10px rgba(var(--shadow-tint), .06);
  --elev-2: 0 2px 6px  rgba(var(--shadow-tint), .05), 0 18px 44px rgba(var(--shadow-tint), .12);
  --elev-3: 0 4px 10px rgba(var(--shadow-tint), .06), 0 24px 60px rgba(var(--shadow-tint), .16);

  /* Slightly see-through surface for things that SCROLL — cards, contact
     pills. They pass under the fixed nav dock, and two identical opaque
     whites merged into one shape at the overlap. Declared solid here and
     upgraded in the @supports block at the end of the file: consumers say
     var(--surface-veil), so if the token itself held a color-mix() a
     non-supporting browser would fail the substitution at computed-value
     time — which resets background to TRANSPARENT, not to any earlier
     declaration. The fallback has to live in the token. */
  --surface-veil: var(--surface-raised);
}

:root[data-theme="dark"] {
  --surface-page:    var(--cool-950);
  --surface-raised:  var(--cool-900);
  --text-primary:    var(--cool-050);
  /* Raised from cool-300 (L 72) to cool-200 (L 82): on the slate shade the
     card surface sat at L 43.7 after the veil, and cool-300 measured only
     3.22:1 there. cool-200 clears at 4.55:1 and is the first ladder step
     that does — the gap between 72 and 96 had no stop in it before.
     Slate has since been dropped from the picker, so the worst case is now
     indigo, which is darker and easier. The value STAYS: the remaining shades
     only gain contrast from it, and stepping back down would buy nothing. */
  --text-secondary:  var(--cool-200);
  --border-subtle:   var(--white-a10);
  --grid-dot:        var(--white-a10);

  --accent-highlight: var(--blue-500);

  /* Plain black, and heavier: there is no page tint left to protect on a
     near-black background, and a coloured glow there reads as a bug. */
  --shadow-tint: 0, 0, 0;
  --elev-1: 0 1px 2px  rgba(0, 0, 0, .04), 0 3px 10px rgba(0, 0, 0, .05);
  --elev-2: 0 2px 6px  rgba(0, 0, 0, .06), 0 18px 44px rgba(0, 0, 0, .16);
  --elev-3: 0 4px 10px rgba(0, 0, 0, .08), 0 24px 60px rgba(0, 0, 0, .22);
}

/* ===== Background shades =====
   A visitor-pickable tint for the page, driven by data-shade on <html>
   — written by js/modules/theme.js, which picks each theme's default. The
   swatch picker that once let a visitor choose is parked in
   PALETTE_BACKUP.md; these tokens stayed behind and are still what
   --surface-page resolves to. Light and dark get COMPLETELY DIFFERENT option
   sets — three each — rather than light/dark variants of one named shade,
   so each option is declared once in the theme block it belongs to and
   data-shade only ever holds a value valid for the current theme.

   RULE: the raised surface is the page lightened by one 6 % L step. That
   relationship already held in the hand-picked values to within 0.4 %, so
   it is written down rather than invented.

   Adding an option: one pair below, one selector line beneath both blocks,
   and, if it is a default, DEFAULT_SHADE in theme.js plus the inline boot
   script in every page's <head>. (LIGHT_SHADES / DARK_SHADES lived in
   bg-shade.js, which went with the picker — see PALETTE_BACKUP.md.) */
:root {
  --shade-violet: var(--violet-050);  --shade-violet-s: var(--warm-000);
  --shade-azure:  var(--azure-050);   --shade-azure-s:  var(--warm-000);
}
:root[data-theme="dark"] {
  --shade-indigo: var(--indigo-800);  --shade-indigo-s: var(--indigo-700);
  --shade-noir:   var(--noir-900);    --shade-noir-s:   var(--noir-800);
}

/* Must come after both theme blocks: only one of each pair is ever defined
   at a time, so there is no override-order hazard here. */
:root[data-shade="violet"] { --surface-page: var(--shade-violet); --surface-raised: var(--shade-violet-s); }
:root[data-shade="azure"]  { --surface-page: var(--shade-azure);  --surface-raised: var(--shade-azure-s); }
:root[data-shade="indigo"] { --surface-page: var(--shade-indigo); --surface-raised: var(--shade-indigo-s); }
:root[data-shade="noir"]   { --surface-page: var(--shade-noir);   --surface-raised: var(--shade-noir-s); }

/* =====================================================================
   LAYER 3 — LEGACY BRIDGE  (temporary)
   The component sheets are untouched in this pass, so their token names
   still have to resolve. Each one points at a semantic role; nothing here
   holds a value of its own. Delete a line as its consumers migrate.
   ===================================================================== */
:root {
  --c-bg:           var(--surface-page);
  --c-surface:      var(--surface-raised);
  --c-surface-veil: var(--surface-veil);
  --c-ink:          var(--text-primary);
  --c-ink-soft:     var(--text-secondary);
  --c-line:         var(--border-subtle);
  --dot-grid:       var(--grid-dot);
  --c-accent:       var(--accent-default);
  --c-highlight:    var(--accent-highlight);

  /* --c-rule was declared but never read by any rule — dropped rather than
     bridged. It was the only fully dead token in the sheet. */

  --fs-100: var(--text-sm);       /* 14px — unchanged by the normalisation */
  --fs-200: var(--text-md);       /* 16px — unchanged */
  --fs-300: var(--text-lg);       /* 20px — unchanged */
  --fs-400: var(--text-xl);       /* 24 → 25px */
  --fs-500: var(--text-2xl);      /* 32–48 → 31.25–48.83px */
  --fs-600: var(--text-display);  /* unchanged */

  --sp-1: var(--space-2);         /*  8px — unchanged */
  --sp-2: var(--space-4);         /* 16px — unchanged */
  --sp-3: var(--space-6);         /* 24px — unchanged */
  --sp-4: var(--space-8);         /* 32px — unchanged */
  --sp-5: var(--space-12);        /* 48px — unchanged */
  --sp-6: var(--space-20);        /* 80px — unchanged */

  --radius-card: var(--radius-lg);    /* 22 → 24px */
  --radius-pill: var(--radius-full);  /* unchanged */

  /* Four names, three levels. xs and sm are NOT collapsed into one: the
     footer pills use xs at rest and sm on hover, and folding both into
     elev-1 would delete that hover. */
  --shadow-xs: var(--elev-1);
  --shadow-sm: var(--elev-2);
  --shadow-md: var(--elev-2);
  --shadow-lg: var(--elev-3);
}

/* --- layout — unchanged, no scale to normalise --- */
:root {
  --container: 72rem;
  --gutter: var(--space-6);
  /* Vertical band the fixed nav dock covers at the bottom of the viewport
     (its own height plus the gap beneath it). Page content that runs to the
     very bottom has to clear this or the dock sits on top of it. */
  --dock-space: 5rem;

  /* Height of the fixed site header (control height + its padding). Declared
     here rather than on .site-header because three other things measure
     against it and none of them is a descendant: the Hero's top padding, the
     parked headline's resting position, and the chat band's top edge.
     The header overrides it for itself at narrow widths. */
  --header-h: calc(38px + var(--space-6) * 2);

  /* --- global stacking order (fixed/page-level layers only) ---
     Local, self-contained stacking (e.g. .nav__pill vs .nav__link inside the
     isolated .nav) is NOT part of this scale — those are small integers
     scoped to their own component and don't compete with anything else.
     This scale exists so cross-component overlap (the exact thing that broke
     once already — fidget tiles hiding under the navbar) has one documented
     source of truth instead of scattered magic numbers. */
  --z-dot-grid:      -1;   /* bottom-most background layer */
  --z-content:        1;   /* normal page content (sections, main) */
  --z-nav:          100;   /* nav dock */
  --z-hero-fidget:  102;   /* fidget tool tiles — must win over --z-nav */
  --z-hero-controls: 103;  /* the Hero's own chat column, above the tiles */
  --z-hero-fidget-held: 104; /* a tile in the hand passes over the chat */
  /* The fixed site header. Above the fidget tiles (102) deliberately: a tile
     dragged up to the top of the Hero has to pass UNDER the header, the same
     way the page's content does, or the thing that is meant to be always
     visible is the one thing a tile can cover. */
  --z-header:       105;
  --z-cursor:       200;   /* the liquid cursor, over everything */
  --z-tooltip:      106;   /* over the tiles and the chat, so the glass can see them */
}

/* --- Hero + AI-chat field (spec 30 Oct) ---------------------------------
   Everything the Hero headline and the ask-about-Cris field read for their
   PROPORTIONS. Fonts and colours are deliberately NOT here — those stay on
   the semantic roles the components already use (--text-primary,
   --surface-raised, --font-*); this block is sizes and timings only, so the
   mock's measurements can be tuned in one place without touching the type or
   palette the rest of the site shares. */
:root {
  /* headline — ~76px at a 1440 frame (10vw capped at 88 was the old size).
     clamp() so it scales down on narrow screens and never grows past 76. */
  --hero-title-size:    clamp(3rem, 6.34vw, 5.7rem);   /* ≈91px @1440 (+20%) */
  --hero-title-leading: 1.05;
  --hero-title-gap:     clamp(64px, 10vh, 140px);      /* headline → field; lifts the headline up */

  /* the field AND the chat zone share this width — they must always line up,
     so both read the one token (never hard-code 519 in either). */
  --chat-field-width:   519px;
  --chat-field-height:  51px;
  --chat-field-pad-l:   20px;              /* sparkle side */
  --chat-field-pad-r:   12px;              /* send side */
  --chat-field-gap:     12px;
  --chat-field-icon:    24px;              /* the Sparkles mark */
  --chat-field-send:    38px;              /* the Send button */
  --chat-field-arrow:   20px;              /* the arrow inside it */
  /* barely-there lift, from the mock. One value for both themes on purpose —
     it is quiet enough to read the same on light and dark. */
  --chat-field-shadow:  0 3px 10px rgba(0, 0, 0, .05), 0 1px 2px rgba(0, 0, 0, .04);
  /* while someone is in the field: a quiet outline and glow in the brand blue */
  --chat-field-focus:   0 0 0 1px color-mix(in srgb, var(--brand-cloud) 65%, transparent),
                        0 0 14px color-mix(in srgb, var(--brand-cloud) 22%, transparent);

  /* how far the field sits above the viewport bottom — the field's resting
     height, shared by .hero's padding-bottom and the chat zone's geometry. */
  --hero-field-bottom:  29vh;

  /* the conversation zone that opens ABOVE the field. Invisible
     (no fill/border/shadow), scrolls inside itself — the page never moves.
     Its height is NOT fixed: it runs from just under the header (so the logo
     and controls stay clear) down to just above the field. */
  --chat-zone-gap:      var(--space-4);    /* 16px zone → field */
  --chat-turn-gap:      var(--space-6);    /* 24px question → answer */
  /* question bubble is a full capsule now — it reads --radius-pill directly,
     so the old dedicated 20px radius token is gone (one chip radius site-wide) */
  --chat-q-pad-y:       14px;
  --chat-q-pad-x:       20px;
  --chat-a-size:        18px;
  --chat-a-leading:     1.65;
  --chat-a-width:       84%;               /* ≈436px of the 519 zone */
  --chat-mask-top:      120px;             /* old turns fade out at the top */
  --chat-mask-bottom:   64px;              /* fade by the field when scrolled up */
  /* word-by-word answer reveal. Both tunable here; the stagger (step) is read
     from this token by hero-chat.js, the per-word fade by the keyframe. */
  --chat-word-dur:      120ms;
  --chat-word-step:     40ms;

  /* --- Nav Dock (macOS Dock) — spec frame 1923:3743 -----------------
     The same icon dock at every width. Sizes:
     5 tiles × 48 + 4 gaps × 10 + 2 pad × 10 = 300 wide; 10 pad + 48 tile +
     16 for the active dot = 74 tall. */
  --dock-height:       74px;
  --dock-pad:          10px;              /* left/right padding — tiles start at x=10 */
  --dock-pad-top:      13px;              /* tiles start at y=13 from the dock top */
  --dock-gap:          10px;
  --dock-tile:         48px;
  --dock-tile-radius:  12px;
  --dock-radius:       22px;
  --dock-icon:         24px;
  --dock-dot:          4px;
  --dock-dot-gap:      5.6px;             /* dot top at 66.6px from dock top ≈ 5.6px under the tile */
  --dock-bottom-space: 13px;              /* 13 top + 48 tile + 13 bottom = 74 */
  /* the base: frosted glass like the macOS Dock. Mostly the blurred page itself;
     the light from above and the rim are only just there. */
  --dock-bg:           linear-gradient(180deg, rgba(255,255,255,0.08), rgba(255,255,255,0.04) 50%, rgba(255,255,255,0.05)), rgba(18, 18, 26, 0.2);
  /* frost does the work: a wide blur, and only a little tint and dimming so the
     white icons (see --dock-icon-lift) hold up over a white screen */
  --dock-glass:        blur(36px) saturate(170%) brightness(0.85);
  --dock-icon-lift:    drop-shadow(0 1px 2px rgba(0,0,0,0.45));
  --dock-rim:          linear-gradient(135deg, rgba(255,255,255,0.2), rgba(255,255,255,0.07) 30%, rgba(255,255,255,0.04) 65%, rgba(255,255,255,0.12));
  --dock-rim-width:    1px;
  --dock-shadow:       inset 0 0 12px rgba(255,255,255,0.04), 0 10px 28px -8px rgba(0,0,0,0.4);
  /* active tile, liquid glass — Figma Site, variant 6 (node 82:1624) */
  --dock-tile-active:  linear-gradient(180deg, #57b2f4, #3264e8);
  --dock-tile-rim:     linear-gradient(135deg, rgba(255,255,255,0.9), rgba(255,255,255,0.12) 38%, rgba(255,255,255,0.05) 62%, rgba(255,255,255,0.6));
  --dock-tile-rim-width: 1.25px;
  --dock-tile-lens:    linear-gradient(180deg, rgba(255,255,255,0.3), rgba(255,255,255,0.03));
  --dock-tile-shadow:  inset 0 0 6px rgba(255,255,255,0.22), 0 7px 16px -5px rgba(18,48,140,0.65);
  --dock-icon-shadow:  drop-shadow(0 1px 2px rgba(14,40,130,0.55));
  --dock-scale-min:    0.65;              /* shrunk size when scrolling down */
  --dock-scale-motion: 600ms cubic-bezier(0.45, 0, 0.15, 1);  /* eases in and lands softly, both ways */
  --dock-divider-gap:  24px;              /* divider sits this far above the dock */
  /* How far the dock floats above the viewport floor (desktop). Was --sp-3
     (24px); dropped to 30% of that, rounded, to sit the dock closer to the
     edge. Only the dock moves — the divider and the fidget floor stay pinned
     to --sp-3. Applied as max(…, safe-area) so a notched device keeps clear. */
  --dock-bottom-offset: 7px;              /* 30% of the old 24px, rounded */

  /* divider line above the dock — the physics floor the fidget tiles rest on */
  --divider-color:     rgba(85, 84, 102, 0.25);   /* #555466 @ 25% */

  /* glass tooltip text — shared by the dock tabs and the fidget tiles */
  --tip-text:          #F5F5F7;

  /* nav-dock tooltip — one shape (capsule + soft-tip tail), outline from Figma
     node 1923:3794 scaled ×1.54 to a 13px label. The capsule width flexes to the
     word, so the clip-path outline + box size (--tip-w/-h/-cap-h/-path) are
     generated per label in nav-pill.js (buildTooltipShapes); only these fixed
     inputs live here. */
  --tooltip-text:      13px;
  --tooltip-pad-x:     13px;              /* ~13px each side of the word (read by the JS, too) */
  --tooltip-tip-gap:   2.6px;             /* tail tip floats this far above the dock top edge */
  /* the glass: a thin dark tint so the page shows through, a soft light from above */
  --tooltip-bg:        linear-gradient(180deg, rgba(255,255,255,0.16), rgba(255,255,255,0.03) 60%), rgba(26, 26, 34, 0.4);
  --tooltip-glass:     blur(14px) saturate(190%) brightness(1.08);

  /* the Sparkles spin: four quarter turns round the circle, each one rotating
     for `duration` and holding for `pause`. Two tokens so the pause can be
     lengthened without editing the keyframe (the stops split each quarter in
     half, so equal values = a clean 1s turn / 1s hold; raise the pause and both
     halves grow together). */
  --sparkle-spin-duration: 1s;
  --sparkle-spin-pause:    1s;

  /* the chat's rotating questions: the old line fades up and out while the new
     one rises into its place, starting a beat later so they cross softly */
  --chat-line-out:   500ms;
  --chat-line-in:    700ms;
  --chat-line-delay: 120ms;
  --chat-line-shift: 40%;
  --chat-line-ease:  cubic-bezier(0.45, 0, 0.25, 1);
}

/* Mobile Safari renders a soft shadow this large as a visibly banded ring
   instead of a smooth gradient — especially over the dark, textured dot-grid
   the nav dock and Hero controls sit on. Trimming the raised level here
   fixes all of them at once. An override of an existing level, not a new
   step on the scale. */
@media (max-width: 768px) {
  :root { --elev-2: 0 1px 2px rgba(var(--shadow-tint), .04), 0 4px 14px rgba(var(--shadow-tint), .07); }
  :root[data-theme="dark"] { --elev-2: 0 1px 2px rgba(0, 0, 0, .05), 0 4px 14px rgba(0, 0, 0, .08); }
}

/* The real translucent veil, where the browser can mix it — see the token's
   declaration in the semantic layer for why the base value stays solid. */
@supports (background: color-mix(in srgb, red 50%, transparent)) {
  :root { --surface-veil: color-mix(in srgb, var(--surface-raised) 82%, transparent); }
}
