/* ──────────────────────────────────────────────────────────────
 * NURI · TOKENS · PRIMITIVE
 * Theme-agnostic catalog of raw values. Pure leaves: every hex,
 * dimension and duration is declared once and is NEVER reactive
 * to [data-theme]. Theming and accent composition happen in
 * tokens-semantic.css, which references these primitives by name.
 *
 * ── Naming → DTCG $type inference (RN sync pipeline) ──────────
 *   --nuri-color-*         → color
 *   --nuri-radius-*        → dimension (corner radii)
 *   --nuri-font-size-*     → dimension
 *   --nuri-font-family-*   → fontFamily
 *   --nuri-font-weight-*   → fontWeight
 *   --nuri-duration-*      → duration
 *
 * ── Deliberately absent token families ───────────────────────
 *   --nuri-line-height-*   no primitive layer · unitless on the
 *                          type scale (decision 29).
 *   --nuri-shadow-*        no primitives today · Elevation
 *                          foundation ships with its first
 *                          consumer (per ROADMAP).
 *   --nuri-color-{green,orange,red}-500
 *                          status primitives do NOT ship · the
 *                          family lands as full Radix scales
 *                          (Jade / Amber / Red / Blue) with the
 *                          first status-using component (Toast,
 *                          Alert, StatusPill) — N+5.7 cleanup.
 *   --nuri-size-*          retired in N+6.0 · primitive layer
 *                          uses direct-pixel naming. The
 *                          --nuri-size-* prefix is reserved for
 *                          the semantic sizing layer arriving in
 *                          N+6.1 (xs · sm · md · lg · xl · 2xl ·
 *                          3xl).
 *
 * ── Hardcoded escapes retired ────────────────────────────────
 *   Historical primitive px escapes were retired; shell/archive/demo
 *   surfaces are not DS contract surfaces for this layer.
 *
 * ── Unit rule ─────────────────────────────────────────────────
 *   rem  → font-size ONLY (mirrors RN allowFontScaling)
 *   px   → everything else (spacing, radius, border)
 *
 * ── Colour shape · Radix 12-step functional scales ────────────
 * Nuri is mobile-first — steps Radix calls "hover" are read as
 * "pressed" here (see decision 3 in decisionlog.md, role legend on
 * pages/foundations/colour/primitive.html).
 *   1  app bg               7  UI border / focus
 *   2  subtle bg            8  pressed border
 *   3  UI element bg        9  solid bg (max bold)
 *   4  UI element pressed  10  solid pressed
 *   5  UI element active   11  low-contrast text
 *   6  subtle border       12  high-contrast text
 *
 * Each colour scale exposes BOTH its light and dark variants as
 * separate primitives (suffix -light / -dark), addressable in
 * parallel. The semantic layer composes mode × accent on top:
 *
 *   bg-canvas (light theme)          → neutral-1-light
 *   bg-canvas (dark theme)           → neutral-1-dark
 *   accent-solid (neutral, light)    → neutral-12-light
 *   accent-solid (neutral, dark)     → neutral-12-dark
 *   accent-solid (lilac, any theme)  → lilac-9-light   (frozen — bright)
 *
 * Exception · the black/white alpha scales have no -light/-dark
 * suffix because they are theme-invariant by nature, not by design.
 *
 * ── Neutral resolution · build-time (N+32 C1) ─────────────────
 * --nuri-color-neutral-N-{light|dark} resolve to ONE active scale,
 * GENERATED from the TS colour SoT (packages/spec/tokens/colours.ts) at the build's
 * --neutral (DEFAULT_NEUTRAL = cream · decision 31). The runtime
 * [data-neutral] switcher (8 per-scale blocks) is RETIRED — build-time
 * selection only; the other six scales stay raw primitives, reserved as
 * --neutral= options. See the NEUTRAL RESOLUTION block below.
 *
 * ── Generated regions (decision 70 · dec-2 reversed for these layers) ─
 * The --nuri-color-* values are WRITTEN by
 * `npm run build` from their TS SoT (packages/spec/tokens/colours.ts)
 * via the Slice-0 in-place passthrough. Edit the SoT, not these values.
 * ────────────────────────────────────────────────────────────── */


/* ══════════════════════════════════════════════════════════════
 * COLOUR · RAW VALUES
 * Candidate neutrals × 12 steps × 2 themes + lilac brand + alpha scales.
 * Pure leaves — never overridden by [data-theme].
 *
 * N+32 C1 · GENERATED from the TS SoT (packages/spec/tokens/colours.ts · decision 70 ·
 * dec-2 reversed for the colour-primitive layer). Edit the SoT, not here —
 * `npm run build` regenerates these values in place (Slice 0 · the
 * dimension-css.js twin). The semantic accent×theme cascade
 * (tokens-semantic.css) stays hand-authored (the cascade is C2).
 * ══════════════════════════════════════════════════════════════ */

:root {
  /* ── Gray ─────────────────────────────────────────────────── */
  --nuri-color-gray-1-light:   #fcfcfc;
  --nuri-color-gray-1-dark:    #111111;
  --nuri-color-gray-2-light:   #f9f9f9;
  --nuri-color-gray-2-dark:    #191919;
  --nuri-color-gray-3-light:   #f0f0f0;
  --nuri-color-gray-3-dark:    #222222;
  --nuri-color-gray-4-light:   #e8e8e8;
  --nuri-color-gray-4-dark:    #2a2a2a;
  --nuri-color-gray-5-light:   #e0e0e0;
  --nuri-color-gray-5-dark:    #313131;
  --nuri-color-gray-6-light:   #d9d9d9;
  --nuri-color-gray-6-dark:    #3a3a3a;
  --nuri-color-gray-7-light:   #cecece;
  --nuri-color-gray-7-dark:    #484848;
  --nuri-color-gray-8-light:   #bbbbbb;
  --nuri-color-gray-8-dark:    #606060;
  --nuri-color-gray-9-light:   #8d8d8d;
  --nuri-color-gray-9-dark:    #6e6e6e;
  --nuri-color-gray-10-light:  #838383;
  --nuri-color-gray-10-dark:   #7b7b7b;
  --nuri-color-gray-11-light:  #646464;
  --nuri-color-gray-11-dark:   #b4b4b4;
  --nuri-color-gray-12-light:  #202020;
  --nuri-color-gray-12-dark:   #eeeeee;

  /* ── Mauve ────────────────────────────────────────────────── */
  --nuri-color-mauve-1-light:   #fdfcfd;
  --nuri-color-mauve-1-dark:    #121113;
  --nuri-color-mauve-2-light:   #faf9fb;
  --nuri-color-mauve-2-dark:    #1a191b;
  --nuri-color-mauve-3-light:   #f2eff3;
  --nuri-color-mauve-3-dark:    #232225;
  --nuri-color-mauve-4-light:   #eae7ec;
  --nuri-color-mauve-4-dark:    #2b292d;
  --nuri-color-mauve-5-light:   #e3dfe6;
  --nuri-color-mauve-5-dark:    #323035;
  --nuri-color-mauve-6-light:   #dbd8e0;
  --nuri-color-mauve-6-dark:    #3c393f;
  --nuri-color-mauve-7-light:   #d0cdd7;
  --nuri-color-mauve-7-dark:    #49474e;
  --nuri-color-mauve-8-light:   #bcbac7;
  --nuri-color-mauve-8-dark:    #625f69;
  --nuri-color-mauve-9-light:   #8e8c99;
  --nuri-color-mauve-9-dark:    #6f6d78;
  --nuri-color-mauve-10-light:  #84828e;
  --nuri-color-mauve-10-dark:   #7c7a85;
  --nuri-color-mauve-11-light:  #65636d;
  --nuri-color-mauve-11-dark:   #b5b2bc;
  --nuri-color-mauve-12-light:  #211f26;
  --nuri-color-mauve-12-dark:   #eeeef0;

  /* ── Slate ────────────────────────────────────────────────── */
  --nuri-color-slate-1-light:   #fcfcfd;
  --nuri-color-slate-1-dark:    #111113;
  --nuri-color-slate-2-light:   #f9f9fb;
  --nuri-color-slate-2-dark:    #18191b;
  --nuri-color-slate-3-light:   #f0f0f3;
  --nuri-color-slate-3-dark:    #212225;
  --nuri-color-slate-4-light:   #e8e8ec;
  --nuri-color-slate-4-dark:    #272a2d;
  --nuri-color-slate-5-light:   #e0e1e6;
  --nuri-color-slate-5-dark:    #2e3135;
  --nuri-color-slate-6-light:   #d9d9e0;
  --nuri-color-slate-6-dark:    #363a3f;
  --nuri-color-slate-7-light:   #cdced6;
  --nuri-color-slate-7-dark:    #43484e;
  --nuri-color-slate-8-light:   #b9bbc6;
  --nuri-color-slate-8-dark:    #5a6169;
  --nuri-color-slate-9-light:   #8b8d98;
  --nuri-color-slate-9-dark:    #696e77;
  --nuri-color-slate-10-light:  #80838d;
  --nuri-color-slate-10-dark:   #777b84;
  --nuri-color-slate-11-light:  #60646c;
  --nuri-color-slate-11-dark:   #b0b4ba;
  --nuri-color-slate-12-light:  #1c2024;
  --nuri-color-slate-12-dark:   #edeef0;

  /* ── Sage ─────────────────────────────────────────────────── */
  --nuri-color-sage-1-light:   #fbfdfc;
  --nuri-color-sage-1-dark:    #101211;
  --nuri-color-sage-2-light:   #f7f9f8;
  --nuri-color-sage-2-dark:    #171918;
  --nuri-color-sage-3-light:   #eef1f0;
  --nuri-color-sage-3-dark:    #202221;
  --nuri-color-sage-4-light:   #e6e9e8;
  --nuri-color-sage-4-dark:    #272a29;
  --nuri-color-sage-5-light:   #dfe2e0;
  --nuri-color-sage-5-dark:    #2e3130;
  --nuri-color-sage-6-light:   #d7dad9;
  --nuri-color-sage-6-dark:    #373b39;
  --nuri-color-sage-7-light:   #cbcfcd;
  --nuri-color-sage-7-dark:    #444947;
  --nuri-color-sage-8-light:   #b8bcba;
  --nuri-color-sage-8-dark:    #5b625f;
  --nuri-color-sage-9-light:   #868e8b;
  --nuri-color-sage-9-dark:    #63706b;
  --nuri-color-sage-10-light:  #7c8481;
  --nuri-color-sage-10-dark:   #717d79;
  --nuri-color-sage-11-light:  #5f6563;
  --nuri-color-sage-11-dark:   #adb5b2;
  --nuri-color-sage-12-light:  #1a211e;
  --nuri-color-sage-12-dark:   #eceeed;

  /* ── Olive ────────────────────────────────────────────────── */
  --nuri-color-olive-1-light:   #fcfdfc;
  --nuri-color-olive-1-dark:    #111210;
  --nuri-color-olive-2-light:   #f8faf8;
  --nuri-color-olive-2-dark:    #181917;
  --nuri-color-olive-3-light:   #eff1ef;
  --nuri-color-olive-3-dark:    #212220;
  --nuri-color-olive-4-light:   #e7e9e7;
  --nuri-color-olive-4-dark:    #282a27;
  --nuri-color-olive-5-light:   #dfe2df;
  --nuri-color-olive-5-dark:    #2f312e;
  --nuri-color-olive-6-light:   #d7dad7;
  --nuri-color-olive-6-dark:    #383a36;
  --nuri-color-olive-7-light:   #cccfcc;
  --nuri-color-olive-7-dark:    #454843;
  --nuri-color-olive-8-light:   #b9bcb8;
  --nuri-color-olive-8-dark:    #5c625b;
  --nuri-color-olive-9-light:   #898e87;
  --nuri-color-olive-9-dark:    #687066;
  --nuri-color-olive-10-light:  #7f847d;
  --nuri-color-olive-10-dark:   #767d74;
  --nuri-color-olive-11-light:  #60655f;
  --nuri-color-olive-11-dark:   #afb5ad;
  --nuri-color-olive-12-light:  #1d211c;
  --nuri-color-olive-12-dark:   #eceeec;

  /* ── Sand ─────────────────────────────────────────────────── */
  --nuri-color-sand-1-light:   #fdfdfc;
  --nuri-color-sand-1-dark:    #111110;
  --nuri-color-sand-2-light:   #f9f9f8;
  --nuri-color-sand-2-dark:    #191918;
  --nuri-color-sand-3-light:   #f1f0ef;
  --nuri-color-sand-3-dark:    #222221;
  --nuri-color-sand-4-light:   #e9e8e6;
  --nuri-color-sand-4-dark:    #2a2a28;
  --nuri-color-sand-5-light:   #e2e1de;
  --nuri-color-sand-5-dark:    #31312e;
  --nuri-color-sand-6-light:   #dad9d6;
  --nuri-color-sand-6-dark:    #3b3a37;
  --nuri-color-sand-7-light:   #cfceca;
  --nuri-color-sand-7-dark:    #494844;
  --nuri-color-sand-8-light:   #bcbbb5;
  --nuri-color-sand-8-dark:    #62605b;
  --nuri-color-sand-9-light:   #8d8d86;
  --nuri-color-sand-9-dark:    #6f6d66;
  --nuri-color-sand-10-light:  #82827c;
  --nuri-color-sand-10-dark:   #7c7b74;
  --nuri-color-sand-11-light:  #63635e;
  --nuri-color-sand-11-dark:   #b5b3ad;
  --nuri-color-sand-12-light:  #21201c;
  --nuri-color-sand-12-dark:   #eeeeec;

  /* ── Cream (Radix custom · warm off-white) ────────────────── */
  --nuri-color-cream-1-light:   #fffdf2;
  --nuri-color-cream-1-dark:    #12110b;
  --nuri-color-cream-2-light:   #fbf9ee;
  --nuri-color-cream-2-dark:    #1a1913;
  --nuri-color-cream-3-light:   #f3f1e2;
  --nuri-color-cream-3-dark:    #242319;
  --nuri-color-cream-4-light:   #ece9da;
  --nuri-color-cream-4-dark:    #2c2a1e;
  --nuri-color-cream-5-light:   #e5e2d1;
  --nuri-color-cream-5-dark:    #343124;
  --nuri-color-cream-6-light:   #dddac9;
  --nuri-color-cream-6-dark:    #3d3b2e;
  --nuri-color-cream-7-light:   #d2cfbf;
  --nuri-color-cream-7-dark:    #4b483b;
  --nuri-color-cream-8-light:   #bfbcac;
  --nuri-color-cream-8-dark:    #636153;
  --nuri-color-cream-9-light:   #8f8c7d;
  --nuri-color-cream-9-dark:    #706d5f;
  --nuri-color-cream-10-light:  #858273;
  --nuri-color-cream-10-dark:   #7e7b6c;
  --nuri-color-cream-11-light:  #666455;
  --nuri-color-cream-11-dark:   #b7b4a4;
  --nuri-color-cream-12-light:  #222013;
  --nuri-color-cream-12-dark:   #f0eee3;

  /* ── Lilac (brand · Radix custom from #BEAAFF) ────────────── */
  /* Bright scale: steps 9 & 10 are identical in light and dark — the
   * brand color keeps its identity across themes. Duplication is
   * intentional and self-documenting at the primitive layer. */
  --nuri-color-lilac-1-light:   #fcfcff;
  --nuri-color-lilac-1-dark:    #120f1a;
  --nuri-color-lilac-2-light:   #faf8ff;
  --nuri-color-lilac-2-dark:    #191523;
  --nuri-color-lilac-3-light:   #f3f0ff;
  --nuri-color-lilac-3-dark:    #282040;
  --nuri-color-lilac-4-light:   #ebe3ff;
  --nuri-color-lilac-4-dark:    #342756;
  --nuri-color-lilac-5-light:   #e2d6ff;
  --nuri-color-lilac-5-dark:    #3e2f63;
  --nuri-color-lilac-6-light:   #d6c6ff;
  --nuri-color-lilac-6-dark:    #493a71;
  --nuri-color-lilac-7-light:   #c4b0ff;
  --nuri-color-lilac-7-dark:    #584785;
  --nuri-color-lilac-8-light:   #ae91ff;
  --nuri-color-lilac-8-dark:    #6c58a3;
  --nuri-color-lilac-9-light:   #beaaff;
  --nuri-color-lilac-9-dark:    #beaaff;  /* identical to -light: brand is theme-invariant */
  --nuri-color-lilac-10-light:  #b39ff3;
  --nuri-color-lilac-10-dark:   #b39ff3;  /* identical to -light: matches step-9 hover */
  --nuri-color-lilac-11-light:  #6f47c0;
  --nuri-color-lilac-11-dark:   #bba7fc;
  --nuri-color-lilac-12-light:  #381b6a;
  --nuri-color-lilac-12-dark:   #e3ddfa;

  /* ── Orange (accent · Radix custom from #ff8c5a) ──────────── */
  /* Second accent ramp (N+56 · slice 2). Bright scale: steps 9 & 10 are
   * identical in light and dark — the brand fill keeps its identity
   * across themes (same convention as lilac). */
  --nuri-color-orange-1-light:   #fffcfb;
  --nuri-color-orange-1-dark:    #15100d;
  --nuri-color-orange-2-light:   #fff5f0;
  --nuri-color-orange-2-dark:    #1e1512;
  --nuri-color-orange-3-light:   #ffe9dd;
  --nuri-color-orange-3-dark:    #361a0e;
  --nuri-color-orange-4-light:   #ffd8c2;
  --nuri-color-orange-4-dark:    #4b1b04;
  --nuri-color-orange-5-light:   #ffc9ae;
  --nuri-color-orange-5-dark:    #5a2308;
  --nuri-color-orange-6-light:   #ffb996;
  --nuri-color-orange-6-dark:    #693015;
  --nuri-color-orange-7-light:   #ffa37a;
  --nuri-color-orange-7-dark:    #814024;
  --nuri-color-orange-8-light:   #fb8856;
  --nuri-color-orange-8-dark:    #a6532f;
  --nuri-color-orange-9-light:   #ff8c5a;
  --nuri-color-orange-9-dark:    #ff8c5a;  /* identical to -light: brand is theme-invariant */
  --nuri-color-orange-10-light:  #f3814f;
  --nuri-color-orange-10-dark:   #f3814f;  /* identical to -light: brand is theme-invariant */
  --nuri-color-orange-11-light:  #c45621;
  --nuri-color-orange-11-dark:   #ff9664;
  --nuri-color-orange-12-light:  #5e280f;
  --nuri-color-orange-12-dark:   #f9d6c8;

  /* ── Black alpha · theme-invariant by nature ─────────────── */
  /* No -light/-dark suffix: opacity over a base is independent of
   * theme. Used for overlays, dividers on coloured surfaces, glass. */
  --nuri-color-black-alpha-1:  rgba(0, 0, 0, 0.05);
  --nuri-color-black-alpha-2:  rgba(0, 0, 0, 0.10);
  --nuri-color-black-alpha-3:  rgba(0, 0, 0, 0.15);
  --nuri-color-black-alpha-4:  rgba(0, 0, 0, 0.20);
  --nuri-color-black-alpha-5:  rgba(0, 0, 0, 0.30);
  --nuri-color-black-alpha-6:  rgba(0, 0, 0, 0.40);
  --nuri-color-black-alpha-7:  rgba(0, 0, 0, 0.50);
  --nuri-color-black-alpha-8:  rgba(0, 0, 0, 0.60);
  --nuri-color-black-alpha-9:  rgba(0, 0, 0, 0.70);
  --nuri-color-black-alpha-10: rgba(0, 0, 0, 0.80);
  --nuri-color-black-alpha-11: rgba(0, 0, 0, 0.90);
  --nuri-color-black-alpha-12: rgba(0, 0, 0, 0.95);

  /* ── White alpha · theme-invariant by nature ─────────────── */
  --nuri-color-white-alpha-1:  rgba(255, 255, 255, 0.05);
  --nuri-color-white-alpha-2:  rgba(255, 255, 255, 0.10);
  --nuri-color-white-alpha-3:  rgba(255, 255, 255, 0.15);
  --nuri-color-white-alpha-4:  rgba(255, 255, 255, 0.20);
  --nuri-color-white-alpha-5:  rgba(255, 255, 255, 0.30);
  --nuri-color-white-alpha-6:  rgba(255, 255, 255, 0.40);
  --nuri-color-white-alpha-7:  rgba(255, 255, 255, 0.50);
  --nuri-color-white-alpha-8:  rgba(255, 255, 255, 0.60);
  --nuri-color-white-alpha-9:  rgba(255, 255, 255, 0.70);
  --nuri-color-white-alpha-10: rgba(255, 255, 255, 0.80);
  --nuri-color-white-alpha-11: rgba(255, 255, 255, 0.90);
  --nuri-color-white-alpha-12: rgba(255, 255, 255, 0.95);

}


/* ══════════════════════════════════════════════════════════════
 * NEUTRAL RESOLUTION · GENERATED (N+32 C1 · decision 70 · decision 31)
 * --nuri-color-neutral-N-{light|dark} resolve to the ACTIVE neutral scale.
 * The active scale is the build's --neutral (DEFAULT_NEUTRAL = cream ·
 * pipeline/parsers/semantic.js); these 24 aliases are GENERATED from the
 * TS colour SoT (packages/spec/tokens/colours.ts) by the Slice-0 in-place passthrough
 * (pipeline/parsers/colour-css.js · the dimension-css.js twin · dec-2
 * reversed for the colour-primitive layer). Edit the SoT / pass --neutral=
 * <scale>, not here — `npm run build` regenerates them (sage rebuilds the
 * whole web system on sage).
 *
 * The runtime [data-neutral] SWITCHER is RETIRED here (N+32 C1 · the brief):
 * the candidate neutrals A/B'd through 8 per-scale [data-neutral="…"] blocks
 * (a nested <nuri-scope> could re-theme its subtree); cream is now locked as
 * the brand's warm default, so selection is BUILD-TIME only and one :root
 * block carries the resolution. The other six scales stay raw primitives
 * above (reserved · build-time --neutral= options). The semantic layer keeps
 * referencing --nuri-color-neutral-* (now → cream), untouched (the cascade is
 * C2). cream ≠ gray is a deliberate WEB-CSS value change (the RN/tokens.ts
 * path already resolved cream since decision 31 · build/* byte-identical).
 * ══════════════════════════════════════════════════════════════ */

:root {
  --nuri-color-neutral-1-light:   var(--nuri-color-cream-1-light);
  --nuri-color-neutral-1-dark:    var(--nuri-color-cream-1-dark);
  --nuri-color-neutral-2-light:   var(--nuri-color-cream-2-light);
  --nuri-color-neutral-2-dark:    var(--nuri-color-cream-2-dark);
  --nuri-color-neutral-3-light:   var(--nuri-color-cream-3-light);
  --nuri-color-neutral-3-dark:    var(--nuri-color-cream-3-dark);
  --nuri-color-neutral-4-light:   var(--nuri-color-cream-4-light);
  --nuri-color-neutral-4-dark:    var(--nuri-color-cream-4-dark);
  --nuri-color-neutral-5-light:   var(--nuri-color-cream-5-light);
  --nuri-color-neutral-5-dark:    var(--nuri-color-cream-5-dark);
  --nuri-color-neutral-6-light:   var(--nuri-color-cream-6-light);
  --nuri-color-neutral-6-dark:    var(--nuri-color-cream-6-dark);
  --nuri-color-neutral-7-light:   var(--nuri-color-cream-7-light);
  --nuri-color-neutral-7-dark:    var(--nuri-color-cream-7-dark);
  --nuri-color-neutral-8-light:   var(--nuri-color-cream-8-light);
  --nuri-color-neutral-8-dark:    var(--nuri-color-cream-8-dark);
  --nuri-color-neutral-9-light:   var(--nuri-color-cream-9-light);
  --nuri-color-neutral-9-dark:    var(--nuri-color-cream-9-dark);
  --nuri-color-neutral-10-light:  var(--nuri-color-cream-10-light);
  --nuri-color-neutral-10-dark:   var(--nuri-color-cream-10-dark);
  --nuri-color-neutral-11-light:  var(--nuri-color-cream-11-light);
  --nuri-color-neutral-11-dark:   var(--nuri-color-cream-11-dark);
  --nuri-color-neutral-12-light:  var(--nuri-color-cream-12-light);
  --nuri-color-neutral-12-dark:   var(--nuri-color-cream-12-dark);
}


/* ══════════════════════════════════════════════════════════════
 * NON-COLOUR PRIMITIVES · theme-invariant
 * ══════════════════════════════════════════════════════════════ */

:root {
  /* ── Radius (px) ───────────────────────────────────────────────
   * sm / md / lg / full moved to the semantic layer at N+6.1.1
   * (amendment 36.1) — sm/md/lg are semantic direct values like space +
   * size; full is a literal 9999px sentinel held in
   * the semantic layer (post-close polish: the 100% experiment
   * ellipsed rectangular `.nuri-tag` boxes since 100% interprets
   * relative to box dimensions; 9999px sentinel saturates correctly
   * via browser min(width/2, height/2) clamp). The sm/md/lg names
   * are reused in the semantic layer with new values (semantic
   * sm=6, md=12, lg=18 vs old primitive 4, 8, 12); cascade re-binds
   * chrome consumers transparently. The leaves below stay primitive:
   * none/xs/xl/2xl are reserved per P11 (RESERVED_TOKENS) pending
   * future semantic promotion when consumers materialise. */
  --nuri-radius-none: 0;
  --nuri-radius-xs:   2px;
  --nuri-radius-xl:   16px;
  --nuri-radius-2xl:  24px;

  /* ── Typography · families ─────────────────────────────────── */
  /* Production default: system stack — each platform renders its own
   * native font. This is what the RN sync pipeline exports. The
   * [data-font="..."] overrides below are preview-only for the docs. */
  --nuri-font-family-sans: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
  --nuri-font-family-mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
  --nuri-font-family-display: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;

  /* ── Typography · weights ──────────────────────────────────── */
  --nuri-font-weight-regular:  400;
  --nuri-font-weight-medium:   500;
  --nuri-font-weight-semibold: 600;
  --nuri-font-weight-bold:     700;

  /* ── Typography · sizes (rem — scales with user pref) ──────────
   * Hand-CSS font-size primitives. Since N+52 (decision 78) the type
   * scale INLINES its sizes (--nuri-type-*-size is the rem literal, no
   * longer var(--nuri-font-size-N)), so these are consumed directly by
   * shell.css (13/15/17/22). 30/57 have no consumer today — RESERVED in
   * the "primitive used or reserved" guardrail pending the Phase 4·3
   * font-primitive flip (which re-decides the ramp's membership). */
  --nuri-font-size-13: 0.8125rem; /* shell */
  --nuri-font-size-15: 0.9375rem; /* shell */
  --nuri-font-size-17: 1.0625rem; /* shell */
  --nuri-font-size-22: 1.375rem;  /* shell */
  --nuri-font-size-30: 1.875rem;  /* reserved */
  --nuri-font-size-57: 3.5625rem; /* reserved */

  /* ── Line-height · NO primitives by design ─────────────────────
   * The type scale below declares --nuri-type-*-line-height as
   * UNITLESS multipliers (1.27, 1.29, 1.20, …) — the RN convention
   * (lineHeight = size × ratio at render). Absolute rem line-heights
   * are explicitly rejected; see decisionlog.md decision 29. */

  /* ── Duration ──────────────────────────────────────────────── */
  --nuri-duration-fast: 120ms;
  --nuri-duration-med:  220ms;
  --nuri-duration-slow: 420ms;

  /* ── Interaction family · cross-component design constants ────
   * Centralize literal design decisions n=3+ components were
   * hardcoding prior to decision 45 (N+6.5). Consumer components
   * reference via @layer tokens alias
   * (--nuri-button-press-scale: var(--nuri-interaction-press-scale)
   * et al.). Family-prefixed (`--nuri-interaction-*`) so the pipeline
   * PRIMITIVE_SET_PREFIXES classifier handles it like the
   * duration/font families. Consumer override allowed when
   * documented geometric justification applies (e.g., switch.css
   * knob 0.92 · smaller knob needs more aggressive feedback). */
  --nuri-interaction-press-scale:      0.97;
  --nuri-interaction-disabled-opacity: 0.4;
}


/* ══════════════════════════════════════════════════════════════
 * TYPE SCALE · PRIMITIVE STEPS
 * A bare numeric scale — no role, no semantics. Six steps plus
 * a deliberate gap at 2xl, reserved for a future intermediate.
 *
 *   xs → sm → md → lg → xl → (2xl) → 3xl
 *
 * Each step exposes four atomic tokens:
 *   -size            font-size
 *   -line-height     line-height (unitless ratio)
 *   -tracking        letter-spacing (em — resolves against -size)
 *   -weight          weight (always regular · 400)
 *
 * EMPHASIS is ORTHOGONAL, not per-step (decision 77 · the N+45 de-fusion): the
 * regular→semibold override is UNIFORM across every size, so it has NO per-step
 * token — the realization references --nuri-font-weight-semibold directly (web
 * [data-type-emphasis] · RN emphasisWeight). The retired per-step -em-weight
 * tokens were all var(--nuri-font-weight-semibold), so this changes nothing but
 * the shape (P11 · no speculative per-size split).
 *
 * Semantic roles (screen title, body, button label, …) come later
 * as aliases on top of this layer.
 *
 * font-family is intentionally NOT part of the step — the active
 * family is set globally via --nuri-font-family-sans and swapped
 * per platform by the [data-font] preview overrides below.
 *
 * Tracking is in em so it rescales with the user's text-size
 * preference; the RN sync pipeline multiplies em × size at export.
 *
 * Line-height is a unitless ratio (CSS multiplies it against the
 * resolved font-size) — matches React Native, which derives the
 * absolute lineHeight from size × ratio at the call site. The
 * comments next to each step show size + the absolute lh you get
 * by multiplying out.
 * ══════════════════════════════════════════════════════════════ */

:root {
  /* ── xs · 13 / 18  (1.38) ─────────────────────────────────── */
  --nuri-type-xs-size:        0.8125rem;
  --nuri-type-xs-line-height: 1.38;
  --nuri-type-xs-tracking:    0;
  --nuri-type-xs-weight:      400;

  /* ── sm · 15 / 20  (1.33) ─────────────────────────────────── */
  --nuri-type-sm-size:        0.90625rem;
  --nuri-type-sm-line-height: 1.33;
  --nuri-type-sm-tracking:    -0.01em;
  --nuri-type-sm-weight:      400;

  /* ── md · 17 / 22  (1.29) ─────────────────────────────────── */
  --nuri-type-md-size:        1.0625rem;
  --nuri-type-md-line-height: 1.29;
  --nuri-type-md-tracking:    -0.02em;
  --nuri-type-md-weight:      400;

  /* ── lg · 22 / 28  (1.27) ─────────────────────────────────── */
  --nuri-type-lg-size:        1.375rem;
  --nuri-type-lg-line-height: 1.27;
  --nuri-type-lg-tracking:    -0.015em;
  --nuri-type-lg-weight:      400;

  /* ── xl · 30 / 36  (1.20) ─────────────────────────────────── */
  --nuri-type-xl-size:        1.875rem;
  --nuri-type-xl-line-height: 1.2;
  --nuri-type-xl-tracking:    -0.015em;
  --nuri-type-xl-weight:      400;

  /* ── 2xl · reserved ───────────────────────────────────────── */
  /* Intentional gap between xl (30) and 3xl (57). Define when a
   * concrete need surfaces — likely ~40/48 with bold emphasis. */

  /* ── 3xl · 57 / 68  (1.19) ────────────────────────────────── */
  --nuri-type-3xl-size:        3.5625rem;
  --nuri-type-3xl-line-height: 1.19;
  --nuri-type-3xl-tracking:    -0.02em;
  --nuri-type-3xl-weight:      400;
}


/* ══════════════════════════════════════════════════════════════
 * FONT FAMILY · PREVIEW OVERRIDES
 * Docs-only. The default :root values above (system stack) are what
 * the RN sync pipeline exports. These [data-font] overrides let us
 * preview how components render under each platform's native font.
 *
 * Attribute-only form (NOT :root-scoped), mirroring the theme / accent /
 * neutral overrides in tokens-semantic.css: a <nuri-scope data-font> can
 * then re-resolve the font family for its subtree alone. The playground
 * device picker relies on this — selecting a phone sets the scope's font
 * so the framed screen renders in that platform's native typeface.
 * ══════════════════════════════════════════════════════════════ */

[data-font="ios"] {
  --nuri-font-family-sans:    -apple-system, BlinkMacSystemFont, "SF Pro Text", "Helvetica Neue", system-ui, sans-serif;
  --nuri-font-family-display: -apple-system, BlinkMacSystemFont, "SF Pro Display", "Helvetica Neue", system-ui, sans-serif;
  --nuri-font-family-mono:    ui-monospace, "SF Mono", SFMono-Regular, Menlo, Monaco, monospace;
}

[data-font="android"] {
  --nuri-font-family-sans:    "Roboto", system-ui, sans-serif;
  --nuri-font-family-display: "Roboto", system-ui, sans-serif;
  --nuri-font-family-mono:    "Roboto Mono", ui-monospace, monospace;
}

[data-font="pixel"] {
  --nuri-font-family-sans:    "Google Sans Text", "Roboto", system-ui, sans-serif;
  --nuri-font-family-display: "Google Sans Text", "Roboto", system-ui, sans-serif;
  --nuri-font-family-mono:    "Roboto Mono", ui-monospace, monospace;
}

/* ──────────────────────────────────────────────────────────────
 * NURI · TOKENS · SEMANTIC
 * Role-based tokens. This is where mode (light/dark) and accent
 * (neutral/lilac) are composed by referencing the theme-agnostic
 * primitives in tokens-primitive.css. Components consume these
 * tokens, not the primitives.
 *
 * ── GENERATED · the accent×theme cascade (N+32 C2 · decision 70 / 72) ──
 * The cascade BLOCKS below (the marked region · #1 … #6b) are GENERATED
 * from the TS SoT (packages/spec/tokens/colours.ts · the chrome + accent matrix) by
 * Slice 0 of scripts/tokens-parser.js (parsers/semantic-css.js · dec-2
 * reversed for the colour SEMANTIC layer — colour is now fully flipped).
 * Edit the SoT, not the marked region — `npm run build` regenerates it in
 * place. The SPACING / SIZING / RADIUS blocks (below the marked region)
 * are likewise generated, from the dimension SoT (packages/spec/tokens/dimensions.ts ·
 * decision 71). This header + the prose below are hand-maintained
 * documentation of the generated structure.
 *
 * ── Composition model ─────────────────────────────────────────
 *   Two attributes drive everything:
 *     data-theme   = "light" | "dark"      (default: light)
 *     data-accent  = "neutral" | "lilac"   (default: lilac, the brand)
 *
 *   Chrome tokens (bg-*, text-*, border-*, focus-ring) depend
 *   only on data-theme. They define the page's neutral surface.
 *
 *   Accent tokens (accent-*) depend on data-accent AND data-theme.
 *   The interaction is declared explicitly per accent — that's
 *   how the asymmetry between SATURATED scales (neutral) and
 *   BRIGHT scales (lilac) becomes visible in the file:
 *
 *     accent=neutral · solid SWAPS:  black in light, white in dark
 *     accent=lilac   · solid FROZEN: #beaaff in both themes (brand)
 *
 *   Read the lilac blocks to see what changes in dark and what stays.
 *   Read the neutral blocks to see full mode-swap symmetry.
 *
 * ── Scoping: attributes, not :root ────────────────────────────
 * Selectors use attribute-only forms ([data-theme="dark"], etc.)
 * rather than :root[...] so that nested scopes work. A demo block
 * inside a page can mark itself <div data-accent="lilac" data-theme="dark">
 * and the semantic tokens within that subtree resolve correctly —
 * useful for the Colour Architecture demo and any side-by-side
 * theme/accent comparison in the docs.
 *
 * ── Cascade ordering ──────────────────────────────────────────
 * Selectors are deliberately ordered (later overrides earlier at
 * equal specificity):
 *   1.  :root, [data-theme="light"]                 — defaults (chrome light + neutral accent light; light re-asserts at any wrapper)
 *   2.  [data-theme="dark"]                          — chrome dark + neutral accent dark (anywhere)
 *   3.  [data-accent="neutral"]                      — neutral accent light (anywhere; explicit override)
 *   4.  [data-accent="neutral"][data-theme="dark"]  — neutral accent dark · SAME element carries both (specificity 0,2,0)
 *   4b. [data-theme="dark"] [data-accent="neutral"] — neutral accent dark · accent SELF-SCOPED under a dark ancestor (0,2,0)
 *   5.  [data-accent="lilac"]                        — lilac accent light (anywhere)
 *   6.  [data-accent="lilac"][data-theme="dark"]    — lilac accent dark overrides only · SAME element (0,2,0)
 *   6b. [data-theme="dark"] [data-accent="lilac"]   — lilac accent dark adapters · accent SELF-SCOPED under a dark ancestor (0,2,0)
 *
 * Don't reorder these blocks. The single-attr selectors (#1's
 * [data-theme="light"], #2, #3, #5) share specificity (0,1,0); source
 * order is what makes the cascade land on the right values (#1 is first,
 * so dark/#2 and the accent overrides win over it). The combined-attr
 * selectors (#4, #6) and the descendant-combinator selectors (#4b, #6b)
 * all win on specificity (0,2,0); #4b/#6b sit right after their #4/#6
 * twin so an equal-specificity tie (an element matching both — only when
 * a scope is nested inside a same-theme outer scope) resolves to the same
 * value either way.
 *
 * ── Why #4b / #6b exist · the self-scoped-accent clobber ──────
 * Tier 2 (Button / IconButton / IconAvatar `accent=` self-scope) sets
 * data-accent on the ELEMENT but NOT data-theme (the element can't know
 * its ancestor's theme). Inside a dark scope, block 2 sets the dark
 * neutral-accent values at the ancestor (inherited down), but block 3
 * (or 5) then RE-DECLARES the light values AT THE ELEMENT — clobbering
 * the inherited dark — while #4 (#6) never matches (no data-theme on the
 * element). The swap IconButton went dark-on-dark (invisible). #4b/#6b
 * re-assert the dark accent values for an accent element that merely has
 * a dark ANCESTOR, beating #3/#5 on specificity. (decision 63 · N+15)
 *
 * KNOWN LIMITATION (revisit-trigger · decision 63): a descendant
 * combinator matches ANY dark ancestor, not the NEAREST theme. So a
 * self-scoped accent inside a LIGHT scope nested inside a DARK scope
 * resolves DARK (wrong — nearest is light). CSS can't express "nearest
 * scope" without a per-element theme. No current consumer nests opposite
 * themes around a self-scoped accent (the playground scopes ONE theme per
 * device frame), so this is accepted per P11; if a nested-opposite-theme
 * consumer lands, revisit (the per-element-theme · RN single-context
 * model · decision 27/62 — is the clean answer there).
 *
 * Some values are intentionally duplicated across blocks. The accent-*
 * duplication (blocks 1/3, 2/4) and the [data-theme="light"] on block 1
 * exist for the same reason: a bare :root only matches the root element,
 * so a nested <nuri-scope> needs an explicit attribute selector to
 * re-declare chrome (light) and neutral-accent tokens for its subtree —
 * otherwise a scoped theme/neutral change is a no-op in light. The
 * duplication is the price of supporting nested scopes.
 *
 * ── Why focus-ring is brand even on accent=neutral ────────────
 * Focus is a user-attention signal. Across most DS conventions
 * (Material, Spectrum, Radix), the focus ring stays brand-coloured
 * regardless of the surrounding accent — it's the only way to
 * guarantee it pops against any background.
 *
 * ── The resolution matrix · Format B → the SoT (decision 33 · 70) ────
 * Format B (decision 33) put each context-dependent token's full
 * resolution matrix in an inline comment at its CANONICAL block. With the
 * C2 flip that matrix moved to its SOURCE — packages/spec/tokens/colours.ts (the
 * chrome + accent objects · each cell a { ref } · the INVERSE / FROZEN-P4
 * patterns documented there). The generated blocks below carry only a
 * terse per-block role comment; the matrix is one indirection away, in the
 * SoT, where it is now authored. Decision 33's intent (the matrix is
 * documented) stands; its LOCATION follows the source of truth.
 * ────────────────────────────────────────────────────────────── */


/* ════════════════════════════════════════════════════════════════════
 * GENERATED · the accent×theme cascade · from packages/spec/tokens/colours.ts (chrome +
 * accent) via scripts/parsers/semantic-css.js · decision 70 / 72.
 * DO NOT EDIT — `npm run build` regenerates this region IN PLACE (Slice 0,
 * the colour-semantic flip). The resolution matrix IS the SoT (colours.ts);
 * the cascade ORDERING + the #4b/#6b self-scope rationale (decision 63) are
 * documented in the file header above.
 * ════════════════════════════════════════════════════════════════════ */

/* 1 · defaults · chrome light + neutral accent light (light re-asserts at any wrapper) */
:root,
[data-theme="light"] {
  --nuri-bg-canvas:                var(--nuri-color-neutral-1-light);
  --nuri-bg-subtle:                var(--nuri-color-neutral-2-light);
  --nuri-bg-strong:                var(--nuri-color-neutral-3-light);
  --nuri-bg-pressed:               var(--nuri-color-neutral-4-light);
  --nuri-bg-inverse:               var(--nuri-color-neutral-1-dark);
  --nuri-bg-inverse-muted:         var(--nuri-color-neutral-11-light);
  --nuri-text-primary:             var(--nuri-color-neutral-12-light);
  --nuri-text-muted:               var(--nuri-color-neutral-11-light);
  --nuri-text-on-inverse:          var(--nuri-color-neutral-12-dark);
  --nuri-border-subtle:            var(--nuri-color-neutral-6-light);
  --nuri-border-default:           var(--nuri-color-neutral-7-light);
  --nuri-border-strong:            var(--nuri-color-neutral-8-light);
  --nuri-focus-ring:               var(--nuri-color-lilac-8-light);
  --nuri-accent-fg:                var(--nuri-color-neutral-12-light);
  --nuri-accent-solid:             var(--nuri-color-neutral-1-dark);
  --nuri-accent-solid-pressed:     var(--nuri-color-neutral-3-dark);
  --nuri-accent-on-solid:          var(--nuri-color-neutral-12-dark);
  --nuri-accent-bg-subtle:         var(--nuri-color-neutral-3-light);
  --nuri-accent-bg-subtle-pressed: var(--nuri-color-neutral-4-light);
}


/* 2 · chrome dark + neutral accent dark (anywhere) */
[data-theme="dark"] {
  --nuri-bg-canvas:                var(--nuri-color-neutral-1-dark);
  --nuri-bg-subtle:                var(--nuri-color-neutral-2-dark);
  --nuri-bg-strong:                var(--nuri-color-neutral-3-dark);
  --nuri-bg-pressed:               var(--nuri-color-neutral-4-dark);
  --nuri-bg-inverse:               var(--nuri-color-neutral-1-light);
  --nuri-bg-inverse-muted:         var(--nuri-color-neutral-11-dark);
  --nuri-text-primary:             var(--nuri-color-neutral-12-dark);
  --nuri-text-muted:               var(--nuri-color-neutral-11-dark);
  --nuri-text-on-inverse:          var(--nuri-color-neutral-12-light);
  --nuri-border-subtle:            var(--nuri-color-neutral-6-dark);
  --nuri-border-default:           var(--nuri-color-neutral-7-dark);
  --nuri-border-strong:            var(--nuri-color-neutral-8-dark);
  --nuri-focus-ring:               var(--nuri-color-lilac-8-dark);
  --nuri-accent-fg:                var(--nuri-color-neutral-12-dark);
  --nuri-accent-solid:             var(--nuri-color-neutral-1-light);
  --nuri-accent-solid-pressed:     var(--nuri-color-neutral-3-light);
  --nuri-accent-on-solid:          var(--nuri-color-neutral-12-light);
  --nuri-accent-bg-subtle:         var(--nuri-color-neutral-3-dark);
  --nuri-accent-bg-subtle-pressed: var(--nuri-color-neutral-4-dark);
}


/* 3 · explicit neutral accent · light (anywhere · nested override) */
[data-accent="neutral"] {
  --nuri-accent-fg:                var(--nuri-color-neutral-12-light);
  --nuri-accent-solid:             var(--nuri-color-neutral-1-dark);
  --nuri-accent-solid-pressed:     var(--nuri-color-neutral-3-dark);
  --nuri-accent-on-solid:          var(--nuri-color-neutral-12-dark);
  --nuri-accent-bg-subtle:         var(--nuri-color-neutral-3-light);
  --nuri-accent-bg-subtle-pressed: var(--nuri-color-neutral-4-light);
}


/* 4 · neutral accent dark · SAME element carries both attrs (specificity 0,2,0) */
[data-accent="neutral"][data-theme="dark"] {
  --nuri-accent-fg:                var(--nuri-color-neutral-12-dark);
  --nuri-accent-solid:             var(--nuri-color-neutral-1-light);
  --nuri-accent-solid-pressed:     var(--nuri-color-neutral-3-light);
  --nuri-accent-on-solid:          var(--nuri-color-neutral-12-light);
  --nuri-accent-bg-subtle:         var(--nuri-color-neutral-3-dark);
  --nuri-accent-bg-subtle-pressed: var(--nuri-color-neutral-4-dark);
}


/* 4b · neutral accent dark · SELF-SCOPED under a dark ancestor (decision 63 · 0,2,0) */
[data-theme="dark"] [data-accent="neutral"] {
  --nuri-accent-fg:                var(--nuri-color-neutral-12-dark);
  --nuri-accent-solid:             var(--nuri-color-neutral-1-light);
  --nuri-accent-solid-pressed:     var(--nuri-color-neutral-3-light);
  --nuri-accent-on-solid:          var(--nuri-color-neutral-12-light);
  --nuri-accent-bg-subtle:         var(--nuri-color-neutral-3-dark);
  --nuri-accent-bg-subtle-pressed: var(--nuri-color-neutral-4-dark);
}


/* 5 · lilac accent · light (anywhere) */
[data-accent="lilac"] {
  --nuri-accent-fg:                var(--nuri-color-lilac-12-light);
  --nuri-accent-solid:             var(--nuri-color-lilac-9-light);
  --nuri-accent-solid-pressed:     var(--nuri-color-lilac-10-light);
  --nuri-accent-on-solid:          var(--nuri-color-lilac-12-light);
  --nuri-accent-bg-subtle:         var(--nuri-color-lilac-3-light);
  --nuri-accent-bg-subtle-pressed: var(--nuri-color-lilac-4-light);
}


/* 6 · lilac accent dark · PARTIAL per P4 (frozen brand omitted · SAME element · 0,2,0) */
[data-accent="lilac"][data-theme="dark"] {
  --nuri-accent-fg:                var(--nuri-color-lilac-12-dark);
  --nuri-accent-bg-subtle:         var(--nuri-color-lilac-3-dark);
  --nuri-accent-bg-subtle-pressed: var(--nuri-color-lilac-4-dark);
}


/* 6b · lilac accent dark · SELF-SCOPED under a dark ancestor (decision 63 · partial per P4) */
[data-theme="dark"] [data-accent="lilac"] {
  --nuri-accent-fg:                var(--nuri-color-lilac-12-dark);
  --nuri-accent-bg-subtle:         var(--nuri-color-lilac-3-dark);
  --nuri-accent-bg-subtle-pressed: var(--nuri-color-lilac-4-dark);
}


/* 7 · orange accent · light (anywhere) */
[data-accent="orange"] {
  --nuri-accent-fg:                var(--nuri-color-orange-12-light);
  --nuri-accent-solid:             var(--nuri-color-orange-9-light);
  --nuri-accent-solid-pressed:     var(--nuri-color-orange-10-light);
  --nuri-accent-on-solid:          var(--nuri-color-orange-12-light);
  --nuri-accent-bg-subtle:         var(--nuri-color-orange-3-light);
  --nuri-accent-bg-subtle-pressed: var(--nuri-color-orange-4-light);
}


/* 8 · orange accent dark · PARTIAL per P4 (frozen brand omitted · SAME element · 0,2,0) */
[data-accent="orange"][data-theme="dark"] {
  --nuri-accent-fg:                var(--nuri-color-orange-12-dark);
  --nuri-accent-bg-subtle:         var(--nuri-color-orange-3-dark);
  --nuri-accent-bg-subtle-pressed: var(--nuri-color-orange-4-dark);
}


/* 8b · orange accent dark · SELF-SCOPED under a dark ancestor (decision 63 · partial per P4) */
[data-theme="dark"] [data-accent="orange"] {
  --nuri-accent-fg:                var(--nuri-color-orange-12-dark);
  --nuri-accent-bg-subtle:         var(--nuri-color-orange-3-dark);
  --nuri-accent-bg-subtle-pressed: var(--nuri-color-orange-4-dark);
}

/* ════════════════ END GENERATED · the accent×theme cascade ════════════════ */


/* ══════════════════════════════════════════════════════════════
 * 7 · SPACING · semantic vocabulary · between elements
 *
 * Role · the T-shirt scale layout consumers reach for when
 * declaring gap / margin / padding between siblings. Leaves own
 * direct values in the dimension SoT. Cascade-invariant today.
 * ══════════════════════════════════════════════════════════════ */

:root {
  /* N+31 · GENERATED from the TS SoT (packages/spec/tokens/dimensions.ts · decision 70 ·
   * dec-2 reversed for the dimension layer). Edit the SoT, not these values —
   * `npm run build` regenerates them in place (Slice 0). */
  /* space.none · the zero leaf · a flush hairline (Separator y-space="none"),
   * a collapsed gutter · literal 0 (decision 32 retired --nuri-px-0; 0 needs no
   * primitive). Added N+8.1 alongside the Separator y-space prop. */
  --nuri-space-none: 0;
  /* space.2xs · finest granularity · hairline gap between adjacent micro-elements */
  --nuri-space-2xs: 2px;
  /* space.xs  · tight gap · icon-to-label inside a control, label-to-glyph */
  --nuri-space-xs:  4px;
  /* space.sm  · compact gap · stacked rows in a dense list */
  --nuri-space-sm:  6px;
  /* space.md  · default gap · siblings inside a card, row-gap in a grid */
  --nuri-space-md:  12px;
  /* space.lg  · roomy gap · button padding-x, comfortable interior */
  --nuri-space-lg:  18px;
  /* space.xl  · section gap · vertical rhythm between content groups */
  --nuri-space-xl:  24px;
  /* space.2xl · major gap · separator between distinct page regions */
  --nuri-space-2xl: 36px;
}


/* ══════════════════════════════════════════════════════════════
 * 8 · SIZING · semantic vocabulary · element dimensions
 *
 * Role · the T-shirt scale layout consumers reach for when
 * declaring min-height / width of an element itself. Leaves own
 * direct values in the dimension SoT. Cascade-invariant today.
 * Asymmetric vs spacing by design —
 * sizing anchors larger (touch targets, control heights), spacing
 * anchors smaller (between-element values). Decision 36 · N+6.1.
 * ══════════════════════════════════════════════════════════════ */

:root {
  /* N+31 · GENERATED from the TS SoT (packages/spec/tokens/dimensions.ts · decision 70 ·
   * dec-2 reversed for the dimension layer). Edit the SoT, not these values —
   * `npm run build` regenerates them in place (Slice 0). */
  /* size.xs  · smallest element · inline tag / chip height */
  --nuri-size-xs:  18px;
  /* size.sm  · compact element · dense list-item height, indicator dot */
  --nuri-size-sm:  24px;
  /* size.md  · medium element · default icon-button, input field height */
  --nuri-size-md:  36px;
  /* size.lg  · standard touch target · iOS minimum + comfortable headroom */
  --nuri-size-lg:  48px;
  /* size.xl  · roomy touch target · primary action button height */
  --nuri-size-xl:  60px;
  /* size.2xl · large element · prominent CTA, screen-header height */
  --nuri-size-2xl: 72px;
  /* size.3xl · hero element · feature card height, large display tile */
  --nuri-size-3xl: 90px;
}


/* ══════════════════════════════════════════════════════════════
 * 9 · RADIUS · semantic vocabulary · corner softness
 *
 * Role · the T-shirt scale border-radius consumers reach for when
 * declaring corner softness. sm / md / lg are direct values;
 * full is a literal
 * 9999px sentinel value — border-radius clamps it to
 * min(width/2, height/2), producing a pill for any rectangular box
 * and a circle when width = height. The 9999 sentinel sits outside
 * the ordinary scale by design; using 100%
 * instead would distort non-square boxes into ellipses (see N+6.1.1
 * post-close polish). Cascade-invariant today; 1-line role
 * descriptions per the dependency-driven trigger in decision 33.
 * Amendment 36.1 · N+6.1.1.
 * ══════════════════════════════════════════════════════════════ */

:root {
  /* N+31 · sm/md/lg GENERATED from the TS SoT (packages/spec/tokens/dimensions.ts ·
   * decision 70 · dec-2 reversed for the dimension layer · `full` is the 9999px
   * literal sentinel). Edit the SoT, not these values — `npm run build`
   * regenerates them in place (Slice 0). */
  /* radius.sm   · tight softening · dense controls, inline chips */
  --nuri-radius-sm:   6px;
  /* radius.md   · default softening · cards, inputs, mid-scale controls */
  --nuri-radius-md:   9px;
  /* radius.lg   · roomy softening · primary action buttons, hero surfaces */
  --nuri-radius-lg:   18px;
  /* radius.full · literal 9999px sentinel · pills (rectangular) + circles (square w=h); browser clamps to min(width/2, height/2) */
  --nuri-radius-full: 9999px;
}


/* ══════════════════════════════════════════════════════════════
 * 10 · RATIO · semantic vocabulary · aspect-ratio (width : height)
 *
 * Role · the box `aspectRatio` field reaches for these to declare a
 * surface's width:height. UNLIKE space/size/radius these are UNITLESS
 * — a bare number, NOT a --nuri-px-N alias (an aspect-ratio carries no
 * length unit · CSS `aspect-ratio: 1.586`, RN `aspectRatio: 1.586` ·
 * the value is identical on both targets, no unit divergence). They sit
 * OUTSIDE the px scale by design (the named-risk surface · no `px`).
 * square = 1:1; card = 1.586:1 (ISO/IEC 7810 ID-1 · the credit-card /
 * payment-card surface). Box-aspect-ratio slice.
 * ══════════════════════════════════════════════════════════════ */

:root {
  /* GENERATED from the TS SoT (packages/spec/tokens/dimensions.ts · decision 70 · dec-2
   * reversed for the dimension layer · UNITLESS · the bare-number ratio). Edit the
   * SoT, not these values — `npm run build` regenerates them in place (Slice 0). */
  /* ratio.square · 1:1 · a square surface (avatar tile, icon plate) */
  --nuri-ratio-square: 1;
  /* ratio.card   · 1.586:1 · the credit-card / payment-card surface (ISO 7810 ID-1) */
  --nuri-ratio-card:   1.586;
}


/* ══════════════════════════════════════════════════════════════
 * 11 · BORDER · semantic vocabulary · hairline widths
 *
 * Role · the shared hairline width consumed by palette outlines and
 * Separator. Only real DS consumers ship leaves.
 * ══════════════════════════════════════════════════════════════ */

:root {
  /* GENERATED from the TS SoT (packages/spec/tokens/dimensions.ts). */
  --nuri-border-1: 1px;
}

/* NURI · TYPOGRAPHY · TYPE-SCALE REALIZATION · GENERATED — DO NOT EDIT BY HAND */

[data-type-style="xs"]  { font-size: var(--nuri-type-xs-size); line-height: var(--nuri-type-xs-line-height); letter-spacing: var(--nuri-type-xs-tracking); font-weight: var(--nuri-type-xs-weight); }
[data-type-style="sm"]  { font-size: var(--nuri-type-sm-size); line-height: var(--nuri-type-sm-line-height); letter-spacing: var(--nuri-type-sm-tracking); font-weight: var(--nuri-type-sm-weight); }
[data-type-style="md"]  { font-size: var(--nuri-type-md-size); line-height: var(--nuri-type-md-line-height); letter-spacing: var(--nuri-type-md-tracking); font-weight: var(--nuri-type-md-weight); }
[data-type-style="lg"]  { font-size: var(--nuri-type-lg-size); line-height: var(--nuri-type-lg-line-height); letter-spacing: var(--nuri-type-lg-tracking); font-weight: var(--nuri-type-lg-weight); }
[data-type-style="xl"]  { font-size: var(--nuri-type-xl-size); line-height: var(--nuri-type-xl-line-height); letter-spacing: var(--nuri-type-xl-tracking); font-weight: var(--nuri-type-xl-weight); }
[data-type-style="3xl"] { font-size: var(--nuri-type-3xl-size); line-height: var(--nuri-type-3xl-line-height); letter-spacing: var(--nuri-type-3xl-tracking); font-weight: var(--nuri-type-3xl-weight); }

[data-type-emphasis] { font-weight: var(--nuri-font-weight-semibold); }
[data-mono] { font-family: ui-monospace, Menlo, Consolas, monospace; }

/* ──────────────────────────────────────────────────────────────
 * NURI · COMPONENT · INPUT  (native form-control host)
 *
 * Plumbing CSS for the descriptor `input` el. Design decisions still come from
 * namespace classes/data attrs the factory applies to <nuri-input>.
 * ────────────────────────────────────────────────────────────── */

@layer rules {
  nuri-input {
    display: block;
    min-inline-size: 0;
    color: inherit;
  }

  nuri-input > input.nuri-input {
    inline-size: 100%;
    min-inline-size: 0;
    border: 0;
    padding: 0;
    margin: 0;
    outline: 0;
    background: transparent;
    color: inherit;
    font: inherit;
  }

  nuri-input > input.nuri-input::placeholder {
    color: var(--nuri-text-muted);
    opacity: 1;
  }

  nuri-input > input.nuri-input:disabled {
    opacity: var(--nuri-interaction-disabled-opacity);
    -webkit-text-fill-color: currentColor;
  }

  .nuri-palette[data-nuri-focus-target][data-nuri-input-focused],
  [data-nuri-focus-target][data-nuri-input-focused] {
    outline: 2px solid var(--nuri-focus-ring);
    outline-offset: 2px;
  }
}

/* ──────────────────────────────────────────────────────────────
 * NURI · NAMESPACE CSS · TYPOGRAPHY · GENERATED — DO NOT EDIT BY HAND
 *
 * GENERATED from the typography AXIS (packages/spec/axes/typography-axis.ts) by
 * prototype/pipeline/css-preview.js, wired into npm run build -w @nuri/prototype (its
 * own build · regenerates IN PLACE over prototype/styles/typography.css). This is the
 * LIVE typography WRAPPER CSS: the pages link it and the web factory styles
 * nuri-typography with it. decision 2 reversed for the namespace layer (decision 74 ·
 * executing decision 70 · the L3c flip · N+38 · carved to @nuri/prototype at N+41) —
 * the hand SoT retired (git-recoverable), the generator is the sole source. WRAPPER
 * ONLY — the type SCALE (packages/spec/tokens/typography.ts · the .nuri-type-{step}
 * utilities → --nuri-type-* primitives · {size, emphasis}) stays CSS-SoT, OUT of scope
 * (a later token flip). The display order is LOAD-BEARING — the :not(:defined) shell
 * before the [align] dispatch so a pre-upgrade aligned node resolves display to block.
 * Re-run npm run build -w @nuri/prototype; freshness gated by
 * prototype/pipeline/typography-css.test.js.
 * ────────────────────────────────────────────────────────────── */

@layer tokens {
  /* Empty by design (decision 37 · the Stack/Box/Palette/Interactive pattern).
   * typography reuses the foundation --nuri-type-* primitives via the .nuri-type-
   * {step} utility classes (styles/typography.css) — no component-token aliasing.
   * Mirrors the hand typography.css. */
}

@layer rules {
  nuri-typography { display: inline; }
  nuri-typography:not(:defined) { display: inline; }
  nuri-typography[data-muted] { color: var(--nuri-text-muted); }
  nuri-typography[align="start"] { display: block; text-align: start; }
  nuri-typography[align="center"] { display: block; text-align: center; }
  nuri-typography[align="end"] { display: block; text-align: end; }
  nuri-typography[flow="truncate"][lines="1"] { display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 1; overflow: hidden; text-overflow: ellipsis; }
  nuri-typography[flow="truncate"][lines="2"] { display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; overflow: hidden; text-overflow: ellipsis; }
  nuri-typography[flow="truncate"][lines="3"] { display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 3; overflow: hidden; text-overflow: ellipsis; }
}

/* ──────────────────────────────────────────────────────────────
 * NURI · NAMESPACE CSS · STACK · GENERATED — DO NOT EDIT BY HAND
 *
 * GENERATED from the Field table (packages/spec/axes/resolve-map.ts ·
 * STACK_FIELDS) + the web spelling layer
 * (prototype/pipeline/parsers/namespace-css.js) by prototype/pipeline/css-preview.js,
 * wired into npm run build -w @nuri/prototype (its own build · regenerates IN PLACE
 * over this file · prototype/styles/stack.css). This is the LIVE namespace CSS — the
 * pages link it and the web factory (prototype/factory/factory.js) styles the nuri-*
 * merged nodes with it. decision 2 reversed for the namespace layer (decision 74 ·
 * executing decision 70 · the L3c flip · N+38 · carved to @nuri/prototype at N+41):
 * the hand SoT retired (git-recoverable) and the generator is the sole source. Re-run
 * npm run build -w @nuri/prototype to regenerate; freshness gated by
 * prototype/pipeline/css-preview.test.js.
 * ────────────────────────────────────────────────────────────── */

@layer tokens {
  /* Empty by design (decision 37 · layout primitives parametrize semantic
   * vocabulary via prop, not via component-token aliasing). Mirrors the hand
   * stack.css. */
}

@layer rules {
  .nuri-stack { display: flex; flex-direction: column; min-width: 0; }
  .nuri-stack[data-direction="row"] { flex-direction: row; }
  .nuri-stack[data-direction="column"] { flex-direction: column; }
  .nuri-stack[data-align="start"] { align-items: flex-start; }
  .nuri-stack[data-align="center"] { align-items: center; }
  .nuri-stack[data-align="end"] { align-items: flex-end; }
  .nuri-stack[data-align="stretch"] { align-items: stretch; }
  .nuri-stack[data-align="baseline"] { align-items: baseline; }
  .nuri-stack[data-justify="start"] { justify-content: flex-start; }
  .nuri-stack[data-justify="center"] { justify-content: center; }
  .nuri-stack[data-justify="end"] { justify-content: flex-end; }
  .nuri-stack[data-justify="between"] { justify-content: space-between; }
  .nuri-stack[data-justify="around"] { justify-content: space-around; }
  .nuri-stack[data-gap="xs"] { gap: var(--nuri-space-xs); }
  .nuri-stack[data-gap="sm"] { gap: var(--nuri-space-sm); }
  .nuri-stack[data-gap="md"] { gap: var(--nuri-space-md); }
  .nuri-stack[data-gap="lg"] { gap: var(--nuri-space-lg); }
  .nuri-stack[data-gap="xl"] { gap: var(--nuri-space-xl); }
  .nuri-stack[data-wrap="true"] { flex-wrap: wrap; }
  .nuri-stack[data-fill="grow"] { flex: 1 0 auto; }
  .nuri-stack[data-fill="grow-shrink"] { flex: 1 1 auto; min-inline-size: 0; }
  .nuri-stack[data-fill="even"] { flex: 1 1 0; min-inline-size: 0; }
  .nuri-stack[data-fill="hug"] { flex: 0 0 auto; }
  .nuri-stack[data-distribute="even"] > * { flex: 1 1 0; min-inline-size: 0; }
}

/* ──────────────────────────────────────────────────────────────
 * NURI · NAMESPACE CSS · BOX · GENERATED — DO NOT EDIT BY HAND
 *
 * GENERATED from the Field table (packages/spec/axes/resolve-map.ts ·
 * BOX_FIELDS) + the web spelling layer
 * (prototype/pipeline/parsers/namespace-css.js) by prototype/pipeline/css-preview.js,
 * wired into npm run build -w @nuri/prototype (its own build · regenerates IN PLACE
 * over this file · prototype/styles/box.css). This is the LIVE namespace CSS — the
 * pages link it and the web factory (prototype/factory/factory.js) styles the nuri-*
 * merged nodes with it. decision 2 reversed for the namespace layer (decision 74 ·
 * executing decision 70 · the L3c flip · N+38 · carved to @nuri/prototype at N+41):
 * the hand SoT retired (git-recoverable) and the generator is the sole source. Re-run
 * npm run build -w @nuri/prototype to regenerate; freshness gated by
 * prototype/pipeline/css-preview.test.js.
 * ────────────────────────────────────────────────────────────── */

@layer tokens {
  /* Empty by design (decision 37 · layout primitives parametrize semantic
   * vocabulary via prop, not via component-token aliasing). Mirrors the hand
   * box.css. */
}

@layer rules {
  .nuri-box { min-width: 0; }
  .nuri-box[data-width="xs"] { inline-size: var(--nuri-size-xs); }
  .nuri-box[data-width="sm"] { inline-size: var(--nuri-size-sm); }
  .nuri-box[data-width="md"] { inline-size: var(--nuri-size-md); }
  .nuri-box[data-width="lg"] { inline-size: var(--nuri-size-lg); }
  .nuri-box[data-width="xl"] { inline-size: var(--nuri-size-xl); }
  .nuri-box[data-width="2xl"] { inline-size: var(--nuri-size-2xl); }
  .nuri-box[data-width="3xl"] { inline-size: var(--nuri-size-3xl); }
  .nuri-box[data-height="xs"] { block-size: var(--nuri-size-xs); }
  .nuri-box[data-height="sm"] { block-size: var(--nuri-size-sm); }
  .nuri-box[data-height="md"] { block-size: var(--nuri-size-md); }
  .nuri-box[data-height="lg"] { block-size: var(--nuri-size-lg); }
  .nuri-box[data-height="xl"] { block-size: var(--nuri-size-xl); }
  .nuri-box[data-height="2xl"] { block-size: var(--nuri-size-2xl); }
  .nuri-box[data-height="3xl"] { block-size: var(--nuri-size-3xl); }
  .nuri-box[data-min-height="xs"] { min-block-size: var(--nuri-size-xs); }
  .nuri-box[data-min-height="sm"] { min-block-size: var(--nuri-size-sm); }
  .nuri-box[data-min-height="md"] { min-block-size: var(--nuri-size-md); }
  .nuri-box[data-min-height="lg"] { min-block-size: var(--nuri-size-lg); }
  .nuri-box[data-min-height="xl"] { min-block-size: var(--nuri-size-xl); }
  .nuri-box[data-min-height="2xl"] { min-block-size: var(--nuri-size-2xl); }
  .nuri-box[data-min-height="3xl"] { min-block-size: var(--nuri-size-3xl); }
  .nuri-box[data-min-width="xs"] { min-inline-size: var(--nuri-size-xs); }
  .nuri-box[data-min-width="sm"] { min-inline-size: var(--nuri-size-sm); }
  .nuri-box[data-min-width="md"] { min-inline-size: var(--nuri-size-md); }
  .nuri-box[data-min-width="lg"] { min-inline-size: var(--nuri-size-lg); }
  .nuri-box[data-min-width="xl"] { min-inline-size: var(--nuri-size-xl); }
  .nuri-box[data-min-width="2xl"] { min-inline-size: var(--nuri-size-2xl); }
  .nuri-box[data-min-width="3xl"] { min-inline-size: var(--nuri-size-3xl); }
  .nuri-box[data-padding="xs"] { padding: var(--nuri-space-xs); }
  .nuri-box[data-padding="sm"] { padding: var(--nuri-space-sm); }
  .nuri-box[data-padding="md"] { padding: var(--nuri-space-md); }
  .nuri-box[data-padding="lg"] { padding: var(--nuri-space-lg); }
  .nuri-box[data-padding="xl"] { padding: var(--nuri-space-xl); }
  .nuri-box[data-padding-x="xs"] { padding-inline: var(--nuri-space-xs); }
  .nuri-box[data-padding-x="sm"] { padding-inline: var(--nuri-space-sm); }
  .nuri-box[data-padding-x="md"] { padding-inline: var(--nuri-space-md); }
  .nuri-box[data-padding-x="lg"] { padding-inline: var(--nuri-space-lg); }
  .nuri-box[data-padding-x="xl"] { padding-inline: var(--nuri-space-xl); }
  .nuri-box[data-padding-y="xs"] { padding-block: var(--nuri-space-xs); }
  .nuri-box[data-padding-y="sm"] { padding-block: var(--nuri-space-sm); }
  .nuri-box[data-padding-y="md"] { padding-block: var(--nuri-space-md); }
  .nuri-box[data-padding-y="lg"] { padding-block: var(--nuri-space-lg); }
  .nuri-box[data-padding-y="xl"] { padding-block: var(--nuri-space-xl); }
  .nuri-box[data-padding-start="xs"] { padding-inline-start: var(--nuri-space-xs); }
  .nuri-box[data-padding-start="sm"] { padding-inline-start: var(--nuri-space-sm); }
  .nuri-box[data-padding-start="md"] { padding-inline-start: var(--nuri-space-md); }
  .nuri-box[data-padding-start="lg"] { padding-inline-start: var(--nuri-space-lg); }
  .nuri-box[data-padding-start="xl"] { padding-inline-start: var(--nuri-space-xl); }
  .nuri-box[data-padding-end="xs"] { padding-inline-end: var(--nuri-space-xs); }
  .nuri-box[data-padding-end="sm"] { padding-inline-end: var(--nuri-space-sm); }
  .nuri-box[data-padding-end="md"] { padding-inline-end: var(--nuri-space-md); }
  .nuri-box[data-padding-end="lg"] { padding-inline-end: var(--nuri-space-lg); }
  .nuri-box[data-padding-end="xl"] { padding-inline-end: var(--nuri-space-xl); }
  .nuri-box[data-padding-top="xs"] { padding-block-start: var(--nuri-space-xs); }
  .nuri-box[data-padding-top="sm"] { padding-block-start: var(--nuri-space-sm); }
  .nuri-box[data-padding-top="md"] { padding-block-start: var(--nuri-space-md); }
  .nuri-box[data-padding-top="lg"] { padding-block-start: var(--nuri-space-lg); }
  .nuri-box[data-padding-top="xl"] { padding-block-start: var(--nuri-space-xl); }
  .nuri-box[data-padding-bottom="xs"] { padding-block-end: var(--nuri-space-xs); }
  .nuri-box[data-padding-bottom="sm"] { padding-block-end: var(--nuri-space-sm); }
  .nuri-box[data-padding-bottom="md"] { padding-block-end: var(--nuri-space-md); }
  .nuri-box[data-padding-bottom="lg"] { padding-block-end: var(--nuri-space-lg); }
  .nuri-box[data-padding-bottom="xl"] { padding-block-end: var(--nuri-space-xl); }
  .nuri-box[data-radius="sm"] { border-radius: var(--nuri-radius-sm); }
  .nuri-box[data-radius="md"] { border-radius: var(--nuri-radius-md); }
  .nuri-box[data-radius="lg"] { border-radius: var(--nuri-radius-lg); }
  .nuri-box[data-radius="full"] { border-radius: var(--nuri-radius-full); }
  .nuri-box[data-radius-top="sm"] { border-start-start-radius: var(--nuri-radius-sm); border-start-end-radius: var(--nuri-radius-sm); }
  .nuri-box[data-radius-top="md"] { border-start-start-radius: var(--nuri-radius-md); border-start-end-radius: var(--nuri-radius-md); }
  .nuri-box[data-radius-top="lg"] { border-start-start-radius: var(--nuri-radius-lg); border-start-end-radius: var(--nuri-radius-lg); }
  .nuri-box[data-radius-top="full"] { border-start-start-radius: var(--nuri-radius-full); border-start-end-radius: var(--nuri-radius-full); }
  .nuri-box[data-aspect-ratio="square"] { aspect-ratio: var(--nuri-ratio-square); }
  .nuri-box[data-aspect-ratio="card"] { aspect-ratio: var(--nuri-ratio-card); }
  .nuri-box[data-center="true"] { margin-inline: auto; }
}

/* ──────────────────────────────────────────────────────────────
 * NURI · NAMESPACE CSS · PALETTE · GENERATED — DO NOT EDIT BY HAND
 *
 * GENERATED from the SURFACE role table (packages/spec/axes/palette-surface.ts)
 * by prototype/pipeline/css-preview.js — the inverse-spelling of resolvePalette
 * (packages/rn/runtime/resolve.ts) — wired into npm run build -w @nuri/prototype (its
 * own build · regenerates IN PLACE over prototype/styles/palette.css). This is the LIVE
 * palette namespace CSS: the pages link it and the web factory styles nuri-palette
 * nodes with it. decision 2 reversed for the namespace layer (decision 74 · executing
 * decision 70 · the L3c flip · N+38 · carved to @nuri/prototype at N+41) — the hand SoT
 * retired (git-recoverable), the generator is the sole source, and packages/rn/generated/data/
 * palette.ts asserts its contract against the same SURFACE SoT (re-sourced at N+40 · the
 * recipe-CSS cross-checks retired with the recipes · scripts/parsers/palette.js).
 * Re-run npm run build -w @nuri/prototype; freshness gated by
 * prototype/pipeline/palette-css.test.js.
 * ────────────────────────────────────────────────────────────── */

@layer tokens {
  /* Empty by design (decision 37 · the Stack/Box pattern). Palette dispatches
   * the semantic accent/chrome vocabulary directly in `@layer rules` via
   * attribute selectors — component-token aliasing would be useless
   * indirection. Mirrors the hand palette.css. */
}

@layer rules {
  .nuri-palette[data-variant="solid"] { background: var(--nuri-accent-solid); color: var(--nuri-accent-on-solid); }
  .nuri-palette[data-variant="soft"] { background: var(--nuri-bg-strong); color: var(--nuri-text-primary); }
  .nuri-palette[data-variant="ghost"] { background: transparent; color: var(--nuri-text-primary); }
  .nuri-palette[data-variant="subtle"] { color: var(--nuri-border-strong); }
  .nuri-palette[data-variant="outline"] { background: transparent; color: var(--nuri-text-muted); border: var(--nuri-border-1) solid var(--nuri-border-subtle); }
  .nuri-palette[data-chrome="canvas"] { background: var(--nuri-bg-canvas); color: var(--nuri-text-primary); }
  .nuri-palette[data-chrome="subtle"] { background: var(--nuri-bg-subtle); color: var(--nuri-text-primary); }
  .nuri-palette[data-chrome="strong"] { background: var(--nuri-bg-strong); color: var(--nuri-text-primary); }
  .nuri-palette[data-chrome="transparent"] { background: transparent; color: var(--nuri-text-primary); }
  .nuri-palette[data-variant="solid"][data-press-color]:active { background: var(--nuri-accent-solid-pressed); }
  .nuri-palette[data-variant="soft"][data-press-color]:active { background: var(--nuri-bg-pressed); }
  .nuri-palette[data-variant="ghost"][data-press-color]:active { background: var(--nuri-bg-subtle); }
  .nuri-palette[data-variant="outline"][data-press-color]:active { background: var(--nuri-bg-subtle); }
}

/* ──────────────────────────────────────────────────────────────
 * NURI · NAMESPACE CSS · INTERACTIVE · GENERATED — DO NOT EDIT BY HAND
 *
 * GENERATED from the interactive EFFECT set (packages/spec/axes/interactive-
 * effects.ts) by prototype/pipeline/css-preview.js — the inverse-spelling of
 * flattenPart (packages/rn/runtime/resolve.ts) — wired into npm run build -w
 * @nuri/prototype (its own build · regenerates IN PLACE over prototype/styles/
 * interactive.css). This is the LIVE interactive namespace CSS: the pages link it and
 * the web factory styles the nuri-interactive merged node with it. decision 2 reversed
 * for the namespace layer (decision 74 · executing decision 70 · the L3c flip · N+38 ·
 * carved to @nuri/prototype at N+41) — the hand SoT retired (git-recoverable), the
 * generator is the sole source. The rule ORDER is LOAD-BEARING — pressScale before
 * disabledGuard so a disabled control's transform resolves to none (the equal-
 * specificity pair · the order guard proves it). Re-run npm run build -w @nuri/
 * prototype; freshness gated by prototype/pipeline/interactive-css.test.js.
 * ────────────────────────────────────────────────────────────── */

@layer tokens {
  /* Empty by design (decision 37 · the Stack/Box/Palette pattern). interactive
   * dispatches the shared --nuri-interaction-* / --nuri-focus-ring / --nuri-
   * duration-fast vocabulary directly in `@layer rules`; a --nuri-interactive-*
   * alias would be useless indirection. Mirrors the hand interactive.css. */
}

@layer rules {
  .nuri-interactive { cursor: pointer; transition: background-color var(--nuri-duration-fast) ease, transform var(--nuri-duration-fast) ease; }
  .nuri-interactive:focus-visible { outline: 2px solid var(--nuri-focus-ring); outline-offset: 2px; }
  .nuri-interactive[data-press-scale]:active, .nuri-interactive[data-press-scale][data-pressed] { transform: scale(var(--nuri-interaction-press-scale)); }
  .nuri-interactive[aria-disabled="true"]:active, .nuri-interactive[aria-disabled="true"][data-pressed] { transform: none; }
  .nuri-interactive:disabled, .nuri-interactive[aria-disabled="true"] { opacity: var(--nuri-interaction-disabled-opacity); }
}

/* ──────────────────────────────────────────────────────────────
 * NURI · RUNTIME · RESET  (web-component environment boilerplate · the plumbing-layer seed)
 *
 * PLUMBING, not a design decision. Hand-written boilerplate that makes the
 * custom-element / browser environment behave — the same category as the
 * `display:contents` wrappers, the `:not(:defined)` pre-upgrade skeletons,
 * the flexbox `min-width:0`, and the global `box-sizing` (today still
 * scattered through the component CSS + shell.css). It resolves NO token and
 * encodes NO design decision, so it stays OUT of the design SoT (tokens =
 * pure data · the `@layer` namespace mappings = the design decisions · the
 * descriptors) AND out of the §9-generation-target namespace CSS — folding it
 * into interactive.css would pollute a to-be-generated file with hand-authored
 * cruft and lose it at generation (operator-directed · 2026-06-22).
 *
 * Lives under factory/ — the runtime web mechanism (beside the web
 * factory). This file is the SEED of the plumbing layer; the full design-SoT /
 * plumbing separation (collecting the scattered wrappers / skeletons /
 * box-sizing / min-width / resets under this roof · a decision in the family
 * of decision 35 · pipeline-source vs build-output physically separated) is
 * its own future session.
 *
 * WHAT'S HERE TODAY — the native-<button> host normalization (introduced at
 * S3 · decision 67). The web interactive host (`.nuri-interactive`, the native
 * <button> nuri-pressable owns) carries UA chrome — a 2px outset border, the
 * bezel (`appearance: auto`), an intrinsic `1px 6px` padding, text-
 * (un)selectability, the UA font. The collapsed recipe shed all of it across
 * .nuri-button's base + size blocks: the base (button.css:188) drops border /
 * appearance / user-select + font:inherit; the size blocks (button.css:213
 * etc.) set `padding: 0 Xpx`, whose `0` zeroes the block axis. The de-collapsed
 * factory styles the SAME button from box ⊕ stack ⊕ palette, so the host needs
 * that normalization too — without it the factory Button (and a bare
 * <nuri-pressable>) renders the raw UA button. Future native-element hosts
 * (form controls, links, lists) add their resets HERE.
 *
 * @layer rules — the SAME cascade layer as the namespace rules, so specificity
 * (not layer priority) decides: `padding: 0` here is OUTRANKED by box's
 * `.nuri-box[data-padding-x="lg"]` (0,2,0 vs this 0,1,0) for the inline axis,
 * so box supplies the inset while this only zeroes the UA `padding-block`. An
 * UNLAYERED reset would beat the layered box rule and wipe the inline padding
 * too — hence the explicit layer.
 *
 * Scope: `.nuri-interactive` only (the pressable host · the factory's
 * interactive `view`). A non-button interactive node (an [aria-disabled] div)
 * is unaffected — the resets are no-ops there. Load this alongside the
 * namespace CSS on any page that mounts the factory or a bare <nuri-pressable>.
 * ────────────────────────────────────────────────────────────── */

@layer rules {
  .nuri-interactive {
    border: 0;
    padding: 0;
    /* The UA `<button>` background (the `buttonface` / `#efefef` bezel fill ·
     * appearance:none alone does NOT clear it cross-browser) — normalized to
     * transparent so a fg-ONLY palette variant (`subtle` · used for an unselected
     * tab item) reveals the canvas behind it, matching RN's Pressable (no UA
     * chrome). A variant that PAINTS a bg (solid/soft/ghost/outline) still wins: its
     * `.nuri-palette[data-variant]` (0,2,0) outranks this `.nuri-interactive`
     * (0,1,0) in the same @layer, so only the no-bg `subtle` falls through here. */
    background: transparent;
    font-family: inherit;
    appearance: none;
    -webkit-appearance: none;
    user-select: none;
  }

  /* MOUNT-A-TREE host display:contents wrapper (the plumbing category named above).
   * A factory mount-a-tree host (<nuri-button> / <nuri-icon-button> / <nuri-tab-bar-item>)
   * wraps a FRESH nuri-* tree whose inner merged node carries the geometry/flex (e.g. a
   * tab-bar-item's `stack:{fill:'even'}` · flex 1 1 0). The host must be layout-INVISIBLE
   * so that merged node becomes the parent's DIRECT flex child — otherwise the `even` flex
   * sits a level too deep and the columns bunch instead of spreading edge-to-edge. This is
   * ONE GENERIC rule keyed on the factory's `data-host-tree` marker (defineNuriComponent
   * sets it on every mount-a-tree host · NOT a per-component selector); the apply-NS-to-host
   * painting nodes (compound topbar / open tab-bar) carry no marker and stay visible. The RN
   * analogue needs nothing (the component IS the node · no wrapper). */
  [data-host-tree] {
    display: contents;
  }
}
