/* ==========================================================================
   UNOVA — "Nova's Dusk" design tokens
   The single source of truth. Every colour, size, duration and easing in this
   site comes from here; nothing downstream hard-codes a value.

   The 3D layer reads the same numbers from js/scene/palette.js. If you change
   a colour, change it in both — they are deliberately two short files rather
   than one clever one that has to be parsed at runtime.
   ========================================================================== */

/* ==========================================================================
   THE TWO PALETTES.

   This site has always had two: DUSK (deep navy, the 3D stage) and STONE
   (warm light, the reading surface). They alternate down the page, and every
   value in both was measured against its own ground for WCAG AA — that work is
   already done and is not repeated below.

   Adding light mode therefore adds NO new colours. It swaps which palette is
   the page and which is the band:

       dark theme   page = dusk,  bands = stone
       light theme  page = stone, bands = dusk

   The rhythm of the design is identical in both; only the ground changes. This
   is why light mode is a remapping rather than a second stylesheet, and why
   nothing needed re-measuring — per `color-dark-mode`, a theme is a tonal
   pairing, never an inversion of the other one.

   The 3D scene keeps working for free: it is only ever visible through a DUSK
   region, which is transparent to the canvas in both themes. Bloom is tuned
   for navy and never has to sit on stone.
   ========================================================================== */

:root {
  /* --- DUSK: raw values, written once --------------------------------- */
  --dusk-ink: 9 13 26;         /* #090D1A  deep, not quite black          */
  --dusk-ink-2: 15 21 38;      /* #0F1526  raised surface / cards         */
  --dusk-ink-3: 26 34 58;      /* #1A223A  hairline / divider             */
  --dusk-ink-deep: 4 6 13;     /* #04060D  vignette floor, scene fog far  */
  --dusk-fg: 243 235 220;      /* #F3EBDC  cream casing → text            */
  --dusk-accent: 79 227 196;   /* #4FE3C4  mint eye → interactive         */
  --dusk-signal: 255 139 110;  /* #FF8B6E  coral cheeks → decorative      */
  --dusk-pulse: 124 245 176;   /* #7CF5B0  antenna green → status         */
  --dusk-text-2: 168 176 200;  /* #A8B0C8  7.2:1                          */
  --dusk-text-3: 138 149 180;  /* #8A95B4  5.2:1                          */

  /* --- STONE: raw values, written once --------------------------------
     Stone, not paper. An earlier version was #F0ECE4 — L* 93, four points off
     white — sitting against an L* 5 navy. Two surfaces that far apart don't
     read as one design with two moods, they read as two sites spliced
     together, and the light half is genuinely uncomfortable at night. L* 85,
     warmed toward the cream, closes most of that gap.

     Every accent is re-darkened for the deeper ground. Mint #4FE3C4 on stone
     is 1.5:1 — invisible. These are the same hues at 4.5:1 or better, measured
     against #DAD5C9 rather than assumed. Never reuse a dark accent on light. */
  --stone-ink: 218 213 201;    /* #DAD5C9  warm stone                     */
  --stone-ink-2: 234 230 220;  /* #EAE6DC  raised card, lifts off it      */
  --stone-ink-3: 194 186 168;  /* #C2BAA8  hairline                       */
  --stone-ink-deep: 205 199 185;
  --stone-fg: 20 26 44;        /* #141A2C  text role flips to deep ink    */
  --stone-accent: 0 102 80;    /* #006650  4.8:1                          */
  --stone-signal: 156 52 25;   /* #9C3419  4.9:1                          */
  --stone-pulse: 20 96 62;
  --stone-text-2: 74 81 100;   /* #4A5164  5.4:1                          */
  --stone-text-3: 86 93 110;   /* #565D6E  4.5:1 — the floor              */

  --focus-ring: var(--nova);

  /* --- Typography --------------------------------------------------------- */
  --font-display: 'Bricolage Grotesque', ui-sans-serif, system-ui, sans-serif;
  --font-body: 'IBM Plex Sans', ui-sans-serif, system-ui, sans-serif;
  --font-mono: 'IBM Plex Mono', ui-monospace, monospace;

  /* Fluid display scale. Never below the 16px body floor. */
  --size-display-xl: clamp(2.75rem, 7.2vw, 6.75rem);
  --size-display-lg: clamp(2.25rem, 5.6vw, 4.75rem);
  --size-display-md: clamp(2rem, 4.5vw, 3.5rem);
  --size-display-sm: clamp(1.5rem, 2.6vw, 2.25rem);
  --size-lede: clamp(1.0625rem, 1.4vw, 1.375rem);
  --size-body: 1rem;
  --size-sm: 0.875rem;
  --size-xs: 0.75rem;

  /* --- Spacing: 4/8 rhythm, plus two fluid steps for section gaps --------- */
  --space-1: 0.25rem;
  --space-2: 0.5rem;
  --space-3: 0.75rem;
  --space-4: 1rem;
  --space-5: 1.25rem;
  --space-6: 1.5rem;
  --space-8: 2rem;
  --space-10: 2.5rem;
  --space-12: 3rem;
  --space-14: 3.5rem;
  --space-16: 4rem;
  --space-section: clamp(5rem, 12vh, 10rem);
  --gutter: clamp(1.25rem, 4vw, 3.5rem);

  --shell: 84rem;
  /* Measured, not guessed: 68ch resolved to 100+ characters per line on the
     larger display sizes, because a `ch` (the width of "0") is much wider than
     the average lowercase glyph. `em` keeps the character count constant across
     font sizes — 27em lands at ~70 characters everywhere for this typeface. */
  --measure: 27em;
  --radius: 14px;
  --radius-pill: 999px;

  /* --- Motion: one easing pair and three durations, used everywhere ------- */
  --ease-enter: cubic-bezier(0.16, 1, 0.3, 1);
  --ease-exit: cubic-bezier(0.7, 0, 0.84, 0);
  --dur-micro: 180ms;
  --dur-base: 280ms;
  --dur-scene: 420ms;

  /* --- Layering. Written down so nothing invents its own z-index. --------- */
  --z-canvas: 0;          /* raised to --z-canvas-loading while loading      */
  --z-content: 10;
  --z-nav: 50;
  --z-transition: 80;
  --z-curtain: 90;        /* loader backdrop — BELOW the canvas, on purpose  */
  --z-canvas-loading: 95;
  --z-loader: 100;
  --z-dock: 70;           /* Nova — above content, below nav and the loader   */

  color-scheme: dark;
}

/* ==========================================================================
   THEME MAPPING — which palette is the ground.

   Three blocks, in this order, and the order is the whole mechanism:

     1. :root                        dark by default
     2. prefers-color-scheme: light  the system's request, unless overridden
     3. [data-theme]                 the visitor's explicit choice, wins

   The `:not([data-theme='dark'])` guard on block 2 is what lets someone on a
   light system pick dark and keep it. Without it the media query would win on
   every repaint and the toggle would appear broken.
   ========================================================================== */

/* Written once as a pair, because a role map that only exists in one direction
   is how a theme ends up half-applied. */
:root,
:root[data-theme='dark'] {
  --ink: var(--dusk-ink);
  --ink-2: var(--dusk-ink-2);
  --ink-3: var(--dusk-ink-3);
  --ink-deep: var(--dusk-ink-deep);
  --vinyl: var(--dusk-fg);
  --nova: var(--dusk-accent);
  --signal: var(--dusk-signal);
  --pulse: var(--dusk-pulse);
  --text-1: var(--vinyl);
  --text-2: var(--dusk-text-2);
  --text-3: var(--dusk-text-3);
  color-scheme: dark;
}

/* The alternating band is always the OTHER palette. */
:root [data-surface='alt'],
:root[data-theme='dark'] [data-surface='alt'] {
  --ink: var(--stone-ink);
  --ink-2: var(--stone-ink-2);
  --ink-3: var(--stone-ink-3);
  --ink-deep: var(--stone-ink-deep);
  --vinyl: var(--stone-fg);
  --nova: var(--stone-accent);
  --signal: var(--stone-signal);
  --pulse: var(--stone-pulse);
  --text-1: var(--vinyl);
  --text-2: var(--stone-text-2);
  --text-3: var(--stone-text-3);
  color-scheme: light;
}

@media (prefers-color-scheme: light) {
  :root:not([data-theme='dark']) {
    --ink: var(--stone-ink);
    --ink-2: var(--stone-ink-2);
    --ink-3: var(--stone-ink-3);
    --ink-deep: var(--stone-ink-deep);
    --vinyl: var(--stone-fg);
    --nova: var(--stone-accent);
    --signal: var(--stone-signal);
    --pulse: var(--stone-pulse);
    --text-1: var(--vinyl);
    --text-2: var(--stone-text-2);
    --text-3: var(--stone-text-3);
    color-scheme: light;
  }
  :root:not([data-theme='dark']) [data-surface='alt'] {
    --ink: var(--dusk-ink);
    --ink-2: var(--dusk-ink-2);
    --ink-3: var(--dusk-ink-3);
    --ink-deep: var(--dusk-ink-deep);
    --vinyl: var(--dusk-fg);
    --nova: var(--dusk-accent);
    --signal: var(--dusk-signal);
    --pulse: var(--dusk-pulse);
    --text-1: var(--vinyl);
    --text-2: var(--dusk-text-2);
    --text-3: var(--dusk-text-3);
    color-scheme: dark;
  }
}

:root[data-theme='light'] {
  --ink: var(--stone-ink);
  --ink-2: var(--stone-ink-2);
  --ink-3: var(--stone-ink-3);
  --ink-deep: var(--stone-ink-deep);
  --vinyl: var(--stone-fg);
  --nova: var(--stone-accent);
  --signal: var(--stone-signal);
  --pulse: var(--stone-pulse);
  --text-1: var(--vinyl);
  --text-2: var(--stone-text-2);
  --text-3: var(--stone-text-3);
  color-scheme: light;
}

:root[data-theme='light'] [data-surface='alt'] {
  --ink: var(--dusk-ink);
  --ink-2: var(--dusk-ink-2);
  --ink-3: var(--dusk-ink-3);
  --ink-deep: var(--dusk-ink-deep);
  --vinyl: var(--dusk-fg);
  --nova: var(--dusk-accent);
  --signal: var(--dusk-signal);
  --pulse: var(--dusk-pulse);
  --text-1: var(--vinyl);
  --text-2: var(--dusk-text-2);
  --text-3: var(--dusk-text-3);
  color-scheme: dark;
}

/* ==========================================================================
   THE BAND ITSELF

   A band is opaque only when it is the STONE half. The dusk half stays
   transparent so the fixed 3D canvas — which sits between the body background
   and the content layer — shows through it. That is true in both themes: in
   dark mode the page is dusk and the scene fills it; in light mode the page is
   opaque stone and the scene appears only inside the dusk bands, like windows.

   The design reads the same either way, and bloom never has to sit on stone.
   ========================================================================== */

[data-surface='alt'] {
  position: relative;
  z-index: var(--z-content);
  color: rgb(var(--text-1));

  /* The seam is a hard edge, deliberately. An earlier version faded the
     surfaces into each other over 5rem, but a gradient between near-black and
     near-white is a ramp through grey, and painted over a live 3D scene that
     grey reads as a smudge — a rendering fault rather than a transition. A
     clean edge with a hairline reads as a decision. */
  border-top: 1px solid rgb(var(--nova) / 0.5);
  border-bottom: 1px solid rgb(var(--nova) / 0.5);
}

/* EVERY surface paints its own ground. No exceptions.

   An earlier version made the dusk band transparent in light mode so the 3D
   canvas could show through it like a window. Two things killed it, both
   measured rather than argued:

   1. When the canvas was absent — no WebGL, Save-Data, reduced motion, or just
      before the lazy import resolves — the band fell through to the stone page
      beneath. Cream text on stone: **1.09:1**. Unreadable, on exactly the
      devices least able to render the thing that was meant to be behind it.
   2. The canvas is fixed at z-index 0 and `main` has no background of its own,
      so in light mode it bled across the whole stone page, not just the bands.

   So: the scene belongs to the dark theme, where the page IS its stage. Light
   mode is the reading mode — stone page, opaque dusk bands, no canvas. Both
   look deliberate; neither depends on decoration to stay legible. */
[data-surface='alt'],
:root[data-theme='light'] { background: rgb(var(--ink)); }

/* No canvas on a stone page, except during intro loading where Nova performs over the dark curtain. */
:root[data-theme='light'] #scene:not([data-loading='true']) { display: none; }
@media (prefers-color-scheme: light) {
  :root:not([data-theme='dark']) { background: rgb(var(--ink)); }
  :root:not([data-theme='dark']) #scene:not([data-loading='true']) { display: none; }
}
html.no-intro[data-theme='light'] #scene { display: none !important; }
@media (prefers-color-scheme: light) {
  html.no-intro:not([data-theme='dark']) #scene { display: none !important; }
}

/* Glass was tuned for a dark room: a white film over deep navy. On stone the
   film has to invert or the cards vanish. Keyed on the resolved palette, not
   on the theme, so it is correct in a band as well as on the page. */
[data-surface='alt'] .glass,
:root[data-theme='light'] .glass {
  background: linear-gradient(
    160deg,
    rgb(var(--ink-2)) 0%,
    rgb(var(--ink-2)) 55%,
    rgb(var(--vinyl) / 0.04) 100%
  );
  border-color: rgb(var(--vinyl) / 0.12);
  box-shadow: 0 1px 2px rgb(var(--vinyl) / 0.04), 0 12px 32px rgb(var(--vinyl) / 0.05);
}

@media (prefers-color-scheme: light) {
  :root:not([data-theme='dark']) .glass {
    background: linear-gradient(
      160deg,
      rgb(var(--ink-2)) 0%,
      rgb(var(--ink-2)) 55%,
      rgb(var(--vinyl) / 0.04) 100%
    );
    border-color: rgb(var(--vinyl) / 0.12);
    box-shadow: 0 1px 2px rgb(var(--vinyl) / 0.04), 0 12px 32px rgb(var(--vinyl) / 0.05);
  }
  /* ...but a dusk band inside a light page needs the ORIGINAL dark glass back. */
  :root:not([data-theme='dark']) [data-surface='alt'] .glass {
    background: linear-gradient(
      160deg,
      rgb(var(--vinyl) / 0.07) 0%,
      rgb(var(--vinyl) / 0.025) 55%,
      rgb(var(--ink-2) / 0.72) 100%
    );
    border-color: rgb(var(--vinyl) / 0.1);
    box-shadow: none;
  }
}
:root[data-theme='light'] [data-surface='alt'] .glass {
  background: linear-gradient(
    160deg,
    rgb(var(--vinyl) / 0.07) 0%,
    rgb(var(--vinyl) / 0.025) 55%,
    rgb(var(--ink-2) / 0.72) 100%
  );
  border-color: rgb(var(--vinyl) / 0.1);
  box-shadow: none;
}

/* The scrim holds contrast over the live canvas. On an opaque stone surface
   there is no canvas to fight, so it would only add a cream halo. */
/* The scrim exists to hold text contrast over the live canvas. On any opaque
   surface — a band in either theme, or the whole page in light mode — there is
   no canvas to fight, so it would only add a halo. */
[data-surface='alt'] .scrim::before,
:root[data-theme='light'] .scrim::before { display: none; }
@media (prefers-color-scheme: light) {
  :root:not([data-theme='dark']) .scrim::before { display: none; }
}

[data-surface='alt'] .btn--ghost { border-color: rgb(var(--vinyl) / 0.24); }

/* On phones, --size-sm carries real body copy (card bodies, summaries, form
   help), so it has to clear the 16px floor — below that iOS auto-zooms on
   focus and small text stops being comfortable one-handed. */
@media (max-width: 640px) {
  :root {
    --size-sm: 1rem;
    --size-xs: 0.8125rem;
  }
}
