/* ============================================================================
   theme.css — Thoughtz design-token layer (CHANGE-036 · repalette CHANGE-015)
   ----------------------------------------------------------------------------
   thoz copy. App and TMS carry a file of the same name with an IDENTICAL token
   vocabulary AND (since CHANGE-015) identical values, kept in sync BY HAND.
   ⚠️ THE THREE FILES ARE NOW FULLY IDENTICAL. The one per-app value that ever
   differed (--thz-caption) lived only inside the [data-theme='classic'] revert,
   deleted 2026-08-21, so nothing app-specific is left here — but they are still
   kept in sync BY HAND, so a change here still has to be made three times.

   TIER 1 — PRIMITIVES: the raw palette. Nothing outside this file may
   reference a primitive; they exist only to feed the semantic tier.
   TIER 2 — SEMANTIC: what stylesheets, inline styles and page JS reference.

   THE PALETTE IS STYLE_GUIDE.md (repo root), and since 2026-08-13 THE ACCENT
   IS ORANGE #f97316. Rule of thumb: orange carries action, ink carries
   structure, everything else stays quiet.

   ⚠️ THE ACCENT TAKES WHITE LETTERING AT 2.80:1, WHICH FAILS EVERY WCAG
   THRESHOLD. That is Glen's explicit, reaffirmed decision, not an oversight —
   see --thz-text-on-action in TIER 2 for the five rules that manage it and for
   the one-line way to see the alternative.

   ⚠️ THE SEMANTIC TIER IS WHERE THE ACCENT ENTERS, NOT THE PRIMITIVE TIER.
   --thz-accent-300 feeds --thz-bg-muted-brand (the primary button fill, which
   must be the accent) AND --thz-text-muted-brand (body text in the App's survey
   screens, which must stay dark). Retinting the primitive would make
   .optionSelected and .answerContainer a:hover accent-on-white. The two roles
   are therefore pointed at different primitives in TIER 2 below.

   NO PALETTE REVERTS (Glen, 2026-08-21). [data-theme='yellow'] and
   [data-theme='classic'] were deleted along with views/partials/themePreview.ejs:
   orange is the only palette and there is no plan to go back. Both blocks were
   pure value overrides adding no token NAMES, so nothing here depends on them.
   They are in git history if a comparison is ever wanted again.

   NOT tokens on purpose: shadows (neutral black/white alphas), the one-off
   decorative literals catalogued in CHANGE-036-token-map.md bucket C, the
   self-contained maintenance/PCScanLink pages, and Bootstrap's own palette
   (Phase 3 maps --bs-* onto these tokens; #0d6efd stays literal until then).
   ========================================================================= */

:root {
	/* ------------------------------------------------------------------ */
	/* TIER 1 — PRIMITIVES                                                */
	/* ------------------------------------------------------------------ */

	/* Structure — ink (STYLE_GUIDE.md §1). These keep the `accent` names so
	   the vocabulary stays diff-able across apps; they are no longer blue. */
	--thz-accent-700: #1a1a1a; /* ink — sidebar, banners, headings, chrome */
	--thz-accent-600: #33302a; /* lifted ink — hover on dark surfaces */
	--thz-accent-400: #5a5647; /* warm mid — carries white text AND reads on bone */
	--thz-accent-300: #4a4738; /* body ink — emphasis text (NOT the button fill) */
	--thz-accent-050: #ede9e1; /* pale warm wash (+ all flat SVG fills) */

	/* Ink alpha variants (seeded literals — a theme must restate them) */
	--thz-accent-700-a25: #1a1a1a40;
	--thz-accent-700-a47: #1a1a1a78;
	--thz-accent-700-a48: #1a1a1a7a;
	--thz-accent-700-a69: #1a1a1ab0;
	--thz-accent-700-a80: #1a1a1acc;
	--thz-accent-700-a95: #1a1a1af2;
	--thz-accent-700-a96: #1a1a1af5;
	--thz-accent-600-a30: #33302a4d;
	--thz-accent-300-a30: #4a47384d;

	/* Brand surfaces — NEUTRAL GREY (Glen, 2026-08-21). These are the panels,
	   rails, banners and bars: the SURFACE half of what used to be one ink family.
	   ⚠️ THEY ARE A SEPARATE FAMILY FROM --thz-accent-* ON PURPOSE. Before this,
	   --thz-bg-brand / --thz-border-brand / --thz-text-brand all resolved to
	   accent-700 ink, so a brand surface and brand LETTERING were the same colour.
	   The surfaces moved to grey; the lettering did NOT — --thz-text-brand still
	   reads accent-700, because #5d5d5d as body/heading ink is 6.59:1 against ink's
	   11.33:1, and nothing asked for that. Keep the two families apart.
	   ⚠️ --thz-border-brand-* MOVED WITH THE FILLS, not with the lettering, and
	   that is load-bearing: the tooltip arrow (tms.css) and the pagination pill are
	   built to match their own fill edge-for-edge, so an ink border on a grey panel
	   renders as a defect rather than a choice. */
	--thz-brand-700: #5d5d5d; /* dark silver — the brand surface. White on it is 6.59:1 */
	--thz-brand-600: #767676; /* lifted — hover on a brand surface */
	--thz-brand-400: #809091; /* slate — the light brand surface.
	   ⚠️ IT TAKES WHITE LETTERING AT 3.32:1, WHICH IS BELOW AA, AND THAT IS GLEN'S
	   EXPLICIT CALL (2026-08-21, Q-046): "I know it is light but it looks way better". He
	   made it looking at the rendered Survey step strip, then generalised it to every
	   brand-400 surface. DO NOT RELITIGATE IT — it is the same class of decision as
	   white-on-accent above, and it is recorded in STYLE_GUIDE.md §8.
	   The numbers, so the trade stays visible: white 3.32:1, ink 5.23:1, and the
	   olive this replaced (accent-400 #5a5647) carried white at 7.35:1.
	   ⚠️ READ --thz-text-on-brand-light, NEVER --thz-text-inverse DIRECTLY. That is what
	   makes this one decision instead of eight, and it is where the size floor lives. */

	/* Brand alpha variants (seeded literals, same discipline as the ink alphas) */
	--thz-brand-700-a25: #5d5d5d40;
	--thz-brand-700-a48: #5d5d5d7a;
	--thz-brand-700-a69: #5d5d5db0;
	--thz-brand-700-a80: #5d5d5dcc;
	--thz-brand-700-a96: #5d5d5df5;
	--thz-brand-600-a30: #7676764d;

	/* Warm sand — the OPTION colour. ⚠️ PORTED FROM THE APP COPY ON 2026-09-07, WHERE IT HAD LIVED
	   ALONE SINCE CHANGE-066 — that copy said "App ONLY: deliberately NOT in TMS's or thoz's theme.css
	   (Glen, 2026-08-25: 'Leave TMS alone for now')" and "a knowing divergence until TMS's turn comes".
	   Its turn came: Glen asked for TMS's demographics strip to match the App's.
	   ⚠️ THIS IS HALF OF A RULE AND THE OTHER HALF IS THE ACCENT. Glen, 2026-08-25:
	   "that is the new style for our options buttons, but action buttons are still orange."
	   OPTIONS — things you pick between: tabs, filters, menus of destinations — are sand.
	   ACTIONS — Continue, Finish, Done, Next, Install, Enquire — stay --thz-bg-action.
	   Deciding which a control is comes BEFORE deciding what it looks like; the class name
	   will not tell you, because .btn-thoughtz-action carries both.
	   Chosen from a mocked-up account page, third value tried that day (stone #e6e1d8,
	   then blush #f7dfd2), and the only one whose lettering clears AA.
	   ⚠️ THE OTHER TWO theme.css FILES ARE OTHERWISE TOKEN-IDENTICAL AND HAND-SYNCED, so
	   this is a knowing divergence until TMS's turn comes. When it does, note this is NOT a
	   drop-in for --thz-brand-700 — that one is DARK (#5d5d5d, white on it at 6.58:1) and
	   sand is LIGHT (white on it is 1.29:1, invisible rather than merely poor), so any
	   surface moved across has to change its lettering in the same edit or it goes blank.
	   ⚠️ SAME LIGHTNESS AS --thz-well #e4dfd5, THE DISABLED FILL — 1.03:1 between them.
	   They separate by HUE alone, so anything that has to read as disabled next to an
	   option needs more than a fill change. (The stone trial was worse: 3/255 apart.) */
	--thz-sand: #efe1c8; /* the fill is 1.29:1 on a white page — a shape, not an edge */
	--thz-sand-alt: #e5d8c0; /* hover/pressed. DARKER, not lifted — the brand pair's
	   direction (700 → 600 lightens) REVERSES on a light surface. */
	--thz-sand-ink: #7a4a15; /* the lettering that goes on it. 7.45:1 on white, so it is
	   safe if the fill is ever dropped and the button falls back to the page. */
	--thz-sand-edge: #ddcba9; /* the 1px edge, Glen's value. DECORATIVE ONLY — 1.23:1 against
	   the fill it outlines and 1.59:1 against the white page, the same class as --thz-line at
	   1.32:1. It shapes the button; it must never be the only thing carrying a meaning. */

	/* Action — ORANGE (STYLE_GUIDE.md §1, §2.1). Takes WHITE lettering, which is
	   Glen's explicit call and fails its measurement — see --thz-text-on-action
	   at the foot of TIER 2. The RAMP is the point: a 600 step for a surface
	   carrying a sentence, a 700 step for small lettering and for the definition
	   border. An accent supplied as a single hex cannot build this system. */
	--thz-action: #f97316; /* accent-500 — the primary fill */
	--thz-action-600: #e2620f; /* accent-600 — hover, and any fill carrying a sentence */
	/* accent-700 — the 1px definition border under an action fill (STYLE_GUIDE.md
	   §3 rule 4, where the border is the SAFETY mechanism: it is what makes a
	   2.80:1 fill read as a shape at all) and the pressed fill.
	   ⚠️ IT IS A SEPARATE PRIMITIVE FROM --thz-action-600 and has to stay one:
	   the definition border must be darker than the hover fill, so the two
	   cannot collapse onto each other. (Under the yellow and classic reverts
	   they did collapse, since neither of those accents needed the steps
	   apart; both reverts were deleted on 2026-08-21.) */
	--thz-action-700: #c2540b;
	--thz-action-050: #fde5d3; /* accent-100 — chip fill and wash */

	/* Neutrals (STYLE_GUIDE.md §2) */
	--thz-white: #ffffff;
	--thz-white-a08: #ffffff14;
	--thz-black: #000000;
	/* ⚠️ The neutrals were RE-HUED with the repalette and it was not cosmetic.
	   The old ramp sat at hue ≈50°, tuned to stop a YELLOW accent going acidic.
	   Beside orange (hue ≈25°) that yellow cast reads faintly green. This ramp is
	   ≈41°: still warm, no longer yellow. (Measured 50.1° → 40.8°; the source
	   spec's "55° → 38°" is one of the seven figures corrected in §8.)
	   ⚠️ --thz-ink and --thz-caption are BUG FIXES, not re-tints. The old caption
	   #8a8672 is 3.66:1 on white and 3.23:1 on BONE — below AA body on both,
	   while carrying Survey titles and card captions. #6e6a61 is 5.39 / 4.82. */
	--thz-ink: #3d3a34; /* text-body — 10.14:1 on bone, 11.33:1 on white */
	--thz-ink-a12: #3d3a341f;
	--thz-ink-a69: #3d3a34b0;
	--thz-paper: #f5f2ec; /* bone — page background / text-on-ink */
	--thz-paper-a89: #f5f2ece3;
	--thz-line: #d9d4c7; /* rule — dividers, hairline borders. 1.32:1, DECORATIVE ONLY */
	--thz-well: #e4dfd5; /* tab-inactive — inset/well background, disabled fill */
	--thz-stripe: #f2efe8; /* alternating row background */
	--thz-silver: #8a867b; /* placeholder/disabled lettering ONLY — 3.64:1, never content */
	--thz-silver-a15: #8a867b26;
	--thz-silver-a38: #8a867b61;
	--thz-gray: #7d7a6a;
	--thz-gray-600: #6f6c5c; /* = nav-dim; was Bootstrap secondary */
	--thz-caption: #6e6a61; /* text-muted — 5.39:1 on white, 4.82:1 on bone */
	/* ⚠️ A SEPARATE PRIMITIVE FROM --thz-silver DESPITE THE SIMILAR LIGHTNESS.
	   Silver is placeholder/disabled lettering ONLY; this one carries CONTENT — the
	   results-page metric labels (RESPONSES / TOP ANSWER / SHARE). 3.64:1 on white,
	   below AA, and that is Glen's deliberate call (2026-08-14) on the designer's
	   results layout. Do not "fix" it to --thz-caption. */
	--thz-caption-light: #888681;
	--thz-faint: #bab6a3;
	--thz-fainter: #aca995;
	--thz-slate: #5a5647;
	--thz-muted-cool: #9a9683;
	--thz-header-dark: #2b2925; /* ink-raised — menus, tooltips, fields on ink */
	--thz-header-dark-a58: #2b292594;
	--thz-scrim: #1a1a1ab0; /* modal/overlay scrim */

	/* Links (STYLE_GUIDE.md §2). ⚠️ accent-700, NEVER accent-500: #f97316 on
	   white is 2.80:1, which is fill-only. Accent lettering on a light ground is
	   always the 700 step. */
	--thz-link: #c2540b; /* 4.60:1 on white — AA body */
	--thz-link-hover: #8a3a00; /* 7.82:1 */

	/* Sidebar rest states (STYLE_GUIDE.md §2) */
	--thz-nav-idle: #b8b3a6; /* icons/labels at rest, on ink. 8.32:1 */
	--thz-nav-dim: #6f6c5c; /* de-emphasised item (e.g. Log out) */

	/* Status (STYLE_GUIDE.md §2.3). These MOVED with the repalette.
	   ⚠️ WARNING HAD TO MOVE AND THE REASON IS ABSOLUTE: the old #ff6600
	   measures 1.05:1 against the accent #f97316 — it is the SAME COLOUR, so a
	   warning would have been indistinguishable from a primary action. The
	   replacement is the retired brand yellow #ffd60a, 1.99:1 against the accent
	   — still not a contrast pair, but it separates on LIGHTNESS (L 0.694 vs
	   0.325), which is the axis the eye uses when two hues are adjacent.
	   §2.3 therefore requires warning be a PALE panel with a triangle icon, never
	   a saturated full-width fill, and ink text when solid (12.33:1).
	   --thz-danger also moves: the old #e24f4f was 3.84:1 on white, below AA. */
	--thz-danger: #b3261e; /* 6.54:1 with white */
	--thz-success: #0f7a4f; /* 5.36:1 with white */
	--thz-warning: #ffd60a; /* 12.33:1 with ink */
	--thz-red: #ff0000;
	--thz-green: #008000;
	--thz-success-vivid: #01b601;
	--thz-toast-danger: #b02a2a;
	--thz-toast-warning: #8a5a00;
	--thz-toast-caution: #a34700;
	--thz-chip-success-bg: #e2f3e8;
	--thz-chip-success-text: #22693f;
	--thz-chip-warning-bg: #fdf1dd;
	--thz-chip-warning-text: #8a5a10;

	/* ── Charts (CHANGE-070, 2026-08-26) ────────────────────────────────────
	   ⚠️ THE THREE SERIES SLOTS ARE A VALIDATED SET, NOT THREE COLOURS THAT LOOK
	   NICE TOGETHER, AND THE ORDER IS PART OF THE SET. Measured on the white chart
	   surface: worst pair ΔE 8.1 under deuteranopia (≥8 target), 16.2 unsimulated
	   (≥15 floor), every slot ≥3:1 on white and ≥4:1 under white lettering. Changing
	   one value re-opens all of that — re-run the check, do not eyeball it.
	   ⚠️ They replace the three INERT values that sat here from CHANGE-036 phase 1
	   (#1a1a1a / #8a8672 / #c2540b). Nothing read them, verified before the swap;
	   #8a8672 in particular is the hex theme.css already labels a FAILED caption
	   colour, and a 3.66:1 grey does no identity work in a chart either.
	   ⚠️ ONLY GENDER USES THESE. A single-series chart takes --thz-chart-measure for
	   every bar — colouring bars by their own value spends the identity channel
	   re-encoding what bar length already shows, and it is what grew the 49-colour
	   list these replace. There is deliberately no slot 4: past three series the
	   answer is a different chart, never a fourth hue. */
	--thz-chart-1: #1f6f9c; /* series 1 — Male.   5.51:1 under white lettering */
	/* ⚠️ DARK PINK SINCE 2026-09-07 (Glen: "make female a dark pink"), AND ITS OLD NOTE IS NOW WRONG
	   IN BOTH HALVES — it read "4.60:1. Same hex as --thz-action-700". It is 7.50:1 under white and it
	   is no longer the accent-700 hex, which is the point: this series and the App chrome were the same
	   colour.
	   ⚠️ CHOSEN BY SEARCH, NOT BY EYE, BECAUSE PINK IS THE HARD CASE FOR DEUTERANOPIA — it loses its red
	   channel and drifts toward the blue the Male series already occupies. Five hand-picked pinks were
	   scored first and EVERY ONE made the palette worse than it is today (worst pair 8.6 to 14.5 against
	   the current 15.1); this is the best of a search over the pink band that also holds white lettering.
	   Worst-case separation from Male, Other AND the single-series teal: 16.4 under deuteranopia, which
	   is better than the palette had before it. */
	--thz-chart-2: #972a5c; /* series 2 — Female. 7.50:1 under white lettering */
	--thz-chart-3: #2f8f5b; /* series 3 — Other.  4.04:1 */

	/* The one hue every SINGLE-series chart uses. TEAL SINCE 2026-09-07 (Glen: "Everything is too
	   orange. Is there a better color we can use for the charts?"). 4.94:1 on white, comfortably over
	   the 3:1 floor for a mark — the orange it replaces was 3.51:1, only just over.
	   ⚠️ THE POINT WAS SEPARATING DATA FROM CHROME, and the measurable part of that is the collision
	   with --thz-chart-2 (Female), which is #c2540b — the same hex as --thz-action-700. The old measure
	   hue sat dE00 7.8 from it unsimulated and 7.9 under deuteranopia, i.e. the single-series bars and
	   the Female series were nearly the same colour on a page that shows both. Teal is 46.0 / 43.8.
	   ⚠️ ITS TIGHTEST PAIR IS NOW --thz-chart-1 (Male, #1f6f9c): dE00 14.7 unsimulated, 8.8 under
	   deuteranopia. That is the number to watch if either value ever moves — but it is still a large
	   improvement on the 7.8/7.9 it replaces, and the two never share a chart, only a page.
	   ⚠️⚠️ THE THREE-SERIES FIGURES RECORDED ABOVE (16.2 / 8.1) COULD NOT BE REPRODUCED, and that is
	   written down rather than papered over. Neither dE76 (61.1 / 40.7) nor dE00 (37.3 / 15.1) gives
	   those numbers from the three hexes in this file, so whatever metric set them is not one of the two
	   standard ones and "re-run the check" could not be honoured literally. Every dE figure in THIS note
	   is dE00, and every comparison in it is like-for-like — the new hue and the one it replaces, scored
	   the same way. The three series are UNCHANGED, so whatever validation they passed still holds. */
	--thz-chart-measure: #0f7c86;

	/* The respondent's OWN bar, in every single-series chart (Glen, 2026-09-07: "make the bar the user
	   chose a lighter version of the teal like this example so it is easy to see which one they chose").
	   ⚠️ IT IS ON --thz-chart-measure's OWN HUE LINE, raised in lightness — a lighter version of the teal
	   rather than a cyan that happens to look near it, so the two stay related if the measure hue moves.
	   ⚠️ 1.91:1 ON WHITE, WHICH IS BELOW THE 3:1 FLOOR A MARK NORMALLY NEEDS, AND THAT IS SOUND HERE
	   ONLY BECAUSE IT IS NEVER THE ONLY CHANNEL. The chosen bar also carries a 2px ring in the MEASURE
	   TEAL (it was ink until Glen asked for the teal on 2026-09-07 — 4.94:1 against the page, so the ring
	   still gives the fill a shape), a bold label and the YOU tag, and its value is printed in ink beside
	   it, never on the fill — the same
	   "never the only identity channel" rule this file states for the legend. Take the outline away and
	   this colour stops being defensible. */
	--thz-chart-mine: #5ecbd7;

	/* The age-group heat grid: ONE hue, light→dark, monotone lightness. A rainbow or
	   a two-hue ramp would state a polarity the data does not have.
	   ⚠️ --thz-heat-1 is 1.21:1 on white ON PURPOSE — near-zero is meant to recede
	   toward the surface. Every cell prints its own number and the tiles are separated
	   by a 2px surface gap, so the value never depends on telling two tints apart.
	   ⚠️ THE LETTERING FLIPS AT STEP 6, NOT BY EYE: steps 1-5 carry ink (≥4.9:1 against
	   --thz-accent-700), step 6 carries white (5.27:1 on the orange ramp, 5.23:1 on the
	   teal one that replaced it). Between those two inks there is a luminance band where
	   NEITHER clears 4.5:1, and these steps are chosen to miss it. Insert a step and that
	   stops being true. */
	/* ⚠️ THE RAMP FOLLOWED THE MEASURE HUE TO TEAL (2026-09-07). It has to: the grid and the bars are
	   the same data on two screens, and a teal bar chart above an orange heat grid says they are
	   different measurements. Step 0 is deliberately NOT on the hue — it is the no-data neutral.
	   ⚠️ EVERY STEP WAS SOLVED FOR, NOT PICKED. Held to the three rules this block already stated:
	   step 1 at 1.21:1 on white (it recedes), steps 1-5 carrying ink at >=4.9:1 (measured 14.43, 12.06,
	   9.75, 7.38, 5.07) and step 6 carrying white (5.23:1, against the orange ramp's recorded 5.27).
	   Monotone lightness verified, and every step checked to sit OUTSIDE the dead band the note below
	   describes — for #1a1a1a ink that band opens at Y >= 0.2245 and for white it closes at Y <= 0.1833,
	   so nothing may land between. Step 5 was first solved at 4.88:1 by aiming at the 4.9 floor exactly
	   and is targeted 5.05 now, so the constraint is met by the colour rather than by rounding. */
	--thz-heat-0: #f2efe8; /* no responses in this cell at all — not a value of zero */
	--thz-heat-1: #d8eef0;
	--thz-heat-2: #b6dee3;
	--thz-heat-3: #90ccd3;
	--thz-heat-4: #65b5be;
	--thz-heat-5: #3997a1;
	--thz-heat-6: #027882;

	/* Rounded-progress dial */
	--thz-progress-track: #e4dfd5;
	--thz-rating-low: #bb3602;
	--thz-rating-mid: #f1b641;
	--thz-rating-good: #9fe7c6;

	/* Menu surface (App + thoz) */
	--thz-menu-bg: #1a1a1a;
	--thz-menu-accent: #f97316;

	/* TMS auth-screen accents (defined everywhere for vocabulary parity) */
	--thz-auth-teal: #1a1a1a;
	--thz-auth-blue: #3a382e;
	--thz-teal: #1a1a1a;
	--thz-teal-a62: #1a1a1a9e;
	--thz-teal-deep-a62: #0000009e;

	/* TMS results/AI panel tints (must follow a re-theme) */
	--thz-tint-panel: #ede9e1; /* panel-header */
	--thz-tint-panel-alt: #f5f2ec;
	--thz-tint-border: #d9d4c7;
	--thz-tint-chip: #fde5d3; /* accent-100 — the chip fill */
	--thz-accent-deep: #7a3d13; /* accent-ink — chip text, 6.92:1 on the chip fill */

	/* ------------------------------------------------------------------ */
	/* TIER 2 — SEMANTIC (the only names the rest of the codebase uses)   */
	/* ------------------------------------------------------------------ */

	/* Text */
	--thz-text-body: var(--thz-ink);
	--thz-text-body-a69: var(--thz-ink-a69);
	--thz-text-strong: var(--thz-black);
	--thz-text-inverse: var(--thz-white);
	--thz-text-on-brand: var(--thz-paper);
	/* ⚠️ LETTERING ON --thz-bg-brand-light, AND THE WHOLE POINT IS THAT IT IS ONE
	   TOKEN. White on #809091 is 3.32:1, below AA — Glen's explicit call (Q-046), see
	   --thz-brand-400 in TIER 1. Every brand-light surface reads THIS rather than
	   --thz-text-inverse, so the decision can be seen, measured and reversed in one
	   place; before this it was scattered across eight rules in three stylesheets.
	   ⚠️ THERE IS NO SIZE OR WEIGHT FLOOR MANAGING THIS, unlike §3's five rules for
	   white-on-accent. Glen approved the strip as it renders, so nothing was resized.
	   The live exposure, for whoever needs it later: TMS's .StepSubHeading is 0.7rem
	   at font-weight 100 — the thinnest, smallest lettering carrying the 3.32:1. If it
	   ever needs rescuing, LIFT THAT WEIGHT rather than repainting the surface.
	   Flip this to var(--thz-text-body) (5.23:1) to see the alternative. */
	--thz-text-on-brand-light: var(--thz-white);
	/* ⚠️ WHITE ON THE ACCENT IS GLEN'S EXPLICIT CALL (2026-08-13) AND IT IS THE
	   ONE THING IN THIS FILE THAT FAILS ITS MEASUREMENT. White on #f97316 is
	   2.80:1 — below even the 3:1 large-text floor; ink on the same fill is 6.21.
	   Recorded, not hidden. It is managed by STYLE_GUIDE.md §3's FIVE RULES, not
	   by argument: sentences move to accent-600, small lettering to accent-700,
	   16px/600 minimum on an accent fill, a 1px accent-700 definition border on
	   light grounds, and filled icons at ≥18px. DROP ANY ONE AND UNREADABLE TEXT
	   REACHES THE SCREEN. Flip this to var(--thz-black) to see the alternative. */
	--thz-text-on-action: var(--thz-white);
	/* Accent lettering on ink — ALLOWED since the repalette, bounded. Orange on
	   ink is 6.21:1, so the yellow-era outright ban is lifted: --thz-text-accent
	   is preferred at #ffb27a (9.86:1), raw accent is permitted at ≥14px/600, and
	   it is NEVER body copy and NEVER on a light ground. */
	--thz-text-action: var(--thz-action);
	/* Lettering on an option control — Glen's own pairing, #7a4a15 on #efe1c8.
	   ✅ 5.78:1 ON THE SAND FILL and 7.45:1 on the quiet (white) one, so it clears AA
	   (4.5:1) in BOTH states. That matters: an option strip shows both at once, and the
	   selected/unselected pair must not be a readable button next to an unreadable one.
	   The buttons are 0.875rem/600 = 14px semibold — NOT "large text" under WCAG, which
	   needs 18.66px bold — so 4.5:1 is the bar, not the 3:1 large-text floor.
	   The two rejected fills, so the choice stays visible: stone #e6e1d8 with ink 8.70:1,
	   blush #f7dfd2 with --thz-action-700 3.60:1 (below AA).
	   ⚠️ DO NOT "TIDY" THIS TO --thz-text-action-deep. That token is #c2540b and measures
	   3.56:1 on sand — it looks like the same brown-orange and is a two-point drop. */
	--thz-text-option: var(--thz-sand-ink);
	/* Accent lettering on a LIGHT ground. ⚠️ --thz-text-action is accent-500, which
	   is 2.80:1 on white and is a FILL colour — STYLE_GUIDE.md §3 forbids it as
	   lettering outright. This is the 700 step, 4.60:1, AA body, and it is the only
	   legal way to write a word in orange on a light surface. Added 2026-08-14 when
	   the App's bottom nav went orange and there was no semantic token for it: the
	   only existing name resolving to #c2540b was --thz-text-link, and borrowing
	   that would make a future link-colour change silently restyle the nav.
	   Added to all three apps to keep the identical-vocabulary contract; inert
	   in TMS and thoz today. */
	--thz-text-action-deep: var(--thz-action-700);
	--thz-text-link: var(--thz-link);
	--thz-text-link-hover: var(--thz-link-hover);
	--thz-text-nav-idle: var(--thz-nav-idle);
	--thz-text-nav-dim: var(--thz-nav-dim);
	--thz-text-brand: var(--thz-accent-700);
	--thz-text-brand-soft: var(--thz-accent-700-a69);
	--thz-text-brand-a47: var(--thz-accent-700-a47);
	--thz-text-brand-a95: var(--thz-accent-700-a95);
	--thz-text-brand-alt: var(--thz-accent-600);
	--thz-text-brand-light: var(--thz-accent-400);
	/* ⚠️ THE BRAND SURFACE GREY, BORROWED AS LETTERING. Everything else in this
	   --thz-text-brand-* family is accent-700 INK, deliberately: brand SURFACES went
	   grey on 2026-08-21 and brand LETTERING did not (see --thz-brand-700 in TIER 1).
	   This token is the one sanctioned exception, for marks that are meant to READ as
	   the brand fill rather than as type — Glen asked for the "Link copied" channel
	   glyphs in --thz-bg-brand on 2026-08-21.
	   ⚠️ IT EXISTS SO NOTHING POINTS `color` AT A `--thz-bg-*` TOKEN. The value is the
	   same primitive; the tier is what keeps a background token from quietly becoming a
	   text token. #5d5d5d on white is 6.59:1, so it is AA body at any size. */
	--thz-text-brand-surface: var(--thz-brand-700);
	--thz-text-muted-brand: var(--thz-accent-300); /* TEXT — must stay dark, see header */
	--thz-text-muted-brand-a30: var(--thz-accent-300-a30);
	--thz-text-placeholder: var(--thz-silver);
	--thz-text-hint: var(--thz-gray);
	--thz-text-secondary: var(--thz-gray-600);
	--thz-text-caption: var(--thz-caption);
	--thz-text-caption-light: var(--thz-caption-light);
	--thz-text-slate: var(--thz-slate);
	--thz-text-muted-cool: var(--thz-muted-cool);
	--thz-text-faint: var(--thz-faint);
	--thz-text-fainter: var(--thz-fainter);
	--thz-text-header-dark: var(--thz-header-dark);
	--thz-text-danger: var(--thz-danger);
	--thz-text-success: var(--thz-success);
	--thz-text-warning: var(--thz-warning);
	--thz-text-error: var(--thz-red);
	--thz-text-positive: var(--thz-green);
	--thz-text-valid: var(--thz-success-vivid);
	--thz-text-chip-success: var(--thz-chip-success-text);
	--thz-text-chip-warning: var(--thz-chip-warning-text);
	--thz-text-chip-info: var(--thz-accent-deep);
	--thz-text-menu-accent: var(--thz-menu-accent);
	--thz-text-auth-accent: var(--thz-auth-teal);

	/* Backgrounds */
	--thz-bg-page: var(--thz-paper);
	--thz-bg-page-a89: var(--thz-paper-a89);
	--thz-bg-surface: var(--thz-white);
	--thz-bg-surface-a08: var(--thz-white-a08);
	--thz-bg-contrast: var(--thz-black);
	--thz-bg-brand: var(--thz-brand-700);
	--thz-bg-brand-soft: var(--thz-brand-700-a69);
	--thz-bg-brand-a48: var(--thz-brand-700-a48);
	--thz-bg-brand-a80: var(--thz-brand-700-a80);
	--thz-bg-brand-a96: var(--thz-brand-700-a96);
	--thz-bg-brand-alt: var(--thz-brand-600);
	--thz-bg-brand-alt-a30: var(--thz-brand-600-a30);
	--thz-bg-brand-light: var(--thz-brand-400);
	/* The option control — see --thz-sand for which controls qualify. */
	--thz-bg-option: var(--thz-sand); /* SELECTED / standalone option */
	--thz-bg-option-alt: var(--thz-sand-alt); /* hover & pressed */
	/* ⚠️ UNSELECTED IS THE PAGE'S OWN SURFACE, NOT A THIRD TINT. The edge and the lettering
	   are what say "option"; the fill is what says "chosen". Give unselected its own colour
	   and the strip stops having a selected state and starts having two colours. */
	--thz-bg-option-quiet: var(--thz-white);
	--thz-bg-muted-brand: var(--thz-action); /* FILL — the primary button, see header */
	--thz-bg-action: var(--thz-action);
	--thz-bg-action-alt: var(--thz-action-600); /* HOVER fill on an action surface */
	/* PRESSED fill (:active). ⚠️ Named `pressed`, not `active`, on purpose: the
	   source spec called it --thz-bg-action-active, but --thz-bg-nav-active
	   already exists (tms.css) and means the SELECTED SIDEBAR ITEM. Two tokens a
	   line apart reading "action-active" and "nav-active" for a pressed button
	   and a selected nav item is a trap. STYLE_GUIDE.md §8 records the deviation. */
	--thz-bg-action-pressed: var(--thz-action-700);
	--thz-bg-action-wash: var(--thz-action-050);
	--thz-bg-wash: var(--thz-accent-050);
	--thz-bg-well: var(--thz-well);
	--thz-bg-stripe: var(--thz-stripe);
	--thz-bg-muted-a15: var(--thz-silver-a15);
	--thz-bg-muted: var(--thz-silver);
	--thz-bg-header-dark: var(--thz-header-dark);
	--thz-bg-header-dark-a58: var(--thz-header-dark-a58);
	--thz-bg-overlay: var(--thz-scrim);
	--thz-bg-danger: var(--thz-danger);
	--thz-bg-success: var(--thz-success);
	--thz-bg-warning: var(--thz-warning);
	--thz-bg-positive: var(--thz-green);
	--thz-bg-toast-danger: var(--thz-toast-danger);
	--thz-bg-toast-warning: var(--thz-toast-warning);
	--thz-bg-toast-caution: var(--thz-toast-caution);
	--thz-bg-chip-success: var(--thz-chip-success-bg);
	--thz-bg-chip-warning: var(--thz-chip-warning-bg);
	--thz-bg-chip-info: var(--thz-tint-chip);
	--thz-bg-accent-deep: var(--thz-accent-deep);
	--thz-bg-panel: var(--thz-tint-panel);
	--thz-bg-panel-alt: var(--thz-tint-panel-alt);
	--thz-bg-menu: var(--thz-menu-bg);
	--thz-bg-teal: var(--thz-teal);
	--thz-bg-teal-a62: var(--thz-teal-a62);
	--thz-bg-teal-deep-a62: var(--thz-teal-deep-a62);
	--thz-bg-progress-track: var(--thz-progress-track);

	/* Charts — the ONLY names the chart module may read (CHANGE-070). It resolves
	   them with getComputedStyle, so a retheme is this block and nothing else. */
	--thz-chart-series-1: var(--thz-chart-1);
	--thz-chart-series-2: var(--thz-chart-2);
	--thz-chart-series-3: var(--thz-chart-3);
	--thz-chart-fill: var(--thz-chart-measure);
	--thz-chart-fill-mine: var(--thz-chart-mine);
	--thz-chart-track: var(--thz-well);
	/* ⚠️ STATUS COLOURS ON A YES/NO SPLIT, WHICH IS A PRODUCT DECISION AND NOT A
	   NEUTRAL ONE — see Q-068. Yes/True/Agree is green and No/False/Disagree is red,
	   which is what the App has always done; it reads correctly on "did you find what
	   you wanted" and states a verdict on "do you support X". Kept because it is the
	   existing behaviour, flagged because it is reversible in these two lines. */
	--thz-chart-positive: var(--thz-success);
	--thz-chart-negative: var(--thz-danger);
	--thz-chart-heat-empty: var(--thz-heat-0);
	--thz-chart-heat-1: var(--thz-heat-1);
	--thz-chart-heat-2: var(--thz-heat-2);
	--thz-chart-heat-3: var(--thz-heat-3);
	--thz-chart-heat-4: var(--thz-heat-4);
	--thz-chart-heat-5: var(--thz-heat-5);
	--thz-chart-heat-6: var(--thz-heat-6);
	--thz-text-on-chart-fill: var(--thz-white);
	--thz-text-on-chart-tint: var(--thz-accent-700);
	--thz-auth-gradient-1: var(--thz-auth-teal);
	--thz-auth-gradient-2: var(--thz-auth-blue);

	/* Borders */
	/* ⚠️ THE BRAND GREYS, NOT THE INK — these are the matching edge of a
	   --thz-bg-brand-* surface, so they track the fill. --thz-text-brand-* does
	   NOT; see the brand primitives in TIER 1. */
	--thz-border-brand: var(--thz-brand-700);
	--thz-border-brand-soft: var(--thz-brand-700-a69);
	--thz-border-brand-a25: var(--thz-brand-700-a25);
	--thz-border-brand-alt: var(--thz-brand-600);
	--thz-border-brand-light: var(--thz-brand-400);
	--thz-border-option: var(--thz-sand-edge); /* BOTH option states, see --thz-sand */
	/* The 1px DEFINITION BORDER under an action fill on a light ground —
	   STYLE_GUIDE.md §3 rule 4. Points at the 700, not the 600: this border is
	   what gives a low-contrast fill an edge, so it takes the darker step. */
	--thz-border-muted-brand: var(--thz-action-700);
	--thz-border-action: var(--thz-action); /* §5 list-card spine — #ffd60a, not the button edge */
	--thz-border-wash: var(--thz-accent-050);
	--thz-border-subtle: var(--thz-line);
	--thz-border-muted: var(--thz-silver);
	--thz-border-muted-a38: var(--thz-silver-a38);
	--thz-border-inverse: var(--thz-white);
	--thz-border-body: var(--thz-ink);
	--thz-border-body-a12: var(--thz-ink-a12);
	--thz-border-danger: var(--thz-danger);
	--thz-border-success: var(--thz-success);
	--thz-border-positive: var(--thz-green);
	--thz-border-panel: var(--thz-tint-border);
	--thz-border-track: var(--thz-progress-track);
}


/* =========================================================================
   BOOTSTRAP RETINT — CHANGE-036 phase 3
   -------------------------------------------------------------------------
   Bootstrap ships its own palette and, until this block, 141 elements across
   the three apps rendered in it: Bootstrap blue #0d6efd, its red #dc3545, a
   cyan that exists nowhere in this product. That is the "half-applied" look
   phase 3 exists to remove.

   ⚠️ THREE MECHANISMS, AND ONLY ONE IS REACHED BY SETTING --bs-<name>.
   Measured against bootstrap.css 5.3.8, not assumed:
     1. .text-* / .bg-* / .border-*  read  rgba(var(--bs-<name>-RGB), <opacity>)
        so they need the CHANNEL TRIPLET. Setting --bs-danger alone does
        NOTHING for them.
     2. .alert-* / .list-group-item-* read the 5.3 "subtle" triple —
        -text-emphasis / -bg-subtle / -border-subtle — which is independent of
        both --bs-<name> and --bs-<name>-rgb.
     3. .btn-* and .table-* are COMPILED TO LITERAL HEX. There is no variable
        to override; they need a rule per variant. Those live in tms.css and
        auth.css, NOT here — see below.

   ⚠️ WHY `:root:root` AND NOT `:root`. Bootstrap defines its palette at
   `:root, [data-bs-theme=light]`, i.e. (0,1,0) — and theme.css is linked
   FIRST, before Bootstrap, in all 19 head-carrying files. A plain `:root`
   block here would TIE Bootstrap and lose on source order, silently, with
   nothing in the console. `:root:root` is (0,2,0) and wins regardless of
   order, which is what lets the retint live in the file the palette already
   lives in — including the three own-head views that load no stylesheet
   AFTER Bootstrap at all (App updatedPIN + emailActivationInstructions,
   TMS PCScanLink). It is not !important, so phase 2's Custom Branding trap
   is untouched.

   ⚠️ THE -rgb LINES ARE LITERALS AND MUST TRACK THE TOKEN BESIDE THEM.
   CSS cannot decompose a hex into channels, so each pair is written together
   deliberately. `scripts/verifyBootstrapRetint.js` recomputes every triplet
   from the token it claims to mirror and fails on drift — that check is the
   safeguard, not this comment.

   ⚠️ THE SUBTLE TRIPLES ARE DERIVED, NOT INVENTED. They apply Bootstrap's own
   transform to Thoughtz's bases — bg-subtle = tint 80% white, border-subtle =
   tint 60% white, text-emphasis = shade 60% black — which is verified in the
   harness by reproducing stock Bootstrap's #dc3545 -> #f8d7da / #f1aeb5 /
   #58151c exactly. Every derived text-on-subtle pair measures 6.0:1 or better.

   ⚠️ --bs-body-bg IS DELIBERATELY NOT SET. No stylesheet in any of the three
   apps sets `body { background }`, so Bootstrap's #fff is the page ground
   everywhere; repointing it at bone would retint every page in the platform,
   which is a design decision and not a retint. --bs-body-COLOR *is* set, and
   is the one line here with platform-wide reach: Bootstrap's #212529 was
   acting as the default text colour for everything our CSS does not colour.
   ========================================================================= */
:root:root {
	/* --- the eight families -------------------------------------------- */
	--bs-primary: var(--thz-action); /* #f97316 */
	--bs-primary-rgb: 249, 115, 22;
	--bs-primary-bg-subtle: #fee3d0;
	--bs-primary-border-subtle: #fdc7a2;
	--bs-primary-text-emphasis: #642e09;

	/* theme.css already calls --thz-gray-600 "was Bootstrap secondary" */
	--bs-secondary: var(--thz-gray-600); /* #6f6c5c */
	--bs-secondary-rgb: 111, 108, 92;
	--bs-secondary-bg-subtle: #e2e2de;
	--bs-secondary-border-subtle: #c5c4be;
	--bs-secondary-text-emphasis: #2c2b25;

	--bs-success: var(--thz-success); /* #0f7a4f */
	--bs-success-rgb: 15, 122, 79;
	--bs-success-bg-subtle: #cfe4dc;
	--bs-success-border-subtle: #9fcab9;
	--bs-success-text-emphasis: #063120;

	--bs-danger: var(--thz-danger); /* #b3261e */
	--bs-danger-rgb: 179, 38, 30;
	--bs-danger-bg-subtle: #f0d4d2;
	--bs-danger-border-subtle: #e1a8a5;
	--bs-danger-text-emphasis: #480f0c;

	--bs-warning: var(--thz-warning); /* #ffd60a */
	--bs-warning-rgb: 255, 214, 10;
	--bs-warning-bg-subtle: #fff7ce;
	--bs-warning-border-subtle: #ffef9d;
	--bs-warning-text-emphasis: #665604;

	/* ⚠️ INFO TAKES accent-700, NOT accent-500, and the reason is the same one
	   the bottom nav and the TMS sidebar already settled: at accent-500 white
	   would be 2.80:1 and accent lettering on white is 2.80:1, both of which §3
	   forbids for text — while the two oranges are 1.64:1 apart, i.e. visibly
	   the same colour. Bootstrap has no cyan equivalent in this palette; the
	   three live .text-info sites are 5rem decorative card glyphs. */
	--bs-info: var(--thz-action-700); /* #c2540b */
	--bs-info-rgb: 194, 84, 11;
	--bs-info-bg-subtle: #f3ddce;
	--bs-info-border-subtle: #e7bb9d;
	--bs-info-text-emphasis: #4e2204;

	/* .text-light is used as near-white lettering ON DARK, so light stays a
	   near-white — bone, not a mid grey. */
	--bs-light: var(--thz-paper); /* #f5f2ec */
	--bs-light-rgb: 245, 242, 236;
	--bs-light-bg-subtle: #fdfcfb;
	--bs-light-border-subtle: #fbfaf7;
	--bs-light-text-emphasis: #62615e;

	/* dark = the CHROME ink (#1a1a1a), not body ink (#3d3a34) — .text-dark is
	   used on headers and modal furniture, which is chrome. */
	--bs-dark: var(--thz-accent-700); /* #1a1a1a */
	--bs-dark-rgb: 26, 26, 26;
	--bs-dark-bg-subtle: #d1d1d1;
	--bs-dark-border-subtle: #a3a3a3;
	--bs-dark-text-emphasis: #0a0a0a;

	/* --- body and the two singletons ----------------------------------- */
	/* ⚠️ .text-body reads --bs-body-color-RGB, not --bs-body-color. */
	--bs-body-color: var(--thz-text-body); /* #3d3a34 */
	--bs-body-color-rgb: 61, 58, 52;
	/* ⚠️ .text-muted reads --bs-secondary-COLOR — not --bs-secondary-rgb — so
	   retinting the secondary family alone leaves every .text-muted behind. */
	--bs-secondary-color: var(--thz-text-caption); /* #6e6a61 */
	--bs-secondary-color-rgb: 110, 106, 97;

	/* --- ⚠️ A FOURTH SET OF LITERALS, FOUND ONLY BY ENUMERATING BOOTSTRAP'S :root ---
	   These are colour LITERALS in Bootstrap's own :root, not `var(--bs-danger)` and
	   friends — so retinting the eight families above does not reach any of them.
	   Every one was checked to reach a real surface in these apps before being added;
	   Bootstrap's raw scale (--bs-blue, --bs-indigo, --bs-gray-500 …) is deliberately
	   left alone because nothing here renders it. */

	/* ⚠️ TMS STYLES NO `a` COLOUR AT ALL, so until this line every plain link in the
	   portal was Bootstrap blue. App sets its own a:link rule and was unaffected.
	   ⚠️ Bootstrap's `a` reads --bs-link-color-RGB, not --bs-link-color. */
	--bs-link-color: var(--thz-text-link); /* #c2540b, 4.60:1 on white */
	--bs-link-color-rgb: 194, 84, 11;
	--bs-link-hover-color: var(--thz-text-link-hover); /* #8a3a00, 7.82:1 */
	--bs-link-hover-color-rgb: 138, 58, 0;

	/* Form validation. ⚠️ --bs-form-invalid-color is a LITERAL #dc3545 in Bootstrap,
	   NOT var(--bs-danger) — so the danger retint above misses it entirely, and the
	   message under a rejected field would have stayed Bootstrap red while everything
	   else went Thoughtz red. 10 views use is-invalid / was-validated. */
	--bs-form-valid-color: var(--thz-success);
	--bs-form-valid-border-color: var(--thz-success);
	--bs-form-invalid-color: var(--thz-danger);
	--bs-form-invalid-border-color: var(--thz-danger);

	/* The ring drawn on every focused control. Stock is rgba(13,110,253,.25) — a blue
	   halo on an orange product, on whatever the user is currently typing in. */
	--bs-focus-ring-color: rgba(249, 115, 22, 0.25);

	/* Hairline on cards, tables and form controls. Stock #dee2e6 is a COOL grey; the
	   palette's rule colour is warm. Same job, same weight. */
	--bs-border-color: var(--thz-border-panel); /* #d9d4c7 */
}
