/* ==========================================================================
   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.
   ========================================================================== */

/* The plat canvas's own five colours. They are NOT theme-aware and must not be
   moved into this file's :root blocks — see the header of canvas-tokens.css for
   why a theme-flipping token cannot colour a surface that does not flip.
   @import must stay the first rule in this file. */
/* @import url('canvas-tokens.css'); — the plat canvas's own five colours; not needed outside the plotter */

/* ── 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. */
  /* STICKY NOTES — ONE PAPER TONE (UIUX B.5: "Not --status-open's amber, which is a mark that
     means *open*; this is a surface, large and pale, and it reads as paper"). No colour picker:
     colours chosen per note invite colour-as-meaning, and every hue here already has a job.
     THE EDGE IS DARKER THAN "CREAM" IMPLIES, AND THAT IS THE MEASUREMENT TALKING: the paper is
     only 1.16:1 against the page, so the BORDER is what separates a note from what is behind it,
     and a border has to clear 3:1 against its GROUND rather than against the paper it edges.
     Measured against the worse of --bg-page and --bg-card, per theme:
       light  text on paper 11.23:1   edge on ground 4.19:1
       dark   text on paper  8.01:1   edge on ground 3.96:1 */
  --note-paper:#fdf6e3; --note-edge:#80693a; --note-text:#3d3524;
  --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 — ONE STEP LIGHTER, 2026-10-01 (UIUX rail-is-navy §2). It was #7690a8, which is
     4.42:1 on the rail's navy #0d2a44 and therefore under the 4.5:1 small text needs — and the rail
     headings are small, thin and uppercase, which is the worst case for it. #7c96ae is the same
     blue-grey a step up at 4.77:1, with enough margin that antialiasing on a thin cap does not eat
     it. Measured, not chosen: this only became checkable once the navy was ruled to be the ground in
     both themes, because before that the ground was a different colour depending on who you asked. */
  --rail-label:#7c96ae;         /* +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-chrome — THE RAIL, THE TOP BAR AND THE DOCK (SH - Build, 2026-10-01; UIUX rail-is-navy §4).
     It was the one --z-* name used and never declared: `.rail` has read `var(--z-chrome, 30)` since
     the panel was built, so the LITERAL FALLBACK has been doing the work and the token it names did
     not exist. 30 is that fallback, kept exactly, so nothing changes paint order.

     IT IS ONE TOKEN, NOT A NEW FIVE-RUNG SCALE. The ladder already exists with eleven rungs, and two
     of the names a new scale would have introduced — --z-drawer and --z-modal — are already declared
     with call sites. A second ladder beside the first is how you get two answers to one question.

     THE LADDER IS SPLIT BY PAGE FAMILY, which is why only five rungs are here. tokens-auth.css
     carries all eleven for the auth pages (which load shared.css); the app loads tokens.css +
     shell.css + platform.css and needs these. A rung declared only in the other family does not
     resolve here — see tests/unit-css-vars-declared.js, which checks per page rather than globally. */
  --z-chrome:30;
  /* ── THE PLATFORM BAND, NAMED AT TODAY'S VALUES (UIUX z-ladder §1.3) ────────────────────────────
     Four literals sat between --z-toast and --z-critical. They are deliberate, so they are named and
     NOT renumbered. --z-ghost-bar is the one that carries a rule: a platform admin acting as someone
     else must never have that fact covered. SEE THE REPORT — that invariant does NOT hold today. */
  --z-platform-sheet:9400;
  --z-platform-menu:9700;
  --z-ghost-bar:9850;
  --z-platform-exit:9900;
  /* --z-base is the content band's floor. Nothing reads it yet and it is kept on purpose (§1.4) so
     the first thing that needs to stack inside a page has a rung to read instead of inventing one. */
  --z-base:1; --z-popover:150; --z-drawer:600; --z-modal:3000;
  /* --z-toast and --z-critical complete the ladder. Nothing in the APP's own sheets reads them yet —
     the auth family does, through tokens-auth.css — and they are declared here so the band order can
     be asserted end to end, and so the first app toast reads a rung instead of inventing one.
     SEE THE REPORT: the 9000-9999 range currently holds fourteen raw literals across thirteen files,
     which is why --z-toast at 9000 is the lowest thing in its own neighbourhood. */
  --z-toast:9000; --z-critical:9999;

  /* +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 PERSON PALETTE — the fill of an initial badge (Mike 2026-09-20: "an initial badge circle with color with distinct colors per
     message thread per user"). CATEGORICAL: a step says WHO, never what state something is in. Made by the ramp method above — --blue-600's
     OKLCH lightness (L .5310), blue's chroma, the hue rotated in 45° steps — so the six are one family with the rest of the palette.
     TWO BANDS ARE LEFT OUT ON PURPOSE (SH-UIUX 2026-09-21): red, 10°–45°, because red means ALERT; and blue, 240°, because blue means
     CLIENT and, by Mike's ruling the same day, a MENTION. A person's circle must not spend either. If a seventh is ever wanted, it does not
     come from those two bands.
     A FILL HAS TWO CONTRASTS, measured 2026-09-20/21: the white label on it 4.98–5.58 · against the light --bg-card #f7f8fa 4.68–5.25 ·
     against the dark --bg-card #171b21 3.10–3.47. ONE HEX SERVES BOTH THEMES because every step clears label ≥4.5 and card ≥3 on both —
     which is why this block has no twin under [data-theme="dark"]. Teal (195°) carries less chroma: sRGB cannot hold blue's there.
     USED BY js/messages-panel.js, which takes a person's step from their user id and moves a second person off a taken step, so no two
     people in one conversation share one until there are seven of them. */
  --avatar-1:#9a5a1b; --avatar-2:#766f03; --avatar-3:#337e48; --avatar-4:#047c7c; --avatar-5:#6661aa; --avatar-6:#90528b;
  /* --control-off (UIUX switch-off-state A): the boundary of a control in its OFF or unchosen state — a switch's ring and knob. The
     neutral step that clears 3:1 against BOTH --bg-card and --bg-page, per theme: light #757e8d 3.86 / 3.27, dark #646c78 3.26 / 3.54
     (measured 2026-09-21, re-measured 2026-09-22). A fill inside a track is not a boundary and needs no 3:1; only the ring and knob do.
     First used by the statement sheet's switches (js/statement-sheet.js); the four-control switch pass adopts it next. */
  --control-off:#757e8d;
  /* --unseen · THE ONE MARK THAT MEANS "addressed to you, not yet seen" (SH-UIUX new-badge ruling §1, 2026-09-22): the New pill on a
     requirement row, the unread dot on a message row, the dashboard's new-requirements count and the top-bar count. One job, one token.
     NOT GREEN — green means closed, three rows away on the same list — and magenta is the one family no status, client mark or AI pane uses.
     THE VALUE IS NOT THE RULING'S STARTING ONE, and the reason is the top bar: it is dark navy (--bg-banner #0d2a44) in the LIGHT theme too,
     where #b8177a measures 2.40 against it and fails the 3:1 the ruling requires. #cc3d93 clears every surface the mark sits on, measured
     2026-09-22: card 4.24 · page 3.60 · subtle row 3.37 · top bar 3.25, with white on it at 4.51. */
  --unseen:#cc3d93;
  --unseen-text:#ffffff;
  /* THE MESSAGE MARKS TAKE THE SAME MAGENTA (Mike, 2026-09-23: "i also need the new message and new mention icons to be magenta as well...
     or at least a brighter color that stands out more than just bolded white"). This OVERRIDES the new-badge ruling's §5.4, which held the
     marks neutral permanently; Mike's is the later word. The mark's SHAPE still tells the two apart — a dot for unread, an `@` for a mention.

     THREE TOKENS AND NOT ONE, because the same magenta cannot do all three jobs and the ratios say so. Measured 2026-09-23:
       · the DOT is a FILL (3:1). --unseen clears every content ground: card 4.24 · page 3.60 · subtle row 3.38 · sunken 3.12.
       · the `@` is TEXT (4.5:1), and --unseen FAILS as text on every light ground (4.24 / 3.60 / 3.38 / 3.12). So the light theme takes a
         darker step of the same family: #a3146c — card 6.88 · page 5.83 · subtle row 5.47 · sunken 5.05. Dark keeps --unseen (#f26fbf):
         card 6.42 · page 6.98 · subtle row 5.52.
       · the RAIL is dark navy in the LIGHT theme too, so a mark on it needs the LIGHT end of the ramp in BOTH themes — the same trap
         --unseen's own value was chosen around. #ff8ccd, measured against the rail and its active item: light 6.90 / 4.77 ·
         dark 9.10 / 7.15. --unseen would be 3.25 / 2.25 on the light rail, which is why the rail does not use it.

     DECLARED HERE AND NOWHERE ELSE. Three JS files each injected their own `:root` block for these, with different values, so which colour
     painted depended on which screen loaded last. That is not a colour decision anyone made. */
  --unread-mark:var(--unseen);
  --mention-mark:#a3146c;
  --rail-mark:#ff8ccd;
  /* --rail-mention — THE COLLAPSED RAIL'S MARK FOR "someone mentioned you" (Mike 10-01; UIUX
     collapsed-rail §1). It has its own name because the first build borrowed --ai-fg, which means
     THE AI everywhere else: retune the AI purple and mentions would have moved with it.

     DEFINED ONCE, LIKE --rail-mark, AND FOR THE SAME REASON. The rail's ground is dark in both
     themes but it is not the SAME dark — #0d2a44 light, #0b0e12 dark — and a token that flips with
     the theme cannot be measured once against it. --ai-fg flips: #c4b5fd dark, #6440a5 light, and
     #6440a5 measures 2.60:1 on #0b0e12 and 1.97:1 on #0d2a44. So the borrowed mark was BELOW the
     3:1 an icon needs for every reader on the light theme — a mention that could not be seen, in
     the one state Mike asked for it. Measured here: 10.48:1 on #0b0e12, 7.94:1 on #0d2a44.

     Checked before it settled, as UIUX required: nothing on the collapsed rail renders in --ai-fg.
     The shell's one AI purple is .role-pill[data-perm="developer"], and that is in the TOP BAR. */
  --rail-mention:#c4b5fd;
  /* --rail-badge-fg / --rail-badge-bg — THE ALERT COUNT ON A RAIL ITEM (2026-10-01). SH - Debug built
     `.rail-count.alert` to pin its fill and text across all five rail-item states and to carry a
     1.5px ring; it reads these two, and declaring them was split to me because tokens.css is mine.

     ONE PAIR IN BOTH THEMES, because the rail is navy in both (UIUX rail-is-navy §1). The values are
     today's render — #ffffff on #c0392f, 5.43:1 — so nothing changes when the fallback goes away.

     THEY ARE NOT ALIASES OF --alert-* ON PURPOSE. --alert-* is measured against the PAGE; this pair
     is measured against the rail's navy, and the two grounds are not the same. Tying them together
     would mean a future retune of the page's alert red silently moving a badge nobody re-measured —
     the same mistake as borrowing --ai-fg for the mention mark, which cost 1.97:1 on the light theme. */
  --rail-badge-fg:#ffffff;
  --rail-badge-bg:#c0392f;
  /* STATUS COLOUR FOLLOWS THE TYPE, NOT THE NAME (SH-UIUX status-colour ruling, approved by Mike 2026-09-18; restated in the new-badge
     ruling REV 2): open → amber, closed → green, archived → grey. Three tokens named for the job, so a sixth status name needs no sixth
     colour. The five --status-<name> tokens below are the old vocabulary and stay until the screens move, so nothing breaks in between.
     These are TEXT on a list row, so each clears 4.5:1 on every surface a status sits on, measured 2026-09-22 (card · page · subtle row):
     light — amber 7.17 / 6.09 / 5.71 · green 6.68 / 5.67 / 5.31 · grey 8.06 / 6.84 / 6.41;
     dark  — amber 11.99 / 13.02 / 10.30 · green 11.34 / 12.32 / 9.74 · grey 6.85 / 7.44 / 5.88. */
  --status-open:var(--warning-fg);
  --status-closed:var(--success-fg);
  /* GREY IS --text-secondary, NOT --text-muted: muted measures 4.09 on a subtle row in the dark theme, under the 4.5 text needs, and an
     archived row is exactly where a subtle ground is used. Secondary clears it at 5.88 dark and 6.41 light. */
  --status-archived:var(--text-secondary);
  /* 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 RAIL AND ALERT TOKENS ARE NOT HERE, AND THAT IS DELIBERATE (2026-10-01, UIUX rail-is-navy §1).
     They were declared here and then UN-DECLARED by a second `:root` further down the file, so these
     values never once reached a screen. The rail is now ruled to be ONE palette in both themes, like
     --rail-mark and --rail-mention, so it is declared once in the first :root and never overridden.
     Adding --bg-rail, --rail-text, --rail-text-active, --rail-label, --bg-rail-hover,
     --bg-rail-active, --alert-bg or --alert-fg here would be a change on every screen. */
  --control-off:#646c78;            /* see :root — 3.26 against the dark card, 3.54 against the dark page */
  /* the dark theme's magenta, as ruled: 6.42 against the dark card, 6.98 against the page, 5.52 against a subtle row, 7.19 against the top
     bar, with #1a1e24 on it at 6.22. In dark the fill lightens and the text darkens — white on pink here is the classic one that reads on a
     designer's screen and nowhere else. */
  --unseen:#f26fbf;
  --unseen-text:#1a1e24;
  /* The `@` is TEXT, and in the dark theme --unseen already clears 4.5:1 as text on every content ground (card 6.42 · page 6.98 ·
     subtle row 5.52), so there is no separate step here — see :root for why the LIGHT theme needs one. --rail-mark does not change
     with the theme: the rail is dark in both. */
  --mention-mark:var(--unseen);
  /* 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;
  /* --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. */
  /* --note-edge WAS #7d7a5e, which measures 2.30:1 on #45432f — under the 3:1 a non-text
     boundary needs, so the dark note had an edge nobody could quite see. Lightened to the
     FIRST passing value in the same hue (#928e6c, 3.01:1) rather than a darker one, so it stays
     an edge instead of becoming a frame. The light pair was already fine at 4.87:1 and is
     untouched. Measured, not eyeballed; the light half passing is what made this easy to
     miss. */
  --note-paper:#45432f; --note-edge:#928e6c; --note-text:#eae6d6;
  --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);
  --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.
   ========================================================================== */

/* ── WHERE THE SECOND `:root` WENT (SH - Build, 2026-10-01; UIUX rail-is-navy §1) ───────────────────
   A SECOND `:root` block stood here and re-declared eleven tokens the blocks above already set.
   `:root` and `[data-theme="dark"]` are BOTH specificity (0,1,0), so source order decided it — and
   because this one came AFTER the dark block, every token it named was pinned to its light value in
   BOTH themes. The dark overrides never applied. Eight tokens were affected:

     --bg-rail  --bg-rail-hover  --bg-rail-active  --rail-text  --rail-text-active  --rail-label
     --alert-bg  --alert-fg

   Its other three (--rail-w, --rail-w-collapsed, --topbar-h) are theme-neutral and were IDENTICAL to
   the first :root's, so they were never doing anything either.

   RESOLVED, NOT MOVED. Every value this block was pinning is the value the app renders today, so the
   duplicate is deleted and the dark block no longer declares those eight. Nothing changes on screen.
   The rail is navy in both themes BY RULING now (UIUX §1) rather than by accident.

   AND THE ALERT PAIR'S DEAD COMMENT ARGUED THE OPPOSITE OF THE MEASUREMENT. It read: "a near-black
   on a saturated red clears contrast comfortably, where white on the same red does not". Measured:
   white #ffffff on #c0392f is 5.43:1 — what renders today. Near-black #1a0a08 on #e0483a, what the
   dark block wanted, is 4.73:1. Both pass 4.5, and the one that never applied is the WORSE of the
   two. So keeping today's values is not merely the no-change option, it is the better one.

   A block that silently un-does an override above it is not visible at the point of use, so the
   check in tests/unit-token-cascade.js now fails if any token is declared in a `:root` that comes
   after `[data-theme="dark"]`.
   ══════════════════════════════════════════════════════════════════════════════════════════════════ */




/* ── COLOUR THEMES DO NOT TOUCH THE RAIL (checked 2026-10-01) ──────────────────────────────────────
   A to-do stood here asking for `--bg-rail` to be added to four [data-color-theme] blocks, "matching
   whatever that theme sets --bg-banner to". It is gone, for three reasons, all measured:

     · it CONTRADICTS the live ruling. UIUX rail-is-navy §1: the rail is ONE palette in both themes.
       A per-colour-theme rail is the opposite of that.
     · it named the wrong file. The [data-color-theme] blocks are in tokens-auth.css, not here.
     · those four blocks declare NO rail token, and nothing in the app ever sets data-color-theme —
       so there was no behaviour to preserve.

   It also carried a CSS rule body inside a comment, which is the shape that let a dead repair sit at
   402-417 looking live. tests/unit-token-cascade.js C5 now fails on any of those.
   ══════════════════════════════════════════════════════════════════════════════════════════════════ */


/* ══ 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);
}


/* --accent-wash · declared 2026-11-04 (UIUX nine tokens): the ground of an offer or a selected card. */
:root { --accent-wash: var(--blue-100); }
[data-theme="dark"] { --accent-wash: var(--blue-900); }
