/* ============================================================================
   UV SPOT — TOKENS
   ----------------------------------------------------------------------------
   This file is :root and nothing else. Nothing here draws anything.

   PROVENANCE, AND IT MOVED ON 30 AUG. The first cut of this module followed
   mt3prototypes/hr: theme.css + nordic.css copied byte-for-byte, this file
   re-pointing the names they read, and a hard rule of zero .cd-* overrides.
   That is HR's position (15 Aug) and it is now the older one.

   mt3prototypes/procurement (16 Aug, 42 screens) reversed it, and live Pulse
   shipped `nav shell v2` on 10 Aug. The current house position has two halves
   and this module now takes both:

     THE SCREENS ARE OURS. Every .cd-* name in Pulse's nordic.css is
     re-implemented in app.css under the SAME NAME at these values. Nothing is
     renamed, so the port is a file swap and not one line of markup changes —
     which is the whole thing copying the stylesheet was protecting. nordic.css
     is no longer loaded; it stays in this directory as the class contract to
     diff against.

     THE CHROME IS THEIRS. The header, the top-nav, the mobile sheet, the
     docked panel and the dock are a faithful port of live Pulse's
     AppHeaderWithMenu -> TopNavMenu -> MobileDock, class name for class name,
     literal for literal, and their colour comes from shell-themes.css, which
     is copied token for token from live Pulse's own file.

   THE PORT BLOCK at the bottom re-points every token name live Pulse uses at
   these values, so pasting it over theme.css's :root re-skins the app.

   WHAT THIS MODULE CHANGES ON TOP OF PROCUREMENT'S SET, and why:

   1. THE TYPE AND TARGET SCALE IS BIGGER. Procurement is read at a desk by a
      buyer comparing two registers. This is read at arm's length, standing,
      beside a flatbed press, sometimes through safety glasses and usually with
      gloves on. --t-cell is 14.5 against its 13.5, --t-num is 34 against 26,
      and --tap (48px) is a floor for every interactive target. Because we own
      the component layer now, those go HERE and reach .cd-btn directly —
      there is no density fork and no override anywhere in this module.

   2. THE STATUS SET GAINS THE QA FIVE. The self-test emits five statuses and
      the fifth is the dangerous one. SKIP is a check that could not look —
      "no die window on this sheet, so the die-window check could not run" —
      and on 29 Aug fifty-five packages carried six skips each while their
      pages read green. So SKIP is its own hatched neutral, and the hard rule
      the whole module is audited against is: SKIP NEVER RENDERS GREEN.
   ========================================================================== */

:root {

/* ── INK — LIVE PULSE'S OWN RAMP, value for value.
      theme.css:22-31's greys, mapped onto the five stops this module reads.
      Procurement re-cut these to a cooler neutral and argued that #64748b
      tints a neutral badge lilac; that argument is real but it is not this
      module's to make. The user's instruction on 30 Aug was plain — the
      colours are live Pulse's — so a screen here and a screen in Pulse are
      the same grey, and the only thing this module changes is SIZE. ────── */
  --ink:      #0f172a;   /* == --color-gray-900: values, cells, h1, numbers   */
  --ink-2:    #334155;   /* == --color-gray-700: secondary prose, labels      */
  --ink-3:    #64748b;   /* == --color-gray-500: headers, meta, breadcrumb    */
  --ink-4:    #94a3b8;   /* == --color-gray-400: placeholders, eyebrows       */
  --ink-5:    #cbd5e1;   /* == --color-gray-300: dash(), resting sort arrow   */
  --muted:    var(--ink-3);
  --faint:    var(--ink-4);

/* ── SURFACES — the page is warmer than the card, so a card reads as a raised
      plane without a shadow doing the work. ────────────────────────────── */
  --page:      #f5f5f7;  /* == theme.css --bg-page                           */
  --surface:   #ffffff;
  --sunken:    #f8fafc;  /* == --color-gray-50: thead, sticky col, wells      */
  --row-hover: #f8fafc;  /* == theme.css --bg-hover                          */
  --row-sel:   #eef2ff;  /* == --nav-active-bg: one "you are here" tint       */
  --scrim:     rgba(15, 23, 42, .44);

/* ── LINES — three weights, and they replace every shadow on a static
      surface. A twenty-column table separated by shadows is soup.
      --line-2 is DARKER than --line here (procurement's sense). ────────── */
  --line:   #e2e8f0;     /* == --color-gray-200: every hairline               */
  --line-2: #cbd5e1;     /* == --color-gray-300: control borders              */
  --line-3: #94a3b8;     /* == --color-gray-400: control hover                */
  --line-strong: var(--line-2);

/* ── ACCENT — live Pulse's brand hue, and the gradient is BACK.

      This reverses a call made earlier the same day. Following procurement,
      this module had moved to #4338ca and retired the gradient on the screens,
      on the argument that a gradient button beside a gradient header is muddy.
      The user's instruction is that the colours match production, and
      production wears #667eea and the 135° brand gradient. That settles it:
      procurement's palette is a proposal, live Pulse is the app.

      #4338ca survives where live Pulse itself uses it — as --nav-active-fg,
      the "you are here" ink in the panel and the dropdowns. It is Pulse's own
      distinction between "the brand" and "the thing you are looking at", and
      keeping it is faithfulness, not a hedge. ─────────────────────────────── */
  --accent:      #667eea;   /* == --color-primary                             */
  --accent-hi:   #5563d4;   /* == --color-primary-dark (hover)                */
  --accent-lo:   #4a56c8;   /* pressed — one stop below dark                  */
  --accent-tint: #eef2ff;   /* == --nav-active-bg                             */
  --accent-line: #c7d0fb;
  --accent-ring: rgba(102, 126, 234, .20);
  /* LINK INK IS A SEPARATE JOB FROM BRAND FILL, and the measurement is why.
     #667eea on white is 3.66:1 — below AA's 4.5 for body text — and it is the
     ink for every id, count and chip in the app (1,606 instances at 1440).
     In a bright press hall that is the module's single largest legibility
     risk, measured by the 30 Aug audit.

     The fix keeps the instruction rather than bending it: --color-primary-dark
     is LIVE PULSE'S OWN TOKEN (theme.css:13) and it measures 5.09:1. So the
     brand hue is unchanged everywhere it is a FILL — the header gradient, the
     primary button, active states, where contrast is measured against white
     ON the fill — and link TEXT on white takes Pulse's darker stop. Nothing
     here is invented, and a screen still reads as Pulse. */
  --link:       #5563d4;   /* == --color-primary-dark · 5.09:1 on white      */
  --link-hover: #4f46e5;   /* == --nav-active-rail    · 6.29:1               */
  --accent-hover: var(--accent-hi);
  --accent-down:  var(--accent-lo);
  --brand-2:      #764ba2;                 /* == --color-secondary            */
  --gradient:     linear-gradient(135deg, #667eea 0%, #764ba2 100%);
  --gradient-primary: var(--gradient);
  /* Live Pulse's navigation ink, kept distinct from the brand hue exactly as
     theme.css:35-37 keeps it. */
  --nav-ink:      #4338ca;
  --nav-rail:     #4f46e5;
  --bad-ring:     rgba(239, 68, 68, .14);
  --odd-tint:     rgba(91, 33, 182, .09);

/* ── STATUS — six meanings, not twenty labels. Every state in this module maps
      into one; the LABEL carries the specificity, the TONE carries what it
      means. Violet is the loud fallback for an unmapped status (plus a
      console.warn in format.js), never a design choice.
      Each tone is a quadruple: fg / bg / line / dot. fg-on-bg >= 7:1. ───── */
  /* fg and bg are nordic.css's own .cd-badge-* pairs; dot is theme.css's
     --color-* for that meaning; line is the tint one step down. So a badge
     here and a badge in Pulse are the same badge. */
  --ok-fg:  #065f46;  --ok-bg:  #d1fae5;  --ok-line:  #a7f3d0;  --ok-dot:  #10b981;
  --info-fg:#1e40af;  --info-bg:#dbeafe;  --info-line:#bfdbfe;  --info-dot:#3b82f6;
  --prog-fg:#155e75;  --prog-bg:#cffafe;  --prog-line:#a5f3fc;  --prog-dot:#06b6d4;
  --warn-fg:#92400e;  --warn-bg:#fef3c7;  --warn-line:#fde68a;  --warn-dot:#f59e0b;
  --bad-fg: #991b1b;  --bad-bg: #fee2e2;  --bad-line: #fecaca;  --bad-dot: #ef4444;
  --mute-fg:#64748b;  --mute-bg:#f1f5f9;  --mute-line:#e2e8f0;  --mute-dot:#94a3b8;
  --odd-fg: #5b21b6;  --odd-bg: #ede9fe;  --odd-line: #ddd6fe;  --odd-dot: #7c3aed;
  /* The short names in use across this module since 30 Aug. */
  --ok:     var(--ok-fg);     --warn:       var(--warn-fg);   --bad:       var(--bad-fg);
  --blue:   var(--info-fg);   --blue-bg:    var(--info-bg);   --blue-line: var(--info-line);
  --grey:   var(--mute-fg);   --grey-bg:    var(--mute-bg);   --grey-line: var(--mute-line);
  --violet: var(--odd-fg);    --violet-bg:  var(--odd-bg);    --violet-line: var(--odd-line);

/* ── THE QA FIVE — the self-test's own statuses, aliased onto the tones above
      so nothing invents a literal.

      SKIP IS THE ONE THAT MATTERS. It is not a pass, not a failure, and not
      the same grey as "not applicable" prose: it is a check that could not
      look. It gets --mute plus a hatch, because on a five-pill strip read at
      arm's length, grey and green at 11px are the same pill. The hatch also
      survives greyscale, a photocopy and a dirty screen.

      INFO is blue and never green either: "17 items were blank on HP's own
      sheet" is a fact worth reading, not a passing grade. ───────────────── */
  --qa-pass-fg: var(--ok-fg);   --qa-pass-bg: var(--ok-bg);   --qa-pass-line: var(--ok-line);
  --qa-warn-fg: var(--warn-fg); --qa-warn-bg: var(--warn-bg); --qa-warn-line: var(--warn-line);
  --qa-fail-fg: var(--bad-fg);  --qa-fail-bg: var(--bad-bg);  --qa-fail-line: var(--bad-line);
  --qa-skip-fg: var(--mute-fg); --qa-skip-bg: var(--mute-bg); --qa-skip-line: var(--mute-line);
  --qa-info-fg: var(--info-fg); --qa-info-bg: var(--info-bg); --qa-info-line: var(--info-line);
  --qa-skip-hatch: repeating-linear-gradient(135deg,
                     #f1f5f9 0 5px, #e2e8f0 5px 10px);

/* ── RELEASE STATE — the delivery gate. Four states, and they are NOT the QA
      statuses: one warn makes a sheet `attention` (deliverable only with a
      typed reason), any fail makes it `blocked` (downloads disabled), and
      `released` is either of those that a named person overrode. ────────── */
  --rel-clean-fg:     var(--ok-fg);     --rel-clean-bg:     var(--ok-bg);
  --rel-attention-fg: var(--warn-fg);   --rel-attention-bg: var(--warn-bg);
  --rel-blocked-fg:   var(--bad-fg);    --rel-blocked-bg:   var(--bad-bg);
  --rel-released-fg:  var(--odd-fg);    --rel-released-bg:  var(--odd-bg);

/* ── CONFIDENCE — three bands over the score the self-test already computes
      from pocket identity, slot residuals, jig match and plate exactness.
      Amber at the pocket level is an instruction ("check this pocket on
      screen"), so it must not read as decoration. ───────────────────────── */
  --conf-high-fg: var(--ok-fg);   --conf-high-bg: var(--ok-bg);
  --conf-med-fg:  var(--warn-fg); --conf-med-bg:  var(--warn-bg);
  --conf-low-fg:  var(--bad-fg);  --conf-low-bg:  var(--bad-bg);

/* ── RUN KIND — shadow (a rehearsal build that never goes to the press)
      against press (the file that prints). Two different things an operator
      must never confuse, so they are not two shades of one hue. ─────────── */
  --run-shadow-fg: #3730a3;  --run-shadow-bg: #eef2ff;  --run-shadow-line: #c7d0fb;
  --run-press-fg:  #7c2d12;  --run-press-bg:  #ffedd5;  --run-press-line: #fed7aa;

/* ── MERGE MODE — none / fixed jig / bed dieline. The third carries a die cut
      and the second does not, which is the whole difference on the floor. ── */
  --merge-none-fg:    var(--mute-fg);  --merge-none-bg:    var(--mute-bg);
  --merge-fixed-fg:   var(--prog-fg);  --merge-fixed-bg:   var(--prog-bg);
  --merge-bedline-fg: var(--odd-fg);   --merge-bedline-bg: var(--odd-bg);

/* ── THE POCKET MAP — the one drawing in this module, a to-scale plan of a
      2.4 m press bed. Every colour is structural: an operator holds this next
      to the bed and compares, so "the pocket outline" and "the die window"
      cannot be two values of one grey. ORIGIN is drawn, not implied. ────── */
  --map-bed:      #ffffff;   /* the sheet itself                             */
  --map-bed-line: #94a3b8;   /* the sheet edge                               */
  --map-slot:     #f8fafc;   /* an imposition slot, empty                    */
  --map-slot-line:#cbd5e1;
  --map-filled:   #eef2ff;   /* a slot with an item in it                    */
  --map-filled-line: var(--accent-line);
  --map-die:      rgba(124, 45, 18, .55);  /* the die window outline         */
  --map-blank:    var(--qa-skip-hatch);    /* HP shipped this placement empty */
  --map-sel:      #fef3c7;   /* the pocket the URL anchors to                */
  --map-sel-line: var(--warn-dot);
  --map-low:      #fef3c7;   /* low identity confidence — check on screen    */
  --map-low-line: var(--warn-dot);
  /* The halo around "the one I tapped" — the selected pocket and its card
     wore two hand-typed rgba(196,120,11,…) at two alphas; one value now. */
  --map-sel-ring: rgba(196, 120, 11, .24);
  --map-origin:   var(--bad-dot);          /* the ORIGIN marker, top-left    */

/* ── TYPE — ten steps, every one with a job.

      "Big" is spent on what you DECIDE with — the page title, the one number
      in a stat cell, the savings headline — and never on the register, where
      every pixel of row height costs a row of real work.

      Base is 15px against procurement's 14 and Pulse's 13, and --t-num is 34
      against 26. That is the floor-panel decision, stated once: this module is
      read standing, at arm's length, at a press. ───────────────────────── */
  --t-micro: 12px;    /* eyebrow, kv key, badge, chip count, stat label       */
  --t-label: 13px;    /* form label, TABLE HEADER, tab label                  */
  --t-meta:  13.5px;  /* subtitle, hint, breadcrumb, activity row             */
  --t-cell:  14.5px;  /* TABLE CELL, dense body, buttons                      */
  --t-body:  15px;    /* BASE. body, inputs, links                            */
  --t-lead:  16.5px;  /* emphasised value: kv value on a detail page          */
  --t-node:  17px;    /* section / card / modal title                         */
  --t-sub:   22px;    /* tab-panel title, group banner                        */
  --t-num:   34px;    /* stat value — on a floor panel the number IS the UI   */
  --t-num-lg:44px;    /* the one headline: the wizard's press-time saving     */
  --t-title: 32px;    /* PAGE TITLE (h1). One per page.                       */

  --w-reg: 400;  --w-med: 500;  --w-semi: 600;  --w-bold: 700;  --w-black: 800;
  /* 600 is the ceiling for TEXT. 700 is for numbers and identifiers that
     decide something. 800 exists in this module and not in procurement's, for
     stat values and the savings headline only — a number read across a room.
     Hierarchy is still structure; this is the one place it is also size. */

  --lh-tight: 1.15;   /* h1 only                                             */
  --lh-cell:  1.4;
  --lh-body:  1.5;
  --lh-prose: 1.6;    /* anything read twice: a failure message, a reason     */
  --lh-cjk:   1.62;   /* app.css reads it; no CJK in this module today        */
  --tr-title: -.022em;  --tr-num: -.015em;  --tr-caps: .06em;

/* ── SPACE — 4px grid, ten steps. No 2px, no 6px, no 18px. ─────────────── */
  --s1:4px;  --s2:8px;  --s3:12px; --s4:16px; --s5:20px;
  --s6:24px; --s7:32px; --s8:40px; --s9:56px; --s10:72px;

/* ── RADII — five, named by use, so "which radius" is a question about what
      the thing IS. ─────────────────────────────────────────────────────── */
  --r-badge:5px; --r-ctl:6px; --r-md:8px; --r-surface:12px; --r-modal:16px;
  --r-pill:999px;
  --r-lg: var(--r-surface);  --r: var(--r-md);  --r-sm: var(--r-badge);

/* ── SHADOWS — four, and three of them are for things that float. A card, a
      table, a node and a stat strip get a hairline, not a shadow.

      NAME NOTE: --sh-pop / --sh-modal / --sh-stick / --sh-col are SHADOWS
      here, as in procurement and HR. shell-themes.css's --sh-<role> names are
      SHELL THEMING and none of them collides with these four. The overlap is
      in the prefix only, and it is called out here so nobody tidies one set
      into the other. ──────────────────────────────────────────────────── */
  --sh-pop:   0 4px 12px -2px rgba(16,22,34,.10), 0 2px 4px -2px rgba(16,22,34,.06);
  --sh-modal: 0 24px 60px -12px rgba(16,22,34,.22), 0 8px 20px -8px rgba(16,22,34,.10);
  --sh-stick: 0 6px 12px -10px rgba(16,22,34,.40);  /* under a sticky bar    */
  --sh-col:   6px 0 8px -8px rgba(16,22,34,.28);    /* right of a sticky col */
  --shadow-pop:   var(--sh-pop);
  --shadow-modal: var(--sh-modal);

/* ── Z — the chrome half is live Pulse's OWN ladder, value for value (header
      500 · dock 900 · scrim 999 · dropdown/sheet 1000 · search pop 1300),
      because the header markup is a file swap and a port that changed the
      stacking would be a port that changed behaviour. Everything that must
      cover the header is lifted above 1300.

      --z-sidepanel and --z-navpanel are the SAME element at two rungs: docked
      it shares the dock's storey so a top-nav dropdown lands OVER it; as the
      mobile overlay it must cover the scrim it raised. ─────────────────── */
  --z-stickycol:2;   --z-thead:3;       --z-rail:20;      --z-actionbar:30;
  --z-header:500;    --z-dock:900;      --z-sidepanel:900;
  --z-scrim:999;     --z-navpanel:1000; --z-searchpop:1300;
  --z-popover:1400;  --z-palette:1500;  --z-modal:1600;   --z-toast:1700;
  --z-tooltip:1800;

/* ── MOTION — three durations, one curve. Everything else is instant, and
      prefers-reduced-motion turns all three off (app.css, one rule). ───── */
  --ease:cubic-bezier(.2,.6,.3,1);
  --d1:110ms;   /* tint / hover */
  --d2:180ms;   /* popover      */
  --d3:240ms;   /* drawer       */

/* ── LAYOUT ───────────────────────────────────────────────────────────────
      60px is live Pulse's own --header-height, so a header lifted out of Pulse
      renders here at the height it renders there and every sticky offset
      agrees with the app this ports into.

      --tap IS A RULE, NOT A SIZE. 48px is the floor for every interactive
      target in this module, because half the sessions are a gloved finger on a
      wall-mounted panel. Because this module owns its component layer, --h-ctl
      and --h-row carry that decision straight into .cd-btn and .cd-table and
      there is no override anywhere. ─────────────────────────────────────── */
  --tap: 48px;
  --h-header:60px;    --w-side:264px;    --w-side-rail:60px;
  --h-dock:58px;      --h-dock-pad:64px;
  --w-rail:340px;     --h-actionbar:64px;
  --w-page:1600px;    --w-narrow:1180px;
  --pad-page:28px;
  --h-row:52px;       --h-row-head:40px;  --h-row-bi:64px;
  /* EVERY button height is the tap floor, including the small one. "Small"
     here means less padding and a smaller type step, never a smaller target —
     a 38px .cd-btn-sm in a table toolbar is exactly the control a gloved
     finger misses, and it is usually the one that exports or filters. The
     module's first cut tried to hold this with an override in uv.css and it
     silently lost the cascade to the density block above it; carrying the
     decision in the token instead means there is nothing to lose. */
  --h-ctl:48px;       --h-ctl-sm:48px;    --h-ctl-lg:56px;

/* ── FONTS ────────────────────────────────────────────────────────────────
      system-ui is deliberately NOT first: it resolves to different faces on
      different installs, and an identifier column that changes face by machine
      is a bug people report as "the barcode looks wrong". ──────────────── */
  --font: -apple-system, BlinkMacSystemFont, "Segoe UI Variable Text", "Segoe UI",
          Roboto, "Helvetica Neue", Arial, sans-serif;
  --mono: ui-monospace, "SF Mono", SFMono-Regular, "Cascadia Mono", "Segoe UI Mono",
          "Roboto Mono", Menlo, Consolas, "Liberation Mono", monospace;
}

/* ============================================================================
   THE PORT BLOCK
   Every token name live Pulse uses, re-pointed at the values above. Two
   payoffs: markup lifted out of a Pulse page renders in-system here, and the
   port back is literally "replace theme.css's :root with this". A Pulse token
   with no home below would be a gap that has to be justified.
   ========================================================================== */
:root {
  --color-primary:#667eea;        --color-primary-dark:#5563d4;
  --color-primary-light:#818cf8;  --color-secondary:#764ba2;
  /* Live Pulse's values, so this block can be pasted over theme.css's :root
     and the app comes out unchanged in colour — which is the point of the
     port block and was not true while the gradient resolved to `none`. */
  --color-white:#fff;
  --color-gray-50:#f8fafc;   --color-gray-100:#f1f5f9;  --color-gray-200:#e2e8f0;
  --color-gray-300:#cbd5e1;  --color-gray-400:#94a3b8;  --color-gray-500:#64748b;
  --color-gray-600:#475569;  --color-gray-700:#334155;  --color-gray-800:#1e293b;
  --color-gray-900:#0f172a;
  --gray-50:#f8fafc;   --gray-100:#f1f5f9;  --gray-200:#e2e8f0;
  --gray-300:#cbd5e1;  --gray-400:#94a3b8;  --gray-500:#64748b;
  --gray-600:#475569;  --gray-700:#334155;  --gray-800:#1e293b;  --gray-900:#0f172a;
  --nav-active-bg:var(--accent-tint); --nav-active-fg:var(--nav-ink);
  --nav-active-rail:var(--nav-rail);
  --color-success:var(--ok-dot);   --color-success-light:var(--ok-bg);
  --color-warning:var(--warn-dot); --color-warning-light:var(--warn-bg);
  --color-error:var(--bad-dot);    --color-error-light:var(--bad-bg);
  --color-info:var(--info-dot);    --color-info-light:var(--info-bg);
  --success:var(--ok-dot);         --success-light:var(--ok-bg);
  --warning:var(--warn-dot);       --warning-light:var(--warn-bg);
  --error:var(--bad-dot);          --error-light:var(--bad-bg);
  --info:var(--info-dot);          --info-light:var(--info-bg);
  --cyan:var(--prog-dot);          --cyan-light:var(--prog-bg);
  --primary:var(--accent);         --primary-dark:var(--accent-hi);
  --secondary:var(--brand-2);
  --bg-page:var(--page);  --bg-card:var(--surface);  --bg-hover:var(--row-hover);
  --text-primary:var(--ink);   --text-secondary:var(--ink-2);
  --text-muted:var(--ink-3);   --text-inverse:#fff;
  --border-light:var(--line);  --border-medium:var(--line-2);
  --font-family:var(--font);   --font-family-mono:var(--mono);
  --text-xs:var(--t-micro); --text-sm:var(--t-cell);  --text-base:var(--t-body);
  --text-lg:var(--t-node);  --text-xl:var(--t-sub);   --text-2xl:var(--t-num);
  --text-3xl:var(--t-title);
  --font-normal:400; --font-medium:500; --font-semibold:600; --font-bold:700;
  --space-1:4px;--space-2:8px;--space-3:12px;--space-4:16px;--space-5:20px;
  --space-6:24px;--space-8:32px;--space-10:40px;--space-12:56px;
  --radius-sm:var(--r-badge);   --radius-md:var(--r-ctl);
  --radius-lg:var(--r-md);      --radius-xl:var(--r-surface);
  --radius-2xl:var(--r-modal);  --radius-full:999px;
  --shadow-sm:none;  --shadow-md:var(--sh-pop);
  --shadow-lg:var(--sh-pop);  --shadow-xl:var(--sh-modal);
  --transition-fast:var(--d1); --transition-normal:var(--d2); --transition-slow:var(--d3);
  --header-height:var(--h-header);  --page-bg:var(--page);
  --sidebar-width:var(--w-side);
  --nordic-header-bg:var(--sunken);      --nordic-border:var(--line);
  --nordic-row-hover:var(--row-hover);   --nordic-text-primary:var(--ink);
  --nordic-text-secondary:var(--ink-3);  --nordic-text-muted:var(--ink-4);
  /* Pulse keeps its sorted-column indigo distinct from the brand hue, so
     the arrow stays findable on a header row that carries brand colour. */
  --nordic-sort-active:#4f46e5;
  --nordic-radius:var(--r-surface);      --nordic-radius-sm:var(--r-ctl);
  --leading-tight:var(--lh-tight); --leading-normal:var(--lh-body);
  --leading-relaxed:var(--lh-prose);
  --z-dropdown:var(--z-popover);   --z-sticky:var(--z-thead);
  --z-notification:var(--z-toast);
}
