/* ==========================================================================
   StrataHawk redesign — PROTOTYPE
   Vanilla HTML/CSS/JS. No framework, no build step. Matches the current stack.

   TOKENS: names and values below are taken from your existing css/tokens.css
   wherever one already existed, so this file is diffable against it. Tokens I
   ADDED are marked "+NEW" and are listed in the handoff notes.
   ========================================================================== */

/* ── permission gate — SECOND COPY, and it must stay in sync ───────────────
   The authoritative copy is inlined in every page <head>, because a linked
   stylesheet can fail to arrive and that failure would fail OPEN. This copy is
   defence in depth, not the gate.

   IT HAS ALREADY DRIFTED ONCE. When super_admin was added, the inline rules
   learned about it and these did not — so a super admin, who outranks an admin,
   saw LESS than one. A stale copy of a fail-closed rule fails closed on the
   wrong people, which reads as a missing feature rather than as a broken gate.
   Change one, change the other, in the same commit.

   Tiers: user < admin < super_admin < developer. Each rule names the lowest tier
   that may see the thing, as a not() list of everyone at or above it. */
html:not([data-perm="admin"]):not([data-perm="super_admin"]):not([data-perm="developer"]) [data-req-role="admin"] { display: none !important; }
html:not([data-perm="developer"]) [data-req-role="developer"] { display: none !important; }

:root {
  /* NATIVE CONTROLS ARE DRAWN BY THE BROWSER, NOT BY THESE TOKENS.
     Mike: "the radio buttons and other buttons are not dark themed properly."
     Without `color-scheme` the user agent paints every native widget in its LIGHT appearance
     whatever the CSS around it says -- checkbox, radio, scrollbar, select arrow, date picker,
     spin buttons. `--bg-input` cannot reach any of them, because we are not the ones painting.
     So this is one declaration, not a per-screen styling miss, and it was declared NOWHERE in
     this app until 2026-09-08. Every screen with a checkbox had it.
     The pair lives here and in [data-theme="dark"]; the app has exactly those two states and no
     system-follows state, so there is no third case to cover. */
  color-scheme: light;

  /* ── HOW THE RAMPS ARE MADE (UIUX alpha-ramp-and-personal-ai §2) — written here because this is where the next step gets added.
     Blue came first and was hand-made; its method was recovered by measuring it in OKLCH (2026-09-13). Every other family follows:
       L   — taken from the BLUE step of the same number. Lightness is what makes a step "a 500"; this is what makes families one palette.
       H   — fixed per family (red 27.9, the hue of the light --danger-fg #b02a24). Blue's own hue drifts ~239→249 toward 900; keep it.
       C   — blue's bell (peak at 500, tapering both ways) × k, where k is the LARGEST scale at which NO step leaves the sRGB gamut.
             The gamut picks k, so the bell stays whole at every step and nobody chooses a number. Red: k = 1.303, set by step 200
             (an earlier 89%-of-ceiling rule clamped 100–400 flat — five steps by the method, four by the gamut).
     300 was missing from blue (its 400 spanned a double step, ΔL 0.148); added as the OKLCH midpoint of 200 and 400. Nothing referenced
     a 300, so adding it changed nothing on screen. */
  --blue-900:#0d2a44; --blue-800:#123f61; --blue-700:#15577f; --blue-600:#1c73a6;
  --blue-500:#2a92cc; --blue-400:#63b3ed; --blue-300:#92caf1; --blue-200:#bfdff5; --blue-100:#e8f2fa;
  --red-900:#471511;  --red-800:#66221c;  --red-700:#87322a;  --red-600:#b14339;
  --red-500:#da594e;  --red-400:#fb8274;  --red-300:#ffa89c;  --red-200:#ffccc4;  --red-100:#feece9;
  /* THE DANGER FILL — a solid red with a white label. --red-600 in BOTH themes: label 5.63:1, light card 5.63:1, dark card #171b21
     3.07:1 (measured 2026-09-14). The lowest red step clearing label ≥4.5 and card ≥3 in each theme. Not --danger-fg: that is a TEXT
     colour, and in dark it is a pale red that a white label disappears on (see --alert-bg below). */
  --danger-solid:var(--red-600);
  --accent-700:var(--blue-700); --accent-500:var(--blue-500); --accent-400:var(--blue-400);
  --accent:var(--blue-700);
  /* ── -fg TOKENS ARE TEXT COLOURS, PER THEME. RAMP STEPS ARE FOR FILLS, BORDERS AND MARKS. (UIUX accent-fg §1.1)
     Written down because four colour defects in a week were this sentence missing: --focus-ring used as an outline, --status-completed
     as a chart series, --danger-fg as a fill, and an accent STEP as text (2.22:1 in dark).
     --accent-fg: the accent as TEXT — crumbs, tabs, inline actions, and the (Client) marker. Dark --blue-500 5.01:1 on #171b21.
     LIGHT WAS --blue-600 AND COULD NOT STAY (UIUX light-theme-darkened §4.1). #1c73a6 was set against a #ffffff card at 5.18; on the
     new grounds it is 4.83 card / 4.22 page / 3.55 sunken -- two failures. --blue-700 #15577f gives 7.31 / 6.21 / 5.38.
     The old reason for rejecting --blue-700 ("7.77 reads close to body text") was true against a 16:1 primary on WHITE; here it is
     7.31 against primary's 16.77, less than half, and it now carries the (Client) suffix and the chevron alongside it. */
  --accent-fg:var(--blue-700);

  /* ── A CONTRAST NUMBER IS MEANINGLESS WITHOUT THE SURFACE IT WAS TAKEN AGAINST. (UIUX border-step-and-developer-pill §4)
     Four defects in one week were this sentence missing: --danger-fg measured as TEXT and used as a FILL; --accent-fg measured on
     #ffffff and then living on #f7f8fa; --ai-rail measured on --bg-sunken and living on --bg-banner; the estate tokens measured on
     sunken and living on the card. Three of the four were found by asking WHERE DOES THIS ACTUALLY APPEAR, not by measuring harder.
     So: when you record a ratio next to a token, record the ground. A number without one goes stale silently.

     LIGHT GROUNDS DARKENED 2026-09-16 (Mike: light mode "is too bright and hard to read in several spots"). The glare was structural,
     not textual — body text was already 17.8:1. Card/page sat at 1.10 and the borders at 1.17-1.31, so nothing had an edge. */
  --bg-page:#e2e6ec; --bg-card:#f7f8fa; --bg-banner:#0d2a44; --bg-input:#ffffff;   /* white is now a FIELD, not a ground (UIUX §2.2) */
  /* THE ALERT / CONFIRM / PROMPT BOX. api.js styles it `background:var(--bg-modal,#fff)`
     and nothing defined --bg-modal, so every one of those dialogs fell back to a HARD
     WHITE card — in dark mode too. It is the same surface as a card, so it takes the same
     value; naming it separately is what api.js already asks for. */
     DARKENED WITH THE CARD 2026-09-16: this comment says it IS a card, so it takes the card's value. Left at #ffffff it would have
     become the one hard-white surface left in the theme -- the exact complaint, surviving in every dialog. */
  --bg-modal:#f7f8fa;
  --bg-subtle:#dbdfe7; --bg-hover:rgba(13,17,23,.06); --bg-sunken:#d2d7df;
  --bg-overlay:rgba(13,17,23,.55);
  --bg-rail:#0d2a44;            /* +NEW  the left rail ground              */
  --bg-rail-hover:rgba(255,255,255,.07);  /* +NEW                          */
  --bg-rail-active:rgba(255,255,255,.12); /* +NEW                          */
  --rail-text:#b8c6d4;          /* +NEW  4.9:1 on --bg-rail               */
  --rail-text-active:#ffffff;   /* +NEW                                    */
  --rail-label:#7690a8;         /* +NEW  group headings, 3.2:1, large only */

  /* SECONDARY AND MUTED MOVED WITH THE GROUNDS, AND HAD TO (UIUX §3). Darkening a ground darkens every text pair on it: muted at
     #636c77 lands 4.34 on the new page and 3.65 on the new sunken -- both fail. Moving muted alone would close the gap to secondary
     to under 0.7 and collapse the three-tier scale into two, so both move. Worst pair in the theme is now muted on sunken, 4.69.
     ALSO FIXED HERE: muted on sunken was 4.37 in the theme as it SHIPPED -- already below 4.5, and read as fine at a glance. */
  --text-primary:#14181d; --text-secondary:#454d57; --text-muted:#545c66;
  --text-on-dark:#fff; --text-on-accent:#fff;
  /* THE LOAD-BEARING CHANGE: --border 1.31 -> 1.65 against the card. Every table row, module edge, settings pane and header hairline
     is drawn by this token, which is why the screen read as one flat sheet.
     --border-subtle is 1.02 against --bg-sunken, and that is INTENDED (UIUX charts-and-pills §4): a quiet edge inside a recessed
     well has nothing to separate -- the recess is already the boundary. Not a miss; do not "fix" it. */
  --border:#bcc4d0; --border-subtle:#d4d9e1; --border-strong:#9fa9b8; --border-focus:#2a92cc;

  /* ── PILLS ARE DERIVED, NOT PICKED (UIUX charts-and-pills §3, border-step §1). Two rules, so the next ground move carries them:
       FILL   = --bg-card mixed 12% toward THE TEXT INSIDE THAT PILL. Not toward the family's most visible member: mixing the AI pill
                toward --ai-rail instead of --ai-fg puts its text at 3.45.
       BORDER = the step that gives a pill's edge THE SAME CONTRAST TO THE CARD THAT --border HAS (1.65). Against #f7f8fa that is 30%,
                landing the six at 1.60-1.65. The rule is the sentence, not the 30: if a ground moves, 30% is wrong and the sentence
                still holds. 24% was rejected -- it makes a pill's edge quieter than a card's, the opposite of the complaint.
     Hand-picked before this, the fills sat at 1.03-1.09 against the card: the card's whiteness was doing the separating and it has
     gone. They are now 1.20-1.21, and every text-on-fill is 5.08-6.04. */
  --success-bg:#dce6e5; --success-fg:#17644a; --success-border:#b4ccc5;
  --warning-bg:#e8e3dc; --warning-fg:#7a4800; --warning-border:#d1c3af;   /* fg: #8a5200 fell to 4.42 on the new sunken; #7a4800 = 5.27 */
  --danger-bg:#eedfe0;  --danger-fg:#b02a24;  --danger-border:#e2baba;
  --info-bg:#dce5ed;    --info-fg:#14578a;    --info-border:#b3c8d8;
  --neutral-bg:#e2e4e7; --neutral-fg:#4b545e; --neutral-border:#c3c7cb;

  --ai-bg:#e5e2f0; --ai-fg:#6440a5; --ai-border:#cbc1e0;
  /* --ai-rail IS NOT A RAIL. Its one use in the app is the developer role pill in the TOP BAR (--bg-banner), which is dark in both
     themes -- so every measurement of it against a light ground described a pairing that does not exist. Name kept, note added:
     renaming it is not free and the note is what stops the next person measuring the wrong thing.
     #8b5cf6 -> #7f4ef0 (2026-09-16): a white label on it was 4.23, under the 4.5 TEXT floor, and failing in both themes since it
     shipped. #7f4ef0 puts the label at 4.94 and drops the pill's own boundary against the banner to 2.97.
     THAT TRADE IS DELIBERATE, and it is --danger-solid's trade run backwards: WCAG 1.4.11 covers visual information REQUIRED TO
     IDENTIFY a component, and a component with a readable label is identified by its label. There the boundary was accepted at 3.07
     to keep the fill on the ramp; here the boundary may drop precisely BECAUSE the label became readable. One rule, both directions. */
  --ai-rail:#7f4ef0;
  /* THE ESTATE PILLS — the one place the pill rule could not be applied as written, and the reason is worth keeping.
     The rule is: a fill is the card mixed 12% toward the text inside it. These fills sat at 1.03-1.08 against the new card, the
     same disappearing act as the other six. But mixing them 12% put their TEXT at 4.16 (surface) and 4.04 (mineral) -- under the
     4.5 floor. The rule assumes text dark enough to survive the fill moving toward it, and these were not: they cleared 4.5 on the
     card by 0.38 and 0.20, and a 12% fill spends exactly that.
     Capping the mix at what the old text allowed was measured and rejected: it comes out at 5% and 2%, i.e. the fills they already
     had. THE FILL CANNOT MOVE WHILE THE TEXT STAYS, so the text moved (SH-UIUX, 2026-09-16).
     --estate-surface is deliberately NOT --blue-700 #15577f, which measures better: that hex is --accent-fg, and the accent means
     LINK and CLIENT. Surface estate means something else. One hex carrying two meanings is the --danger-fg collision, and the
     constraint here is 'distinct from the accent', which nothing between blue-600 and blue-700 satisfies -- so #176084 is OFF the
     ramp on purpose. Noted honestly: it sits 1.13 against --accent-fg, so the two are told apart by hue and context, not by
     lightness. That was never the constraint; sharing a token was.
     Measured on --bg-card: text 6.49 / 6.02 / 7.01 · fills 1.19 / 1.19 / 1.20 · text on its own fill 5.45 / 5.08 / 5.84.
     Mineral on --bg-sunken is 4.43, below the floor -- and it never lands there: every estate TEXT use is inside .tr-card,
     .ow-est-band or .estate, all of which sit on --bg-card. Recorded so the number is not rediscovered as a defect. */
  --estate-surface:#176084; --estate-surface-bg:#dce6ec;
  --estate-mineral:#8b510d; --estate-mineral-bg:#eae4de;
  --estate-both:#6440a5;    --estate-both-bg:#e5e2f0;

  --font-sans:-apple-system,BlinkMacSystemFont,'Segoe UI','Inter',Roboto,'Helvetica Neue',Arial,sans-serif;
  --font-mono:'SFMono-Regular','SF Mono',Consolas,'Liberation Mono',Menlo,monospace;

  --text-2xs:.6875rem; --text-xs:.75rem; --text-sm:.8125rem; --text-base:.875rem;
  --text-md:.9375rem; --text-lg:1.0625rem; --text-xl:1.25rem; --text-2xl:1.5rem;
  --weight-regular:400; --weight-medium:500; --weight-semibold:600; --weight-bold:700;
  --leading-tight:1.25; --leading-snug:1.4; --leading-normal:1.55;
  --tracking-wide:.06em; --tracking-tight:-.01em;

  --space-1:2px; --space-2:4px; --space-3:6px; --space-4:8px; --space-5:12px;
  --space-6:16px; --space-7:20px; --space-8:24px; --space-9:32px; --space-10:40px; --space-11:56px;
  --gutter:var(--space-8); --gutter-phone:var(--space-6);
  --row-py:var(--space-5); --row-py-dense:var(--space-4);

  --radius-xs:4px; --radius-sm:6px; --radius-btn:8px; --radius-input:8px;
  --radius-md:10px; --radius-card:12px; --radius-lg:16px; --radius-pill:999px;

  --shadow-card:0 1px 2px rgba(13,17,23,.05),0 2px 10px rgba(13,17,23,.07);
  --shadow-popover:0 4px 8px rgba(13,17,23,.07),0 12px 32px rgba(13,17,23,.15);
  --shadow-modal:0 8px 16px rgba(13,17,23,.10),0 24px 56px rgba(13,17,23,.22);

  --dur-fast:120ms; --dur-base:180ms; --dur-slow:280ms;
  --ease-out:cubic-bezier(.22,1,.36,1);
  --focus-ring:0 0 0 3px rgba(42,146,204,.35);
  --target-min:44px; --target-min-dense:32px;
  --alert-bg:#c0392f; --alert-fg:#ffffff;   /* +NEW  4.9:1 — the Today pip */

  --z-dropdown:20; --z-popover:150; --z-banner:250; --z-drawer:600; --z-modal:3000;

  /* +NEW layout constants — the three places that must agree */
  --rail-w:236px; --rail-w-collapsed:60px; --topbar-h:52px;
  /* STATUS PALETTE — named for the statuses, ALIASING the tones the Requirements board already uses
     (user-requirements.js STATUS_TONE), so no colour is defined a second time. SH-UIUX's throughput spec §10:
     "--status-planning and --status-completed must be real tokens ... hex literals would be the third place
     those colours are defined." Dark mode follows through the aliases, which resolve on this element.
     NOTE: the board shows Completed in the MUTED tone, not production's green — so this token is grey. */
  --status-planning:var(--info-fg); --status-in-progress:var(--success-fg); --status-on-hold:var(--warning-fg);
  --status-completed:var(--text-muted); --status-cancelled:var(--danger-fg);
  /* CHART SERIES that are NOT statuses (SH-UIUX, UIUX-to-SH-BUILD2-dashboard-layout-correction.md §3): the throughput
     chart's Open is every open status aggregated and Completed every closed one, so they must not borrow the status
     palette. Chosen for legibility and VALIDATED with the dataviz palette validator. #38a169 was rejected: 2.95:1. One value for
     both themes, as validated.
     VALIDATED AGAINST --bg-card #f7f8fa (light) and #171b21 (dark). MOVING EITHER INVALIDATES THIS — and that is the whole reason
     this comment names them: it previously recorded "validated" without recording against WHAT, so when the light grounds moved on
     2026-09-16 nobody could tell whether the change mattered. Re-run: the palette passed every check unchanged, because the lightness
     band, the chroma floor and the CVD separation do not depend on the surface at all. Hand-lifting the two values by eye would have
     destroyed all three to fix a ratio that was not broken.
     On --bg-page/--bg-subtle/--bg-sunken the validator WARNs (sub-3:1) and requires RELIEF. The relief these charts carry is: axis
     labels, the crosshair readout, and Export to Excel. NOT a table view -- Mike retired the Chart | Table switch on 2026-09-11 and
     Export replaced it. If you remove one of those three, this comment is what tells you what you took with it. */
  --chart-level:var(--accent-500); --chart-completed:#2f9e5b;
  /* THE PRIMARY BUTTON — ONE DEFINITION for `.rw-btn.primary` (index.html) and `.pc-btn-primary` (shell.css), so "primary" means
     one thing and the two can't drift (UIUX grid-label-and-button-colors §2.1). A fill has TWO contrasts — its label (≥4.5:1) and the
     card behind it (≥3:1) — and the card changes with the theme, so the fill does too (UIUX fill-has-two-contrasts). Light: --accent-700
     (label 7.77, card 7.77). Dark: --blue-600, set in [data-theme="dark"] — the ONLY blue step clearing both there (label 5.18, card 3.33).
     No hover token: the hover is an inset edge, not a fill change (UIUX hover-and-red-ramp §2), because dark has no second step. */
  --btn-primary-bg:var(--accent-700); --btn-primary-fg:var(--text-on-accent);
  /* THE HOVER EDGE, PER THEME (UIUX alpha-ramp-and-personal-ai §1): one mechanism — a white inset line — with the alpha set so it LOOKS
     the same on each theme's fill. White .28 over --accent-700 is 1.98:1 against the fill; dark's lighter fill needs .37 for the same
     1.98:1 (at .28 it was 1.69). Change both together, never one — that is how they came apart. */
  --btn-hover-edge:rgba(255,255,255,.28);
}

[data-theme="dark"] {
  /* The primary FILL per theme — see --btn-primary-bg in :root. --blue-600 is the only blue step clearing a white label ≥4.5:1 AND
     the dark card #171b21 ≥3:1 (5.18 / 3.33, measured 2026-09-13). Lighter loses the label; darker loses the card. */
  --btn-primary-bg:var(--blue-600);
  --btn-hover-edge:rgba(255,255,255,.37);   /* 1.98:1 against --blue-600, matching light — see :root */
  --accent-fg:var(--blue-500);              /* accent as TEXT: 5.01:1 on the dark card #171b21 — see :root */
  /* The other half of the pair declared on :root. Without this, native controls stay light while
     everything we paint ourselves goes dark -- which is the exact symptom that was reported. */
  color-scheme: dark;

  --alert-bg:#e0483a; --alert-fg:#1a0a08;
  /* --bg-input WAS #0f1216, IDENTICAL TO --bg-page, so in dark theme every field sitting on the
     page background was invisible except for its 1px border. Mike, on the document details panel:
     "any field that is an entry is the same background color as the panel itself and it makes it
     hard to identify things". Light theme never had this — there --bg-input (#f8f9fb) is LIGHTER
     than --bg-page (#f3f4f6), so the separation existed and only the dark value collided.
     The requirements panel looked right for a different reason: its fields sit inside .rw-card
     (--bg-card #171b21), so they contrasted against the CARD rather than against the page. Fixing
     the token rather than wrapping that one panel in a card fixes every screen with the same
     shape, and leaves card-wrapping as a layout choice instead of a contrast workaround.
     #0b0e12 is not a new colour: it is --bg-sunken, already used by --bg-rail, so a recessed
     input now reads at the same depth as the rail rather than introducing a hex of its own. */
  --bg-page:#0f1216; --bg-card:#171b21; --bg-banner:#080b0f; --bg-input:#0b0e12;
  --bg-modal:#171b21;
  --bg-subtle:#212832; --bg-hover:rgba(255,255,255,.05); --bg-sunken:#0b0e12;
  --bg-overlay:rgba(0,0,0,.72);
  --bg-rail:#0b0e12; --bg-rail-hover:rgba(255,255,255,.06);
  --bg-rail-active:rgba(255,255,255,.10);
  --rail-text:#9aa4b1; --rail-text-active:#e6e9ed; --rail-label:#6b7580;

  --text-primary:#e6e9ed; --text-secondary:#9aa4b1; --text-muted:#7e8794;
  --border:#2b323b; --border-subtle:#232932; --border-strong:#3a424d;

  --success-bg:#0f2a20; --success-fg:#6ee7b7; --success-border:#1d4a38;
  --warning-bg:#2b1f0c; --warning-fg:#fcd34d; --warning-border:#4a3a14;
  --danger-bg:#2c1315;  --danger-fg:#fca5a5;  --danger-border:#4d2124;
  --info-bg:#0e2233;    --info-fg:#7cc4f0;    --info-border:#1c3f5c;
  --neutral-bg:#212832; --neutral-fg:#9aa4b1; --neutral-border:#2b323b;

  --ai-bg:#1c1533; --ai-fg:#c4b5fd; --ai-border:#332a52;
  --estate-surface:#63b3ed; --estate-surface-bg:#0e2233;
  --estate-mineral:#e0a33e; --estate-mineral-bg:#2b1f0c;
  --estate-both:#a78bfa;    --estate-both-bg:#1c1533;

  --shadow-card:0 1px 2px rgba(0,0,0,.4),0 2px 10px rgba(0,0,0,.3);
  --shadow-popover:0 4px 8px rgba(0,0,0,.4),0 12px 32px rgba(0,0,0,.5);
  --shadow-modal:0 8px 16px rgba(0,0,0,.5),0 24px 56px rgba(0,0,0,.6);
}
/* ==========================================================================
   StrataHawk — tokens.css ADDITIONS
   Step 1 of the rewiring order. Append this to the end of css/tokens.css.

   Everything below is NEW. I checked each name against the existing file — no
   token here collides with one already defined, and nothing already in
   tokens.css is redefined or overridden. Appending is safe; nothing changes
   appearance until a rule references one of these.

   Eleven tokens in four groups. The left rail needs six, the alert pip needs
   two, and the shell layout needs three.

   WHY THE RAIL NEEDS ITS OWN SURFACE TOKENS
   The rail is a dark surface in BOTH themes — it is chrome, not content, and
   the existing --bg-card / --bg-page pair inverts between themes by design.
   Reusing them would give a navy rail in dark mode and a white one in light,
   which is not the intent. The banner already has this treatment (--bg-banner);
   the rail is the same kind of surface and gets the same kind of token.
   ========================================================================== */

:root {

  /* ── LEFT RAIL ───────────────────────────────────────────────────────────
     Chrome, not content. Dark in both themes, like --bg-banner above.
     Contrast measured against --bg-rail:
       --rail-text          4.9:1   nav items at rest        (AA body)
       --rail-text-active  13.1:1   the item you are on
       --rail-label         3.2:1   group headings — uppercase 11px semibold,
                                    which is large-text by WCAG, so 3:1 applies */
  --bg-rail:        #0d2a44;
  --bg-rail-hover:  rgba(255,255,255,0.07);
  --bg-rail-active: rgba(255,255,255,0.12);
  --rail-text:        #b8c6d4;
  --rail-text-active: #ffffff;
  --rail-label:       #7690a8;

  /* ── ALERT PIP ───────────────────────────────────────────────────────────
     A filled count badge — the "3" on Today, an overdue marker on a row.

     This exists because --danger-fg was being used as a FILL with white text on
     it. In light mode --danger-fg is #b02a24 and that reads fine, so the bug is
     invisible until you switch themes: in dark mode --danger-fg is #fca5a5, a
     pale red, and white-on-pale-red is roughly 1.6:1 — the number disappears.

     The -fg tokens are foregrounds. Anything that needs a red FILL uses this
     pair instead. Worth grepping for `background:var(--danger-fg)` and
     `background:var(--warning-fg)` elsewhere; the same mistake is easy to make. */
  --alert-bg: #c0392f;   /* 4.9:1 with --alert-fg */
  --alert-fg: #ffffff;

  /* ── SHELL LAYOUT ────────────────────────────────────────────────────────
     Three constants the grid, the rail and the sticky table header all have to
     agree on. --topbar-h in particular is read in two places: the shell grid
     row, and the `top:` offset of any element that sticks below the bar. */
  --rail-w:           236px;
  --rail-w-collapsed: 60px;
  --topbar-h:         52px;
}


/* ══ DARK THEME OVERRIDES ══════════════════════════════════════════════════
   Append these three lines INSIDE the existing [data-theme="dark"] block
   rather than as a new block, so they sit with the rest of the dark palette.
   ══════════════════════════════════════════════════════════════════════════

[data-theme="dark"] {
  --bg-rail:    #0b0e12;
  --rail-text:  #9aa4b1;   --rail-text-active: #e6e9ed;   --rail-label: #6b7580;
  --alert-bg:   #e0483a;   --alert-fg: #1a0a08;
}

   Note --alert-fg goes DARK on dark. A near-black on a saturated red clears
   contrast comfortably, where white on the same red does not — the inverse of
   the light-mode pairing, and the reason these are a pair rather than a single
   background token with an assumed white foreground.
   ═══════════════════════════════════════════════════════════════════════════ */


/* ══ COLOUR THEMES ═════════════════════════════════════════════════════════
   The existing file carries [data-color-theme="slate"] and ="forest" blocks in
   both light and dark. --bg-rail is the only addition that should follow the
   colour theme, since it is a brand surface like the banner. Add one line to
   each of the four blocks, matching whatever that theme sets --bg-banner to:

     [data-color-theme="slate"]  { --bg-rail: <its --bg-banner>; }
     [data-color-theme="forest"] { --bg-rail: <its --bg-banner>; }
     …and the same inside each theme's dark variant.

   The other ten tokens are theme-neutral and need no per-theme value.
   ═══════════════════════════════════════════════════════════════════════════ */


/* ══ WHAT IS NOT HERE, DELIBERATELY ════════════════════════════════════════

   The prototype and records-table.js also use --blue-*, --accent-*, --bg-*,
   --text-*, --border*, --success/warning/danger/info/neutral-*, --ai-*,
   --estate-*, --font-*, --text-2xs…2xl, --weight-*, --leading-*, --tracking-*,
   --space-1…11, --gutter, --gutter-phone, --row-py, --row-py-dense,
   --radius-*, --shadow-*, --dur-*, --ease-*, --focus-ring, --target-min,
   --target-min-dense and the --z-* ladder.

   Every one of those already exists in tokens.css with the values the new work
   assumes. Nothing needed changing, which is the useful finding: the design
   system was already sufficient. The redesign is not blocked on tokens — it is
   blocked on the 842 inline style attributes in index.html and the ~380
   innerHTML template strings in the user-*.js modules, which is where the
   system stops being applied.
   ═══════════════════════════════════════════════════════════════════════════ */

/* ── ALIASES FOR SIX NAMES THE THEME NEVER DEFINED (SH - Debug, 2026-09-16; SH - Build approved) ─────────────────────────────────────
 * --warn-bg, --warn-bd, --warn-fg, --surface-2, --line and --fg-muted are used — with hardcoded LIGHT fallbacks — in
 *   js/user-tract-record.js · js/opinion-tract-import.js · js/match-audit.js · js/requirements-byuser.js · js/tract-picker.js · css/shell.css
 * Undefined, every one of them rendered its light fallback in dark mode (Mike, prod recording mu4ogaml-wvjldo: the tract removal-request
 * banner "is not themed properly (dark)"). They are defined here ONLY as var() references to the real tokens, never as colors: each alias
 * resolves wherever it is used, so it follows the light and dark values above and cannot drift from them. Not dead code — remove an alias
 * only after renaming its call sites. */
:root {
  --warn-bg: var(--warning-bg);
  --warn-bd: var(--warning-border);
  --warn-fg: var(--warning-fg);
  --surface-2: var(--bg-subtle);
  --line: var(--border);
  --fg-muted: var(--text-muted);
}
