/* ============================================================================
   DASH · Design Tokens  ·  tokens.css
   ----------------------------------------------------------------------------
   DASH = Data for School Hauora. Student-wellbeing dashboard for NZ secondary
   schools. Light + dark are both first-class. UK English throughout.

   Version : v2.3.1
   Date    : 2026-09-02
   Changelog:
     v2.3.1  Nested-theming fix (UI overhaul cR, finding C2): --e-focus is now re-declared
             in the dark block like every other ALIASED token (the v2.1 rule at the head of
             that block), so a nested [data-theme="dark"] pane recomputes the focus ring
             against ITS --colour-focus-ring instead of inheriting <html>'s already-
             substituted light ring. No new token; no <html>-level value changed.
     v2.3.0  App shell / chrome (UI overhaul cD). Added the global-chrome tokens the
             design/app-shell.html demo specifies (DESIGN.md section 5): --nav-h (the
             sticky top-nav height), --brand-mark-s / --brand-mark-l (the mark's header
             + hero sizes), --loading-bar-fill (the boot progress-mark fill = --primary;
             re-declared in the dark block so a nested dark pane recomputes it), and the
             on-nav interaction overlays --colour-nav-hover / --colour-nav-active /
             --colour-nav-border (neutral translucent-white - the nav is DARK in both
             themes, so they are theme-invariant and need no dark re-declaration). No new
             hue; no existing value changed; all additive. AA evidence for the chrome
             pairings in a11y-audit.md section 9; demo: app-shell.html.
     v2.2.0  Profile layer (P1-P3). Added --colour-scrim (light+dark) for the dimmed
             roster behind the profile sheet, and --maxw-sheet / --profile-rail-w
             layout dims. No existing value changed; all additive. AA evidence for
             the profile pairings in a11y-audit-profile.md; demos: profile-shell.html,
             profile-cards.html.
     v2.1.0  Nested theming ([data-theme] works on any subtree; aliases
             re-declared in the dark block). WCAG 2.2 AA pass: 10 contrast
             failures fixed (see a11y-audit.md section 4). Added --ncea-*-ink
             text tokens and a dark --primary-on. Generalised houses to 8 slots.
     v2.0.0  Extracted v1's token architecture into this consumable file.
   Status  : SINGLE SOURCE OF TRUTH.
             The demo pages in this folder consume this file; they are its
             DEMOS, not its source. If a value there disagrees, this wins.

   How to consume:
     <link rel="stylesheet" href="tokens.css">
     <html data-theme="light">   ... or  data-theme="dark"
   Dropping this file + a data-theme attribute onto a blank page is enough for
   every demo page in this set to render: those demos style components with the custom
   properties defined below and declare NO colours of their own.

   Rules baked into these tokens:
     - Pick by token, never invent a raw hex or px in product code.
     - Semantic-first: reach for --colour-* / role tokens before a raw --n-*.
     - Graded data (attendance, effort, status) uses the --grade ramp, NEVER the
       semantic colours - they read as alerts, not data.
     - Never rely on hue alone. The ramp is one CHANNEL; components must add a
       second (text, weight, icon, shape, position). See components-table.html.
     - Dark theme overrides VALUES only. No token is added or removed per theme.

   Token families (jump list):
     1  Neutrals            --n-0 .. --n-900
     2  Primary             --primary*
     3  Semantic roles      --success / --warning / --error / --info (+fill/border)
     4  Graded data ramp    --grade-1..6, --att-*, --eff-*, --status-*
     5  Houses (tenant)     --house-1..8 (+fill/border)   << generalised in v2
     6  NCEA grades         --ncea-{na,a,m,e} (+fill/border)
     7  Surface/text alias  --colour-*
     8  Type scale          --t-{micro,body,ui,strong,heading,title}-{size,lh,w}
     9  Fonts               --font-{heading,stack,mono}
     10 Spacing             --sp-1..10
     11 Radius              --r-*
     12 Elevation           --e-0..4, --e-focus
     13 Motion              --dur, --dur-fast, --ease
     14 Z-index             --z-*            << named in v2
     15 Content widths      --maxw-*         << named in v2
     16 Table               --row-h, --cell-px ...  << named in v2 (the table spec's needs)
     17 Target sizes        --tap-*          << named in v2
     18 App shell / chrome  --nav-h, --brand-mark-*, --loading-bar-fill, --colour-nav-*  << v2.3
   ========================================================================== */

:root {
  /* ── 1 · Neutrals ─────────────────────────────────────────────────────────
     One ramp. Replaces 40+ greys mixed from slate / gray / material / stone.
     Pick by step number; never invent. */
  --n-0:   #ffffff;   /* surface · cards, table body, modal */
  --n-50:  #f6f8fb;   /* page background */
  --n-100: #eef2f7;   /* hover row, muted surface, toolbar */
  --n-150: #e3e8f0;   /* divider hairline */
  --n-200: #d4dbe6;   /* default border, input border */
  --n-300: #b2bcca;   /* strong border, disabled foreground */
  --n-400: #677180;   /* tertiary text, placeholder · v2 AA fix: was #8390a3 (3.24:1 on surface) */
  --n-500: #5b6678;   /* secondary text */
  --n-600: #404a5b;   /* label, table header text */
  --n-700: #2a3240;   /* heading */
  --n-800: #181d27;   /* primary text */
  --n-900: #0c1018;   /* nav bar, deepest ink */

  /* ── 2 · Primary ──────────────────────────────────────────────────────────
     Teal, deliberately NOT blue. Blue is reserved for House and the NCEA Merit
     grade, so the interactive accent must sit in a different hue. */
  --primary:        #0e7c7b;   /* default · links, active, focus */
  --primary-hover:  #0a6362;   /* hover, pressed */
  --primary-fill:   #e1f3f2;   /* selected row, light chip fill */
  --primary-border: #a4d6d3;   /* chip border on primary-fill */
  --primary-on:     #ffffff;   /* foreground on primary */

  /* ── 3 · Semantic roles ───────────────────────────────────────────────────
     Banners, status badges, inline alerts. NOT for data cells. */
  --success:        #137a3a;   /* v2 AA fix: was #15803d (4.47:1 ink on success-fill) */
  --success-fill:   #e8f5ec;
  --success-border: #b8dcc4;

  --warning:        #92591b;
  --warning-fill:   #fbf1de;
  --warning-border: #f1ddae;

  --error:          #b3261e;
  --error-fill:     #fbeaea;
  --error-border:   #ecbcbc;

  --info:           #0a6362;   /* v2 AA fix: was #0e7c7b (4.37:1 ink on info-fill). Darker teal; blue still reserved */
  --info-fill:      #e1f3f2;
  --info-border:    #a4d6d3;

  /* ── 4 · Graded data ramp ─────────────────────────────────────────────────
     Single 6-step green->red ramp used for ALL graded cells.
     Attendance picks all 6. Effort picks 1/2/4/6. Status fills pick 1/3/5/6.
     Never use semantic colours for graded cells.
     COLOUR IS ONE CHANNEL ONLY - components MUST add a second (see components-table.html). */
  --grade-1: #b8e0bc;   /* best */
  --grade-2: #d4ebd0;
  --grade-3: #f7e8a8;
  --grade-4: #fad9a2;
  --grade-5: #f6bd80;
  --grade-6: #f4bcbc;   /* worst */
  --grade-ink: #2a3240; /* text on any grade cell · always n-700 in light */

  /* Named aliases preserve the existing variable contract. Do not inline the
     ramp steps in product code - bind through these. */
  --att-1: var(--grade-1); --att-2: var(--grade-2);
  --att-3: var(--grade-3); --att-4: var(--grade-4);
  --att-5: var(--grade-5); --att-6: var(--grade-6);
  --eff-1: var(--grade-1); --eff-2: var(--grade-2);
  --eff-3: var(--grade-4); --eff-4: var(--grade-6);
  --status-good:    var(--grade-1);
  --status-watch:   var(--grade-3);
  --status-concern: var(--grade-5);
  --status-poor:    var(--grade-6);

  /* ── 5 · Houses · TENANT-BINDABLE SLOTS ───────────────────────────────────
     v1 hard-coded four houses (--house-barker/iron/pisa/roy). DASH is going
     multi-tenant: schools have 4-8 houses, each with its OWN name and colour.
     So houses are now eight numbered SLOTS. A tenant binds a slot to a brand
     colour and a display name; product code only ever references the slot.

     Binding example (per-tenant stylesheet, e.g. mac.css) - MAC = Mount
     Aspiring College, mapped here from v1's original four:

        :root[data-tenant="mac"] {
          --house-1: #2f7a3f;  --house-1-name: "Barker";   ( green  )
          --house-2: #b8332b;  --house-2-name: "Iron";     ( red    )
          --house-3: #c89014;  --house-3-name: "Pisa";     ( mustard)
          --house-4: #2659c4;  --house-4-name: "Roy";      ( blue   )
          ( houses 5-8 unset -> this tenant simply has four )
        }

     A house with no colour bound falls back to the neutral defaults below, so
     a misconfigured tenant degrades gracefully rather than rendering invisibly.
     Each slot ships a -fill (tinted chip/row background) and -border companion.
     Houses are brand colour: identical in light and dark EXCEPT the -fill /
     -border companions, which re-tint per theme (overridden below).
     House pills carry the house NAME as text, so they never rely on hue alone. */
  --house-1: #2f7a3f;  --house-1-fill: #e8f5ec;  --house-1-border: #b8dcc4;  /* default: Barker green  */
  --house-2: #b8332b;  --house-2-fill: #fbeaea;  --house-2-border: #ecbcbc;  /* default: Iron red      */
  --house-3: #94690e;  --house-3-fill: #fbf1de;  --house-3-border: #f1ddae;  /* default: Pisa mustard · v2 AA: darkened from #c89014 so a white-text solid pill clears 4.5:1. Prefer the house CHIP (neutral text + dot) regardless */
  --house-4: #2659c4;  --house-4-fill: #e3ecfa;  --house-4-border: #b8c9ec;  /* default: Roy blue      */
  --house-5: #6b3fa0;  --house-5-fill: #efe9f6;  --house-5-border: #cdb8e4;  /* default: purple        */
  --house-6: #0f6e8c;  --house-6-fill: #e2f0f5;  --house-6-border: #b3d6e1;  /* default: deep cyan     */
  --house-7: #b85718;  --house-7-fill: #fbeadd;  --house-7-border: #f2c8a6;  /* default: burnt orange  */
  --house-8: #a8306e;  --house-8-fill: #fbe8f1;  --house-8-border: #ecb8d2;  /* default: magenta       */

  /* ── 6 · NCEA grades (NA / A / M / E) ──────────────────────────────────────
     Hue choices fixed by the NCEA framework. Always rendered as a LETTER badge
     so they stay distinct from house pills even where hues collide. */
  --ncea-na: #b03028;   --ncea-na-fill: #fbeaea;   --ncea-na-border: #ecbcbc;
  --ncea-a:  #2f7a3f;   --ncea-a-fill:  #e8f5ec;   --ncea-a-border:  #b8dcc4;
  --ncea-m:  #2659c4;   --ncea-m-fill:  #e3ecfa;   --ncea-m-border:  #b8c9ec;
  --ncea-e:  #c89014;   --ncea-e-fill:  #fbf1de;   --ncea-e-border:  #f1ddae;
  /* Text inks for the subdued (letter-on-fill) form. The saturated base hues
     drive SOLID badges + dots; --ncea-e base fails as text (2.51:1 on its fill)
     and a solid E badge needs DARK ink (white on gold = 2.82:1), so E text binds
     --ncea-e-ink and the solid E badge uses --grade-ink, never white. */
  --ncea-na-ink: #b03028;  --ncea-a-ink: #2f7a3f;  --ncea-m-ink: #2659c4;  --ncea-e-ink: #8a610d;

  /* ── Chart categorical palette (Chart.js series · see charts.md) ──────────
     8 distinguishable, CVD-aware series colours from the system hues. Assign in
     slot order; slot 1 is always the focus series. Graded data uses the grade
     ramp, houses use house slots; these are for categorical comparison only. */
  --cat-1: #0e7c7b;  --cat-2: #2659c4;  --cat-3: #c89014;  --cat-4: #2f7a3f;
  --cat-5: #b8332b;  --cat-6: #6b3fa0;  --cat-7: #0f6e8c;  --cat-8: #a8306e;

  /* ── 7 · Surface & text aliases (semantic) ────────────────────────────────
     Reach for these before a raw --n-*. They flip correctly per theme. */
  --colour-bg:              var(--n-50);
  --colour-surface:         var(--n-0);
  --colour-surface-muted:   var(--n-100);
  --colour-surface-hover:   var(--n-100);
  --colour-border:          var(--n-200);
  --colour-border-strong:   var(--n-300);
  --colour-divider:         var(--n-150);
  --colour-text:            var(--n-800);
  --colour-text-secondary:  var(--n-500);
  --colour-text-tertiary:   var(--n-400);
  --colour-text-on-dark:    rgba(255,255,255,0.92);
  --colour-nav:             var(--n-900);
  --colour-focus-ring:      rgba(14, 124, 123, 0.32);
  --colour-scrim:           rgba(12, 16, 24, 0.44);  /* v2.2 · dimmed roster behind the profile sheet/overlay. Non-text; carries the modal backdrop. */

  /* ── 8 · Type scale ───────────────────────────────────────────────────────
     Six roles. micro = table meta · body = prose · ui = controls ·
     strong = emphasised data · heading = section heads · title = page heads.
     Never set a raw px font-size in product code - choose a role.
     Line height is bundled; weight is a default you may override. */
  --t-micro-size:   11px; --t-micro-lh:   14px; --t-micro-w:   600;
  --t-body-size:    12px; --t-body-lh:    16px; --t-body-w:    400;
  --t-ui-size:      13px; --t-ui-lh:      18px; --t-ui-w:      500;
  --t-strong-size:  14px; --t-strong-lh:  20px; --t-strong-w:  600;
  --t-heading-size: 17px; --t-heading-lh: 24px; --t-heading-w: 600;
  --t-title-size:   22px; --t-title-lh:   28px; --t-title-w:   700;

  /* ── 9 · Font stacks ──────────────────────────────────────────────────────*/
  --font-heading: 'Plus Jakarta Sans', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
  --font-stack:   -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Helvetica Neue', sans-serif;
  --font-mono:    'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;

  /* ── 10 · Spacing (4px base) ───────────────────────────────────────────────
     Stack/section/cell gaps come from here. Never a magic number. */
  --sp-1: 4px;  --sp-2: 8px;  --sp-3: 12px; --sp-4: 16px;
  --sp-5: 20px; --sp-6: 24px; --sp-7: 32px; --sp-8: 40px;
  --sp-9: 48px; --sp-10: 64px;

  /* ── 11 · Radius ──────────────────────────────────────────────────────────*/
  --r-input:   6px;   /* inputs, buttons, small chips */
  --r-surface: 8px;   /* cards, modals, popovers, banners */
  --r-cell:    4px;   /* data-cell pills, table-cell rounding */
  --r-pill:   999px;  /* badges, pills, filter chips */
  --r-circle:  50%;   /* icon buttons, avatars, dots */

  /* ── 12 · Elevation ───────────────────────────────────────────────────────*/
  --e-0: none;
  --e-1: 0 1px 2px rgba(15, 18, 23, 0.06);     /* toolbar, slight lift */
  --e-2: 0 2px 8px rgba(15, 18, 23, 0.08);     /* cards on hover */
  --e-3: 0 6px 20px rgba(15, 18, 23, 0.10);    /* popovers, dropdowns */
  --e-4: 0 18px 40px rgba(15, 18, 23, 0.18);   /* modals */
  --e-focus: 0 0 0 3px var(--colour-focus-ring); /* the ONE focus ring */

  /* ── 13 · Motion ──────────────────────────────────────────────────────────
     Honour prefers-reduced-motion in components (see a11y-audit.md). */
  --dur-fast: 120ms;
  --dur:      180ms;
  --ease:     cubic-bezier(.2,.6,.2,1);

  /* ── 14 · Z-index scale ───────────────────────────────────────────────────
     v1 implied a stack but never named it. Layers, low to high. Never write a
     bare z-index in product code; every layer is one of these. */
  --z-base:    0;     /* in-flow content */
  --z-raised:  10;    /* hover lift, dragged row */
  --z-sticky:  20;    /* sticky table header & sticky first column */
  --z-nav:     100;   /* top nav bar */
  --z-overlay: 1000;  /* modal scrim / backdrop */
  --z-popover: 1100;  /* anchored popovers, dropdowns, column-settings */
  --z-modal:   1200;  /* modal dialog (sits above its own scrim) */
  --z-toast:   1300;  /* transient toasts - above everything */

  /* ── 15 · Content widths ──────────────────────────────────────────────────
     Laptop-first (1366-1920). Clamp reading measures and overlay widths. */
  --maxw-page:        1440px;  /* app content gutter on wide screens */
  --maxw-content:     1240px;  /* reference / doc column */
  --maxw-prose:       68ch;    /* readable paragraph measure */
  --maxw-popover:     320px;   /* anchored popover natural max */
  --maxw-modal:       480px;   /* standard modal */
  --maxw-modal-wide:  720px;   /* multi-column / table modal */
  --maxw-sheet:       820px;   /* v2.2 · profile side-sheet natural width (clamps to 92vw) */
  --profile-rail-w:   184px;   /* v2.2 · sticky in-profile section rail */

  /* ── 16 · Table ───────────────────────────────────────────────────────────
     The flagship surface. components-table.html is the full spec;
     these are the dimensions it binds to. */
  --row-h:            40px;   /* default row height (comfortable scan) */
  --row-h-compact:    32px;   /* compact density (power users, >25 rows visible) */
  --header-h:         36px;   /* sticky header row height */
  --cell-px:          12px;   /* cell horizontal padding (= --sp-3) */
  --cell-py:          6px;    /* cell vertical padding, default density */
  --cell-py-compact:  3px;    /* cell vertical padding, compact density */
  --col-sticky-w:     220px;  /* width of the sticky Student (first) column */
  --table-zebra:      transparent;             /* zebra off by default; row borders carry rhythm */
  --table-sticky-shadow: 2px 0 0 var(--colour-divider), 6px 0 8px -6px rgba(15,18,23,0.18); /* sticky 1st-col edge */
  --row-flag-fill:    #fdf3e7;  /* at-risk / flagged row tint (light) - warm, below warning */
  --row-flag-bar:     var(--warning); /* 3px left bar = the non-hue second channel for flags */
  --row-selected:     var(--primary-fill); /* selected row background */

  /* ── 17 · Target sizes (WCAG 2.2) ──────────────────────────────────────────
     --tap-min is the AAA-grade comfortable target (use for primary touch
     surfaces). --tap-min-aa is the 2.2 AA floor; controls smaller than this
     (e.g. a 20px badge) MUST have >=24px clear spacing around them. */
  --tap-min:     44px;
  --tap-min-aa:  24px;

  /* ── 18 · App shell / chrome (v2.3) ────────────────────────────────────────
     The global chrome the design/app-shell.html demo specifies (DESIGN.md s5,
     a11y-audit.md s9). The nav bar is --colour-nav (= --n-900) - the deepest ink
     in BOTH themes - and is a PERMANENTLY-DARK surface: in the product it is scoped
     data-theme="dark", so the mark on it resolves to mint (dark --primary), the
     notification dot to the bright dark --error, and text to --colour-text-on-dark,
     regardless of the page theme (this fixes the light-mode dot, which was 2.91:1 as
     the muddier light --error - see a11y-audit.md s9). Only the page BELOW the nav
     follows the user's light/dark choice. The on-nav interaction overlays below are
     neutral translucent-white and read identically on the dark bar, so they need NO
     dark re-declaration. --loading-bar-fill is the one alias here (-> --primary,
     used on the theme-FOLLOWING loading surface) and IS re-declared in the dark block
     so a nested dark pane recomputes it. No new hue. */
  --nav-h:            56px;   /* sticky top-nav height */
  --brand-mark-s:     26px;   /* mark height in the header (standard, small) */
  --brand-mark-l:     72px;   /* mark height on the loading + sign-in hero (expressive) */
  --w-menu:           18rem;  /* the account/settings drop-down width (c2; theme-invariant, no dark re-decl) */
  --loading-bar-fill: var(--primary);          /* boot progress-mark filled bar; re-declared in dark */
  --colour-nav-hover:  rgba(255,255,255,0.08);  /* hover overlay on an on-nav control (nav link, icon btn) */
  --colour-nav-active: rgba(255,255,255,0.14);  /* active / selected overlay on an on-nav control */
  --colour-nav-border: rgba(255,255,255,0.16);  /* hairline for an on-nav chip (the scope pill) */
}

/* ============================================================================
   DARK THEME · token VALUE overrides only. No token added or removed.
   Selector is the bare attribute [data-theme="dark"] (not html[...]) so the
   theme can be scoped to ANY subtree - this lets a single page show light and
   dark side by side (e.g. states-matrix.html), not just flip the whole document.
   Put data-theme="dark" on <html> for the app, or on a wrapper for a demo pane.
   ========================================================================== */
[data-theme="dark"] {
  /* 1 · Neutrals (ramp inverts; --colour-* aliases follow automatically) */
  --n-0:   #1a1f2a;
  --n-50:  #11141c;
  --n-100: #232a37;
  --n-150: #2a3240;
  --n-200: #353e4f;
  --n-300: #475064;
  --n-400: #828ea0;  /* v2 AA fix: was #6a7587 (3.54:1 tertiary on surface) */
  --n-500: #8b94a6;
  --n-600: #b1b9c8;
  --n-700: #d2d8e3;
  --n-800: #ebeef3;
  --n-900: #0a0d13;

  /* Semantic aliases MUST be re-declared here for nested theming to work.
     Custom properties substitute at their declaring element: the :root aliases
     resolved --n-* / --grade-* while those were still light, then inherit as
     flat literals. Re-declaring them (identical expressions) forces them to
     recompute against the dark raw tokens inside ANY themed subtree - not just
     when data-theme sits on the same element (<html>) as :root.
     Without this, a nested [data-theme="dark"] pane shows light surfaces. */
  --colour-bg:             var(--n-50);
  --colour-surface:        var(--n-0);
  --colour-surface-muted:  var(--n-100);
  --colour-surface-hover:  var(--n-100);
  --colour-border:         var(--n-200);
  --colour-border-strong:  var(--n-300);
  --colour-divider:        var(--n-150);
  --colour-text:           var(--n-800);
  --colour-text-secondary: var(--n-500);
  --colour-text-tertiary:  var(--n-400);
  --colour-nav:            var(--n-900);
  --colour-scrim:          rgba(0, 0, 0, 0.62);   /* v2.2 · deeper scrim on the dark surface */

  --att-1: var(--grade-1); --att-2: var(--grade-2);
  --att-3: var(--grade-3); --att-4: var(--grade-4);
  --att-5: var(--grade-5); --att-6: var(--grade-6);
  --eff-1: var(--grade-1); --eff-2: var(--grade-2);
  --eff-3: var(--grade-4); --eff-4: var(--grade-6);
  --status-good: var(--grade-1);  --status-watch: var(--grade-3);
  --status-concern: var(--grade-5); --status-poor: var(--grade-6);

  /* 2 · Primary */
  --primary:        #3ec1bf;
  --primary-hover:  #62d4d2;
  --primary-fill:   #163331;
  --primary-border: #224e4c;
  --primary-on:     #06302f;   /* v2 AA fix: dark ink on the bright dark-theme primary. v1 inherited #fff (2.19:1) */

  /* 3 · Semantic roles */
  --success:        #4ecf83;
  --success-fill:   #163022;
  --success-border: #24503a;
  --warning:        #f0b65a;
  --warning-fill:   #322516;
  --warning-border: #59421f;
  --error:          #f08079;
  --error-fill:     #361b1b;
  --error-border:   #5c2a2a;
  --info:           #3ec1bf;
  --info-fill:      #163331;
  --info-border:    #224e4c;

  /* 4 · Graded ramp · darkened so --grade-ink (now near-white) reads on top */
  --grade-1: #2f6a48;  --grade-2: #347051;  /* grade-2 darkened: v2 AA fix (was #3a7a55, 4.41:1 under --grade-ink) */
  --grade-3: #7c6628;  --grade-4: #8c5c2a;
  --grade-5: #99502a;  --grade-6: #8b3a3a;
  --grade-ink: #ebeef3;

  /* 5 · Houses · base colours are brand-fixed (unchanged); only the -fill /
        -border companions re-tint for the dark surface. */
  --house-1-fill: #16301f;  --house-1-border: #2a5238;
  --house-2-fill: #361b1b;  --house-2-border: #5c2a2a;
  --house-3-fill: #322516;  --house-3-border: #59421f;
  --house-4-fill: #182a44;  --house-4-border: #2a456e;
  --house-5-fill: #281c38;  --house-5-border: #45356a;
  --house-6-fill: #14333d;  --house-6-border: #245260;
  --house-7-fill: #341f12;  --house-7-border: #5e3c20;
  --house-8-fill: #34182a;  --house-8-border: #5e2c4a;

  /* 6 · NCEA · v2 fix. v1 left the NCEA fill/border tints at their LIGHT values,
        so subdued grade cells were unreadable in night mode (an AA failure -
        logged and diffed in a11y-audit.md). Re-tinted for the dark surface;
        the saturated base hues (used by solid letter badges with white text)
        are unchanged. */
  --ncea-na-fill: #361b1b;  --ncea-na-border: #5c2a2a;
  --ncea-a-fill:  #163022;  --ncea-a-border:  #24503a;
  --ncea-m-fill:  #182a44;  --ncea-m-border:  #2a456e;
  --ncea-e-fill:  #322516;  --ncea-e-border:  #59421f;
  --ncea-na-ink: #f0a8a2;  --ncea-a-ink: #7fd39d;  --ncea-m-ink: #8fb3ef;  --ncea-e-ink: #e7c074;
  /* chart series brightened for the dark surface; slot order/meaning unchanged */
  --cat-1: #3ec1bf;  --cat-2: #6f9bff;  --cat-3: #e0b14e;  --cat-4: #5bb877;
  --cat-5: #f08079;  --cat-6: #b08be0;  --cat-7: #4fb6d6;  --cat-8: #e487bc;

  /* 7 · Surface/text · focus ring + on-dark */
  --colour-focus-ring:   rgba(62, 193, 191, 0.40);
  --colour-text-on-dark: rgba(255,255,255,0.92);

  /* 12 · Elevation · deeper shadows on dark */
  --e-1: 0 1px 2px rgba(0,0,0,0.4);
  --e-2: 0 2px 8px rgba(0,0,0,0.45);
  --e-3: 0 6px 20px rgba(0,0,0,0.55);
  --e-4: 0 18px 40px rgba(0,0,0,0.65);
  --e-focus: 0 0 0 3px var(--colour-focus-ring); /* alias re-declared: nested panes recompute (v2.3.1) */

  /* 16 · Table · warm flag tint that works on the dark surface */
  --table-sticky-shadow: 2px 0 0 var(--colour-divider), 6px 0 8px -6px rgba(0,0,0,0.5);
  --row-flag-fill: #2c2417;

  /* 18 · App shell · re-declare ONLY the aliased chrome token so a nested
        [data-theme="dark"] pane recomputes it against the dark --primary (mint).
        The --nav-* overlays are literal translucent-white and theme-invariant
        (the nav is dark in both themes), so they are NOT re-declared here. */
  --loading-bar-fill: var(--primary);
}
