/* ============================================================================
   Self-hosted type (U40 / D10): a kiosk, a menuboard or a storefront must
   never depend on fonts.googleapis.com — a store's internet drop, a content
   filter, or a walled captive network would silently swap the brand face for
   a system fallback. DM Sans (SIL Open Font License 1.1) is the platform
   default; a tenant's own display/body faces arrive the same way through the
   brand kit (brand.displayFont / brand.bodyFont) once uploaded to the tenant
   asset store. Variable font: one file covers weights 100–1000.
   ============================================================================ */
@font-face {
  font-family: 'DM Sans';
  font-style: normal;
  font-weight: 100 1000;
  font-display: swap;
  src: url(fonts/dm-sans-normal.woff2) format('woff2');
}
@font-face {
  font-family: 'DM Sans';
  font-style: italic;
  font-weight: 100 1000;
  font-display: swap;
  src: url(fonts/dm-sans-italic.woff2) format('woff2');
}

/* ============================================================================
   TENANT BRAND KIT — the --brand-* namespace (U40 / D10).

   Customer surfaces (kiosk, storefront, order, track, order-ready, customer
   display, menuboards, signage) paint from THESE tokens, never from the
   operator palette in part3-tokens.css. NX.applyBrand() overwrites them at
   runtime from tenant.brand (GCC → Brand & Appearance); the values below are
   the neutral defaults a surface renders when no tenant has been applied yet
   (first paint, an unbound device, a preview) so nothing ever flashes
   vendor-coloured. Every customer surface therefore carries ZERO hex
   literals — the U01 gate (verify-nexus-tokens) proves it.

   Kit shape (tenant.brand), all optional beyond primary/secondary:
     primary, secondary        two brand colours (CTAs, tags, accents)
     light, dark               the two canvases; brand.canvas picks one
     surface                   card colour on the dark canvas
     text                      ink on the dark canvas
     ink                       ink on the light canvas
     onPrimary                 ink on the primary colour (defaults white)
     logoUrl, heroImageUrl     tenant asset store
     fontFamily | displayFont + bodyFont   self-hosted faces
     canvas: 'light'|'dark'    photoTreatment: 'cutout'|'card'
     attractLine, attractLine2, rewardsJoinUrl, statutory, featuredItemId
   ============================================================================ */
:root {
  --brand-primary: #3F7CFF;
  --brand-secondary: #FFC84A;
  --brand-light: #F7F7F9;
  --brand-dark: #14161C;
  --brand-surface: #1E2129;
  --brand-text: #F4F6FA;
  --brand-ink: #1A1D24;
  --brand-on-primary: #FFFFFF;
  /* High-contrast accessibility mode (kiosk/storefront toggle): pure ink on
     pure white, brand kept only for the accent. */
  --brand-hc-ink: #000000;
  --brand-hc-canvas: #FFFFFF;
  --brand-hc-mute: #333333;
  --brand-hc-sunk: #F2F2F2;
  /* QR codes render ink-on-white regardless of canvas so every scanner reads them. */
  --brand-qr-ink: #000000;
  --brand-qr-paper: #FFFFFF;
}

/* ============================================================================
   CUSTOMER CANVAS TOKENS (U40 / D9 / D10) — the --k-* layer every guest-facing
   surface paints from: kiosk, storefront, track, order-ready, customer
   display. Derived from the tenant brand kit (--brand-*, brand-kit.css,
   overwritten at runtime by NX.applyBrand) so one tenant edit re-skins every
   customer surface at once and no surface carries a colour of its own.

   Two canvases: light (the default) and dark, switched by the ONE theme
   attribute [data-theme] on <html> (D13) — set by the surface from
   brand.canvas, never from the device. High contrast (body.k-hc) is the
   accessibility toggle: pure ink on white, brand kept only for the accent.
   ============================================================================ */
:root{
  --k-canvas: var(--brand-light);
  --k-card: var(--brand-hc-canvas);
  --k-ink: var(--brand-ink);
  --k-ink-2: color-mix(in srgb, var(--k-ink) 64%, var(--k-canvas));
  --k-mute: color-mix(in srgb, var(--k-ink) 46%, var(--k-canvas));
  --k-line: color-mix(in srgb, var(--k-ink) 10%, var(--k-canvas));
  --k-sunk: color-mix(in srgb, var(--k-ink) 5%, var(--k-canvas));
  --k-accent: var(--brand-primary);
  --k-accent-2: var(--brand-secondary);
  --k-on-accent: var(--brand-on-primary);
  --k-deep: var(--brand-dark);   /* the brand's darkest tone — the ground the flame gradient burns out of, on every canvas */
  --k-good: var(--nx-good);
  --k-bad: var(--nx-bad);
  --k-display: var(--brand-display-font, var(--brand-font, var(--nx-font-sans)));
  --k-body: var(--brand-body-font, var(--brand-font, var(--nx-font-sans)));
  --k-accent-font: var(--brand-accent-font, var(--k-display));
  --k-r: 22px;
  --k-r-sm: 14px;
  --k-shadow: 0 10px 30px rgba(0,0,0,.08), 0 2px 6px rgba(0,0,0,.05);
  --k-shadow-lift: 0 18px 44px rgba(0,0,0,.14);
  --k-photo-shadow: drop-shadow(0 14px 16px rgba(0,0,0,.18));
  --k-tap: var(--nx-tap-heavy, 64px);
  --k-rail-w: 208px;
  --k-bar-h: 116px;
  --k-a11y-h: 60px;
  --k-gutter: var(--nx-space-5);
  --k-scale: 1;
}
:root[data-theme="dark"]{
  --k-canvas: var(--brand-dark);
  --k-card: var(--brand-surface);
  --k-ink: var(--brand-text);
  --k-shadow: 0 10px 30px rgba(0,0,0,.35), 0 2px 6px rgba(0,0,0,.25);
  --k-shadow-lift: 0 18px 44px rgba(0,0,0,.5);
  --k-photo-shadow: drop-shadow(0 14px 16px rgba(0,0,0,.45));
}
/* High contrast (accessibility toggle): black on white, brand kept only for the accent. */
body.k-hc{ --k-canvas:var(--brand-hc-canvas); --k-card:var(--brand-hc-canvas); --k-ink:var(--brand-hc-ink); --k-ink-2:var(--brand-hc-ink); --k-mute:var(--brand-hc-mute); --k-line:var(--brand-hc-ink); --k-sunk:var(--brand-hc-sunk); --k-shadow:none; --k-shadow-lift:none; --k-photo-shadow:none; }
body.k-hc .k-card, body.k-hc .k-rail button, body.k-hc .k-mod, body.k-hc .k-size, body.k-hc .k-sheet{ border:2px solid var(--brand-hc-ink); }

/* Brand voice: a tenant whose headlines are uppercase (HJ) says so once in the
   kit (headlineCase:'upper'); every customer surface follows. */
/* A brand face that ships one weight (Block Pro) must never be faux-bolded by the browser. */
html[data-nx-kit]{ font-synthesis-weight: none; font-synthesis: none; }
[data-brand-caps] .k-caps, [data-brand-caps] h1.k-h, [data-brand-caps] .k-att-cta h1, [data-brand-caps] .or-name, [data-brand-caps] .cd-name, [data-brand-caps] .k-cat-head h2, [data-brand-caps] .or-h2 { text-transform: uppercase; letter-spacing: .01em; }
.k-flash { font-family: var(--k-accent-font); font-weight: 400; letter-spacing: 0; text-transform: none; }

/* The brand's ground. HJ's campaign art is lit from below — near-black with
   the red burning up from the bottom-left — never a flat field of the
   primary. Every customer screen that needs a branded backdrop uses this. */
.k-flame { background: radial-gradient(110% 95% at 18% 108%, var(--k-accent) 0%, color-mix(in srgb, var(--k-accent) 62%, var(--k-deep)) 30%, color-mix(in srgb, var(--k-accent) 22%, var(--k-deep)) 62%, color-mix(in srgb, var(--k-accent) 8%, var(--k-deep)) 100%); }

/* ============================================================================
   NEXUS INTERFACE STANDARD — PART 3 TOKENS (D1-D12, U01)
   The canonical, closed token set. Twenty colour tokens, no more — a surface
   that needs an twenty-first is wrong, not this file. Declared ONCE here;
   _surface/tokens.css and _surface/surface.css (the pre-existing kit) are
   bundled alongside this file into nexus.css but are NOT rewritten to consume
   these yet — that reconciliation is later, deliberate work (see the PR body
   for the exact collision this avoided). These tokens are additive and inert
   until a surface's restyle wave binds its CSS to them.

   D9: one theme per context, never inherited from the device. Both themes are
   declared at :root; dark is overridden by token only via the explicit
   [data-theme="dark"] attribute (set once per surface in its markup) — never
   a prefers-color-scheme media query, never data-nx-auto-theme. color-scheme
   always matches the painted ground.
   ============================================================================ */
:root {
  color-scheme: light;

  /* ---- Structure ---------------------------------------------------------- */
  --nx-ground: #F4F6FA;
  --nx-surface: #FFFFFF;
  --nx-sunk: #ECEEF3;
  --nx-line: #DDE1EA;
  --nx-line-soft: #E9ECF2;

  /* ---- Text ----------------------------------------------------------------
     --nx-surface and --nx-line collide by name with the pre-existing kit
     roles of the same name (surface.css --nx-surface / --nx-line); those were
     renamed to --nxk-surface / --nxk-line so the two systems can coexist
     without either silently overriding the other's live value. */
  --nx-ink: #1A1D24;
  --nx-ink-2: #4A4F5B;
  --nx-mute: #7A808D;
  --nx-on-brand: #FFFFFF;

  /* ---- Brand, tenant-themed -------------------------------------------------
     Placeholder neutral brand until a tenant seed is applied. A tenant brand
     that happens to be red (e.g. Hungry Jack's) lives ONLY here — never in
     --nx-bad (see the hard rule below). */
  --nx-brand: #3F7CFF;
  --nx-brand-hover: #2E6AEF;
  --nx-brand-soft: #E8F0FF;

  /* ---- Semantic --------------------------------------------------------------
     HARD RULE: --nx-bad is never the brand red. When the tenant brand is red,
     danger must still read as unmistakably danger, so this fixed danger red
     is independent of --nx-brand no matter what a tenant's seed is. */
  --nx-good: #1A7F4B;      --nx-good-soft: #E7F6EE;
  --nx-warn: #9A6300;      --nx-warn-soft: #FDF3E2;
  --nx-bad: #C62F3B;       --nx-bad-soft: #FDECEE;
  --nx-info: #3F7CFF;      --nx-info-soft: #E8F0FF;

  /* ---- Type: one scale, eight steps, nothing between, no decimals -------- */
  --nx-text-1: 12px; --nx-text-2: 14px; --nx-text-3: 16px; --nx-text-4: 18px;
  --nx-text-5: 22px; --nx-text-6: 28px; --nx-text-7: 36px; --nx-text-8: 48px;
  --nx-font-sans: "DM Sans", Inter, system-ui, -apple-system, "Segoe UI", Roboto, Lato, Arial, sans-serif;
  --nx-font-mono: "JetBrains Mono", "DejaVu Sans Mono", ui-monospace, SFMono-Regular, Menlo, monospace;
  --nx-lh-body: 1.5;
  --nx-lh-display: 1.2;

  /* ---- Space: 4px base, nothing else ever -------------------------------- */
  --nx-space-1: 4px;  --nx-space-2: 8px;  --nx-space-3: 12px; --nx-space-4: 16px;
  --nx-space-5: 24px; --nx-space-6: 32px; --nx-space-7: 48px; --nx-space-8: 64px;

  /* ---- Shape -------------------------------------------------------------- */
  --nx-radius-control: 6px;
  --nx-radius-card: 10px;
  --nx-radius-pill: 999px;
  --nx-elev-raised: 0 1px 2px rgba(0,0,0,.06), 0 1px 3px rgba(0,0,0,.05);
  --nx-elev-modal: 0 16px 40px rgba(0,0,0,.35);

  /* ---- Motion --------------------------------------------------------------
     prefers-reduced-motion is honoured globally below — this is the one
     motion-related media query the standard requires, distinct from the
     theme-following media query D9 forbids. */
  --nx-dur-state: 120ms;
  --nx-dur-entry: 200ms;

  /* ---- Touch ---------------------------------------------------------------
     44px floor everywhere; 64px on NexusPOS/NexusFlow/NexusCook/NexusQueue/
     NexusServe (set --nx-tap-min: var(--nx-tap-heavy) on those surfaces). */
  --nx-tap-min: 44px;
  --nx-tap-heavy: 64px;
  --nx-tap-gap: 8px;
}

:root[data-theme="dark"] {
  color-scheme: dark;
  --nx-ground: #111318;
  --nx-surface: #181B21;
  --nx-sunk: #0C0D11;
  --nx-line: #262A33;
  --nx-line-soft: #1D2029;
  --nx-ink: #F4F6FA;
  --nx-ink-2: #C7CCD6;
  --nx-mute: #8A909C;
  --nx-on-brand: #FFFFFF;
  --nx-brand: #3F7CFF;
  --nx-brand-hover: #5C8FFF;
  --nx-brand-soft: #1B2740;
  --nx-good: #3FCE85;      --nx-good-soft: #10241A;
  --nx-warn: #E2A53C;      --nx-warn-soft: #241A09;
  --nx-bad: #E5646F;       --nx-bad-soft: #2A1417;
  --nx-info: #5C8FFF;      --nx-info-soft: #1B2740;
}

.nx-tnum, .nx-num, .nx-price, [data-tnum] {
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum" 1;
}

@media (prefers-reduced-motion: reduce) {
  *, ::before, ::after {
    animation-duration: .01ms !important; animation-iteration-count: 1 !important;
    transition-duration: .01ms !important; scroll-behavior: auto !important;
  }
}

/* ============================================================================
   NEXUS DESIGN TOKENS - the OKLCH colour ladder + the semantic layer built on
   it. Injected FIRST, ahead of _surface/surface.css.

   R-030 (2026-08-12): the platform's Tier-1 scale (type, space, radius,
   motion, focus) now lives in _surface/surface.css as `--nx-*`. The Tier-1
   block below is aliases of it, so no name carries two values. This file
   still owns the --step-* ladder, the --color-* semantic roles bound to it,
   the surface contexts, and the global primitives.

   Three tiers (never skip a tier):
     TIER 1  primitives  - raw scales: type, space, radius, motion, and the
                            12-step colour ladder (--step-1..12). The colour
                            steps are the ONLY thing that changes per tenant;
                            NX.applyBrand() -> NXTheme.generatePalette() rewrites
                            them at runtime (WCAG 2.2 AA validated) scoped by
                            [data-tenant]. The values below are the validated
                            NEUTRAL NEXUS default, so an unbranded tenant or a
                            no-JS render still looks intentional.
     TIER 2  semantic    - role-named aliases (--color-bg, --color-text,
                            --color-accent, --color-border, meaning colours,
                            elevation). Components bind to THESE, never to a raw
                            step or hex.
     TIER 3  component   - per-component knobs (--btn-*, --input-*, --card-*).

   Rules encoded here: hierarchy from weight+size not colour; 8px spacing (4px
   intra only); near-monochrome + ONE accent used only for meaning; tinted
   neutrals (never pure grey/#000); hairline borders; tokenised motion honouring
   prefers-reduced-motion; six-state interactive primitives; skeletons.
   ============================================================================ */

:root {
  /* ---- TIER 1 - ALIASES OF THE CANONICAL --nx-* SCALE --------------------
     R-030: surface.css owns the platform scale. Everything in this block is
     an ALIAS, so a name can never carry two values again. (Custom-property
     substitution is lazy, so pointing at --nx-* declared in the file injected
     after this one resolves correctly.)

     Two names USED to collide here and silently lose to surface.css:
       --radius-sm  declared 6px here, 14px there  -> effective 14px
       --font-mono  two different fallback stacks  -> effective surface.css's
     --radius-sm is now declared ONLY in surface.css (it is a legacy 14px that
     matches no scale step; 9 surfaces depend on that value). */
  --font-sans: var(--nx-font-body);
  --font-display: var(--nx-font-head);
  --font-mono: var(--nx-font-mono);

  /* Major-Third-derived display scale (12/14/16/20/25/31/39/49). */
  --text-xs: var(--nx-text-xs); --text-sm: var(--nx-text-sm);
  --text-base: var(--nx-text-base); --text-lg: var(--nx-text-lg);
  --text-xl: var(--nx-text-xl); --text-2xl: var(--nx-text-2xl);
  --text-3xl: var(--nx-text-3xl); --text-4xl: var(--nx-text-4xl);

  --fw-regular: var(--nx-fw-regular); --fw-medium: var(--nx-fw-medium);
  --fw-semibold: var(--nx-fw-semibold); --fw-bold: var(--nx-fw-bold);
  --fw-heavy: var(--nx-fw-heavy);
  --lh-tight: var(--nx-lh-tight); --lh-snug: var(--nx-lh-snug);
  --lh-normal: var(--nx-lh-normal); --lh-relaxed: var(--nx-lh-relaxed);
  --tracking-tight: var(--nx-track-tight); --tracking-normal: var(--nx-track-normal);
  --tracking-wide: 0.02em;

  /* ---- TIER 1 - SPACE (strict 8px; 4px intra-component only) ------------ */
  --space-0: var(--nx-space-0); --space-1: var(--nx-space-1); --space-2: var(--nx-space-2);
  --space-3: var(--nx-space-3); --space-4: var(--nx-space-4); --space-5: var(--nx-space-5);
  --space-6: var(--nx-space-6); --space-7: var(--nx-space-7); --space-8: var(--nx-space-8);
  --space-9: var(--nx-space-9);

  /* ---- TIER 1 - RADIUS (surface.css owns radius, radius-xs and radius-sm) */
  --radius-md: var(--nx-radius-md); --radius-lg: var(--nx-radius-lg);
  --radius-xl: var(--nx-radius-xl); --radius-full: var(--nx-radius-pill);

  /* ---- TIER 1 - MOTION (honour prefers-reduced-motion below) ------------ */
  --motion-fast: var(--nx-dur-fast); --motion-standard: var(--nx-dur-base);
  --motion-slow: var(--nx-dur-slow);
  --ease-out: var(--nx-ease-out);
  --ease-in-out: cubic-bezier(.5, 0, .2, 1);
  --ease-standard: var(--nx-ease-standard);

  /* ---- TIER 1 - FOCUS --------------------------------------------------- */
  --focus-ring-width: var(--nx-focus-width);

  /* ---- TIER 1 - COLOUR LADDER (NEUTRAL NEXUS default, light) ------------
     Generated + WCAG-AA-validated by _surface/theme-engine.js. Overridden
     per-tenant at runtime; text binds to 11/12, never the raw seed. */
  --step-1: #fdfdff; --step-2: #f8f8fc; --step-3: #f0f1f7; --step-4: #e8e9f2;
  --step-5: #e1e2ed; --step-6: #d8dae6; --step-7: #8e8f9c; --step-8: #b8b9c8;
  --step-9: #665ee6; --step-10: #584cd3; --step-11: #646683; --step-12: #2e2f3d;
  --on-accent: #FFFFFF;
  --accent-seed: #6B5CFF;         /* exact brand hex - reserved for logos only */
  --shadow-rgb: 46, 47, 61;       /* brand-tinted shadow base (from step-12) */

  /* ---- TIER 2 - SEMANTIC ROLES ----------------------------------------- */
  --color-bg: var(--step-1);
  --color-bg-subtle: var(--step-2);
  --color-surface: #ffffff;
  --color-panel: var(--step-2);
  --color-component: var(--step-3);
  --color-component-hover: var(--step-4);
  --color-component-active: var(--step-5);
  --color-border-subtle: var(--step-6);
  --color-border: var(--step-7);
  --color-border-strong: var(--step-8);
  --color-accent: var(--step-9);
  --color-accent-hover: var(--step-10);
  --color-on-accent: var(--on-accent);
  --color-accent-subtle: color-mix(in srgb, var(--step-9) 12%, var(--color-surface));
  /* --color-accent doubles as TEXT on --color-accent-subtle's tint (kds.html
     .f-station.is-on, .f-bind.is-bound) as well as a SOLID FILL with white
     text elsewhere (.f-ident .logo, .f-views button.is-on) — two roles, one
     token. --step-9 is tuned for the fill role; on the subtle tint it
     measured 3.30:1 in dark (kds.html, real composited background), under
     the 4.5:1 floor. Not fixed by darkening --step-9 (would break the fill
     role's white-text contrast) or by a bare --step-10 (4.52:1 in dark —
     right at the floor, no margin against measurement noise). This is the
     dedicated text-safe role instead. */
  --color-accent-on-subtle: var(--step-9);
  --color-text: var(--step-12);
  --color-text-secondary: var(--step-11);
  --color-text-on-solid: var(--on-accent);
  /* R-030: was --step-8 (#b8b9c8), which rendered a 1.56:1 focus ring on a
     white surface — well under SC 1.4.11's 3:1. Now the canonical focus role
     (4.57:1 light / 5.60:1 dark). */
  --color-focus: var(--nx-focus);

  /* Meaning colours - fixed, tinted, INDEPENDENT of the brand accent so danger
     never reads as brand. (Light.) */
  --color-danger: #c62f3b; --color-danger-hover: #ad2531; --color-on-danger: #ffffff;
  --color-danger-subtle: #fdecee; --color-danger-border: #f1bec4;
  --color-success: #1a7f4b; --color-success-hover: #156b3f; --color-on-success: #ffffff;
  --color-success-subtle: #e7f6ee; --color-success-border: #b3ddc6;
  --color-warning: #9a6300; --color-warning-hover: #825300; --color-on-warning: #ffffff;
  --color-warning-subtle: #fdf3e2; --color-warning-border: #eecf9a;
  --color-info: var(--step-9); --color-info-subtle: var(--color-accent-subtle);

  /* Elevation - soft, brand-tinted. Dark theme replaces these with near-none
     (lift comes from lighter surfaces, not shadow). */
  --elevation-0: none;
  --elevation-1: 0 1px 2px rgba(var(--shadow-rgb), .06), 0 1px 3px rgba(var(--shadow-rgb), .05);
  --elevation-2: 0 2px 4px rgba(var(--shadow-rgb), .06), 0 8px 20px rgba(var(--shadow-rgb), .09);
  --elevation-3: 0 12px 28px rgba(var(--shadow-rgb), .13), 0 32px 64px rgba(var(--shadow-rgb), .17);

  /* ---- TIER 3 - COMPONENT TOKENS --------------------------------------- */
  --btn-height: 40px; --btn-height-sm: 32px; --btn-height-lg: 48px;
  --btn-radius: var(--radius-md); --btn-pad-x: var(--space-4);
  --btn-font-size: var(--text-sm); --btn-font-weight: var(--fw-semibold);
  --input-height: 40px; --input-radius: var(--radius-md);
  --input-bg: var(--color-surface); --input-border: var(--color-border);
  --input-border-hover: var(--color-border-strong);
  --card-radius: var(--radius-lg); --card-bg: var(--color-surface);
  --card-border: var(--color-border-subtle); --card-shadow: var(--elevation-1);
  --card-pad: var(--space-5);
}

/* ============================================================================
   DARK THEME - default neutral steps, applied ONLY by the explicit
   [data-theme="dark"] attribute (D9: one theme per context, never inherited
   from the device — no data-nx-auto-theme, no prefers-color-scheme media
   query, anywhere). NX.applyBrand rewrites --step-* per tenant; these are the
   neutral fallback.
   ============================================================================ */
:root[data-theme="dark"] {
  color-scheme: dark;
  --step-1: #111112; --step-2: #18191b; --step-3: #222227; --step-4: #2b2b32;
  --step-5: #34353d; --step-6: #3e3f49; --step-7: #61626d; --step-8: #696b78;
  --step-9: #665ee6; --step-10: #7a77ff; --step-11: #aeb2d2; --step-12: #e4e6fa;
  --on-accent: #FFFFFF;
  --shadow-rgb: 0, 0, 0;
  --color-surface: var(--step-2);
  /* See --color-accent-on-subtle's definition above for why this needs its
     own value in dark rather than falling back to --step-9: measured
     3.30:1/3.31:1 on kds.html's real composited --color-accent-subtle tint. */
  --color-accent-on-subtle: #918fff;
  --color-danger: #e5646f; --color-danger-hover: #ef7b85; --color-on-danger: #1a0d0f;
  --color-danger-subtle: #2a1417; --color-danger-border: #572329;
  --color-success: #3fce85; --color-success-hover: #57d896; --color-on-success: #08160e;
  --color-success-subtle: #10241a; --color-success-border: #1f4a34;
  --color-warning: #e2a53c; --color-warning-hover: #edb457; --color-on-warning: #1c1405;
  --color-warning-subtle: #241a09; --color-warning-border: #4a3a1a;
  --elevation-1: 0 1px 2px rgba(0, 0, 0, .4);
  --elevation-2: 0 2px 6px rgba(0, 0, 0, .45), 0 10px 24px rgba(0, 0, 0, .4);
  --elevation-3: 0 16px 40px rgba(0, 0, 0, .55), 0 40px 80px rgba(0, 0, 0, .6);
}

/* ============================================================================
   SURFACE CONTEXTS - the same tokens, tuned for the room the screen lives in.
     glare   : counter / POS in daylight - bump weight + border presence.
     kitchen : KDS / Cook - forced dark, oversized, maximum glance contrast.
   ============================================================================ */
[data-surface="glare"] {
  --card-border: var(--color-border);
  --input-border: var(--color-border-strong);
  --btn-font-weight: var(--fw-bold);
}
[data-surface="kitchen"] {
  --btn-height: 56px; --btn-font-size: var(--text-lg); --btn-font-weight: var(--fw-bold);
  --card-border: var(--color-border);
}

/* ============================================================================
   GLOBAL PRIMITIVES - the base every component inherits.
   ============================================================================ */
/* Keyboard-visible focus ring (state 3 of 6) - one consistent affordance.
   R-030: solid, not a 70% tint. A tinted ring composites toward the surface
   behind it and measured 1.56:1; SC 1.4.11 requires 3:1. */
:where(a, button, input, select, textarea, [tabindex], [role="button"]):focus-visible {
  outline: var(--nx-focus-width) solid var(--nx-focus);
  outline-offset: var(--nx-focus-offset);
  border-radius: var(--radius-sm);
}
/* Tabular numerals for every money / count / timer. */
.nx-tnum, .nx-num, .nx-price, [data-tnum] { font-variant-numeric: tabular-nums; font-feature-settings: "tnum" 1; }

/* Skeletons, not spinners, for loading (state 6 of 6). */
@keyframes nx-skeleton-shimmer { 0% { background-position: 200% 0; } 100% { background-position: -200% 0; } }
.nx-skeleton {
  background: linear-gradient(90deg, var(--color-component) 25%, var(--color-component-hover) 37%, var(--color-component) 63%);
  background-size: 200% 100%;
  animation: nx-skeleton-shimmer 1.4s var(--ease-in-out) infinite;
  border-radius: var(--radius-sm);
  color: transparent !important;
}

/* Motion respects the user's reduced-motion preference (never override intent). */
@media (prefers-reduced-motion: reduce) {
  *, ::before, ::after {
    animation-duration: .01ms !important; animation-iteration-count: 1 !important;
    transition-duration: .01ms !important; scroll-behavior: auto !important;
  }
}

/* ============================================================================
   NEXUS SURFACE KIT — the ONE design system for every device surface.
   Single source of truth. Injected into every surface's <style data-nx-surface>
   block by scripts/build-surfaces.mjs (verify-surfaces.mjs gates freshness).
   Do NOT edit the injected copies in the .html files — edit THIS file.

   R-030 (2026-08-12): this file is now the canonical TOKEN LAYER for the
   platform. Everything below is expressed as `--nx-*` custom properties in
   three tiers, and every component class in this file binds to those tokens —
   never to a raw literal.

     TIER 1  scales      --nx-space-* --nx-text-* --nx-radius-* --nx-elev-*
                         --nx-font-* --nx-dur-* --nx-ease-* --nx-tap*
                         + the fixed BRAND PRIMITIVES (--nx-violet, --nx-teal …)
     TIER 2  roles       --nx-bg --nxk-surface --nxk-ink --nx-border --nx-accent
                         --nx-success/-warning/-danger --nx-focus …
                         Light in :root, dark in the theme blocks below.
     TIER 3  component   --nx-btn-* --nx-input-* --nx-card-* --nx-chip-* …

   RULES
     1. Components bind to TIER 2/3. Never to a brand primitive, never to a hex.
     2. Brand primitives are FIXED brand identity (logos, family colours,
        tenant seeds). They are not roles and are not theme-aware.
     3. Colour roles meet WCAG 2.1 AA (4.5:1 body text) in BOTH themes, and the
        focus ring meets SC 1.4.11 (3:1 non-text). Measured ratios are recorded
        in docs/design/R030_DESIGN_SYSTEM.md — re-measure before changing one.
     4. A token is declared in exactly ONE place. Names shared with
        _surface/tokens.css are declared THERE as aliases of the --nx-* scale
        (tokens.css is injected first), never duplicated here.

   Brand values are the SAME literals the R-030 transactional email templates
   use (docs/design/R030_EMAIL_TEMPLATES.md → default.html :root), so the email
   system and the UI cannot drift apart.
   ============================================================================ */

:root {
  /* ==== TIER 1 — BRAND PRIMITIVES ========================================
     Fixed brand identity. Surfaces reference these directly for family
     colours and logo marks. NOT theme-aware, NOT roles — never bind body
     text or a control boundary to one. */
  --nx-obsidian:#0B1020; --nx-slate:#1A2338; --nx-navy:#11182B;
  --nx-violet:#6B5CFF;   --nx-teal:#22C7BE;  --nx-action:#3F7CFF;
  --nx-green:#19B56B;    --nx-amber:#F4A524; --nx-red:#E04F5F;
  --nx-rose:#D84FB4;     --nx-indigo:#4A59E8;
  --nx-panel:#F5F7FB;    --nxk-line:#D7DEEB;  --nxk-line-dark:#2B3652;
  --nx-text:#243047;     --nx-muted:#66758F;
  --nx-text-light:#EAF0FF; --nx-muted-dark:#9AA6BF;

  /* ==== TIER 1 — TYPE ====================================================
     One stack per role. Supersets of the two stacks that used to disagree
     between this file and tokens.css (--font-body vs --font-sans). */
  --nx-font-head:"DM Sans",Inter,system-ui,-apple-system,"Segoe UI",Roboto,Lato,Arial,sans-serif;
  --nx-font-body:Inter,system-ui,-apple-system,"Segoe UI",Roboto,Lato,Arial,sans-serif;
  --nx-font-mono:"JetBrains Mono","DejaVu Sans Mono",ui-monospace,SFMono-Regular,Menlo,monospace;

  /* Major-third display scale (shared with tokens.css --text-*). */
  --nx-text-xs:12px; --nx-text-sm:14px; --nx-text-base:16px; --nx-text-lg:20px;
  --nx-text-xl:25px; --nx-text-2xl:31px; --nx-text-3xl:39px; --nx-text-4xl:49px;
  /* Dense UI sizes — the sub-14px steps the operator surfaces actually use.
     Anything smaller than --nx-text-2xs is banned. */
  --nx-text-2xs:11px; --nx-text-xs-plus:13px;

  --nx-fw-regular:400; --nx-fw-medium:500; --nx-fw-semibold:600;
  --nx-fw-bold:700;    --nx-fw-heavy:800;
  --nx-lh-tight:1.1; --nx-lh-snug:1.25; --nx-lh-body:1.45;
  --nx-lh-normal:1.5; --nx-lh-relaxed:1.6;
  --nx-track-tight:-.02em; --nx-track-normal:0; --nx-track-wide:.04em;

  /* ==== TIER 1 — SPACE (8px rhythm; 4px intra-component only) ============ */
  --nx-space-0:0;    --nx-space-1:4px;  --nx-space-2:8px;  --nx-space-3:12px;
  --nx-space-4:16px; --nx-space-5:24px; --nx-space-6:32px; --nx-space-7:48px;
  --nx-space-8:64px; --nx-space-9:96px;

  /* ==== TIER 1 — RADIUS ================================================== */
  --nx-radius-xs:6px;  --nx-radius-sm:8px;  --nx-radius-md:10px;
  --nx-radius-lg:12px; --nx-radius-xl:16px; --nx-radius-2xl:18px;
  --nx-radius-pill:9999px;

  /* ==== TIER 1 — ELEVATION (brand-tinted; dark theme flattens these) ===== */
  --nx-elev-0:none;
  --nx-elev-1:0 1px 2px rgba(11,16,32,.06),0 2px 8px rgba(11,16,32,.05);
  --nx-elev-2:0 8px 30px rgba(11,16,32,.12);
  --nx-elev-3:0 8px 16px rgba(14,21,38,.08),0 24px 56px rgba(14,21,38,.16);

  /* ==== TIER 1 — MOTION (prefers-reduced-motion honoured in tokens.css) == */
  --nx-dur-fast:120ms; --nx-dur-base:200ms; --nx-dur-slow:320ms;
  --nx-ease-out:cubic-bezier(.22,.8,.28,1);
  --nx-ease-standard:cubic-bezier(.2,.6,.3,1);

  /* ==== TIER 1 — TARGET SIZE ============================================= */
  --nx-tap:48px;      /* touch surfaces (POS, kiosk, KDS) */
  --nx-tap-sm:40px;   /* pointer-first surfaces (GCC, reports) */

  /* ==== TIER 2 — COLOUR ROLES · LIGHT ====================================
     Values are the R-030 email/GCC brand literals; see the doc for the
     measured contrast of every text/background pair. */
  --nx-bg:#F6F7FA;              /* page ground                        */
  --nx-bg-sunken:#EEF1F8;       /* wells, inset regions               */
  --nxk-surface:#FFFFFF;         /* cards, sheets, menus               */
  --nx-surface-sunken:#F3F5F9;  /* table headers, sub-toolbars        */
  --nx-chrome:#FFFFFF;          /* app bar / rails                    */
  --nx-chrome-deep:#0B1020;     /* the navy band (nav, email header)  */
  --nx-on-chrome-deep:#FFFFFF;

  --nxk-ink:#0E1526;             /* body + headings   18.20:1 on surface */
  --nxk-ink-2:#404D68;           /* secondary copy     8.47:1            */
  --nx-ink-muted:#5F6B84;       /* captions, hints    5.35:1            */
  --nx-ink-inverse:#FFFFFF;

  --nx-border:#E3E7EF;          /* hairline dividers (decorative)     */
  --nx-border-strong:#C7CEDD;   /* emphasised dividers                */
  --nx-border-control:#858FA6;  /* control boundaries — 3.24:1 (1.4.11) */

  --nx-accent:#6B5CFF;          /* brand action        4.57:1          */
  --nx-accent-hover:#5A4BE6;
  --nx-accent-ink:#5A4BE6;      /* accent text on --nx-accent-soft     */
  --nx-accent-soft:#EDEBFF;
  --nx-on-accent:#FFFFFF;

  --nx-success:#1A7F4B; --nx-success-soft:#E7F6EE; --nx-on-success:#FFFFFF;
  --nx-warning:#9A6300; --nx-warning-soft:#FDF3E2; --nx-on-warning:#FFFFFF;
  --nx-danger:#C62F3B;  --nx-danger-soft:#FDECEE;  --nx-on-danger:#FFFFFF;
  /* Warning as a SOLID fill (toast): --nx-warning is tuned for text on a soft
     wash and is too light to carry white. 7.47:1. */
  --nx-warning-solid:#7A4A08; --nx-on-warning-solid:#FFFFFF;
  --nxk-info:#5A4BE6;    --nxk-info-soft:#EDEBFF;
  --nx-money:#0E7A73;           /* price ink — 5.19:1 (teal is 2.10:1) */

  --nx-row-hover:#F8F9FC;
  --nx-row-zebra:#FBFCFE;
  --nx-scrim:rgba(11,16,32,.55);
  --nx-placeholder-a:#E9EDF6;   /* image/skeleton wash */
  --nx-placeholder-b:#DDE3F0;

  /* ==== TIER 2 — FOCUS ===================================================
     ONE visible affordance for the whole platform. SOLID — never a low
     opacity tint — so it clears SC 1.4.11's 3:1 against any adjacent
     surface: 4.57:1 light, 5.60:1 dark. The two rings it replaces measured
     1.56:1 (tokens.css --color-focus @70%) and 1.86:1 (.nx-btn accent @45%). */
  --nx-focus:var(--nx-accent);
  --nx-focus-width:3px;
  --nx-focus-offset:2px;
  --nx-focus-ring:0 0 0 var(--nx-focus-width) var(--nx-focus);

  /* ==== TIER 3 — COMPONENT KNOBS ========================================= */
  --nx-btn-h:var(--nx-tap);       --nx-btn-h-sm:36px;  --nx-btn-h-lg:56px;
  --nx-btn-pad-x:18px;            --nx-btn-pad-x-sm:12px; --nx-btn-pad-x-lg:24px;
  --nx-btn-radius:var(--nx-radius-md);
  --nx-btn-font:var(--nx-text-sm); --nx-btn-weight:var(--nx-fw-bold);

  --nx-input-h:var(--nx-tap);
  --nx-input-pad-x:14px;
  --nx-input-radius:var(--nx-radius-md);

  --nx-card-radius:var(--nx-radius-2xl);
  --nx-card-pad:var(--nx-space-5);

  --nx-chip-pad:7px 14px;
  --nx-badge-pad:2px 8px;

  --nx-table-pad:10px 12px;

  /* ==== TENANT BRAND ======================================================
     Defaults are the Nexus palette; NX.applyBrand() overrides these at
     runtime from the tenant's GCC-managed brand settings
     (tenants.settings_json $.brand). Surfaces style against --brand-* so
     every tenant renders in THEIR brand; nothing is hardcoded per tenant. */
  --brand-primary:var(--nx-violet); --brand-secondary:var(--nx-amber);
  --brand-dark:var(--nx-obsidian);  --brand-surface:var(--nx-slate);
  --brand-light:var(--nx-panel);    --brand-text:var(--nx-text-light);

  /* ==== LEGACY ALIASES ====================================================
     The short names 29 shipped surfaces already bind to. They are now thin
     pointers at the roles above — one value, one declaration site — so a
     shell adopting the canonical classes changes nothing underneath it.
     Retire these per-shell as each wave lands (see the adoption plan). */
  --bg:var(--nx-bg); --surface:var(--nxk-surface); --chrome:var(--nx-chrome);
  --fg:var(--nxk-ink); --fg-muted:var(--nx-ink-muted); --line:var(--nx-border);
  --accent:var(--nx-accent); --accent-fg:var(--nx-on-accent);
  --radius:var(--nx-radius-2xl);
  --radius-xs:var(--nx-radius-md);
  /* --radius-sm is 14px in shipped surfaces (9 files) and matches no scale
     step. Declared ONCE here so it can no longer collide with tokens.css
     (which used to declare the same name as 6px and silently lose). It
     retires to --nx-radius-lg when those 9 surfaces adopt. */
  --radius-sm:14px;
  --shadow-1:var(--nx-elev-1); --shadow-2:var(--nx-elev-2);
  --font-head:var(--nx-font-head); --font-body:var(--nx-font-body);
  --tap:var(--nx-tap);
}

/* ============================================================================
   COLOUR ROLES · DARK
   D9: one theme per context, never inherited from the device. An explicit
   author-set attribute/class is the ONLY switch — no data-nx-auto-theme, no
   prefers-color-scheme media query, anywhere.

   D13 (14 Sep 2026): ONE attribute. [data-theme="dark"] on <html> is the
   only dark switch, for every surface — the kit-authored ones and the Part 3
   ones alike. The legacy [data-nx-theme="dark"] / .nx-dark arms that this
   block used to carry additively are retired; every surface that set them
   now sets data-theme="dark" (U48). Do not add them back.
   ============================================================================ */
[data-theme="dark"] {
  --nx-bg:#0B1020; --nx-bg-sunken:#080D1A;
  --nxk-surface:#141A2B; --nx-surface-sunken:#101728;
  --nx-chrome:#11182B; --nx-chrome-deep:#070B16;
  --nxk-ink:#E8ECF6; --nxk-ink-2:#AEB7CC; --nx-ink-muted:#8B96AD;
  --nx-ink-inverse:#0B1020;
  --nx-border:#26304A; --nx-border-strong:#3A4560; --nx-border-control:#657390;
  --nx-accent:#8E82FF; --nx-accent-hover:#A79EFF;
  --nx-accent-ink:#8E82FF; --nx-accent-soft:#1C1A38; --nx-on-accent:#0B1020;
  --nx-success:#3FCE85; --nx-success-soft:#10241A; --nx-on-success:#08160E;
  --nx-warning:#E2A53C; --nx-warning-soft:#241A09; --nx-on-warning:#1C1405;
  --nx-danger:#E5646F;  --nx-danger-soft:#2A1417;  --nx-on-danger:#1A0D0F;
  --nxk-info:#8E82FF;    --nxk-info-soft:#1C1A38;
  --nx-warning-solid:#E2A53C; --nx-on-warning-solid:#1C1405;
  --nx-money:#22C7BE;
  --nx-row-hover:#182036; --nx-row-zebra:#161D31;
  --nx-scrim:rgba(0,0,0,.62);
  --nx-placeholder-a:#1B2338; --nx-placeholder-b:#141A2B;
  --nx-elev-1:0 1px 2px rgba(0,0,0,.4);
  --nx-elev-2:0 10px 36px rgba(0,0,0,.5);
  --nx-elev-3:0 16px 40px rgba(0,0,0,.55),0 40px 80px rgba(0,0,0,.6);
}

/* ============================================================================
   BASE
   The bare-element rules (*, html, body, h1-4, a, img) are gated behind the
   [data-nx-kit] opt-in attribute: a tokens-only surface (GCC) loads this same
   file for the custom properties but must NOT have its raw elements restyled
   by a kit it never asked for. Component classes (.nx-*) are unscoped, same
   as before — a surface that never uses a class name is unaffected by it.
   ============================================================================ */
[data-nx-kit],[data-nx-kit] *,[data-nx-kit] *::before,[data-nx-kit] *::after{ box-sizing:border-box; }
[data-nx-kit],[data-nx-kit] body{ margin:0; padding:0; }
[data-nx-kit] body{
  font-family:var(--nx-font-body); color:var(--nxk-ink); background:var(--nx-bg);
  -webkit-font-smoothing:antialiased; text-rendering:optimizeLegibility;
  font-size:15px; line-height:var(--nx-lh-body);
}
[data-nx-kit] h1,[data-nx-kit] h2,[data-nx-kit] h3,[data-nx-kit] h4,.nx-title{
  font-family:var(--nx-font-head); font-weight:var(--nx-fw-heavy);
  letter-spacing:var(--nx-track-tight); margin:0;
}
.nx-mono{ font-family:var(--nx-font-mono); font-variant-numeric:tabular-nums; }
.nx-muted{ color:var(--nx-ink-muted); }
.nx-lede{ color:var(--nxk-ink-2); line-height:var(--nx-lh-normal,1.5); }
[data-nx-kit] a{ color:var(--nx-accent); }
[data-nx-kit] img{ max-width:100%; display:block; }

/* App bar / context ribbon ------------------------------------------------- */
.nx-appbar{
  display:flex; align-items:center; gap:var(--nx-space-3);
  padding:var(--nx-space-3) var(--nx-space-4);
  background:var(--nx-chrome); border-bottom:1px solid var(--nx-border); min-height:60px;
}
.nx-appbar .nx-brand{ display:flex; align-items:center; gap:var(--nx-space-2); font-family:var(--nx-font-head); font-weight:var(--nx-fw-heavy); font-size:17px; }
.nx-appbar .nx-brand .nx-logo{ width:30px;height:30px;border-radius:var(--nx-radius-sm);display:grid;place-items:center;color:var(--nx-on-accent);font-weight:var(--nx-fw-heavy);background:var(--nx-accent); }
.nx-appbar .nx-spacer{ flex:1; }
/* U10 — the kit-mounted Home link (NX.mountHome). Same control, same place, on every operator surface. */
.nx-home-link{ display:inline-flex; align-items:center; gap:6px; min-height:44px; padding:0 var(--nx-space-3);
  border:1px solid var(--nx-border); border-radius:var(--nx-radius-pill); color:inherit; text-decoration:none;
  font-family:var(--nx-font-head); font-weight:var(--nx-fw-bold); font-size:var(--nx-text-sm); white-space:nowrap; }
.nx-home-link:hover{ background:var(--nx-surface-2, var(--nx-surface)); }
.nx-home-link:focus-visible{ outline:2px solid var(--nx-focus, var(--nx-accent)); outline-offset:2px; }
aside .brand .nx-home-link{ margin-top:var(--nx-space-2); }
/* (.nx-home is the GCC's home dashboard container — the link is .nx-home-link so the two never collide.) */
.nx-ribbon{
  display:flex; align-items:center; gap:var(--nx-space-2); flex-wrap:wrap;
  font-size:12.5px; color:var(--nx-ink-muted);
}
.nx-ribbon .nx-store{ font-family:var(--nx-font-head); font-weight:var(--nx-fw-heavy); font-size:var(--nx-text-sm); color:var(--nxk-ink); }
.nx-ribbon .nx-sep{ opacity:.4; }
.nx-ribbon .nx-id{ font-family:var(--nx-font-mono); font-size:11.5px; }
.nx-env{
  display:inline-flex;align-items:center;gap:6px;font-weight:var(--nx-fw-bold);
  font-size:var(--nx-text-2xs);text-transform:uppercase;letter-spacing:var(--nx-track-wide);
  padding:3px 9px;border-radius:var(--nx-radius-pill);
}
.nx-env::before{ content:"";width:7px;height:7px;border-radius:50%;background:currentColor; }
/* R-030: was --nx-green on a 12% green wash (2.37:1) / --nx-amber on a 14%
   amber wash (1.85:1) — both illegible. Now the AA semantic roles. */
.nx-env.live{ color:var(--nx-success); background:var(--nx-success-soft); }
.nx-env.demo{ color:var(--nx-warning); background:var(--nx-warning-soft); }

/* ============================================================================
   BUTTONS — one primitive, six variants, three sizes.
   ============================================================================ */
.nx-btn{
  font-family:var(--nx-font-head); font-weight:var(--nx-btn-weight); font-size:15px;
  cursor:pointer;
  border:1px solid var(--nx-border-control); background:var(--nxk-surface); color:var(--nxk-ink);
  border-radius:var(--nx-btn-radius); padding:0 var(--nx-btn-pad-x); min-height:var(--nx-btn-h);
  display:inline-flex; align-items:center; justify-content:center; gap:var(--nx-space-2);
  transition:transform var(--nx-dur-fast) var(--nx-ease-out),
             box-shadow var(--nx-dur-fast) var(--nx-ease-out),
             background var(--nx-dur-fast) var(--nx-ease-out);
  user-select:none; white-space:nowrap;
}
/* NB: no base :hover background. A `.nx-btn:hover` rule is (0,2,0) and would
   out-specify the filled variants' (0,1,0) background, greying out danger /
   success / secondary on hover. Hover belongs on each variant. */
.nx-btn:active{ transform:translateY(1px); }
.nx-btn:focus-visible{
  outline:var(--nx-focus-width) solid var(--nx-focus);
  outline-offset:var(--nx-focus-offset);
}
.nx-btn-primary{ background:var(--nx-accent); color:var(--nx-on-accent); border-color:transparent; box-shadow:var(--nx-elev-1); }
.nx-btn-primary:hover{ background:var(--nx-accent-hover); box-shadow:var(--nx-elev-2); }
.nx-btn-secondary{ background:var(--nxk-surface); color:var(--nxk-ink); border-color:var(--nx-border-control); }
.nx-btn-secondary:hover{ background:var(--nx-row-hover); }
.nx-btn-ghost{ background:transparent; border-color:transparent; }
.nx-btn-ghost:hover{ background:var(--nx-row-hover); }
.nx-btn-danger{ background:var(--nx-danger); color:var(--nx-on-danger); border-color:transparent; }
.nx-btn-success{ background:var(--nx-success); color:var(--nx-on-success); border-color:transparent; }
.nx-btn-sm{ font-size:var(--nx-text-xs-plus); min-height:var(--nx-btn-h-sm); padding:0 var(--nx-btn-pad-x-sm); border-radius:var(--nx-radius-sm); }
.nx-btn-lg{ font-size:17px; min-height:var(--nx-btn-h-lg); padding:0 var(--nx-btn-pad-x-lg); border-radius:var(--radius-sm); }
.nx-btn-block{ width:100%; }
.nx-btn-icon{ padding:0; width:var(--nx-btn-h); }
.nx-btn[disabled],.nx-btn[aria-disabled="true"]{ opacity:.45; cursor:not-allowed; }
.nx-btn[aria-busy="true"]{ cursor:progress; opacity:.7; }
/* Button row — the canonical footer for cards, modals and forms. */
.nx-actions{ display:flex; gap:var(--nx-space-3); justify-content:flex-end; margin-top:var(--nx-space-5); flex-wrap:wrap; }
.nx-actions-center{ justify-content:center; }
.nx-actions-between{ justify-content:space-between; }

/* ============================================================================
   FORM CONTROLS — field / label / control / hint, one shape for all three.
   ============================================================================ */
.nx-field{ margin-bottom:var(--nx-space-4); }
.nx-label{
  display:block; font-family:var(--nx-font-head); font-size:var(--nx-text-xs-plus);
  font-weight:var(--nx-fw-bold); color:var(--nxk-ink); margin-bottom:6px;
}
.nx-label .nx-req{ color:var(--nx-danger); margin-left:3px; }
.nx-input,.nx-select,.nx-textarea{
  width:100%; min-height:var(--nx-input-h); padding:0 var(--nx-input-pad-x);
  background:var(--nxk-surface); color:var(--nxk-ink);
  border:1px solid var(--nx-border-control); border-radius:var(--nx-input-radius);
  font-size:15px; font-family:var(--nx-font-body); line-height:var(--nx-lh-body);
  transition:border-color var(--nx-dur-fast) var(--nx-ease-out),
             box-shadow var(--nx-dur-fast) var(--nx-ease-out);
}
.nx-textarea{ padding:10px var(--nx-input-pad-x); min-height:96px; resize:vertical; }
.nx-select{ appearance:none; padding-right:34px;
  background-image:linear-gradient(45deg,transparent 50%,currentColor 50%),linear-gradient(135deg,currentColor 50%,transparent 50%);
  background-position:calc(100% - 18px) calc(50% + 2px),calc(100% - 13px) calc(50% + 2px);
  background-size:5px 5px,5px 5px; background-repeat:no-repeat; cursor:pointer; }
.nx-input::placeholder,.nx-textarea::placeholder{ color:var(--nx-ink-muted); opacity:1; }
.nx-input:focus,.nx-select:focus,.nx-textarea:focus{
  outline:none; border-color:var(--nx-focus); box-shadow:var(--nx-focus-ring);
}
.nx-input:focus-visible,.nx-select:focus-visible,.nx-textarea:focus-visible{
  outline:var(--nx-focus-width) solid var(--nx-focus); outline-offset:var(--nx-focus-offset);
}
.nx-input[disabled],.nx-select[disabled],.nx-textarea[disabled]{ background:var(--nx-bg-sunken); color:var(--nx-ink-muted); cursor:not-allowed; }
.nx-input.is-valid,.nx-select.is-valid{ border-color:var(--nx-success); }
.nx-input.is-invalid,.nx-select.is-invalid{ border-color:var(--nx-danger); }
.nx-hint{ font-size:var(--nx-text-xs); color:var(--nx-ink-muted); margin-top:6px; min-height:15px; line-height:1.35; }
.nx-hint.is-good{ color:var(--nx-success); }
.nx-hint.is-warn{ color:var(--nx-warning); }
.nx-hint.is-bad{ color:var(--nx-danger); }

/* ============================================================================
   NOTES & ALERTS — inline messaging. Tone comes from the semantic roles, so
   every one of them clears 4.5:1 against its own soft background.
   ============================================================================ */
.nx-note,.nx-alert{
  display:flex; gap:var(--nx-space-2); align-items:flex-start;
  padding:12px 14px; border-radius:var(--nx-radius-md);
  font-size:var(--nx-text-xs-plus); line-height:1.45;
  border:1px solid transparent;
}
.nx-note{ background:var(--nx-accent-soft); border-color:var(--nx-accent); color:var(--nxk-ink); }
.nx-note .nx-note-ico,.nx-alert .nx-note-ico{ flex:none; }
.nx-alert{ font-weight:var(--nx-fw-semibold); }
.nx-alert-info{ background:var(--nxk-info-soft); color:var(--nxk-info); }
.nx-alert-success{ background:var(--nx-success-soft); color:var(--nx-success); }
.nx-alert-warn{ background:var(--nx-warning-soft); color:var(--nx-warning); }
.nx-alert-danger{ background:var(--nx-danger-soft); color:var(--nx-danger); }

/* ============================================================================
   CHIPS / BADGES / TAGS
   ============================================================================ */
.nx-chip{
  display:inline-flex; align-items:center; gap:6px;
  font-weight:var(--nx-fw-bold); font-size:var(--nx-text-xs-plus);
  padding:var(--nx-chip-pad); border-radius:var(--nx-radius-pill);
  border:1px solid var(--nx-border-control);
  background:var(--nxk-surface); color:var(--nxk-ink); cursor:pointer; white-space:nowrap;
}
.nx-chip:focus-visible{ outline:var(--nx-focus-width) solid var(--nx-focus); outline-offset:var(--nx-focus-offset); }
.nx-chip[aria-selected="true"], .nx-chip.is-active{ background:var(--nx-accent); color:var(--nx-on-accent); border-color:transparent; }
/* Non-interactive status pill. Tone = soft background + its AA ink. */
.nx-badge{
  display:inline-flex; align-items:center; gap:5px;
  font-size:var(--nx-text-2xs); font-weight:var(--nx-fw-bold);
  padding:3px 9px; border-radius:var(--nx-radius-pill); white-space:nowrap;
  background:var(--nx-bg-sunken); color:var(--nxk-ink-2);
}
.nx-badge-accent{ background:var(--nx-accent-soft); color:var(--nx-accent-ink); }
.nx-badge-success{ background:var(--nx-success-soft); color:var(--nx-success); }
.nx-badge-warn{ background:var(--nx-warning-soft); color:var(--nx-warning); }
.nx-badge-danger{ background:var(--nx-danger-soft); color:var(--nx-danger); }
.nx-tag{
  display:inline-block; font-size:var(--nx-text-2xs); font-weight:var(--nx-fw-bold);
  padding:var(--nx-badge-pad); border-radius:var(--nx-radius-xs);
  background:var(--nx-accent-soft); color:var(--nx-accent-ink);
}
/* Money. Brand teal is 2.10:1 on white — the money role is the readable
   equivalent (5.19:1 light, 8.23:1 dark). */
.nx-price{ font-family:var(--nx-font-mono); font-weight:var(--nx-fw-bold); color:var(--nx-money); font-variant-numeric:tabular-nums; }

/* ============================================================================
   CARDS
   ============================================================================ */
.nx-card{
  background:var(--nxk-surface); border:1px solid var(--nx-border);
  border-radius:var(--nx-card-radius); box-shadow:var(--nx-elev-1);
}
.nx-card-head{
  display:flex; align-items:center; gap:var(--nx-space-3);
  padding:var(--nx-space-4) var(--nx-card-pad);
  border-bottom:1px solid var(--nx-border);
  font-family:var(--nx-font-head); font-weight:var(--nx-fw-heavy); font-size:17px;
}
.nx-card-body{ padding:var(--nx-card-pad); }
.nx-card-foot{
  padding:var(--nx-space-4) var(--nx-card-pad);
  border-top:1px solid var(--nx-border); background:var(--nx-surface-sunken);
  border-radius:0 0 var(--nx-card-radius) var(--nx-card-radius);
}

/* ============================================================================
   TABLE — the canonical record grid.
   ============================================================================ */
.nx-table-wrap{ width:100%; overflow-x:auto; -webkit-overflow-scrolling:touch; }
.nx-table{ width:100%; border-collapse:collapse; font-size:var(--nx-text-xs-plus); }
.nx-table caption{ text-align:left; padding:0 0 var(--nx-space-2); color:var(--nx-ink-muted); font-size:var(--nx-text-xs); }
.nx-table th{
  text-align:left; padding:var(--nx-table-pad);
  background:var(--nx-surface-sunken); color:var(--nxk-ink-2);
  font-family:var(--nx-font-head); font-weight:var(--nx-fw-bold);
  font-size:var(--nx-text-2xs); text-transform:uppercase; letter-spacing:var(--nx-track-wide);
  border-bottom:1px solid var(--nx-border-strong); white-space:nowrap; position:sticky; top:0; z-index:1;
}
.nx-table td{ padding:var(--nx-table-pad); border-bottom:1px solid var(--nx-border); color:var(--nxk-ink); vertical-align:middle; }
.nx-table tbody tr:hover td{ background:var(--nx-row-hover); }
.nx-table.nx-zebra tbody tr:nth-child(even) td{ background:var(--nx-row-zebra); }
.nx-table.nx-zebra tbody tr:nth-child(even):hover td{ background:var(--nx-row-hover); }
.nx-table tbody tr[aria-selected="true"] td{ background:var(--nx-accent-soft); }
.nx-table .nx-num{ text-align:right; font-family:var(--nx-font-mono); font-variant-numeric:tabular-nums; }
.nx-table-empty td{ padding:var(--nx-space-6); text-align:center; color:var(--nx-ink-muted); }

/* Photo menu grid (the headline customer view) ----------------------------- */
.nx-menu-grid{ display:grid; grid-template-columns:repeat(auto-fill,minmax(210px,1fr)); gap:var(--nx-space-4); }
.nx-item{
  background:var(--nxk-surface); border:1px solid var(--nx-border); border-radius:var(--radius-sm);
  overflow:hidden; box-shadow:var(--nx-elev-1); cursor:pointer; display:flex; flex-direction:column;
  transition:transform .1s var(--nx-ease-out), box-shadow .14s var(--nx-ease-out); text-align:left;
}
.nx-item:hover{ transform:translateY(-2px); box-shadow:var(--nx-elev-2); }
.nx-item:focus-visible{ outline:var(--nx-focus-width) solid var(--nx-focus); outline-offset:var(--nx-focus-offset); }
.nx-item .nx-photo{ width:100%; aspect-ratio:4/3; object-fit:cover; background:linear-gradient(135deg,var(--nx-placeholder-a),var(--nx-placeholder-b)); }
.nx-item .nx-photo.ph{ display:grid; place-items:center; color:var(--nx-ink-muted); font-family:var(--nx-font-head); font-weight:var(--nx-fw-heavy); font-size:var(--nx-text-xs-plus); }
.nx-item .nx-body{ padding:12px 13px 14px; display:flex; flex-direction:column; gap:5px; flex:1; }
.nx-item .nx-name{ font-family:var(--nx-font-head); font-weight:var(--nx-fw-bold); font-size:15.5px; line-height:var(--nx-lh-snug); }
.nx-item .nx-desc{ font-size:12.5px; color:var(--nx-ink-muted); line-height:1.35; display:-webkit-box; -webkit-line-clamp:2; -webkit-box-orient:vertical; overflow:hidden; }
.nx-item .nx-foot{ display:flex; align-items:center; justify-content:space-between; margin-top:auto; padding-top:6px; }
.nx-item .nx-add{ font-family:var(--nx-font-head); font-weight:var(--nx-fw-heavy); font-size:var(--nx-text-xs-plus); color:var(--nx-on-accent); background:var(--nx-accent); border:none; border-radius:var(--nx-radius-sm); padding:8px 12px; min-height:38px; cursor:pointer; }

/* ============================================================================
   EMPTY STATE
   ============================================================================ */
.nx-empty{ text-align:center; padding:var(--nx-space-7) var(--nx-space-5); color:var(--nx-ink-muted); }
.nx-empty .nx-empty-ico{ font-size:34px; opacity:.5; margin-bottom:10px; }
.nx-empty .nx-empty-title{ font-family:var(--nx-font-head); font-weight:var(--nx-fw-heavy); font-size:17px; color:var(--nxk-ink); margin-bottom:6px; }
.nx-empty .nx-empty-body{ max-width:44ch; margin:0 auto; line-height:1.5; }
.nx-empty .nx-actions{ justify-content:center; }

/* ============================================================================
   TOAST / MODAL
   Toast tones were white-on-brand-fill and failed AA (ok 2.67:1, err 3.85:1).
   They now use the semantic roles: ok 5.02:1, err 5.43:1, warn 7.47:1.
   ============================================================================ */
.nx-toasts{ position:fixed; left:50%; bottom:var(--nx-space-5); transform:translateX(-50%); z-index:9999; display:flex; flex-direction:column; gap:var(--nx-space-2); align-items:center; pointer-events:none; }
.nx-toast{
  background:var(--nx-chrome-deep); color:var(--nx-on-chrome-deep);
  padding:12px 18px; border-radius:var(--nx-radius-lg); box-shadow:var(--nx-elev-2);
  font-weight:var(--nx-fw-semibold); font-size:var(--nx-text-sm); max-width:88vw;
  animation:nx-rise .18s var(--nx-ease-out);
}
.nx-toast.err{ background:var(--nx-danger); color:var(--nx-on-danger); }
.nx-toast.ok{ background:var(--nx-success); color:var(--nx-on-success); }
.nx-toast.warn{ background:var(--nx-warning-solid); color:var(--nx-on-warning-solid); }
@keyframes nx-rise{ from{ opacity:0; transform:translateY(8px);} to{ opacity:1; transform:none; } }
.nx-modal-bg{ position:fixed; inset:0; background:var(--nx-scrim); z-index:9998; display:grid; place-items:center; padding:var(--nx-space-5); }
.nx-modal{ background:var(--nxk-surface); color:var(--nxk-ink); border-radius:var(--nx-card-radius); box-shadow:var(--nx-elev-3); max-width:440px; width:100%; padding:22px; }
.nx-modal h3{ margin:0 0 var(--nx-space-2); font-size:19px; }
.nx-modal .nx-modal-actions{ display:flex; gap:var(--nx-space-3); justify-content:flex-end; margin-top:var(--nx-space-4); }

/* ============================================================================
   UTILITIES
   ============================================================================ */
.nx-row{ display:flex; align-items:center; gap:var(--nx-space-3); }
.nx-col{ display:flex; flex-direction:column; gap:var(--nx-space-3); }
.nx-wrap{ max-width:1280px; margin:0 auto; padding:var(--nx-space-5); width:100%; }
.nx-scroll{ overflow:auto; -webkit-overflow-scrolling:touch; }
.nx-sr-only{ position:absolute; width:1px; height:1px; padding:0; margin:-1px; overflow:hidden; clip:rect(0 0 0 0); white-space:nowrap; border:0; }
.nx-skel{
  background:linear-gradient(90deg,var(--nx-placeholder-a) 25%,var(--nx-surface-sunken) 37%,var(--nx-placeholder-a) 63%);
  background-size:400% 100%; animation:nx-shimmer 1.3s var(--nx-ease-standard) infinite;
  border-radius:var(--nx-radius-sm);
}
@keyframes nx-shimmer{ from{ background-position:100% 0;} to{ background-position:-100% 0; } }
@media (max-width:640px){
  .nx-menu-grid{ grid-template-columns:repeat(auto-fill,minmax(150px,1fr)); gap:var(--nx-space-3); }
  .nx-wrap{ padding:var(--nx-space-3); }
  .nx-actions{ flex-direction:column-reverse; }
  .nx-actions .nx-btn{ width:100%; }
}

/* ============================================================================
   LEGACY PER-SURFACE COLOUR VALUES — generated by the U01 migration scripts to
   move each surface's existing hex literals out of *.html (so the CI hex-literal
   gate can be zero) WITHOUT changing a single rendered pixel: every var here is
   the exact hex the page already used, whether it appeared in a <style> block, a
   style="" attribute, or a JS string assigned to .style/.cssText/.setProperty.
   These are NOT Part 3 tokens — named --lg-N (legacy) on purpose, scoped under
   [data-nx-page="<slug>"], so nobody mistakes them for the closed 20-token
   palette. Consolidating a surface onto the real --nx-* palette is later,
   deliberate restyle work (U03+).

   EXCEPTIONS to "not a single rendered pixel changed": --lg-63/--lg-66 under
   [data-nx-page="default"], --lg-19 under [data-nx-page="pos"], and
   --lg-5/--lg-9 under [data-nx-page="downloads"] were measured (real
   screenshot, composited background) failing WCAG 2.2 SC 1.4.3 against
   their paired text colour — see docs/design/CONTRAST_P0_KPI_TILES.md.
   These values were nudged (darker/lighter along the same hue, not
   re-coloured) to clear the floor; every other swatch in this file is
   untouched.
   ============================================================================ */
[data-nx-page="admin"] {
  --lg-1: #f5f7fb;
  --lg-2: #fff;
  --lg-3: #243047;
  --lg-4: #66758f;
  --lg-5: #d7deeb;
  --lg-6: #6b5cff;
  --lg-7: #5a4be6;
  --lg-8: #22c7be;
  --lg-9: #3f7cff;
  --lg-10: #19b56b;
  --lg-11: #f4a524;
  --lg-12: #e04f5f;
  --lg-13: #0b1020;
  --lg-14: #1a2338;
  --lg-15: #11182b;
  --lg-16: #2b3652;
  --lg-17: #eaf0ff;
  --lg-18: #9aa6bf;
  --lg-19: #e8f8f0;
  --lg-20: #fde8ea;
  --lg-21: #fef3e0;
  --lg-22: #b8760c;
  --lg-23: #e8f0fe;
  --lg-24: #eeebff;
  --lg-25: #f8f7ff;
  --lg-26: #f1f2f8;
  --lg-27: #3e4263;
  --lg-28: #fff8e5;
  --lg-29: #ffefc2;
  --lg-30: #7a5500;
  --lg-31: #e5c25c;
  --lg-32: #b26a00;
  --lg-33: #1fae6a;
  --lg-34: #c7c9d6;
  --lg-35: #9095ad;
  --lg-36: #9ca3af;
  --lg-37: #fafbfd;
  --lg-38: #0f1729;
  --lg-39: #e3e8f1;
}
[data-nx-page="aura-request"] {
  --lg-1: #fff;
  --lg-2: #9a6a08;
}
[data-nx-page="aura"] {
  --lg-1: #b03040;
  --lg-2: #12784a;
}
[data-nx-page="connect"] {
  --lg-1: #0f9a92;
  --lg-2: #2bb673;
  --lg-3: #e0556a;
}
[data-nx-page="control"] {
  --lg-1: #2bb673;
  --lg-2: #e6a817;
  --lg-3: #e0556a;
  --lg-4: #157a4e;
  --lg-5: #8a6410;
  --lg-6: #a12f40;
  --lg-7: #000;
  --lg-8: #8894a6;
  --lg-9: #0b0f16;
  --lg-10: #cfe3ff;
}
[data-nx-page="cook"] {
  --lg-1: #8a93a8;
  --lg-2: #f6b93b;
  --lg-3: #e0556a;
  --lg-4: #e6a817;
  --lg-5: #7a8699;
}
[data-nx-page="customer-display"] {
  --lg-1: #000;
  --lg-2: #fff;
}
[data-nx-page="default"] {
  --lg-1: #f6f7fa;
  --lg-2: #fff;
  --lg-3: #0e1526;
  --lg-4: #404d68;
  --lg-5: #79849b;
  --lg-6: #e3e7ef;
  --lg-7: #6b5cff;
  --lg-8: #5a4be6;
  --lg-9: #19b56b;
  --lg-10: #f4a524;
  --lg-11: #e04f5f;
  --lg-12: #22c7be;
  --lg-13: #3f7cff;
  --lg-14: #0b1020;
  --lg-15: #1a2338;
  --lg-16: #243047;
  --lg-17: #c7d0e1;
  --lg-18: #f8f9fc;
  --lg-19: #fbfcfe;
  --lg-20: #f3f5f9;
  --lg-21: #141a2b;
  --lg-22: #e8ecf6;
  --lg-23: #aeb7cc;
  --lg-24: #7a86a0;
  --lg-25: #26304a;
  --lg-26: #8e82ff;
  --lg-27: #7a6cff;
  --lg-28: #2fd17f;
  --lg-29: #f4b948;
  --lg-30: #ff6b7a;
  --lg-31: #070b16;
  --lg-32: #141c30;
  --lg-33: #1e2840;
  --lg-34: #182036;
  --lg-35: #161d31;
  --lg-36: #101728;
  --lg-37: #10162400;
  --lg-38: #a9b2c6;
  --lg-39: #7a8699;
  --lg-40: #5a6478;
  --lg-41: #2a3654;
  --lg-42: #b98a1c;
  --lg-43: #e0b24a;
  --lg-44: #3fe08c;
  --lg-45: #4a3fd6;
  --lg-46: #1aa39d;
  --lg-47: #2c63d9;
  --lg-48: #14914f;
  --lg-49: #d68a14;
  --lg-50: #c43e4d;
  --lg-51: #3d4a66;
  --lg-52: #26334d;
  --lg-53: #ec5d9c;
  --lg-54: #d04284;
  --lg-55: #c6f0d4;
  --lg-56: #f8f7ff;
  --lg-57: #f1f2f8;
  --lg-58: #3e4263;
  --lg-59: #fff8e5;
  --lg-60: #ffefc2;
  --lg-61: #7a5500;
  --lg-62: #e5c25c;
  --lg-63: #995b00;
  --lg-64: #c7c9d6;
  --lg-65: #9095ad;
  --lg-66: #8a919d;
  --lg-67: #1fae6a;
  --lg-68: #c7cbee;
  --lg-69: #8a6d1d;
  --lg-70: #e5cf8a;
  --lg-71: #fdf6e0;
  --lg-72: #e9ecf5;
  --lg-73: #0e7a43;
  --lg-74: #e2f6ea;
  --lg-75: #52351f;
  --lg-76: #f1e2dc;
  --lg-77: #222;
  --lg-78: #1a0f08;
  --lg-79: #ffd23f;
  --lg-80: #1c1512;
  --lg-81: #fff8f0;
  --lg-82: #d62300;
  --lg-83: #fdbb30;
  --lg-84: #fff8ef;
  --lg-85: #1a1a1a;
  --lg-86: #777;
  --lg-87: #333;
  --lg-88: #111;
  --lg-89: #eee;
  --lg-90: #888;
  --lg-91: #ccc;
  --lg-92: #fee;
  --lg-93: #e8ebf1;
  --lg-94: #9ca3af;
  --lg-95: #9b59b6;
  --lg-96: #e67e22;
  --lg-97: #f4d03f;
  --lg-98: #f9fafb;
  --lg-99: #fef1de;
  --lg-100: #1a1a2e;
  --lg-101: #ef4444;
  --lg-102: #f4f4f4;
  --lg-103: #374151;
  --lg-104: #d1d5db;
  --lg-105: #dc2626;
  --lg-106: #6366f1;
  --lg-107: #667;
  --lg-108: #e9edf6;
  --lg-109: #dde3f0;
  --lg-110: #000;
  --lg-111: #ffe9ec;
  --lg-112: #999;
  --lg-113: #fef3e0;
  --lg-114: #b8760c;
  --lg-115: #8c2a35;
  --lg-116: #fff5f5;
  --lg-117: #f5f7fb;
  --lg-118: #f59e0b;
  --lg-119: #2e7d32;
  --lg-120: #e0e0e0;
  --lg-121: #4caf50;
  --lg-122: #2ea043;
  --lg-123: #f85149;
  --lg-124: #d29922;
  --lg-125: #c44;
  --lg-126: #262;
  --lg-127: #666;
  --lg-128: #c33;
  --lg-129: #6b5413;
  --lg-130: #f1f3f7;
  --lg-131: #0f1729;
  --lg-132: #e3e8f1;
  --lg-133: #2a211b;
  --lg-134: #1e9e5a;
  --lg-135: #f4c430;
  --lg-136: #14241a;
  --lg-137: #1c2e22;
  --lg-138: #f1f8f2;
  --lg-139: #f4fbf5;
  --lg-140: #0f62fe;
  --lg-141: #08bdba;
  --lg-142: #0b1b2b;
  --lg-143: #12283c;
  --lg-144: #f0f6ff;
  --lg-145: #f2f8ff;
  --lg-146: #111827;
  --lg-147: #0a0a0a;
  --lg-148: #f7f7f7;
  --lg-149: #fafafa;
  --lg-150: #f6f7fb;
  --lg-151: #6b4a2f;
  --lg-152: #c97a1c;
  --lg-153: #b3362b;
  --lg-154: #7fb069;
  --lg-155: #d9a92f;
  --lg-156: #2f6b4f;
  --lg-157: #7b5ea7;
  --lg-158: #12506e;
  --lg-159: #c2618f;
  --lg-160: #ffffff;
  --lg-161: #555;
  --lg-162: #888888;
  --lg-163: #222222;
  --lg-164: #fde8ea;
  --lg-165: #eeeaff;
  --lg-166: #12345;
  --lg-167: #fca;
  --lg-168: #1f9d55;
  --lg-169: #d9a406;
  --lg-170: #d64545;
  --lg-171: #16a34a;
  --lg-172: #e7f6ee;
  --lg-173: #0e7a46;
  --lg-174: #bce5ce;
  --lg-175: #8a6a10;
  --lg-176: #f3c2ca;
}
/* LIVE-TILL FIX (2026-09-06): this whole block above is a LIGHT-only legacy
   extraction (--lg-1:#f6f7fa background) with no dark counterpart anywhere
   in this file, yet default.html now runs in dark mode at runtime
   (<html data-theme="dark"> is set by JS). --lg-14 and --lg-58 are the two
   named-broken instances (Home screen stat-tile numbers, .nx-home-stat-row
   strong / .nx-home-stat-row) - verified via a real Playwright render:
   #0B1020-on-#111112 = 1.00:1 and #3E4263-on-#111112 = 1.94:1, both far
   under the 4.5:1 minimum. Fixed here only for these two - NOT a full sweep
   of this block's other ~170 slots, which is real, separate, much larger
   work (this page's legacy palette has never had a dark variant at all;
   auditing which of its ~170 --lg-N slots are foreground-on-dark vs.
   background/border/decorative is comparable in scope to the U01-U03 token
   migration already tracked as backlog on main, not a one-line fix).
   Values reused from surface.css's own proven dark --nxk-ink/--nxk-ink-2
   (E8ECF6/AEB7CC), verified 15.96:1 and 9.39:1 on this page's real dark
   background (#111112, tokens.css --step-1 dark). */
[data-nx-page="default"][data-theme="dark"] {
  --lg-14: #E8ECF6;
  --lg-58: #AEB7CC;
}
[data-nx-page="downloads"] {
  --lg-1: #f5f7fb;
  --lg-2: #fff;
  --lg-3: #0f1729;
  --lg-4: #3d4a66;
  --lg-5: #545e6e;
  --lg-6: #e3e8f1;
  --lg-7: #6b5cff;
  --lg-8: #19b56b;
  --lg-9: #885707;
  --lg-10: #e7f8ef;
  --lg-11: #fff4e0;
}
[data-nx-page="drive-thru"] {
  --lg-1: #4fd08c;
  --lg-2: #ff8a97;
  --lg-3: #8fb4ff;
}
[data-nx-page="home"] {
  --lg-1: #111318;
  --lg-2: #1e2128;
  --lg-3: #15171c;
  --lg-4: #2b2f3a;
  --lg-5: #1b2030;
  --lg-6: #eef1f8;
  --lg-7: #96a0b5;
  --lg-8: #fff;
  --lg-9: #6b7590;
  --lg-10: #6b5cff;
}
[data-nx-page="kds"] {
  --lg-1: #111318;
  --lg-2: #15171c;
  --lg-3: #1e2128;
  --lg-4: #262a33;
  --lg-5: #2c303a;
  --lg-6: #23262e;
  --lg-7: #d64545;
  --lg-8: #b7791f;
  --lg-9: #1d4ed8;
  --lg-10: #fff;
  --lg-11: #7c3aed;
  --lg-12: #0e7490;
  --lg-13: #a16207;
  --lg-14: #be185d;
  --lg-15: #b3362b;
  --lg-16: #7c1f17;
  --lg-17: #6d5518;
  --lg-18: #e2a53c;
  --lg-19: #ff8a7a;
  --lg-20: #1c5b66;
  --lg-21: #7fb8c4;
  --lg-22: #4dd0e1;
  --lg-23: #2f6fed;
  --lg-24: #000;
  --lg-25: #444;
  --lg-26: #2a2a2a;
  --lg-27: #2b6cff;
  --lg-28: #1b4dff;
  --lg-29: #3ad668;
  --lg-30: #ff5b5b;
  --lg-31: #ffd23f;
  --lg-32: #111;
  --lg-33: #333;
  --lg-34: #1f9d55;
  --lg-35: #8c8c8c;
  --lg-36: #c00000;
  --lg-37: #40566e;
  --lg-38: #2d3b4d;
  --lg-39: #458b;
  --lg-40: #c0202a;
  --lg-41: #f2c200;
  --lg-42: #1f7a3d;
  --lg-43: #8f0043;
  --lg-44: #0e6b82;
  --lg-45: #5a3ec8;
  --lg-46: #a34c00;
  --lg-47: #0b6bb3;
  --lg-48: #7a5c00;
  --lg-49: #555;
}
[data-nx-page="kiosk"] {
  --lg-1: #f4d03f;
  --lg-2: #fff;
  --lg-3: #000;
  --lg-4: #111;
  --lg-5: #ddd;
  --lg-6: #ffd700;
  --lg-7: #0f0;
  --lg-8: #12203a;
}
[data-nx-page="loyalty"] {
  --lg-1: #c98a12;
}
[data-nx-page="menuboard"] {
  --lg-1: #0d0a08;
  --lg-2: #fff8f0;
  --lg-3: #7a2c14;
  --lg-4: #3f1409;
  --lg-5: #180a05;
  --lg-6: #ffd23f;
  --lg-7: #201400;
  --lg-8: #c8102e;
  --lg-9: #fff;
  --lg-10: #1e7d43;
  --lg-11: #146c3c;
  --lg-12: #0c4a28;
  --lg-13: #d9f7e4;
  --lg-14: #ff5a45;
  --lg-15: #ffab52;
  --lg-16: #d97a1c;
  --lg-17: #6fc0ff;
  --lg-18: #1c6fd9;
  --lg-19: #6e2a12;
  --lg-20: #2a0f06;
  --lg-21: #6e4612;
  --lg-22: #2a1a06;
  --lg-23: #6e1240;
  --lg-24: #2a0618;
  --lg-25: #12506e;
  --lg-26: #061e2a;
  --lg-27: #4d6e12;
  --lg-28: #1c2a06;
  --lg-29: #6e1212;
  --lg-30: #2a0606;
  --lg-31: #b00020;
  --lg-32: #ffffff;
}
[data-nx-page="mobile"] {
  --lg-1: #111318;
  --lg-2: #1e2128;
  --lg-3: #15171c;
  --lg-4: #2b2f3a;
  --lg-5: #96a0b5;
  --lg-6: #1b2030;
  --lg-7: #eef1f8;
  --lg-8: #fff;
  --lg-9: #e04f5f;
  --lg-10: #cdd5e4;
  --lg-11: #3d4a66;
  --lg-12: #3f7cff;
  --lg-13: #2a4067;
  --lg-14: #f4a524;
  --lg-15: #ff8d99;
  --lg-16: #ffc466;
  --lg-17: #e8b168;
  --lg-18: #aab3c7;
  --lg-19: #17784d;
  --lg-20: #7c3038;
  --lg-21: #6b7590;
  --lg-22: #7ea6ff;
  --lg-23: #ff9aa5;
}
[data-nx-page="music-player"] {
  --lg-1: #000;
  --lg-2: #fff;
}
[data-nx-page="onboarding"] {
  --lg-1: #fff;
}
[data-nx-page="order-ready"] {
  --lg-1: #070b16;
  --lg-2: #0e1526;
  --lg-3: #1d2942;
  --lg-4: #eaf0ff;
  --lg-5: #8395b8;
  --lg-6: #f6b93b;
  --lg-7: #17c06b;
}
[data-nx-page="order"] {
  --lg-1: #fff;
  --lg-2: #111;
}
[data-nx-page="pos"] {
  --lg-1: #111318;
  --lg-2: #15171c;
  --lg-3: #1e2128;
  --lg-4: #262a33;
  --lg-5: #2c303a;
  --lg-6: #eef0f6;
  --lg-7: #9aa1b2;
  --lg-8: #6b4a2f;
  --lg-9: #52351f;
  --lg-10: #c97a1c;
  --lg-11: #a05b10;
  --lg-12: #b3362b;
  --lg-13: #7fb069;
  --lg-14: #d9a92f;
  --lg-15: #c2618f;
  --lg-16: #2f6b4f;
  --lg-17: #7b5ea7;
  --lg-18: #3a3f4b;
  --lg-19: #206f40;
  --lg-20: #38b96c;
  --lg-21: #c8423a;
  --lg-22: #caa53d;
  --lg-23: #fff;
  --lg-24: #33270c;
  --lg-25: #e8c05a;
  --lg-26: #57430f;
  --lg-27: #ff8a7a;
  --lg-28: #86b6ff;
  --lg-29: #35507c;
  --lg-30: #d99a90;
  --lg-31: #ffd9d4;
  --lg-32: #8a6d1d;
  --lg-33: #5c2622;
  --lg-34: #6e5716;
  --lg-35: #a3485e;
  --lg-36: #4c7a42;
  --lg-37: #8f2f28;
  --lg-38: #3d4250;
  --lg-39: #4fd08c;
  --lg-40: #ff7a70;
  --lg-41: #8fe0b0;
  --lg-42: #0e1f16;
  --lg-43: #05070b;
  --lg-44: #201803;
  --lg-45: #2e5b3f;
  --lg-46: #7fdca4;
  --lg-47: #6a4a17;
  --lg-48: #b45309;
  --lg-49: #6b5cff;
}
[data-nx-page="queue"] {
  --lg-1: #070b16;
  --lg-2: #0e1526;
  --lg-3: #121b30;
  --lg-4: #223052;
  --lg-5: #eaf1ff;
  --lg-6: #8798bd;
  --lg-7: #17c06b;
  --lg-8: #f6b93b;
  --lg-9: #ff5468;
  --lg-10: #10203f;
  --lg-11: #3f7cff;
  --lg-12: #6b5cff;
  --lg-13: #fff;
  --lg-14: #22345c;
  --lg-15: #16203a;
  --lg-16: #ffd9d4;
  --lg-17: #131a2c;
  --lg-18: #e8eefc;
  --lg-19: #1a2542;
  --lg-20: #04140b;
  --lg-21: #a8bcd8;
  --lg-22: #0b1120;
  --lg-23: #8b93a7;
  --lg-24: #2c3f6b;
}
[data-nx-page="reports"] {
  --lg-1: #ff5468;
  --lg-2: #0b3;
  --lg-3: #e34;
  --lg-4: #2bb673;
  --lg-5: #3a82f6;
  --lg-6: #b07cf0;
  --lg-7: #fff;
}
[data-nx-page="reset"] {
  --lg-1: #0b1020;
  --lg-2: #141a2e;
  --lg-3: #e8ecf6;
  --lg-4: #aeb7cc;
  --lg-5: #665ee6;
  --lg-6: #e06060;
  --lg-7: #19b56b;
  --lg-8: #2a3350;
  --lg-9: #0e1428;
  --lg-10: #fff;
}
[data-nx-page="secure"] {
  --lg-1: #0a0d15;
  --lg-2: #111624;
  --lg-3: #161d30;
  --lg-4: #263252;
  --lg-5: #e9efff;
  --lg-6: #8595ba;
  --lg-7: #19c07a;
  --lg-8: #f2b23d;
  --lg-9: #ff5064;
  --lg-10: #4d8dff;
  --lg-11: #8f7bff;
  --lg-12: #131c36;
  --lg-13: #fff;
  --lg-14: #b02030;
  --lg-15: #55627f;
}
[data-nx-page="storefront"] {
  --lg-1: #fff;
  --lg-2: #111;
}
[data-nx-page="table-service"] {
  --lg-1: #fff;
}
[data-nx-page="tables"] {
  --lg-1: #111318;
  --lg-2: #15171c;
  --lg-3: #1e2128;
  --lg-4: #262a33;
  --lg-5: #2c303a;
  --lg-6: #eef0f6;
  --lg-7: #9aa1b2;
  --lg-8: #2e9e5b;
  --lg-9: #2f6fed;
  --lg-10: #d9a92f;
  --lg-11: #c8423a;
  --lg-12: #fff;
  --lg-13: #8a6d1d;
  --lg-14: #4fd08c;
  --lg-15: #35507c;
  --lg-16: #16202e;
  --lg-17: #86b6ff;
  --lg-18: #12241a;
  --lg-19: #241d0b;
  --lg-20: #e8c05a;
  --lg-21: #2a1210;
  --lg-22: #ff8a7a;
  --lg-23: #1f4a34;
  --lg-24: #57430f;
}
[data-nx-page="track"] {
  --lg-1: #fff;
}
[data-nx-page="vision-player"] {
  --lg-1: #fff;
  --lg-2: #b00020;
  --lg-3: #ffffff;
}

/* ============================================================================
   NEXUS COMPONENT SET — U02. TWENTY-NINE components, amended down from an
   initial 31 (Chris's ruling, 2026-09-05, logged in full on PR #82 — read
   that before re-litigating this number). The brief's own itemised U02
   paragraph names 31 distinct things when tallied literally (not 26 as its
   own summary sentence claims — that discrepancy is the brief's, not an
   addition made here); of those 31, two were found on necessity review to
   be pure CSS variants with zero unique behaviour and folded:
     - Sheet -> .nxc-modal--sheet (a Modal modifier). Sheet's JS was already
       `Sheet(el,opts) { return Modal(el,opts); }`, byte-identical to Modal.
       What survives the fold, deliberately: the bottom-anchored,
       thumb-reachable geometry and transform-based show/hide, because that
       IS a real, distinct interaction contract (stay-in-context vs
       stop-and-decide) even though the code underneath was never distinct.
     - IconButton -> .nxc-btn--icon (a Button modifier). What survives the
       fold, deliberately: the square, tap-floor-pinned sizing contract
       (width: var(--nxc-tap), zero horizontal padding) — folding this
       into a generic Button variant without preserving that would have
       reintroduced the exact class of touch-target regression this file's
       own :root --nxc-tap fix (below) already had to catch once.
   Three other candidates (NumberPad, StatTile, KeyValueList) were reviewed
   and deliberately KEPT: each stands in for an already-observed failure
   mode (a hand-rolled PIN pad, a hand-rolled KPI box, a table reached for
   where a table is wrong) rather than being a convenience. 29 is the
   number; do not re-derive 26 or 31 from this file without reading PR #82
   first. Final 29, by category: AppBar, StoreContextStrip, NavRail/NavList,
   SearchPalette, RoleHome = 5; Button (incl. the icon variant), NumberPad,
   Toggle, SegmentedControl = 4; TextField, Select, Combobox, DatePicker,
   MoneyField, QuantityStepper = 6; Card, DataTable, KeyValueList, StatTile,
   Badge/Pill, Timeline = 6; EmptyState, LoadingSkeleton, ErrorState,
   SignedOutState, OfflineBanner = 5; Modal (incl. the sheet variant),
   ConfirmDialog, Toast = 3.

   Namespace: every class is `.nxc-*` (Nexus Component), never `.nx-*` — the
   existing kit (_surface/surface.css) already OWNS `.nx-btn`, `.nx-card`,
   `.nx-modal`, `.nx-input`, `.nx-table`, `.nx-badge`, `.nx-chip` and more,
   live on 30 shipped surfaces sharing this same bundle. Reusing those names
   here would silently redefine them the moment this file joins the bundle —
   exactly the "DON'T migrate any screen in this task" this file must not
   cause by accident. `.nxc-*` is a disjoint namespace: inert everywhere
   until U03 deliberately points a screen's markup at it.

   Everything binds to the Part 3 tokens (_surface/part3-tokens.css) — never
   a raw hex, never a raw px outside the 8-step type/space scales. Both
   themes come free: light at :root, dark at :root[data-theme="dark"],
   nothing here re-declares either.

   DEMO STATE HOOKS — the gallery must render hover/focus/disabled states
   as static, screenshot-stable markup, not as "move your mouse here": every
   interactive state has both the real pseudo-class AND an `.is-*` class
   that reproduces it, so `<button class="nxc-btn is-hover">` looks exactly
   like a real :hover without a live cursor.
   ============================================================================ */

/* ---- shared interactive primitive ---------------------------------------
   Every focusable control in this file inherits this floor: the 44px touch
   target and the visible focus ring. `--nxc-tap` defaults to --nx-tap-min
   and is overridden to --nx-tap-heavy by a page that opts a whole component
   into the heavy floor (NexusPOS/Flow/Cook/Queue/Serve) — never per instance.
   Declared on :root (inherited by everything), NOT only inside the :where()
   block below — a custom property referenced by a selector outside that
   block (.nxc-input, .nxc-select, ...) would otherwise see an unset
   --nxc-tap and silently drop the floor (found by the gallery's own
   touch-target audit reporting 18px inputs before this line existed). */
:root { --nxc-tap: var(--nx-tap-min); }
:where(.nxc-btn, .nxc-toggle, .nxc-seg__opt, .nxc-numpad__key,
  .nxc-stepper__btn, .nxc-chip, .nxc-table__sort, .nxc-nav__item,
  .nxc-modal__close, .nxc-toast__close, .nxc-combobox__opt, .nxc-datepicker__day) {
  --nxc-tap: var(--nx-tap-min);
  min-height: var(--nxc-tap);
}
:where(.nxc-btn, .nxc-toggle, .nxc-seg__opt, .nxc-numpad__key,
  .nxc-stepper__btn, .nxc-chip, .nxc-table__sort, .nxc-nav__item,
  .nxc-modal__close, .nxc-toast__close, .nxc-combobox__opt, .nxc-datepicker__day):focus-visible,
:where(.nxc-btn, .nxc-toggle, .nxc-seg__opt, .nxc-numpad__key,
  .nxc-stepper__btn, .nxc-chip, .nxc-table__sort, .nxc-nav__item,
  .nxc-modal__close, .nxc-toast__close, .nxc-combobox__opt, .nxc-datepicker__day).is-focus {
  outline: 2px solid var(--nx-brand);
  outline-offset: 2px;
}
.nxc-heavy-tap { --nxc-tap: var(--nx-tap-heavy); }

/* ============================================================================
   SHELL — AppBar, StoreContextStrip, NavRail/NavList, SearchPalette, RoleHome
   ============================================================================ */
.nxc-appbar {
  display: flex; align-items: center; gap: var(--nx-space-4);
  min-height: 56px; padding: 0 var(--nx-space-4);
  background: var(--nx-surface); border-bottom: 1px solid var(--nx-line);
  color: var(--nx-ink); font-family: var(--nx-font-sans);
}
.nxc-appbar__app { font-weight: 700; font-size: var(--nx-text-3); }
.nxc-appbar__screen { color: var(--nx-mute); font-size: var(--nx-text-2); }
.nxc-appbar__spacer { flex: 1; }
.nxc-appbar__account {
  display: flex; align-items: center; gap: var(--nx-space-2);
  font-size: var(--nx-text-2); color: var(--nx-ink-2);
}

.nxc-storectx {
  display: flex; align-items: center; gap: var(--nx-space-3); flex-wrap: wrap;
  padding: var(--nx-space-2) var(--nx-space-4);
  background: var(--nx-sunk); border-bottom: 1px solid var(--nx-line-soft);
  font-size: var(--nx-text-1); color: var(--nx-mute);
}
.nxc-storectx__sep { opacity: .5; }
.nxc-storectx__store { color: var(--nx-ink-2); font-weight: 600; }

.nxc-nav { display: flex; flex-direction: column; gap: var(--nx-space-1); width: 220px; }
.nxc-nav--rail { width: 64px; align-items: center; }
.nxc-nav__item {
  display: flex; align-items: center; gap: var(--nx-space-3);
  width: 100%; padding: 0 var(--nx-space-3); border-radius: var(--nx-radius-control);
  background: transparent; border: 0; color: var(--nx-ink-2);
  font-family: var(--nx-font-sans); font-size: var(--nx-text-2); text-align: left;
  cursor: pointer; transition: background var(--nx-dur-state) ease;
}
.nxc-nav--rail .nxc-nav__item { justify-content: center; padding: 0; width: var(--nxc-tap); }
.nxc-nav__item:hover, .nxc-nav__item.is-hover { background: var(--nx-sunk); }
.nxc-nav__item[aria-current="page"], .nxc-nav__item.is-active {
  background: var(--nx-brand-soft); color: var(--nx-brand); font-weight: 600;
}
.nxc-nav__item:disabled, .nxc-nav__item.is-disabled { color: var(--nx-mute); opacity: .5; cursor: not-allowed; }

.nxc-palette {
  position: fixed; inset: 0; display: none; align-items: flex-start; justify-content: center;
  padding-top: 12vh; background: rgba(0,0,0,.4); z-index: 100;
}
.nxc-palette.is-open, .nxc-palette[open] { display: flex; }
.nxc-palette__box {
  width: min(560px, 92vw); background: var(--nx-surface); border-radius: var(--nx-radius-card);
  box-shadow: var(--nx-elev-modal); overflow: hidden; border: 1px solid var(--nx-line);
}
.nxc-palette__input {
  width: 100%; border: 0; border-bottom: 1px solid var(--nx-line);
  padding: var(--nx-space-4); font-size: var(--nx-text-4); background: transparent;
  color: var(--nx-ink); font-family: var(--nx-font-sans);
}
.nxc-palette__input:focus { outline: none; }
.nxc-palette__list { max-height: 320px; overflow-y: auto; padding: var(--nx-space-2); }
.nxc-palette__opt {
  display: flex; align-items: center; gap: var(--nx-space-3); width: 100%;
  min-height: var(--nxc-tap); padding: 0 var(--nx-space-3); border: 0; background: transparent;
  border-radius: var(--nx-radius-control); color: var(--nx-ink); font-size: var(--nx-text-2);
  text-align: left; cursor: pointer;
}
.nxc-palette__opt[aria-selected="true"], .nxc-palette__opt.is-hover { background: var(--nx-brand-soft); }
.nxc-palette__hint { color: var(--nx-mute); font-size: var(--nx-text-1); margin-left: auto; }

.nxc-rolehome { display: grid; gap: var(--nx-space-4); grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)); }
.nxc-rolehome__tile {
  display: flex; flex-direction: column; gap: var(--nx-space-2); padding: var(--nx-space-4);
  border-radius: var(--nx-radius-card); background: var(--nx-surface); border: 1px solid var(--nx-line);
  min-height: 96px; cursor: pointer; text-align: left; color: var(--nx-ink); font-family: var(--nx-font-sans);
  transition: box-shadow var(--nx-dur-state) ease;
}
.nxc-rolehome__tile:hover, .nxc-rolehome__tile.is-hover { box-shadow: var(--nx-elev-raised); }
.nxc-rolehome__tile.is-locked { opacity: .5; cursor: not-allowed; }
.nxc-rolehome__title { font-weight: 700; font-size: var(--nx-text-3); }
.nxc-rolehome__desc { color: var(--nx-mute); font-size: var(--nx-text-1); }

/* ============================================================================
   ACTION — Button (incl. the icon variant), NumberPad, Toggle, SegmentedControl
   ============================================================================ */
/* text-decoration: none is here because .nxc-btn is a visual role, not a
   tag requirement — U03 uses it on <a href> download/action links as much
   as <button>, and only <a> carries a browser-default underline. Found by
   screenshotting downloads.html's Download link after migrating it off
   its own .btn class, which set this explicitly. */
.nxc-btn {
  display: inline-flex; align-items: center; justify-content: center; gap: var(--nx-space-2);
  padding: 0 var(--nx-space-4); border-radius: var(--nx-radius-control); border: 1px solid transparent;
  font-family: var(--nx-font-sans); font-size: var(--nx-text-2); font-weight: 600;
  text-decoration: none; cursor: pointer; transition: background var(--nx-dur-state) ease, opacity var(--nx-dur-state) ease;
  background: var(--nx-brand); color: var(--nx-on-brand);
}
.nxc-btn:hover, .nxc-btn.is-hover { background: var(--nx-brand-hover); }
.nxc-btn:disabled, .nxc-btn.is-disabled { opacity: .45; cursor: not-allowed; }
.nxc-btn.is-loading { color: transparent; position: relative; }
.nxc-btn.is-loading::after {
  content: ""; position: absolute; width: 16px; height: 16px; border-radius: 50%;
  border: 2px solid rgba(255,255,255,.5); border-top-color: var(--nx-on-brand); animation: nxc-spin .7s linear infinite;
}
@keyframes nxc-spin { to { transform: rotate(360deg); } }

.nxc-btn--secondary { background: var(--nx-surface); color: var(--nx-ink); border-color: var(--nx-line); }
.nxc-btn--secondary:hover, .nxc-btn--secondary.is-hover { background: var(--nx-sunk); }
.nxc-btn--ghost { background: transparent; color: var(--nx-ink-2); }
.nxc-btn--ghost:hover, .nxc-btn--ghost.is-hover { background: var(--nx-sunk); }
.nxc-btn--danger { background: var(--nx-bad); color: var(--nx-on-brand); }
.nxc-btn--danger:hover, .nxc-btn--danger.is-hover { filter: brightness(.92); }

/* IconButton, folded from its own class into a Button modifier (Chris's
   ruling, 2026-09-05, 31 -> 29): no unique behaviour of its own, always a
   native <button>, nothing IconButton did that Button-plus-a-modifier
   couldn't. What must NOT be lost in the fold: the square, tap-floor-pinned
   sizing contract — width forced to --nxc-tap and horizontal padding
   zeroed, so an icon glyph never inherits Button's text padding and drifts
   under the touch floor. .nxc-btn is already in the shared :where() floor
   above, so min-height comes for free; width is the only thing this
   modifier has to pin itself. */
.nxc-btn--icon {
  width: var(--nxc-tap); padding: 0;
  background: var(--nx-surface); color: var(--nx-ink-2); border-color: var(--nx-line);
}
.nxc-btn--icon:hover, .nxc-btn--icon.is-hover { background: var(--nx-sunk); }

.nxc-numpad { display: grid; grid-template-columns: repeat(3, 1fr); gap: var(--nx-space-2); width: min(320px, 100%); }
.nxc-numpad__key {
  min-height: var(--nxc-tap); border-radius: var(--nx-radius-control); border: 1px solid var(--nx-line);
  background: var(--nx-surface); color: var(--nx-ink); font-size: var(--nx-text-5); font-weight: 600;
  cursor: pointer; font-family: var(--nx-font-mono);
}
.nxc-numpad__key:hover, .nxc-numpad__key.is-hover { background: var(--nx-sunk); }
.nxc-numpad__key:disabled, .nxc-numpad__key.is-disabled { opacity: .45; cursor: not-allowed; }

.nxc-toggle {
  position: relative; display: inline-flex; align-items: center; width: 52px;
  border-radius: var(--nx-radius-pill); background: var(--nx-line); border: 0; cursor: pointer; padding: var(--nx-space-1);
}
.nxc-toggle::before {
  content: ""; width: 22px; height: 22px; border-radius: 50%; background: var(--nx-on-brand);
  box-shadow: var(--nx-elev-raised); transition: transform var(--nx-dur-state) ease;
}
.nxc-toggle[aria-checked="true"], .nxc-toggle.is-checked { background: var(--nx-brand); }
.nxc-toggle[aria-checked="true"]::before, .nxc-toggle.is-checked::before { transform: translateX(22px); }
.nxc-toggle:disabled, .nxc-toggle.is-disabled { opacity: .45; cursor: not-allowed; }

.nxc-seg { display: inline-flex; background: var(--nx-sunk); border-radius: var(--nx-radius-control); padding: var(--nx-space-1); gap: var(--nx-space-1); }
.nxc-seg__opt {
  padding: 0 var(--nx-space-3); border-radius: calc(var(--nx-radius-control) - 2px); border: 0;
  background: transparent; color: var(--nx-mute); font-family: var(--nx-font-sans); font-size: var(--nx-text-2);
  font-weight: 600; cursor: pointer;
}
.nxc-seg__opt[aria-selected="true"], .nxc-seg__opt.is-active { background: var(--nx-surface); color: var(--nx-ink); box-shadow: var(--nx-elev-raised); }
.nxc-seg__opt:disabled, .nxc-seg__opt.is-disabled { opacity: .45; cursor: not-allowed; }

/* ============================================================================
   INPUT — TextField, Select, Combobox, DatePicker, MoneyField, QuantityStepper
   ============================================================================ */
.nxc-field { display: flex; flex-direction: column; gap: var(--nx-space-1); }
.nxc-field__label { font-size: var(--nx-text-1); font-weight: 600; color: var(--nx-ink-2); }
.nxc-field__hint { font-size: var(--nx-text-1); color: var(--nx-mute); }
.nxc-field__error { font-size: var(--nx-text-1); color: var(--nx-bad); }

.nxc-input, .nxc-select {
  min-height: var(--nxc-tap); padding: 0 var(--nx-space-3); border-radius: var(--nx-radius-control);
  border: 1px solid var(--nx-line); background: var(--nx-surface); color: var(--nx-ink);
  font-family: var(--nx-font-sans); font-size: var(--nx-text-2); width: 100%;
}
.nxc-input:hover, .nxc-input.is-hover, .nxc-select:hover, .nxc-select.is-hover { border-color: var(--nx-mute); }
.nxc-input:focus-visible, .nxc-input.is-focus, .nxc-select:focus-visible, .nxc-select.is-focus {
  outline: 2px solid var(--nx-brand); outline-offset: -1px; border-color: var(--nx-brand);
}
.nxc-input:disabled, .nxc-input.is-disabled, .nxc-select:disabled, .nxc-select.is-disabled {
  background: var(--nx-sunk); color: var(--nx-mute); cursor: not-allowed;
}
.nxc-field.is-error .nxc-input, .nxc-field.is-error .nxc-select { border-color: var(--nx-bad); }

.nxc-combobox { position: relative; }
.nxc-combobox__list {
  position: absolute; top: calc(100% + 4px); left: 0; right: 0; z-index: 20;
  background: var(--nx-surface); border: 1px solid var(--nx-line); border-radius: var(--nx-radius-control);
  box-shadow: var(--nx-elev-raised); max-height: 240px; overflow-y: auto; display: none;
}
.nxc-combobox.is-open .nxc-combobox__list { display: block; }
.nxc-combobox__opt {
  display: flex; align-items: center; width: 100%; padding: 0 var(--nx-space-3); border: 0;
  background: transparent; color: var(--nx-ink); font-size: var(--nx-text-2); text-align: left; cursor: pointer;
}
.nxc-combobox__opt[aria-selected="true"], .nxc-combobox__opt.is-hover { background: var(--nx-brand-soft); }
.nxc-combobox__empty { padding: var(--nx-space-3); color: var(--nx-mute); font-size: var(--nx-text-1); }

.nxc-datepicker { display: inline-block; background: var(--nx-surface); border: 1px solid var(--nx-line); border-radius: var(--nx-radius-card); padding: var(--nx-space-3); }
.nxc-datepicker__head { display: flex; align-items: center; justify-content: space-between; margin-bottom: var(--nx-space-2); font-weight: 600; color: var(--nx-ink); }
.nxc-datepicker__grid { display: grid; grid-template-columns: repeat(7, 1fr); gap: 2px; }
.nxc-datepicker__day {
  width: 100%; min-height: var(--nxc-tap); border: 0; background: transparent; border-radius: var(--nx-radius-control);
  color: var(--nx-ink-2); font-size: var(--nx-text-1); cursor: pointer; font-family: var(--nx-font-mono);
}
.nxc-datepicker__day:hover, .nxc-datepicker__day.is-hover { background: var(--nx-sunk); }
.nxc-datepicker__day[aria-selected="true"], .nxc-datepicker__day.is-active { background: var(--nx-brand); color: var(--nx-on-brand); }
.nxc-datepicker__day:disabled, .nxc-datepicker__day.is-disabled { color: var(--nx-line); cursor: not-allowed; }

.nxc-money { position: relative; }
.nxc-money__prefix {
  position: absolute; left: var(--nx-space-3); top: 50%; transform: translateY(-50%);
  color: var(--nx-mute); font-family: var(--nx-font-mono); font-size: var(--nx-text-2);
}
.nxc-money .nxc-input { padding-left: calc(var(--nx-space-3) * 2 + var(--nx-space-1)); font-family: var(--nx-font-mono); font-variant-numeric: tabular-nums; text-align: right; }

.nxc-stepper { display: inline-flex; align-items: stretch; border: 1px solid var(--nx-line); border-radius: var(--nx-radius-control); overflow: hidden; }
.nxc-stepper__btn {
  width: var(--nxc-tap); border: 0; background: var(--nx-sunk); color: var(--nx-ink); font-size: var(--nx-text-4);
  cursor: pointer;
}
.nxc-stepper__btn:hover, .nxc-stepper__btn.is-hover { background: var(--nx-line-soft); }
.nxc-stepper__btn:disabled, .nxc-stepper__btn.is-disabled { opacity: .4; cursor: not-allowed; }
.nxc-stepper__val {
  min-width: 48px; display: flex; align-items: center; justify-content: center;
  font-family: var(--nx-font-mono); font-variant-numeric: tabular-nums; font-size: var(--nx-text-3);
  color: var(--nx-ink); background: var(--nx-surface);
}

/* ============================================================================
   DISPLAY — Card, DataTable, KeyValueList, StatTile, Badge/Pill, Timeline
   ============================================================================ */
.nxc-card {
  background: var(--nx-surface); border: 1px solid var(--nx-line); border-radius: var(--nx-radius-card);
  padding: var(--nx-space-4); box-shadow: var(--nx-elev-raised);
}
.nxc-card__title { font-weight: 700; font-size: var(--nx-text-3); color: var(--nx-ink); margin: 0 0 var(--nx-space-2); }

.nxc-table-wrap { border: 1px solid var(--nx-line); border-radius: var(--nx-radius-card); overflow: hidden; }
.nxc-table { width: 100%; border-collapse: collapse; font-family: var(--nx-font-sans); font-size: var(--nx-text-2); }
.nxc-table thead th {
  text-align: left; padding: var(--nx-space-3); background: var(--nx-sunk); color: var(--nx-ink-2);
  font-size: var(--nx-text-1); font-weight: 700; text-transform: uppercase; letter-spacing: .04em;
}
.nxc-table__sort { background: transparent; border: 0; color: inherit; font: inherit; cursor: pointer; display: inline-flex; align-items: center; gap: 4px; }
.nxc-table tbody td { padding: var(--nx-space-3); border-top: 1px solid var(--nx-line-soft); color: var(--nx-ink); }
.nxc-table tbody tr:hover, .nxc-table tbody tr.is-hover { background: var(--nx-sunk); }
.nxc-table__toolbar { display: flex; align-items: center; gap: var(--nx-space-2); padding: var(--nx-space-3); background: var(--nx-surface); }
.nxc-table__toolbar .nxc-spacer { flex: 1; }
.nxc-table__pager { display: flex; align-items: center; gap: var(--nx-space-2); padding: var(--nx-space-3); background: var(--nx-sunk); font-size: var(--nx-text-1); color: var(--nx-mute); }

.nxc-kv { display: grid; grid-template-columns: max-content 1fr; gap: var(--nx-space-2) var(--nx-space-4); }
.nxc-kv__k { color: var(--nx-mute); font-size: var(--nx-text-1); }
.nxc-kv__v { color: var(--nx-ink); font-size: var(--nx-text-2); font-variant-numeric: tabular-nums; }

.nxc-stat { display: flex; flex-direction: column; gap: var(--nx-space-1); padding: var(--nx-space-4); background: var(--nx-surface); border: 1px solid var(--nx-line); border-radius: var(--nx-radius-card); }
.nxc-stat__label { font-size: var(--nx-text-1); color: var(--nx-mute); text-transform: uppercase; letter-spacing: .04em; }
.nxc-stat__value { font-size: var(--nx-text-6); font-weight: 700; color: var(--nx-ink); font-variant-numeric: tabular-nums; font-family: var(--nx-font-mono); }
.nxc-stat__delta { font-size: var(--nx-text-1); }
.nxc-stat__delta.is-up { color: var(--nx-good); }
.nxc-stat__delta.is-down { color: var(--nx-bad); }

.nxc-badge {
  display: inline-flex; align-items: center; padding: var(--nx-space-1) var(--nx-space-2); border-radius: var(--nx-radius-pill);
  font-size: var(--nx-text-1); font-weight: 700; background: var(--nx-sunk); color: var(--nx-ink-2);
}
.nxc-badge--good { background: var(--nx-good-soft); color: var(--nx-good); }
.nxc-badge--warn { background: var(--nx-warn-soft); color: var(--nx-warn); }
.nxc-badge--bad { background: var(--nx-bad-soft); color: var(--nx-bad); }
.nxc-badge--info { background: var(--nx-info-soft); color: var(--nx-info); }
.nxc-badge--brand { background: var(--nx-brand-soft); color: var(--nx-brand); }

.nxc-timeline { display: flex; flex-direction: column; gap: 0; }
.nxc-timeline__item { display: flex; gap: var(--nx-space-3); padding-bottom: var(--nx-space-4); position: relative; }
.nxc-timeline__item::before {
  content: ""; position: absolute; left: 5px; top: 16px; bottom: 0; width: 1px; background: var(--nx-line);
}
.nxc-timeline__item:last-child::before { display: none; }
.nxc-timeline__dot { width: 11px; height: 11px; border-radius: 50%; background: var(--nx-brand); margin-top: var(--nx-space-1); flex-shrink: 0; }
.nxc-timeline__item.is-bad .nxc-timeline__dot { background: var(--nx-bad); }
.nxc-timeline__item.is-good .nxc-timeline__dot { background: var(--nx-good); }
.nxc-timeline__body { display: flex; flex-direction: column; }
.nxc-timeline__title { color: var(--nx-ink); font-size: var(--nx-text-2); }
.nxc-timeline__time { color: var(--nx-mute); font-size: var(--nx-text-1); font-variant-numeric: tabular-nums; }

/* ============================================================================
   STATE — EmptyState, LoadingSkeleton, ErrorState, SignedOutState, OfflineBanner
   Four distinct components on purpose (never substituted for one another):
   EmptyState = "nothing here yet, by design"; ErrorState = "something broke,
   here's what to do"; LoadingSkeleton = "still arriving"; SignedOutState =
   "you're not signed in" (never "no data"). Each carries its OWN markup and
   class so a screen can never accidentally reach for the wrong one.
   ============================================================================ */
.nxc-empty, .nxc-error, .nxc-signedout {
  display: flex; flex-direction: column; align-items: center; text-align: center; gap: var(--nx-space-2);
  padding: var(--nx-space-8) var(--nx-space-4); color: var(--nx-mute);
}
.nxc-empty__icon, .nxc-error__icon, .nxc-signedout__icon { font-size: var(--nx-text-7); line-height: 1; margin-bottom: var(--nx-space-2); }
.nxc-empty__title, .nxc-error__title, .nxc-signedout__title { font-size: var(--nx-text-4); font-weight: 700; color: var(--nx-ink); }
.nxc-empty__desc, .nxc-error__desc, .nxc-signedout__desc { font-size: var(--nx-text-2); max-width: 44ch; }
.nxc-error__icon { color: var(--nx-bad); }

.nxc-skeleton {
  background: linear-gradient(90deg, var(--nx-sunk) 25%, var(--nx-line-soft) 37%, var(--nx-sunk) 63%);
  background-size: 400% 100%; animation: nxc-shimmer 1.4s ease infinite; border-radius: var(--nx-radius-control);
  color: transparent !important;
}
@keyframes nxc-shimmer { 0% { background-position: 200% 0; } 100% { background-position: -200% 0; } }
@media (prefers-reduced-motion: reduce) { .nxc-skeleton { animation: none; } }
.nxc-skeleton--text { height: 1em; width: 100%; }
.nxc-skeleton--title { height: var(--nx-text-4); width: 40%; }
.nxc-skeleton--block { height: 80px; width: 100%; }

.nxc-offline {
  display: flex; align-items: center; gap: var(--nx-space-2); padding: var(--nx-space-2) var(--nx-space-4);
  background: var(--nx-warn-soft); color: var(--nx-warn); font-size: var(--nx-text-1); font-weight: 600;
}

/* ============================================================================
   OVERLAY — Modal (incl. the sheet variant), ConfirmDialog, Toast
   ============================================================================ */
.nxc-scrim { position: fixed; inset: 0; background: rgba(0,0,0,.45); z-index: 90; display: none; }
.nxc-scrim.is-open { display: block; }

.nxc-modal {
  position: fixed; inset: 0; z-index: 91; display: none; align-items: center; justify-content: center; padding: var(--nx-space-4);
}
.nxc-modal.is-open { display: flex; }
.nxc-modal__box {
  width: min(480px, 100%); max-height: 86vh; overflow-y: auto; background: var(--nx-surface);
  border-radius: var(--nx-radius-card); box-shadow: var(--nx-elev-modal); padding: var(--nx-space-5);
  position: relative;
}
.nxc-modal__title { font-size: var(--nx-text-4); font-weight: 700; color: var(--nx-ink); margin: 0 0 var(--nx-space-2); padding-right: var(--nx-space-7); }
.nxc-modal__body { color: var(--nx-ink-2); font-size: var(--nx-text-2); }
.nxc-modal__actions { display: flex; justify-content: flex-end; gap: var(--nx-space-2); margin-top: var(--nx-space-4); }
.nxc-modal__close {
  position: absolute; top: var(--nx-space-3); right: var(--nx-space-3); width: var(--nxc-tap); min-height: var(--nxc-tap);
  border: 0; background: transparent; color: var(--nx-mute); cursor: pointer; border-radius: var(--nx-radius-control);
}
.nxc-modal__close:hover, .nxc-modal__close.is-hover { background: var(--nx-sunk); }

/* Sheet, folded from its own class into a Modal variant (Chris's ruling,
   2026-09-05, 31 -> 29): the JS was already Sheet(el,opts) === Modal(el,opts)
   with zero behavioural difference, so the separate name bought nothing the
   CSS modifier below doesn't. What must NOT be lost in the fold: the
   bottom-anchored, thumb-reachable geometry and the transform-based
   show/hide (not display:none/flex like base Modal) — that distinction is
   real UX (stay-in-context vs stop-and-decide), so it survives as
   .nxc-modal--sheet, not as a plain .nxc-modal. Markup: <div
   class="nxc-modal nxc-modal--sheet" ...> in place of the old
   class="nxc-sheet" — trapFocus's `el.querySelector('.nxc-modal__box') ||
   el` fallback already handles a sheet with no __box wrapper, unchanged. */
.nxc-modal.nxc-modal--sheet {
  display: block; align-items: unset; justify-content: unset;
  inset: auto; left: 0; right: 0; bottom: 0; top: auto;
  background: var(--nx-surface); border-radius: var(--nx-radius-card) var(--nx-radius-card) 0 0;
  box-shadow: var(--nx-elev-modal); padding: var(--nx-space-5);
  transform: translateY(100%); transition: transform var(--nx-dur-entry) ease; max-height: 80vh; overflow-y: auto;
}
.nxc-modal.nxc-modal--sheet.is-open { transform: translateY(0); }
.nxc-modal__grip { width: 36px; height: 4px; border-radius: var(--nx-radius-pill); background: var(--nx-line); margin: 0 auto var(--nx-space-3); }

.nxc-confirm .nxc-modal__blast {
  background: var(--nx-bad-soft); color: var(--nx-bad); border-radius: var(--nx-radius-control);
  padding: var(--nx-space-2) var(--nx-space-3); font-size: var(--nx-text-1); font-weight: 600; margin-top: var(--nx-space-2);
}

.nxc-toast-region { position: fixed; bottom: var(--nx-space-4); right: var(--nx-space-4); display: flex; flex-direction: column; gap: var(--nx-space-2); z-index: 95; }
.nxc-toast {
  display: flex; align-items: center; gap: var(--nx-space-2); min-width: 260px; max-width: 380px;
  padding: var(--nx-space-3) var(--nx-space-4); border-radius: var(--nx-radius-control); box-shadow: var(--nx-elev-raised);
  background: var(--nx-ink); color: var(--nx-ground); font-size: var(--nx-text-2);
}
.nxc-toast--good { background: var(--nx-good); color: var(--nx-on-brand); }
.nxc-toast--bad { background: var(--nx-bad); color: var(--nx-on-brand); }
.nxc-toast__close { margin-left: auto; background: transparent; border: 0; color: inherit; cursor: pointer; width: 28px; height: 28px; min-height: 0; }
