/* ============================================================================
   NOVA — DESIGN TOKENS                                              spec §2
   ----------------------------------------------------------------------------
   Components reference tokens only, never raw values. Dark mode is a token
   swap, not a second stylesheet.

   Every colour pair in here is asserted by tools/contrast-check.mjs, which
   parses THIS FILE rather than a copy of its values, so the check cannot drift
   from what ships.  npm run check:contrast
   ========================================================================= */

:root {
  /* ---- Colour · base ------------------------------------------------------
     Near-monochrome. 95% of every page. */
  --bg-base:          #FFFFFF;
  --bg-subtle:        #F6F7F9;
  --bg-raised:        #FFFFFF;

  /* Hairlines are 1px at low alpha, never solid grey. These two are
     DECORATIVE — dividers, card edges, structural rules. WCAG 2.2 1.4.11
     exempts them, and it has to: a 7% alpha rule can only ever reach about
     1.4:1 against the page. Anything that communicates a component boundary
     or a state uses --border-interactive below, which does clear 3:1. */
  --border-hairline:  rgba(0,0,0,.07);
  --border-strong:    rgba(0,0,0,.14);
  --border-interactive: rgba(0,0,0,.46);   /* form fields, toggles, chips */

  --text-primary:     #0B0C0E;
  --text-secondary:   #52565E;
  --text-tertiary:    #6B7078;             /* 4.65:1 on bg-subtle — it ends up on 12-14px text, so it has to pass at that size */

  /* ---- Colour · accent ----------------------------------------------------
     ONE colour. Clickable things, and the single most important number on the
     page. Never body text, never a section background, never a decorative
     shape.

     #1D5BD6 was chosen by measurement, not by eye: it is the most saturated
     point on Nova's blue that still clears 4.5:1 BOTH as link text on the page
     (5.76:1) and as a fill under white label text (5.88:1). The brighter
     #0A84FF the app uses reaches only ~3.1:1 under white and cannot carry a
     button label. */
  --accent:           #1D5BD6;
  --accent-hover:     #1749AE;
  --accent-contrast:  #FFFFFF;

  /* ---- Colour · semantic --------------------------------------------------
     Marking language. Product UI only, never chrome. */
  --signal-strong:    #12794A;   /* topic mastered */
  --signal-warn:      #9A5B00;   /* needs work */
  --signal-danger:    #C0271F;   /* not started / wrong */

  /* ---- Colour · paper ----------------------------------------------------
     The §3.9 dart is a sheet of exam paper, and exam paper is white in a dark
     room too. So these do NOT invert with the theme: a lit sheet reads as
     paper in both, and flipping it to dark-grey-on-dark would make the fold
     invisible at the exact moment it has to be legible. Dark mode dims the
     stock very slightly so it is not a torch.
     Illustration only — never text on a page, so 1.4.3 does not apply, and
     the ink measures ~16:1 on the stock regardless. */
  --paper:          #FAFAF7;
  --paper-back:     #EFEFE9;     /* the reverse, and two layers of it */

  /* The §3.9 dart also renders FULL-SCREEN behind the section's copy, and a
     white sheet behind body text would destroy its contrast. So in that mode
     the sheet is lit BETWEEN these two tones — both a small step from the page
     background — and the fold reads through shading and creases rather than
     through brightness. Text over the brightest of it still measures >17:1
     here and >10:1 in the dark theme. */
  --paper-shadow:   #E7E7E1;
  --paper-lit:      #FFFFFF;
  --paper-rule:     rgba(0,0,0,.10);
  --paper-crease:   rgba(0,0,0,.15);
  --paper-ink:      #16181A;
  --paper-ink-dim:  rgba(0,0,0,.48);
  --paper-scrawl:   rgba(0,0,0,.14);

  /* ---- Type --------------------------------------------------------------
     One sans, one mono. No third family. Inter is self-hosted as a single
     variable Latin subset (47kB) rather than three static weights. */
  --font-sans: 'Inter var', 'Inter', -apple-system, BlinkMacSystemFont,
               'Segoe UI', Helvetica, Arial, sans-serif;
  --font-mono: 'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, monospace;

  /* Six sizes. That is the entire scale — if a design needs a seventh, the
     design is wrong. Tracking tightens as size grows, which is what stops
     large type looking loose and small type looking cramped. */
  --text-xs:    12px;  --lh-xs:   16px;  --tr-xs:   0;
  --text-sm:    14px;  --lh-sm:   20px;  --tr-sm:   0;
  --text-base:  16px;  --lh-base: 26px;  --tr-base: -0.005em;
  --text-lg:    20px;  --lh-lg:   30px;  --tr-lg:   -0.01em;
  --text-xl:    30px;  --lh-xl:   36px;  --tr-xl:   -0.02em;
  --text-2xl:   clamp(32px, 5vw, 44px);  --lh-2xl: 1.09;  --tr-2xl: -0.028em;
  --text-3xl:   clamp(36px, 8vw, 64px);  --lh-3xl: 1.0;   --tr-3xl: -0.035em;

  --measure: 68ch;                 /* body copy max width */

  /* ---- Space · 4px scale ------------------------------------------------- */
  --s-1: 4px;   --s-2: 8px;   --s-3: 12px;  --s-4: 16px;
  --s-6: 24px;  --s-8: 32px;  --s-12: 48px; --s-16: 64px;
  --s-24: 96px; --s-32: 128px;
  --section-y: var(--s-24);        /* 96 mobile, 128 desktop — see below */

  /* ---- Radius ----------------------------------------------------------- */
  --r-sm: 6px;  --r-md: 10px;  --r-lg: 16px;  --r-xl: 24px;

  /* ---- Elevation --------------------------------------------------------
     Border plus a very soft shadow. Two levels, maximum. */
  --e-1: 0 1px 2px rgba(0,0,0,.04);
  --e-2: 0 1px 2px rgba(0,0,0,.04), 0 8px 24px rgba(0,0,0,.04);

  /* ---- Motion ----------------------------------------------------------- */
  --ease-out:    cubic-bezier(0.16, 1, 0.3, 1);
  --ease-in-out: cubic-bezier(0.65, 0, 0.35, 1);
  --dur-instant: 100ms;
  --dur-fast:    180ms;
  --dur-base:    260ms;
  --dur-slow:    420ms;
  --stagger:     40ms;

  /* ---- Scroll reveal ------------------------------------------------------
     Deliberately far slower than the UI durations above, and kept in its own
     tokens so raising it never makes a menu or a toast sluggish.

     These are the values measured off apple.com/mac-mini rather than guessed:
     copy rises 35px over 1.4s on ease-out-quart, with siblings about 80ms
     apart. The curve puts nearly all the movement in the first third and then
     glides, which is what makes the motion read as weight rather than as an
     animation playing. At the 420ms the reveals used before, the same movement
     reads as a UI transition — correct for a button, wrong for a headline
     arriving. */
  --ease-reveal: cubic-bezier(0.25, 1, 0.5, 1);
  --dur-reveal:  1400ms;
  --reveal-rise: 35px;
  --reveal-step: 80ms;

  --focus-ring: 2px solid var(--accent);
  --focus-offset: 2px;
}

@media (min-width: 768px) {
  :root { --section-y: var(--s-32); }
}

/* ============================================================================
   DARK — a token swap only. No selector below this line may appear anywhere
   else in the codebase.
   ========================================================================= */
:root[data-theme="dark"] {
  --bg-base:          #000000;
  --bg-subtle:        #0E1011;
  --bg-raised:        #141618;

  --border-hairline:  rgba(255,255,255,.08);
  --border-strong:    rgba(255,255,255,.16);
  --border-interactive: rgba(255,255,255,.38);

  --text-primary:     #F2F3F5;
  --text-secondary:   #9BA1A9;
  --text-tertiary:    #80868E;             /* 4.94:1 on bg-raised; was #6B7178 at 3.68:1 */

  /* Inverted relationship: on a near-black page the accent has to be LIGHT to
     clear 4.5:1 as link text, which in turn means its label must be dark. */
  --accent:           #5B9DFF;
  --accent-hover:     #7FB2FF;
  --accent-contrast:  #08090A;

  /* Paper dims slightly in the dark, but stays paper — see the light block. */
  --paper:        #ECECE6;
  --paper-back:   #DEDED7;
  --paper-shadow: #12161A;
  --paper-lit:    #313A41;

  --signal-strong:    #3FD08A;
  --signal-warn:      #F0B45C;
  --signal-danger:    #FF6B5E;

  --e-1: 0 1px 2px rgba(0,0,0,.5);
  --e-2: 0 1px 2px rgba(0,0,0,.5), 0 8px 24px rgba(0,0,0,.4);
}

/* Follow the OS unless the user has explicitly chosen light. Both directions
   are declared so a toggle wins either way. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --bg-base:#000000; --bg-subtle:#0E1011; --bg-raised:#141618;
    --border-hairline:rgba(255,255,255,.08); --border-strong:rgba(255,255,255,.16);
    --border-interactive:rgba(255,255,255,.38);
    --text-primary:#F2F3F5; --text-secondary:#9BA1A9; --text-tertiary:#80868E;
    --accent:#5B9DFF; --accent-hover:#7FB2FF; --accent-contrast:#08090A;
    --signal-strong:#3FD08A; --signal-warn:#F0B45C; --signal-danger:#FF6B5E;
    --paper:#ECECE6; --paper-back:#DEDED7;
    --paper-shadow:#12161A; --paper-lit:#313A41;
    --e-1: 0 1px 2px rgba(0,0,0,.5);
    --e-2: 0 1px 2px rgba(0,0,0,.5), 0 8px 24px rgba(0,0,0,.4);
  }
}

/* ============================================================================
   FONT — self-hosted, one file. font-display:optional means the browser uses
   the fallback rather than swapping late, so this can never cause a layout
   shift or a flash of re-rendered text. It is preloaded in each document head,
   which is what makes it usually available anyway.
   ========================================================================= */
@font-face {
  font-family: 'Inter var';
  font-style: normal;
  font-weight: 100 900;
  font-display: optional;
  src: url('../assets/fonts/inter-latin-var.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6,
                 U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191,
                 U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

/* ============================================================================
   REDUCED MOTION — hard requirement, no exceptions. Transitions collapse and
   transforms become opacity-only.
   ========================================================================= */
@media (prefers-reduced-motion: reduce) {
  :root {
    --dur-instant: 1ms; --dur-fast: 1ms; --dur-base: 1ms; --dur-slow: 1ms;
    --stagger: 0ms;
  }
  *, *::before, *::after {
    animation-duration: 1ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 1ms !important;
    scroll-behavior: auto !important;
  }
}
