/* ==========================================================================
   8-BIT NES · tokens.css  —  SINGLE SOURCE OF TRUTH
   NES arcade CRT, dark-only. Modern-crisp: hi-res mono, not lo-res pixel font.
   Change the look here, never in components. Every hex/space flows from :root.
   ========================================================================== */

@layer tokens, base, components, utilities;

/* ---- fonts: self-host woff2 subset (Latin + Vietnamese). Drop files in ./fonts ---- */
/* ---- fonts: self-hosted woff2, Latin + Vietnamese subset (verified full VN coverage).
   Crisp-render note: preload the two critical files in <head> for zero-FOUT:
   <link rel="preload" as="font" type="font/woff2" href="./fonts/nes-mono-400.woff2" crossorigin>
   <link rel="preload" as="font" type="font/woff2" href="./fonts/nes-sans-var.woff2" crossorigin> ---- */
@font-face {
  font-family: "NES Mono";
  src: url("./fonts/nes-mono-400.woff2") format("woff2");
  font-weight: 400;
  font-style: normal;
  font-display: swap;
  unicode-range: U+0000-024F, U+0259, U+1E00-1EFF, U+2000-206F, U+20AB, U+2190-21FF, U+25A0-25FF;
}

@font-face {
  font-family: "NES Mono";
  src: url("./fonts/nes-mono-700.woff2") format("woff2");
  font-weight: 700;
  font-style: normal;
  font-display: swap;
  unicode-range: U+0000-024F, U+0259, U+1E00-1EFF, U+2000-206F, U+20AB, U+2190-21FF, U+25A0-25FF;
}

@font-face {
  font-family: "NES Sans";
  src: url("./fonts/nes-sans-var.woff2") format("woff2");
  font-weight: 300 700;
  font-style: normal;
  font-display: swap;
  unicode-range: U+0000-024F, U+0259, U+1E00-1EFF, U+2000-206F, U+20AB;
}

@layer tokens {
  :root {
    /* ---- grounds: navy, chosen (not grey). The cabinet → screen → panels ---- */
    --bg: #07071c;
    /* outer cabinet */
    --screen: #0a0a24;
    /* the "screen" surface, page bg */
    --panel: #15153f;
    /* raised card face */
    --panel-2: #1c1c56;
    /* layer-2 / secondary control */
    --slot: #0d0d2b;
    /* recessed: code, input, empty seg */
    --line: #000000;
    /* dark border + hard shadow — pure black for crisp definition */
    --line-hi: #4646a0;
    /* inner highlight border / hover — brighter for definition */

    /* ---- ink hierarchy: four rungs, and the ladder is built on TWO axes.
       Luminance was never the problem — every rung already cleared AAA. The problem
       was chroma: the old ladder got *bluer* as it got darker (7 → 23 → 49 → 58),
       so --muted was blue text on a navy ground. The eye had luminance separation
       and almost no hue separation, which reads as soft however high the ratio is.
       Chroma is nearly flat now (7 → 23 → 24 → 30) and the lightness steps are
       even (Δ L* 4.9 / 6.3 / 7.7 — the old 12.2 cliff between text and muted is
       what made muted feel like it dropped out). Ratios on --panel, measured by
       `pnpm check:contrast`, which fails the build below AAA on any ground. ---- */
    --ink: #f8f9ff;
    /* titles, numbers                    · L* 98.0 · 16.50:1 */
    --text: #e8eaff;
    /* body / description                 · L* 93.1 · 14.57:1 */
    --muted: #d6d8ee;
    /* labels, meta, table cells          · L* 86.8 · 12.32:1 (was 10.46) */
    --dim: #c0c2de;
    /* scope, hints, captions             · L* 79.1 ·  9.92:1 (was  7.95) */

    /* ---- brand palette: each accent = base + deep pair.
       base   → fills, dots, bars, text-on-dark
       -d     → hover / pressed / borders-on-fill / gradients / charts ---- */
    --blue: #5c94fc;
    --blue-d: #2f4fb0;    /* primary (Mario sky) */
    --gold: #fbd000;
    --gold-d: #c99700;    /* XP / highlight / eyebrow / CTA */
    --cyan: #33e0e0;
    --cyan-d: #1f9e9e;    /* code / gem / info */
    --purple: #b759e5;
    --purple-d: #7d33a8;  /* special / magic — 4.60:1 as text on --panel (was 4.42, under AA) */

    /* ---- extended wheel: fills the hue gaps so `data-accent` spans the circle.
       Same base + -d contract; each vivid enough for --ink-on-accent on the fill. */
    --lime: #b8e62e;
    --lime-d: #75980f;    /* fresh / eco / level-up */
    --teal: #2ad8b8;
    --teal-d: #158f78;    /* calm info / secondary */
    --indigo: #7c7cff;
    --indigo-d: #4a45c8;  /* deep primary / link */
    --pink: #ff6ec7;
    --pink-d: #c23d90;    /* playful / accent-2 / like */
    --steel: #9aa2d8;
    --steel-d: #565d95;   /* neutral chip / muted tag */

    /* ---- semantic palette: same base + deep contract, kept apart from brand ---- */
    --good: #56d364;
    --good-d: #2f9e3f;    /* active / success / stage clear */
    --warn: #ff9e2c;
    --warn-d: #c46e00;    /* pending / caution */
    --crit: #f23c4e;
    --crit-d: #a81f2e;    /* critical / error / blocked / boss — 4.57:1 as text on
                             --panel (was 4.17, i.e. error text failed AA), and its
                             fill went 4.71 → 5.16 against --ink-on-accent */

    /* ink drawn ON TOP of a solid accent (never white on gold/green/cyan) */
    --ink-on-accent: #0a0a1a;

    /* ---- primary = the go / active color. Green (success) by design so the
       palette maps to intuition: green = primary/positive, gold = warn/reward,
       red = error. Every component's default --accent points here — retheme in
       one line. ---- */
    --primary: var(--good);
    --primary-d: var(--good-d);

    /* ---- typography roles: 2 faces, deliberate pairing ---- */
    --font-mono: "NES Mono", ui-monospace, "Cascadia Code", Menlo, monospace;
    --font-body: "NES Sans", system-ui, -apple-system, sans-serif;

    /* type scale (rem, 16px base). Only the rungs actually used — and every rung is
       an INTEGER px at the default root size. A fractional font size gives every
       text-sized box a fractional height, which puts its hard border on a half
       pixel; in a system with no blur and no radius there is nothing to hide that
       behind. (Kept in rem so a reader's own font size still scales the UI.) */
    --fs-label: .5625rem;
    /* 9px  · eyebrow / scope / uppercase mono   */
    --fs-chip: .6875rem;
    /* 11px · chip / value / code inline         */
    --fs-h3: .75rem;
    /* 12px · section mono heading               */
    --fs-body: .875rem;
    /* 14px · sans body                          */
    --fs-lead: 1rem;
    /* 16px · lead paragraph / prose h2          */
    --fs-h2: 1.0625rem;
    /* 17px · world / card title                 */
    --fs-h1: 1.625rem;
    /* 26px · hero / power number                */
    --fs-code: .9em;
    /* inline code / @mention — RELATIVE on purpose: an atom inside a sentence
       tracks the sentence, whatever rung that sentence is set in. */

    --lh-tight: 1.25;
    --lh-body: 1.65;
    --lh-heading: 1.4;
    --lh-none: 1;
    /* single-line chrome: a key cap, a close ×, a rating star — the box owns the
       height (--ctrl-h-*), so the line must not add any of its own. */
    --ls-chrome: .06em;
    /* letter-spacing for uppercase mono chrome  */

    /* ---- weight: EXACTLY the weights the bundled faces ship.
       NES Mono has 400 and 700 only; NES Sans is variable 300–700. Asking for 500
       or 600 on mono makes the browser synthesise a bold — it smears the stems and
       kills the crispness the whole system is built on. Stay on these three. ---- */
    --fw-regular: 400;
    --fw-medium: 450;
    /* sans body — a hair above regular, still one axis stop, never synthetic */
    --fw-bold: 700;

    /* ---- icon glyph sizes: their own scale, because an icon is sized by its box,
       not by the text rung next to it. All on the 2px grid. ---- */
    --icon-sm: 14px;
    --icon-md: 20px;
    --icon-lg: 28px;
    --icon-xl: 40px;

    /* ---- spacing scale (4-based, DRY) ---- */
    --sp-1: .25rem;
    --sp-2: .5rem;
    --sp-3: .75rem;
    --sp-4: 1rem;
    --sp-5: 1.5rem;
    --sp-6: 2rem;
    --sp-7: 3rem;

    /* THE GRID LAW — two rules, enforced by `pnpm check` (scripts/check-scale.mjs):
       · space  lands on 4px steps → always a --sp-* rung (or --sp-hair below)
       · size   lands on 2px steps → a square marker edge never falls mid-pixel
       A hard 90° system has nothing to hide a half-pixel behind, so a 9.6px dot
       beside an 11.2px marker reads as two different greys. On-grid = crisp. */

    /* the one sub-grid rung: a hairline of space. Same weight as --bw-2, so a
       seam between tiles and a border between them are the same pixel. Use it
       for grid/menu gaps and for chips too short for --sp-1. */
    --sp-hair: 2px;

    /* ---- box padding roles: the three densities the recipes actually use.
       Inline padding sits one rung above block padding — the system's optical
       rule (text needs more air sideways). Retune density for a whole app in
       three lines instead of hunting 40 declarations. ---- */
    --pad-tight: var(--sp-1) var(--sp-2);
    /*  4/8  · badges, dense rows      */
    --pad-snug: var(--sp-2) var(--sp-3);
    /*  8/12 · list rows, cells, chips */
    --pad-box: var(--sp-3) var(--sp-4);
    /* 12/16 · cards, panels, sections */

    /* ---- inline padding, in em so it scales with the text it sits in.
       Two optical classes, because they answer different questions:
       chip = a standalone thing in a row (kbd, tooltip, handle)
       atom = a thing INSIDE a sentence (inline code, @mention) — must not
              fatten the line box, so its block padding stays near zero. ---- */
    --chip-py: .25em;
    --chip-px: .5em;
    --atom-py: .05em;
    --atom-px: .35em;

    /* ---- shape: system is square (90° corners), never a radius.
       --notch only drives the optional .pixel-box helper — not buttons. ---- */
    --notch: 6px;
    /* corner cut size for the .pixel-box utility */
    --bw: 3px;
    /* standard "pixel box" border */
    --bw-2: 2px;
    /* small elements */
    --bw-1: 1px;
    /* hairline: dots, tag pills, tiny markers */
    --accent-bar: 6px;
    /* left classifier bar */
    /* No radius token by design — the whole system is square, no exceptions. */

    /* square markers — the two that repeat across modules. Both on the 2px grid
       so their edges stay hard. Anything else square is a one-off: snap it to
       2px and keep it local (see --sz / --wb-* for that pattern). */
    --dot: 10px;
    /* run-state dot: .agent · .sandbox · .runbar · .deploy-stage · radio pip */
    --pip: 12px;
    /* step marker: .trace-step · .check-item · .ckpt-item · legend swatch     */

    /* ---- SIGNATURE elevation: hard shadow, zero blur, pure black ---- */
    --sh-1: 1px 1px 0 var(--line);
    /* pressed */
    --sh-2: 2px 2px 0 var(--line);
    /* badge / small control */
    --sh-3: 3px 3px 0 var(--line);
    /* button / logo */
    --sh-4: 4px 4px 0 var(--line);
    /* standard card */
    --sh-5: 6px 6px 0 var(--line);
    /* hero / big card */

    /* ---- motion: smooth & high-FPS. Short durations + a single ease-out curve.
       Prefer animating transform/opacity (compositor-only) for 60fps+ feel. ---- */
    --dur-fast: .12s;
    /* hover / press micro-motion */
    --dur-mid: .2s;
    /* enters / toasts */
    --dur-slow: .34s;
    /* progress fills */
    --ease: cubic-bezier(.22, 1, .36, 1);
    /* ease-out (quint) — snappy start, soft landing */
    --ease-fill: cubic-bezier(.22, 1, .36, 1);
    /* progress glides, no stutter */

    /* ---- form controls: ONE shared size scale so a button, input, select,
       chip… at the same size line up pixel-for-pixel in a row. Pick a rung with
       data-size="xs|sm|md|lg|xl" (or the .xs/.sm/.lg/.xl button classes); md is
       the default. Each rung fixes height + inline-padding + chrome font. ---- */
    --ctrl-h-xs: 1.75rem;   /* 28px */
    --ctrl-h-sm: 2rem;      /* 32px */
    --ctrl-h-md: 2.25rem;   /* 36px · default */
    --ctrl-h-lg: 2.75rem;   /* 44px · WCAG 2.5.5 touch */
    --ctrl-h-xl: 3.25rem;   /* 52px */

    --ctrl-px-xs: var(--sp-2);
    --ctrl-px-sm: var(--sp-3);
    --ctrl-px-md: var(--sp-3);
    --ctrl-px-lg: var(--sp-4);
    --ctrl-px-xl: var(--sp-5);

    /* chrome (mono, uppercase) font per rung — for buttons/chips/etc. */
    --ctrl-fs-xs: var(--fs-label);
    --ctrl-fs-sm: var(--fs-label);
    --ctrl-fs-md: var(--fs-chip);
    --ctrl-fs-lg: var(--fs-h3);
    --ctrl-fs-xl: var(--fs-h2);

    /* the active rung (md); data-size / the size classes remap these three */
    --ctrl-h: var(--ctrl-h-md);
    --ctrl-pad: var(--ctrl-px-md);
    --ctrl-fs: var(--ctrl-fs-md);

    /* ---- touch: two tap sizes, and the reason each exists.
       WCAG 2.5.8 (AA) sets the FLOOR at 24×24px — nothing interactive may be
       smaller on a coarse pointer, and this library's check measures it on a real
       phone viewport. 2.5.5 (AAA) asks for 44×44, which is what a thumb actually
       wants, so that is the default. --tap-dense is for list rows and chips where
       44px each would push the content of a phone screen off the bottom; it still
       clears the floor by 16px. A visual box never has to grow to satisfy this —
       an invisible ::after hit area works and keeps the pixel look (see .wt-dot,
       .checkbox, .radio, .switch). ---- */
    --tap: var(--ctrl-h-lg);
    /* 44px · anything you press to act */
    --tap-dense: 2.5rem;
    /* 40px · rows in a list you scroll through */

    /* ---- stacking: ONE ladder, so nothing ever has to guess a number. Each rung
       answers a different question about how far out of the flow a thing sits. ---- */
    --z-raised: 1;
    /* a sibling lifted above its neighbours (a pressed button in a group)  */
    --z-pop: 2;
    /* something popping over its OWN module (annotation popover, drag ghost) */
    --z-sticky: 20;
    /* sticks inside the page (a rail, a table header)                       */
    --z-drawer: 30;
    /* off-canvas panel + its scrim (scrim = calc(var(--z-drawer) - 1))      */
    --z-chrome: 40;
    /* app chrome that outranks the page (sticky top bar)                    */
    --z-overlay: 100;
    /* dropdown menu / tooltip / toast / dialog */
    --z-top: 9999;
    /* the CRT overlay, and nothing else — it must never be covered          */

    /* ---- state opacity: two answers, not eight ---- */
    --op-disabled: .6;
    /* :disabled, and anything already decided/settled */
    --op-dim: .3;
    /* data pushed to the background (a dimmed series, a raw source)         */

    /* ---- focus ring: retheme focus in ONE line by setting --ring-c.
       Width and colour only — the offset stays in base.css because it has to
       cancel the border, and box-shadow is deliberately untouched so a focused
       control keeps its hard shadow. ---- */
    --ring-w: var(--bw-2);
    --ring-c: var(--gold);

    /* ---- breakpoints: THREE, and only three.
       This system is intrinsic-first — flex-wrap, auto-fit grids, clamp(),
       min()/max() — so a module normally needs no media query at all (6k lines
       of components.css contain two). These are the only widths anything is
       allowed to switch at, so two modules on one page never disagree.

       CSS cannot read a var() inside @media, so a query writes the literal rem
       value; `pnpm check` fails if a literal drifts off this ladder. JS reads
       the token instead: matchMedia(`(min-width: ${bp("lg")})`). ---- */
    --bp-sm: 36rem;
    /*  576px · a split module drops to one column      */
    --bp-lg: 56rem;
    /*  896px · a persistent sidebar/rail fits          */
    --bp-xl: 74rem;
    /* 1184px · a THIRD column (right rail) fits        */
    --bp-2xl: 100rem;
    /* 1600px · a large desktop: the shell can breathe and the type steps up */

    /* ---- app shell: the page frame, named once so two apps built on 8-bit
       line up — and so "make it tighter" is one line, not a sweep. ---- */
    --gutter: var(--sp-4);
    /* page side padding. Stepped on the ladder below, not clamp(…4vw…): a fluid
       gutter lands on 47.36px at one width and 43.2px at another, and this system
       is rung-based everywhere else (type, controls, space). 16 → 24 → 48. */
    --chrome-h: var(--ctrl-h-xl);
    /* sticky top bar — exactly one xl control tall, so a search input fills it */
    --nav-w: 15rem;
    /* 240px · left navigation column  */
    --rail-w: 14rem;
    /* 224px · right rail (<nes-toc>, .wb-rail) */

    /* ---- atmosphere (kept restrained · Chanel rule) ---- */
    --scanline-opacity: .06;
    /* CRT lines, subtle */
    --maxw: 960px;

    /* reading measure for .prose children — the text, not the container, so a table
       or a diagram beside it keeps the full width it needs */
    --prose-measure: 72ch;

    /* diagram label size for <nes-mermaid>. Its own rung because a label is not body
       copy: it may go smaller than the reading size without dragging prose down.
       Falls back to --fs-body so a page that sets nothing stays coherent. */
    --mmd-fs: var(--fs-body);
  }

  /* the gutter is the one token that steps with the viewport — the two queries the
     ladder exists for. Mobile-first `min-width`, so the phone value needs no query. */
  @media (min-width: 36rem) {
    :root {
      --gutter: var(--sp-5);
    }
  }

  @media (min-width: 56rem) {
    :root {
      --gutter: var(--sp-7);
    }
  }

  /* ---- large desktop (--bp-2xl): one step up the SAME scale, not a new one.
     14px body is right for a laptop and small on a 27" screen at arm's length, so
     every rung moves one notch: 10 · 12 · 13 · 16 · 18 · 20 · 32. Still integer px
     at the default root, still monotonic — the two things `pnpm check` and
     scripts/scale-check.html assert about the type scale.

     Stepped, never fluid: `clamp()` would land body copy on 15.37px at some widths,
     and a fractional font size gives every text-sized box a fractional height.
     Spacing does NOT step — density stays; the extra room goes to the container
     (see --doc-maxw in docs.html), which is what a wide screen is actually for. ---- */
  @media (min-width: 100rem) {
    :root {
      --fs-label: .625rem;
      /* 10px */
      --fs-chip: .75rem;
      /* 12px */
      --fs-h3: .8125rem;
      /* 13px */
      --fs-body: 1rem;
      /* 16px */
      --fs-lead: 1.125rem;
      /* 18px */
      --fs-h2: 1.25rem;
      /* 20px */
      --fs-h1: 2rem;
      /* 32px */
    }
  }
}