/*
 * Attestree — design tokens
 * v0 design language (ADR 2026-05-03-004), retained by ADR 2026-09-06-001
 *
 * Drop-in target: src/Wep.ControlPlane.Web/wwwroot/css/tokens.css (Unit 4.5).
 * Light is the :root palette; dark overrides it under [data-theme="dark"] on
 * <html>. The console writes that attribute from the operator's wep_theme
 * cookie and serves dark when no cookie is set (ThemePreferenceService, #793).
 * No system-preference auto-flip — operators choose deliberately and it persists.
 *
 * The language is v0's, as design.md §3 describes it: warm limestone neutrals,
 * one ledger-blue accent, hard rules, mono numerals, sharp 2px/4px radii.
 * From 2026-06-07 (42cdfdc7, PR #792) this file carried the Nightfall v2
 * values under the same names. ADR 2026-09-06-001 retired them, and PR #2063
 * (ef732f6cd, 2026-09-07) put v0's values back as a values-only remap; #3465
 * reverted the one name it missed (shadow-pop, in both themes). The ADR's "Outcome 2026-10-01"
 * section records which names reverted, which were kept and why.
 * ClassOneTokensHoldV0Tests holds the 96 names v0 declared to v0's values.
 *
 * Do NOT "revert" by restoring an older copy of this file from git history.
 * The names minted since v0 (the accent-derived roles, danger and
 * danger-text, the brand and shell tokens, the verdict cells and every later state ramp)
 * are not in it, and a var() that points at a missing token is not a CSS
 * error: it silently renders the property's initial value. The retired
 * palette is kept as provenance in docs/design/archive/nightfall/.
 *
 * Naming convention: --wep-{category}-{role}-{step?}
 * Decision: tokens are the contract. Components never hard-code values.
 */

/* ─────────── Self-hosted faces (plan 2026-09-06-2140 U4) ───────────
 *
 * Target operators run isolated networks. Until this block existed, no font
 * file shipped at all: --wep-font-sans named a face and fell through to
 * Segoe UI Variable, and the 31 prototypes pulled theirs from
 * fonts.googleapis.com / rsms.me. Neither host resolves on an air-gapped
 * network, so §3.1's decision — "Inter Tight, not plain Inter … signals we
 * made a choice" — had never reached a single operator machine. A token that
 * names a face the tree does not serve is the same defect class as the five
 * falsehoods the contract-truth plan corrects.
 *
 * URL shape is load-bearing. tokens.css is read from TWO roots: docs/design/
 * in the repo (where the artboards link ../tokens.css) and wwwroot/css/ in
 * the app. A CSS relative URL resolves against the STYLESHEET, so only a path
 * that is valid under both works — hence ./assets/fonts/ on both sides, and
 * hence the csproj copies into wwwroot/css/assets/fonts/ rather than the
 * flatter wwwroot/fonts/. An absolute /fonts/ would have been simpler and
 * would break under a non-root PathBase.
 *
 * Variable faces (one file per subset, wght axis) rather than four statics
 * each: the console uses 400/500/600 via --wep-weight-* plus a stray 700, and
 * the whole set is 190KB against ~340KB of statics.
 *
 * Both families are SIL OFL 1.1, which permits redistribution inside the
 * shipped image; the licences sit beside the faces, which the OFL requires.
 * Subsets are latin + latin-ext only — the console's own chrome is English,
 * and package/publisher names from the catalogs stay inside latin-ext.
 * unicode-range values are fontsource's, not hand-written.
 *
 * ─── font-display: block, not swap (2026-09-07) ───────────────────────────
 *
 * These faces shipped at `font-display: swap`, which is the right default for
 * a font pulled from a CDN over an unknown network: a zero block period means
 * text is never invisible, however slow the font is. That mitigation is priced
 * against network latency, and it is the ONLY thing it buys.
 *
 * There is no network here. The faces are same-origin, self-hosted, 45-90KB
 * woff2 read off local disk — that is the entire point of U4, because the
 * target operators run isolated networks. So `swap` was paying the mitigation's
 * full cost and collecting none of its benefit: a zero block period means
 * Chrome paints the system fallback FIRST, every cold load, and re-lays-out
 * when the real face lands a few milliseconds later.
 *
 * That reflow is not hypothetical and it is not small. Measured 2026-09-07 by
 * rendering audit-evidence with the woff2 files present and then absent: the
 * fallback render and the Inter Tight render differ across 16.3% of a
 * 1440x1100 viewport. That is the size of the jump the operator gets on first
 * visit — content moving under a cursor that may already be over a control.
 *
 * `block` gives a bounded block period (Chrome: 3s) during which text is laid
 * out with the fallback's metrics but not painted, then swaps. On a local face
 * that window is milliseconds and the operator simply never sees Segoe UI.
 *
 * THE TRADEOFF, HONESTLY: a block period means text can be briefly invisible.
 * Two things bound it. The period ends as soon as the font load COMPLETES —
 * and a failed load completes, so a 404 or a bad PathBase renders the fallback
 * about as fast as `swap` would rather than blanking for three seconds; the
 * three seconds is the cap for a font that HANGS, on an origin that is by then
 * failing to serve the page too. And a broken font path no longer has to be
 * noticed by eye: scripts/screenshot-design.ps1 fails any capture whose sans
 * face did not arrive, so the visual-regression gate says so first.
 *
 * This is a product decision about what the operator sees on a cold load. It is
 * NOT a fix for the screenshot harness, and it must not be re-litigated as one:
 * measured the same day, the old chrome.exe --screenshot backend raced the font
 * load and split ten/six across sixteen captures under `block`, versus nine/
 * seven under `swap`. The harness race was cured by making the capture await
 * document.fonts.ready (tools/Wep.Tools.DesignCapture); the settled render is
 * byte-identical under both values, so this change invalidates no baseline.
 *
 * ─── the symbols too (2026-09-07) ──────────────────────────────────────────
 *
 * U4 self-hosted the two TEXT faces and stopped there, so every character
 * OUTSIDE their latin / latin-ext subsets still came from whatever the machine
 * happened to have. That is 24 codepoints the console renders for real —
 * the ⌘ ⏎ ⇧ keycaps, the → in a hundred labels, ⚙ in the shell chrome of every
 * artboard, ✓ ✗, ≤ ≥ ≠ ∅, ▸ ▾, ⚠, and the ─ │ ├ └ of the audit-bundle tree.
 * On an isolated network that is not a download problem, it is worse: the
 * operator gets whichever face Windows font-links to, and NOBODY DECIDED IT.
 *
 * It is also not merely per-machine. Measured 2026-09-07 on DESKTOP-9URPA3H,
 * the SAME box and the SAME chrome.exe, capturing each artboard once from the
 * operator's interactive session and once from a SYSTEM scheduled task (the
 * runner service's context):
 *
 *   _shell-chrome-light      128 pixels differ — ONE 16×21 box, the ⏎ in the
 *                            ⌘ ⏎ keycap, drawn by two different typefaces.
 *   admin-licence-light      3,190 pixels on ONE line. The line opens with →;
 *                            the two fallback arrows have different ADVANCE
 *                            WIDTHS, so every word after it shifts a pixel.
 *   flow-c-audit-export      2.5% of the viewport — the box-drawing tree.
 *   home-light, kit-charts   byte-identical. They render no uncovered symbol.
 *
 * That last row is the control: pages with no fallback glyph survived the
 * session change untouched, which is what rules out ClearType. (Checked
 * directly too: HKCU and HKU\S-1-5-18 both carry FontSmoothing=2,
 * FontSmoothingType=2 — the sessions AGREE about subpixel AA.)
 *
 * So one face, "Wep Symbols", 5.9 KB, in BOTH stacks — the → in a mono table
 * cell and the → in a sans label should be the same arrow, and giving mono its
 * own symbol face is the only alternative. Its unicode-range is exactly the 23
 * codepoints the census found, which is also what stops a page with no symbol
 * on it from fetching the face at all. Provenance, the per-codepoint list and
 * the regeneration command are in LICENSE-wep-symbols.txt beside the face.
 */
@font-face {
  font-family: "Inter Tight";
  font-style: normal;
  font-display: block;
  font-weight: 100 900;
  src: url(./assets/fonts/inter-tight-latin-wght-normal.woff2) format("woff2-variations");
  unicode-range: U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD;
}

@font-face {
  font-family: "Inter Tight";
  font-style: normal;
  font-display: block;
  font-weight: 100 900;
  src: url(./assets/fonts/inter-tight-latin-ext-wght-normal.woff2) format("woff2-variations");
  unicode-range: U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF;
}

@font-face {
  font-family: "JetBrains Mono";
  font-style: normal;
  font-display: block;
  font-weight: 100 800;
  src: url(./assets/fonts/jetbrains-mono-latin-wght-normal.woff2) format("woff2-variations");
  unicode-range: U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD;
}

@font-face {
  font-family: "JetBrains Mono";
  font-style: normal;
  font-display: block;
  font-weight: 100 800;
  src: url(./assets/fonts/jetbrains-mono-latin-ext-wght-normal.woff2) format("woff2-variations");
  unicode-range: U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF;
}

/* One weight, not a wght axis: the source face is static. `font-weight: 100 900`
   claims the whole range on purpose — it tells the browser this face serves any
   weight, so a ✓ inside a 600-weight row is NOT synthetically emboldened. A
   faux-bold icon is a blur; these are marks, not letters. */
@font-face {
  font-family: "Wep Symbols";
  font-style: normal;
  font-display: block;
  font-weight: 100 900;
  src: url(./assets/fonts/wep-symbols.woff2) format("woff2");
  unicode-range: U+0394,U+2192,U+2194,U+21E7,U+2205,U+2208,U+2260,U+2264,U+2265,U+2308-2309,U+2318,U+23CE,U+2500,U+2502,U+2514,U+251C,U+25B8,U+25BE,U+2699,U+26A0,U+2713,U+2717;
}

:root {
  /* ─────────── Typography ─────────── */
  /* Inter Tight — modern, even, dense-friendly, and now served from our own
     origin by the @font-face block above. The system fallbacks stay behind it:
     they carry the page's METRICS through the block period, they render it
     outright if the face cannot be served, and they are all there is on the one
     surface that has no stylesheet at all (a hard 500 before the pipeline runs). */
  /* "Wep Symbols" sits BEHIND the text face and IN FRONT of the system stacks
     in both tokens. Behind, because its unicode-range holds only the 23
     symbols, so it is never reached for a letter. In front, because the system
     stacks are exactly what it exists to displace — everything after it is a
     face we do not ship and therefore cannot predict. */
  --wep-font-sans: "Inter Tight", "Wep Symbols", "Segoe UI Variable", "Segoe UI", system-ui, sans-serif;

  /* JetBrains Mono — numbers, digests, timestamps, CVE IDs, code. Also
     self-hosted; Cascadia and Consolas remain as installed-on-Windows
     fallbacks for the same reason. */
  --wep-font-mono: "JetBrains Mono", "Wep Symbols", "Cascadia Code", "Consolas", ui-monospace, monospace;

  /* ADR 2026-10-12-001 (#3742): prose is 14px and nothing is below 12px.
     3xs was 10px, 2xs was 11px and sm was 13px (docs/design/CHANGELOG.md). */
  --wep-size-text-3xs: 12px;   /* @deprecated 2026-10 — replaced by --wep-size-text-xs; uses move under #4183 */
  --wep-size-text-2xs: 12px;   /* @deprecated 2026-10 — replaced by --wep-size-text-xs; uses move under #4183 */
  --wep-size-text-xs:  12px;   /* the floor: labels, pills, chips, table headers and cells, row meta */
  --wep-size-text-sm:  14px;   /* buttons and inputs, drawer body, banner body, prose */
  --wep-size-text-md:  14px;   /* default body */
  --wep-size-text-lg:  16px;   /* section headings */
  --wep-size-text-xl:  20px;   /* page title */
  --wep-size-text-2xl: 28px;   /* bootstrap wizard step heading only */

  --wep-line-tight:    1.20;
  --wep-line-snug:     1.35;
  --wep-line-relaxed:  1.55;

  --wep-weight-regular: 400;
  --wep-weight-medium:  500;
  --wep-weight-semi:    600;

  --wep-tracking-tight:  -0.005em;   /* large/display headings */
  --wep-tracking-normal:  0;
  --wep-tracking-wide:    0.04em;    /* uppercase labels, status pills */

  /* ─────────── Spacing (4px base, unchanged) ─────────── */
  --wep-space-0:  0;
  --wep-space-1:  4px;
  --wep-space-2:  8px;
  --wep-space-3:  12px;
  --wep-space-4:  16px;
  --wep-space-5:  20px;
  --wep-space-6:  24px;
  --wep-space-8:  32px;
  --wep-space-10: 40px;
  --wep-space-12: 48px;
  --wep-space-16: 64px;

  /* ─────────── Radii (v0: 2px / 4px ceiling, design.md §3.3) ─────────── */
  --wep-radius-0:   0;
  --wep-radius-xs:  2px;   /* pills, chips, inputs */
  --wep-radius-sm:  4px;   /* buttons, drawers, cards */
  --wep-radius-pill: 999px;

  /* ─────────── Elevation (borders before shadows, design.md §3.3) ─────────── */
  --wep-shadow-0:    none;
  --wep-shadow-sm:   0 1px 0 0 rgb(15 23 36 / 0.04);
  /* shadow-pop is v0's (42cdfdc7^) in both themes again (#3465). The #2063
   * revert enumerated v0 one line at a time and missed the two declarations
   * that span two lines, so this one kept Nightfall's deeper 16/48 blur in
   * Nightfall's rgb(16 24 40) ink beside a v0 shadow-sm and shadow-drawer.
   * ClassOneTokensHoldV0Tests now holds all 96 names v0 declared to v0. */
  --wep-shadow-pop:  0 8px 24px -8px rgb(15 23 36 / 0.18),
                     0 2px 4px -1px rgb(15 23 36 / 0.08);              /* popovers, cmd-k */
  --wep-shadow-drawer: -16px 0 32px -16px rgb(15 23 36 / 0.22);

  /* ─────────── Motion (unchanged) ─────────── */
  --wep-motion-instant: 0ms;
  --wep-motion-fast:    120ms;
  --wep-motion-base:    200ms;
  --wep-motion-slow:    320ms;
  --wep-ease-standard:  cubic-bezier(0.2, 0, 0.1, 1);
  --wep-ease-decelerate: cubic-bezier(0, 0, 0.1, 1);

  /* ─────────── Z-index (unchanged) ─────────── */
  --wep-z-base:     0;
  --wep-z-sticky:   100;
  --wep-z-drawer:   400;
  --wep-z-popover:  500;
  --wep-z-cmdk:     600;
  --wep-z-modal:    700;
  --wep-z-toast:    800;

  /* ─────────── Color: Light theme (:root) ─────────── */
  /* v0 limestone neutrals: a warm gray, not a pure gray (design.md §3.2). */
  --wep-color-bg-0:   #faf9f7;   /* canvas */
  --wep-color-bg-1:   #ffffff;   /* surface */
  --wep-color-bg-2:   #f3f1ec;   /* sunken (table headers, code blocks) */
  --wep-color-bg-3:   #e9e6df;   /* hover */
  --wep-color-bg-inv: #0f1724;   /* inverse micro-surfaces in light mode */

  --wep-color-fg-1:   #0f1724;   /* primary text */
  --wep-color-fg-2:   #3a4254;   /* secondary text */
  --wep-color-fg-3:   #5d6677;   /* tertiary / metadata */
  --wep-color-fg-4:   #8a93a3;   /* disabled / placeholder */
  --wep-color-fg-inv: #faf9f7;

  --wep-color-border-1: #d9d4c9;  /* default rule */
  --wep-color-border-2: #c2bcae;  /* emphasized rule */
  --wep-color-border-3: #0f1724;  /* hard rule (table outer, drawer header) */
  --wep-color-border-focus: #1f5fff;

  /* Accent — v0 ledger blue, the single accent (design.md §3.2). accent-1
     carries the NON-text roles: focus ring, selected-row tint, rails, borders. */
  --wep-color-accent-1: #1f5fff;
  --wep-color-accent-2: #1849c4;   /* hover */
  --wep-color-accent-bg: #e8efff;  /* selected row tint */
  --wep-color-accent-fg: #ffffff;

  /* Solid — the fill for controls that carry a LABEL (#1764). A filled
   * control's label is TEXT, so the 4.5:1 floor governs it — not the 3:1
   * non-text floor accent-1 is held to. When #1764 split this role out, the
   * light accent-1 was Nightfall's, and white on it measured 4.47:1. The
   * pairing had never been censused because #1750/#1753 both swept elements
   * whose *foreground* was accent-1; this is the reversed pairing (accent-fg
   * ON accent-1), which no anchor/text census could reach.
   * Same shape as the link split: a role token with literal values, so an
   * accent re-tune cannot drag a control label back under AA. The revert put
   * accent-1 back to v0's ledger blue (white on it: 5.07:1) and kept these
   * values unchanged (ADR 2026-09-06-001, verdict 1):
   *   #3d63dd -> 5.21:1 with accent-fg (white)
   *   #3252c0 -> 6.77:1 with accent-fg (white), hover
   * #1764 set the rest value equal to the accent-2 of the day. v0's accent-2
   * is #1849c4, so the rest value equals no accent token now. The roles were
   * always separate: accent-2 is the accent's hover/pressed state and moves
   * with the accent; the solid fill must not.
   * LabelledFillContrastTests holds both labels to 4.5:1 in both themes. */
  --wep-color-accent-solid: #3d63dd;
  --wep-color-accent-solid-hover: #3252c0;

  /* Link — the accent's hue family, tuned DARKER so link *text* clears WCAG AA
   * 4.5:1 on every light backdrop a link can land on (#1750). accent-1 as
   * text does not. #1750 measured Nightfall's light accent at 4.47:1 on bg-1,
   * 4.21:1 on bg-0 and 3.89:1 on the accent-bg row tint. v0's ledger blue,
   * shipped again since the revert, does better and still fails three of
   * five: 5.07:1 on bg-1 and 4.82:1 on bg-0, but 4.495:1 on bg-2, 4.07:1 on
   * bg-3 (hover) and 4.40:1 on accent-bg. accent-1 keeps its NON-text roles
   * (focus ring, selected-row tint, rails/borders), where the 3:1 non-text
   * floor governs and it passes; only the text role moves here, so fixing
   * link legibility does not re-tune the console's chrome.
   *
   * CORRECTED by #1764: this list used to name "button fill" as a settled
   * non-text role. That was wrong, and it is why the defect survived three
   * accent passes. A fill is only non-text when nothing is written on it — a
   * button's label IS text, so the pairing (accent-fg on accent-1) answers to
   * 4.5:1, and the 4.47:1 #1750 recorded on bg-1 IS that pairing: contrast is
   * symmetric, so "accent-1 as text on white" and "white as text on accent-1"
   * are the same measurement. The failing figure was sitting in this comment
   * the whole time, filed under the wrong floor. Labelled fills now use
   * --wep-color-accent-solid.
   * Light: H 227.9 (rest) / 227.7 (hover), 5 degrees from accent-1's 222.9;
   * L 54% rest / 47% hover. #1750 tuned them on Nightfall's accent hue, and
   * the revert kept them (ADR 2026-09-06-001, verdict 1). On v0's grounds:
   *   #3a5ad9 -> 5.76:1 on bg-1, 5.48:1 on bg-0, 5.10:1 on bg-2,
   *              5.00:1 on accent-bg, 4.62:1 worst case (bg-3, hover)
   *   #2d4cc4 -> 7.14:1 on bg-1, 6.78:1 on bg-0, 6.32:1 on bg-2,
   *              6.19:1 on accent-bg, 5.73:1 worst case (bg-3, hover)
   * Literal values, not var(--wep-color-accent-*): the point of the token is
   * that a future accent re-tune cannot silently drop link text back under AA.
   * AccentTextRolesContrastAndHueTests (#3466) holds link, link-hover and
   * accent-text to 4.5:1 on bg-0..bg-3 and accent-bg in both themes, and every
   * accent-* and link* token to within 6 degrees of accent-1's hue. */
  --wep-color-link: #3a5ad9;
  --wep-color-link-hover: #2d4cc4;

  /* Accent TEXT (#1753) — the non-anchor half of the same split #1750 made for
   * links. The measurements above are a property of accent-1 as TEXT, not of
   * anchors: ~14 non-anchor uses were still colouring text with accent-1 (the
   * active nav label, subtab counts, tag/badge text, the diff "modified"
   * state, the fleet row-hover affordance). None of them is an anchor, so
   * .wep-link never reached them and the whole set failed AA in light.
   * Same value as --wep-color-link, and deliberately a SEPARATE token: the
   * roles are separate, so a later re-tune of one (say, text sitting on the
   * accent-bg fill) must not silently drag the other — that coupling is
   * exactly the defect #1750 broke, and re-introducing it under a new name
   * would be the same mistake. accent-1 keeps every NON-text role (focus
   * ring, border, selected-row tint) where the looser 3:1 floor governs and
   * it passes, so the console's chrome is untouched. A button fill is not
   * one of them: its label is text (#1764, accent-solid above).
   * Literal, not var(--wep-color-accent-*), for #1750's reason.
   * No -hover sibling: every consumer is either a rest state or is itself a
   * hover state that lands on this value. Mint one when a consumer needs it. */
  --wep-color-accent-text: #3a5ad9;

  /* ─────────── Brand ─────────── */
  /* The Attestree brand family (#4654, approved 2026-10-22). This file is
   * the master for these values: the brand assets in assets/ paint them as
   * literals (favicon.svg, og-card.svg, the marks' default color), and
   * attestree-site's palette copies them. BrandLockTests holds the assets
   * and these tokens to each other. Navy is sampled from the master artwork.
   *
   * These are theme-independent constants. The dark block below does not
   * redeclare any of them, and must not: the brand reads the same in both
   * themes.
   *
   * They replace --wep-brand-teal, --wep-brand-indigo and --wep-brand-gradient,
   * the teal -> indigo accent copied from the old site lock. No stylesheet
   * referenced the three when they retired (their last consumers were the two
   * gradient tiles the tree glyph replaced), so there was nothing to alias.
   *
   * Contrast, WCAG 2.x: white on navy 17.9:1, mist on navy 10.4:1, navy on
   * paper 16.6:1, slate on paper 6.6:1. */
  --wep-brand-navy:     #021639;
  --wep-brand-navy-800: #0B2550;   /* raised: hover, pill and field grounds */
  --wep-brand-navy-600: #1B3A70;   /* rules, and the current page's ground */
  --wep-brand-mist:     #B8C6E0;
  --wep-brand-white:    #FFFFFF;
  --wep-brand-paper:    #F5F7FB;
  --wep-brand-slate:    #4A5878;

  /* ─────────── Shell (the context bar and the nav rail) ─────────── */
  /* The shell is navy in BOTH themes (#4654 U0, approved 2026-10-22): only
   * the page under it follows the theme. So these are declared once, here,
   * and the dark block never redeclares them; ShellPaletteTests fails if it
   * does. The page's own surfaces, text and accent tokens are untouched,
   * and the shell's rules reference only these, never a --wep-color-* token
   * that would flip with the theme.
   *
   * Every text pair on the shell clears WCAG AA 4.5:1, measured:
   *   fg (white)        on bg 17.9, on raised 15.1, on current 11.1
   *   fg-muted (mist)   on bg 10.4, on raised  8.7, on current  6.5
   *   label             on bg  6.4, on raised  5.4
   *   key               on bg  5.3
   * and the focus ring, white with a 2px offset onto navy, is 17.9:1 against
   * a 3:1 floor. label is never set on current (4.0:1): the current page
   * draws its text in fg and its key in fg-muted. */
  --wep-shell-bg:       var(--wep-brand-navy);       /* the bar and the rail */
  --wep-shell-raised:   var(--wep-brand-navy-800);   /* hover; the tenant pill and the ⌘K field */
  --wep-shell-current:  var(--wep-brand-navy-600);   /* aria-current="page" */
  --wep-shell-border:   var(--wep-brand-navy-600);   /* every rule on and around the shell */
  --wep-shell-fg:       var(--wep-brand-white);      /* the brand, hover and current text, the settings glyph */
  --wep-shell-fg-muted: var(--wep-brand-mist);       /* nav text, the tenant, the operator id */
  --wep-shell-label:    #8A9BBF;                     /* section labels, the ⌘K placeholder */
  --wep-shell-key:      #7A8DB3;                     /* key hints, the nav footer */
  --wep-shell-focus:    var(--wep-brand-white);      /* the focus outline on the shell */
  /* ─────────── Semantic state ramp ─────────── */
  /* -fg = text, -bg = pill/tint fill, -rule = stripe/border (kept punchy for scan).
   *
   * The first seven ramps are v0's. Most of the later ramps were written while
   * this file held Nightfall's values, and their comments compared them with
   * Nightfall's neighbours. The neighbour hexes named below are the SHIPPED
   * values (re-stated for #3160), and every "distinct from" claim that named
   * a Nightfall neighbour was re-checked by hue against the shipped one; the
   * rendered pills were not re-captured for it. */

  --wep-state-clean-fg:   #0d6b3a;
  --wep-state-clean-bg:   #e3f4ea;
  --wep-state-clean-rule: #2c8c5a;

  --wep-state-detonating-fg:   #5a3b00;
  --wep-state-detonating-bg:   #fbefcf;
  --wep-state-detonating-rule: #b88410;

  --wep-state-sbomready-fg:   #1f4d8a;
  --wep-state-sbomready-bg:   #e3edfa;
  --wep-state-sbomready-rule: #356bb0;

  --wep-state-cveblocked-fg:   #8a1729;
  --wep-state-cveblocked-bg:   #fae3e7;
  --wep-state-cveblocked-rule: #c2304a;

  --wep-state-quarantined-fg:   #5d4475;
  --wep-state-quarantined-bg:   #efe6f4;
  --wep-state-quarantined-rule: #7d5a9c;

  --wep-state-promoted-fg:   #0e4f5a;
  --wep-state-promoted-bg:   #d7eef2;
  --wep-state-promoted-rule: #1c7886;

  --wep-state-rolledback-fg:   #6b3a00;
  --wep-state-rolledback-bg:   #fae3cf;
  --wep-state-rolledback-rule: #b85e10;

  /* Plan 2026-06-13-001 U5 (KTD8) — winget recognition ramps. Two NEW ramps,
   * not aliases of an existing pill color (the repo convention forbids it).
   *   recognizedconfident = informational-teal — a brighter cyan-teal,
   *     DISTINCT from the deeper promoted-teal ("currently serving"): a
   *     confident read-only catalog correlation, not a shipping state.
   *   recognizedpossible = muted advisory — a desaturated slate-blue,
   *     DISTINCT from the muted-grey Foreign ramp: the row IS correlated but
   *     unconfirmed, so it must not read identically to an un-correlated row. */
  --wep-state-recognizedconfident-fg:   #0e7490;
  --wep-state-recognizedconfident-bg:   #dcf3fa;
  --wep-state-recognizedconfident-rule: #1a8aa8;

  --wep-state-recognizedpossible-fg:   #4a5a78;
  --wep-state-recognizedpossible-bg:   #eaeef6;
  --wep-state-recognizedpossible-rule: #6478a0;

  /* Plan 2026-06-16-001 U3 (KTD5) — auto-rollback HOLD ramp. A protective
   * amber-gold (ochre): the breached version is FROZEN installed, neither a
   * rolledback-orange demotion nor a detonating-amber coverage limit. Its OWN
   * ramp — not an alias of rolledback/quarantined (the repo convention forbids
   * reusing a pill color). Deliberately distinct from rolledback (#6b3a00,
   * warmer orange: hue 32 vs 45) so a held endpoint never reads as a generic
   * rollback. */
  --wep-state-held-fg:   #7a5c00;
  --wep-state-held-bg:   #fbf0cc;
  --wep-state-held-rule: #b08900;

  /* Plan 2026-06-29-002 U8 (R4/R5) — per-user last-known / unverified ramp. A
   * muted warm "aged stone": a remembered per-user snapshot for an unobservable
   * SID (logout/headless), present but not re-observed. Its OWN ramp — NOT an
   * alias of held-gold (#7a5c00, a rollback froze a version), the muted-grey
   * observed-unattested, or the recognizedpossible slate (#4a5a78) — so an
   * "unverified/last-known" row never reads as any of those. Deliberately
   * low-saturation: it is an aging signal, not an alarm. */
  --wep-state-lastknownunverified-fg:   #6b6152;
  --wep-state-lastknownunverified-bg:   #f2efe9;
  --wep-state-lastknownunverified-rule: #9a8c72;

  /* Plan 2026-09-08-1838 U3 (KTD4) — the management axis's `not driven` rung: a
   * catalog-covered row the platform has NO evidence of placing. A cool, desaturated
   * graphite — deliberately OFF the clean green (the defect being fixed is a green
   * `match` that claimed management it never checked) and NOT an alias of the
   * observed-unattested grey (`provenance pending` holds that ramp in the same
   * column and means the opposite thing), the recognizedpossible slate (#4a5a78 —
   * bluer and more saturated; an identity hedge), the lastknownunverified taupe
   * above (warm; an aging signal), or the foreign purple. Neutral on purpose: it is
   * an absence of proof, not an alarm. */
  --wep-state-notdriven-fg:   #4f5b66;
  --wep-state-notdriven-bg:   #e9edf1;
  --wep-state-notdriven-rule: #7d8a96;

  /* #2540 — `decommissioned` endpoint ramp. A muted stone: warmer and lighter than the
   * notdriven graphite above, cooler and less yellow than the lastknownunverified
   * taupe, and not the offline fg-3/bg-2 grey (a retired identity must not read as a
   * machine that is merely switched off). Quiet on purpose: a settled decision. */
  --wep-state-decommissioned-fg:   #5c5750;
  --wep-state-decommissioned-bg:   #eeece8;
  --wep-state-decommissioned-rule: #8f897f;

  /* #2597 — `not checking in (probing)` endpoint ramp: the agent answers the wake probe
   * but its check-in loop is silent (stuck) — actionable, the endpoint page offers
   * "restart agent". A deep teal-cyan (hue ~188°), the one free slot on the wheel: NOT an
   * alias of the offline grey (this box IS reachable — that is the whole point), the
   * settling slate-blue (#2b5583 — "nothing is wrong, wait", the opposite claim), the
   * degraded warn-amber (`update stuck` sits in the same status cell and means a
   * different thing), the rolledback orange / cveblocked red (failures of a step), or
   * the decommissioned stone. Its attention reads through the ATTENTION forced-colours
   * family and the label, not through yet another warm hue. */
  --wep-state-notcheckingin-fg:   #0b5f6b;
  --wep-state-notcheckingin-bg:   #ddf3f5;
  --wep-state-notcheckingin-rule: #2a8c99;

  /* Plan 2026-07-08-001 (#1251) — requires-user-context ramp. A warm
   * copper/sepia amber: a per-user installer exited 0 as SYSTEM into
   * HKU\S-1-5-18 — installed, but visible to no real user; un-converged, NOT
   * failed. Its OWN ramp — NOT an alias of held-gold (#7a5c00, a rollback
   * froze a version), detonating-amber (#5a3b00), rolledback-orange
   * (#6b3a00, a demotion), or the cveblocked red (a real failure) — so a
   * requires-user-context row never reads as any of those. */
  --wep-state-requiresusercontext-fg:   #7d4a22;
  --wep-state-requiresusercontext-bg:   #f8eee1;
  --wep-state-requiresusercontext-rule: #a5682e;

  /* Plan 2026-07-16-001 (#1421) U1 — withdrawn-upstream tombstone ramp. A muted
   * slate-violet: the vendor deleted this version's winget-pkgs manifest upstream
   * after ingest — a never-served ghost, terminal. Its OWN ramp — NOT an alias of
   * the quarantined violet (#5d4475, a pipeline-broke triage verdict the operator
   * re-detonates), the recognizedpossible slate (#4a5a78, bluer), or the
   * muted-grey observed-unattested — so a withdrawn ghost never reads as any of
   * those. Deliberately low-saturation and cool: a "gone upstream" tombstone, not
   * an alarm.
   * Measured at the shipped values (#3160), the quarantined neighbour is the
   * weak one. This ramp was written against Nightfall's saturated quarantined
   * violet. v0's is muted too, so the two -fg sit 19 degrees of hue apart
   * (252 vs 271) at similar saturation and lightness — OKLab distance 0.026,
   * about as close as link and accent-solid. The label, not the hue, is what
   * tells these two pills apart. */
  --wep-state-withdrawnupstream-fg:   #544c74;
  --wep-state-withdrawnupstream-bg:   #ecebf3;
  --wep-state-withdrawnupstream-rule: #7a7196;

  /* Plan 2026-07-18-001 U1 (#1454) — app-in-use deferred ramp. A muted
   * olive/moss green: the target application is running in a user session, so
   * the silent SYSTEM upgrade is confirmed blocked and patiently backed off
   * until the app closes — a waiting state that self-heals, not an alarm. Its
   * OWN ramp — NOT an alias of requiresusercontext-copper (#7d4a22, needs a
   * user context), held-gold (#7a5c00, a rollback froze a version),
   * detonating-amber (#5a3b00, in-flight), or the clean/attested greens (a
   * converged verdict) — so an app-in-use row never reads as any of those.
   * Deliberately low-saturation: "waiting on the user", not "broken". */
  --wep-state-appinusedeferred-fg:   #55622a;
  --wep-state-appinusedeferred-bg:   #f0f3e2;
  --wep-state-appinusedeferred-rule: #7c8a44;

  /* Plan 2026-07-23-004 U4 (#1569, R6) — self-rejecting rollout member ramp. A muted
   * mustard-amber (a dark yellow, hue ~52°): a ring member excluded from the agent-rollout health
   * denominators for rejecting two or more distinct unsurpassed agent versions — a caution that
   * self-heals on forward convergence, NOT a failure. Its OWN ramp — NOT an alias of the
   * cveblocked red / rolledback-orange (#6b3a00, a real failure this deliberately is not), the
   * cross-cutting offline grey (an excluded member must never read as offline, R6), the quarantined
   * violet (#5d4475, a catalog-admin hold — a different axis; KTD6), the held-gold (#7a5c00) /
   * detonating-amber (#5a3b00), the requiresusercontext copper (#7d4a22), or the appinusedeferred
   * olive (#55622a) — so a self-rejecting row never reads as any of those. Deliberately more yellow
   * than the gold ramps and warmer than the olive, so it sits in its own hue slot. */
  --wep-state-selfrejecting-fg:   #6b6012;
  --wep-state-selfrejecting-bg:   #f6f1cf;
  --wep-state-selfrejecting-rule: #998a1e;

  /* Plan 2026-07-28-002 U7 (#1616) — ring member that JOINED already on the target and is
   * settling into its new ring's desired state. A calm informational slate-blue (hue ~215°),
   * because the whole defect being fixed is that this member READ AS A BROKEN UPGRADE: anything
   * in the warm half of the wheel would re-create that at one hue's remove. Its OWN ramp — NOT an
   * alias of the detonating-amber (#5a3b00, in-flight, the exact misread), the selfrejecting
   * mustard (#6b6012, an EXCLUSION, which this is not — a settling member still counts toward
   * ring health), the rolledback orange / cveblocked red (failures this deliberately is not), or
   * the cross-cutting offline grey (a settling box is checking in, not silent). Cool, low-
   * saturation, and quiet on purpose: it says "nothing is wrong here, wait". */
  --wep-state-settling-fg:   #2b5583;
  --wep-state-settling-bg:   #dfeaf6;
  --wep-state-settling-rule: #4c7bab;

  /* Plan 2026-08-09-001 U5 (#1733) — undeclared/looping SYSTEM-hollow debt ramp. A muted
   * plum/magenta (hue ~320°): a machine uninstall stuck in backoff because only the
   * SYSTEM-profile hollow copy remains — actionable remediation debt burning a span per
   * cycle, NOT a failure verdict. Its OWN ramp — NOT an alias of the cveblocked red
   * (#8a1729, a real failure), the quarantined violet (#5d4475) / withdrawnupstream
   * slate-violet (#544c74, catalog axes), the requiresusercontext copper (#7d4a22 — the
   * DECLARED+held sibling shape rendered on the same Holds surface, which must read
   * distinct at a glance), or the held gold (#7a5c00, the auto-rollback domain — the
   * no-aliasing convention forbids reusing it). */
  --wep-state-uninstalllooping-fg:   #7c2f5f;
  --wep-state-uninstalllooping-bg:   #f8e8f2;
  --wep-state-uninstalllooping-rule: #a8517f;

  /* #1061 U3 — dev-grade / not-sandbox-verified serve badge (ServedUnverified).
   * Pale-saffron ramp whose PRIMARY discriminator is the dashed-outline pill
   * treatment (the honest "unfinished" read): the warm family is saturated —
   * detonating amber (#5a3b00, in-flight), held gold (#7a5c00, auto-rollback),
   * requiresusercontext copper (#7d4a22), selfrejecting mustard (#6b6012),
   * rolledback orange (#6b3a00) — so hue alone cannot carry a new warm state,
   * and this one's meaning ("actively served WITHOUT detonation evidence") is
   * exactly what a dashed outline says. NOT ObservedUnattested grey: passive
   * inventory vs actively-served is the whole warning. */
  --wep-state-servedunverified-fg:   #8a5a12;
  --wep-state-servedunverified-bg:   #fdf7ea;
  --wep-state-servedunverified-rule: #c08a2d;

  /* Cross-cutting: warn + danger (typed-confirm, destructive). */
  --wep-color-warn-fg:   #5a3b00;
  --wep-color-warn-bg:   #fbefcf;
  --wep-color-danger:    #e0364a;
  --wep-color-danger-fg: #ffffff;
  --wep-color-danger-bg: #b22338;
  --wep-color-danger-bg-hover: #8a1729;

  /* Danger as TEXT (#1759). --wep-color-danger cannot do this job. Nightfall
   * minted it as the danger FILL, equal to danger-bg with white danger-fg
   * painted on top, so it was tuned for white to sit ON it, which is exactly
   * what made it too light to BE text. The revert moved danger-bg back to
   * v0's crimson and kept danger (ADR 2026-09-06-001, verdict 3), so the two
   * are no longer equal. Since #1759 danger paints only the NON-text danger
   * role (rails, borders, outlines), and it is still too light to be text.
   * Measured as a foreground it fails AA on EVERY light surface:
   *   #e0364a -> 4.38:1 on bg-1, 4.16:1 on bg-0, 3.88:1 on bg-2,
   *              3.51:1 on bg-3 (hover), 3.80:1 on accent-bg (selected)
   * bg-2 is the one that matters most: .wep-decision-row-tier0 sets it as the
   * row surface. Dark clears the flat surfaces but drops under AA on hover
   * (4.03:1) and when selected (4.13:1) — the two states an operator is most
   * likely to be in while reading the row.
   *
   * #1759 copied these values from --wep-state-cveblocked-fg, the console's
   * text-tuned crimson and already its semantic colour for this meaning. That
   * was Nightfall's cveblocked-fg. The revert moved cveblocked-fg to v0's
   * #8a1729 and kept danger-text (verdict 3), so the two no longer match.
   * They were separate tokens on purpose, as accent-text/link are (#1753):
   * either can be re-tuned without dragging the other.
   *   #b21d34 -> 6.73:1 on bg-1, 6.40 bg-0, 5.97 bg-2, 5.84 accent-bg,
   *              5.40:1 worst case (bg-3, hover)
   * LabelledFillContrastTests.DangerText_ClearsAa_OnEverySurfaceARowCanLandOn
   * holds it to 4.5:1 on all five surfaces in both themes.
   * NON-text danger (rails, borders, outlines) stays on --wep-color-danger:
   * the 3:1 floor governs there and it passes on every surface. */
  --wep-color-danger-text: #b21d34;

  /* ─────────── Verdict-glyph cell fill patterns ─────────── */
  /* Plan 2026-06-25-001 (safe-to-deploy verdict) U2 — the no-color-alone (R11)
   * substrate for the WepVerdict glyph. Each of the four headline legs is drawn
   * as a cell whose FILL/SHAPE encodes its LegState, never color alone:
   *   VerifiedPass -> solid clean cell.
   *   NotEvaluated -> a diagonal HATCH (visibly NOT solid) — the "gap", never a pass.
   *   Fail         -> a HOLLOW cell with a rule border + an × mark.
   * No hatch primitive existed in the ramp set, and §11 forbids a component
   * hardcoding the pattern, so the gradient + rules live here as named tokens.
   * These are DERIVED tokens (references to the already-theme-aware state ramps),
   * so — exactly like --wep-focus-ring below — a single definition in :root
   * re-resolves correctly under [data-theme="dark"]; no duplicate dark block is
   * needed, and both themes inherit the §29-guaranteed WCAG-AA ramp contrast.
   * The consuming .wep-verdict-cell-* classes are mirrored in
   * WepVerdict.razor.css + prototypes/_shell.css (both copies move together — #887). */
  --wep-verdict-cell-pass-fg:   var(--wep-state-clean-fg);
  --wep-verdict-cell-pass-bg:   var(--wep-state-clean-bg);
  --wep-verdict-cell-pass-rule: var(--wep-state-clean-rule);
  --wep-verdict-cell-fail-fg:   var(--wep-state-cveblocked-fg);
  --wep-verdict-cell-fail-rule: var(--wep-state-cveblocked-rule);
  --wep-verdict-cell-noteval-rule: var(--wep-color-border-3);
  /* 45° hatch at a 3px pitch — a muted "gap" texture distinct from every solid
   * pill fill, legible at the dense 12px row scale. Stripe = the muted fg-4 over
   * the sunken bg-2; both ends of the gradient are theme-aware tokens. */
  --wep-verdict-cell-noteval-hatch:
    repeating-linear-gradient(45deg,
      var(--wep-color-bg-2) 0, var(--wep-color-bg-2) 2px,
      var(--wep-color-fg-4) 2px, var(--wep-color-fg-4) 3px);

  /* ─────────── Focus ring (formula unchanged; picks up new accent) ─────────── */
  --wep-focus-ring: 0 0 0 2px var(--wep-color-bg-1),
                    0 0 0 4px var(--wep-color-border-focus);
}

/* ─────────── Color: Dark theme (NOC / dark-first home) ─────────── */
[data-theme="dark"] {
  --wep-color-bg-0:   #0a0e17;
  --wep-color-bg-1:   #0f1724;
  --wep-color-bg-2:   #161e2d;
  --wep-color-bg-3:   #1f2839;
  --wep-color-bg-inv: #faf9f7;

  --wep-color-fg-1:   #e8eaf0;
  --wep-color-fg-2:   #b3b9c6;
  --wep-color-fg-3:   #8a93a3;
  --wep-color-fg-4:   #5d6677;
  --wep-color-fg-inv: #0f1724;

  --wep-color-border-1: #232c3e;
  --wep-color-border-2: #344057;
  --wep-color-border-3: #e8eaf0;
  --wep-color-border-focus: #6d97ff;

  --wep-color-accent-1: #6d97ff;
  --wep-color-accent-2: #4d7fff;   /* hover (darker than accent-1 in v0's dark) */
  --wep-color-accent-bg: #16244a;  /* selected row tint */

  /* #1764 — dark flips the label to INK, it does not darken the fill. White on
   * the dark accent of the day (Nightfall's) measured 3.163:1 live, and its
   * hover was lighter still, so hovering made it worse. Darkening the fill
   * instead would have cost dark theme its one bright affordance; inverting
   * the label kept the button as prominent and clears AA with room. The label
   * sits on accent-solid (below), not on accent-1:
   *   #0a0e17 on accent-solid       (#5b8cff) -> 6.10:1
   *   #0a0e17 on accent-solid-hover (#6f9bff) -> 7.18:1   (hover the BETTER state)
   * At #1764 the prototype artboards' dark blocks already set accent-fg to
   * near-black, as v0 did; shipped tokens.css had drifted to #ffffff in BOTH
   * themes under Nightfall. The value is bg-0's ink so the label reads as the
   * canvas colour, not a new near-black. #1764 set Nightfall's bg-0; the
   * revert set v0's #0a0e17, which is also v0's own value for this token
   * (ADR 2026-09-06-001, class one). NOTE print.css must keep this WHITE for
   * dark-theme operators: print flattens the accent fills to #000000, and
   * ink-on-ink is invisible. */
  --wep-color-accent-fg: #0a0e17;

  /* Solid — labelled fills (#1764). #1764 fixed dark's label, not its fill, so
   * the fill kept the dark accent values of the day (Nightfall's accent-1 and
   * accent-2). The revert moved dark accent-1/accent-2 to v0's and kept these
   * (ADR 2026-09-06-001, verdict 1), so in dark the primary button no longer
   * paints accent-1: it paints a colour within 1 degree of accent-1's hue.
   * Separate tokens still exist in dark so consumers name ONE role across
   * both themes. */
  --wep-color-accent-solid: #5b8cff;
  --wep-color-accent-solid-hover: #6f9bff;

  /* Link (#1750). Dark already cleared AA at the accent values of the day, so
   * #1750 kept them and only light moved; the revert kept them too (verdict
   * 1), so dark link no longer equals dark accent-1. On v0's dark grounds:
   *   #5b8cff -> 6.10:1 on bg-0, 5.68 bg-1, 5.28 bg-2, 4.79 accent-bg,
   *              4.67:1 worst case (bg-3, hover)
   *   #6f9bff -> 7.18:1 on bg-0, 6.68 bg-1, 6.21 bg-2, 5.64 accent-bg,
   *              5.50:1 worst case (bg-3, hover)
   * Held as literals for the same reason as light: the link role must not
   * follow a future accent re-tune back under the 4.5:1 floor. */
  --wep-color-link: #5b8cff;
  --wep-color-link-hover: #6f9bff;

  /* Accent text (#1753). Dark cleared AA at the accent values already, so
   * #1753 kept the colour operators saw — the token exists so the light split
   * has a name in BOTH themes (a rule that only exists in one theme is a rule
   * that gets forgotten), and so a future dark accent re-tune cannot reach
   * text either. Equal to --wep-color-link here, intentionally. It no longer
   * equals dark accent-1, which the revert moved and this token did not. */
  --wep-color-accent-text: #5b8cff;

  --wep-state-clean-fg:   #6fcf97;
  --wep-state-clean-bg:   #143324;
  --wep-state-clean-rule: #2c8c5a;

  --wep-state-detonating-fg:   #f0c050;
  --wep-state-detonating-bg:   #2e2410;
  --wep-state-detonating-rule: #b88410;

  --wep-state-sbomready-fg:   #6d97ff;
  --wep-state-sbomready-bg:   #14224a;
  --wep-state-sbomready-rule: #356bb0;

  --wep-state-cveblocked-fg:   #f57a8b;
  --wep-state-cveblocked-bg:   #2e1218;
  --wep-state-cveblocked-rule: #c2304a;

  --wep-state-quarantined-fg:   #c79df0;
  --wep-state-quarantined-bg:   #251a30;
  --wep-state-quarantined-rule: #7d5a9c;

  --wep-state-promoted-fg:   #5fc4d2;
  --wep-state-promoted-bg:   #0e2a30;
  --wep-state-promoted-rule: #1c7886;

  --wep-state-rolledback-fg:   #f0a050;
  --wep-state-rolledback-bg:   #2e1f10;
  --wep-state-rolledback-rule: #b85e10;

  /* Plan 2026-06-13-001 U5 (KTD8) — winget recognition ramps (dark). Mirrors
   * the light ramps' INTENT, not their values: brighter cyan-teal for the
   * confident pill (distinct from the dark promoted-teal #5fc4d2) and a muted
   * slate-blue for the possible pill (distinct from the dark Foreign grey). */
  --wep-state-recognizedconfident-fg:   #62d8f0;
  --wep-state-recognizedconfident-bg:   rgb(60 200 235 / 0.14);
  --wep-state-recognizedconfident-rule: #3cc8eb;

  --wep-state-recognizedpossible-fg:   #9fb0d0;
  --wep-state-recognizedpossible-bg:   rgb(120 140 190 / 0.14);
  --wep-state-recognizedpossible-rule: #788cba;

  /* Plan 2026-06-16-001 U3 (KTD5) — auto-rollback HOLD ramp (dark). A warm
   * gold, brighter on dark, distinct from the dark rolledback-orange (#f0a050)
   * so a held endpoint reads as "frozen-protected", not "demoted". */
  --wep-state-held-fg:   #ffd766;
  --wep-state-held-bg:   rgb(220 170 30 / 0.14);
  --wep-state-held-rule: #e0b020;

  /* Plan 2026-06-29-002 U8 (R4/R5) — per-user last-known / unverified ramp (dark).
   * A muted warm taupe, brighter on dark, distinct from the dark held-gold
   * (#ffd766) and the recognizedpossible slate (#9fb0d0) so an "unverified"
   * per-user row reads as "aged/last-known", not "frozen" or "correlated". */
  --wep-state-lastknownunverified-fg:   #c9bfa8;
  --wep-state-lastknownunverified-bg:   rgb(150 135 105 / 0.14);
  --wep-state-lastknownunverified-rule: #a89a7e;

  /* Plan 2026-09-08-1838 U3 (KTD4) — `not driven` ramp (dark). Cool graphite,
   * brighter on dark, distinct from the recognizedpossible slate (#9fb0d0 — bluer)
   * and the lastknownunverified taupe above (warm), so "no evidence we placed this"
   * never reads as "correlated" or "aged". */
  --wep-state-notdriven-fg:   #aab7c4;
  --wep-state-notdriven-bg:   rgb(120 135 150 / 0.16);
  --wep-state-notdriven-rule: #8393a3;

  /* #2540 — `decommissioned` ramp (dark). Muted stone, distinct from the notdriven
   * graphite above and the lastknownunverified taupe. */
  --wep-state-decommissioned-fg:   #b8b1a6;
  --wep-state-decommissioned-bg:   rgb(140 132 120 / 0.14);
  --wep-state-decommissioned-rule: #948d82;

  /* #2597 — `not checking in (probing)` ramp (dark). Teal-cyan, lifted for dark contrast,
   * distinct from the dark settling slate-blue (#93bde8) and the offline grey. */
  --wep-state-notcheckingin-fg:   #7fd3dd;
  --wep-state-notcheckingin-bg:   rgb(60 170 185 / 0.14);
  --wep-state-notcheckingin-rule: #3fa5b3;

  /* Plan 2026-07-08-001 (#1251) — requires-user-context ramp (dark). A warm
   * copper/tan, brighter on dark, distinct from the dark held-gold (#ffd766),
   * rolledback-orange (#f0a050), and lastknownunverified taupe (#c9bfa8) so a
   * requires-user-context row reads "needs a user context", not "frozen",
   * "demoted", or "aged". */
  --wep-state-requiresusercontext-fg:   #e8b48c;
  --wep-state-requiresusercontext-bg:   rgb(190 130 80 / 0.14);
  --wep-state-requiresusercontext-rule: #c98a54;

  /* Plan 2026-07-16-001 (#1421) U1 — withdrawn-upstream tombstone ramp (dark). A
   * muted slate-violet, brighter on dark, distinct from the dark quarantined violet
   * (#c79df0, more saturated), the recognizedpossible slate (#9fb0d0, bluer),
   * and the lastknownunverified taupe (#c9bfa8) so a withdrawn ghost reads "gone
   * upstream", not "quarantined", "correlated", or "aged". */
  --wep-state-withdrawnupstream-fg:   #b7add8;
  --wep-state-withdrawnupstream-bg:   rgb(130 115 175 / 0.14);
  --wep-state-withdrawnupstream-rule: #8f82b8;

  /* Plan 2026-07-18-001 U1 (#1454) — app-in-use deferred ramp (dark). A muted
   * olive/moss, brighter on dark, distinct from the dark held-gold (#ffd766),
   * requiresusercontext copper (#e8b48c), and lastknownunverified taupe
   * (#c9bfa8) so an app-in-use row reads "waiting for the app to close", not
   * "frozen", "needs a user context", or "aged". */
  --wep-state-appinusedeferred-fg:   #c6d08e;
  --wep-state-appinusedeferred-bg:   rgb(150 165 80 / 0.14);
  --wep-state-appinusedeferred-rule: #9aa860;

  /* Plan 2026-07-23-004 U4 (#1569, R6) — self-rejecting rollout member ramp (dark). A muted
   * mustard-yellow, brighter on dark, distinct from the dark held-gold (#ffd766) / detonating gold
   * (#f0c050), the requiresusercontext copper (#e8b48c), the appinusedeferred olive (#c6d08e), and
   * the lastknownunverified taupe (#c9bfa8) so a self-rejecting row reads "excluded — rejects
   * updates", not "frozen", "in-flight", "needs a user context", "waiting on an app", or "aged". */
  --wep-state-selfrejecting-fg:   #ddd07a;
  --wep-state-selfrejecting-bg:   rgb(200 185 70 / 0.14);
  --wep-state-selfrejecting-rule: #c2b23c;

  /* Plan 2026-07-28-002 U7 (#1616) — joined-at-target "settling" ramp (dark). A calm slate-blue,
   * lifted for dark contrast and kept clearly cooler than every warm state ramp so a settling row
   * never reads as in-flight, frozen, excluded or failed. Distinct from the dark detonating gold
   * (#f0c050), selfrejecting mustard (#ddd07a) and offline grey. */
  --wep-state-settling-fg:   #93bde8;
  --wep-state-settling-bg:   rgb(90 150 215 / 0.14);
  --wep-state-settling-rule: #5f93c6;

  /* Plan 2026-08-09-001 U5 (#1733) — undeclared/looping SYSTEM-hollow debt ramp (dark).
   * A muted plum, brighter on dark, distinct from the dark cveblocked rose (#f57a8b, a
   * real failure), the quarantined violet (#c79df0), the withdrawnupstream slate-violet
   * (#b7add8), and the requiresusercontext copper (#e8b48c — the declared+held sibling
   * shape) so a looping row reads "stuck uninstall loop", never "failed", "quarantined",
   * "gone upstream", or "needs a user context". */
  --wep-state-uninstalllooping-fg:   #e3a4c9;
  --wep-state-uninstalllooping-bg:   rgb(190 95 150 / 0.14);
  --wep-state-uninstalllooping-rule: #bd6f9c;

  /* #1061 U3 — dev-grade serve badge, dark twin: same dashed-outline-first
   * discipline; a near-transparent bg keeps the outline the loudest element. */
  --wep-state-servedunverified-fg:   #ecc27e;
  --wep-state-servedunverified-bg:   rgb(215 160 70 / 0.10);
  --wep-state-servedunverified-rule: #c99a4e;

  --wep-color-warn-fg:   #f0c050;
  --wep-color-warn-bg:   #2e2410;
  --wep-color-danger:    #f04657;
  --wep-color-danger-fg: #ffffff;
  --wep-color-danger-bg: #c2304a;
  --wep-color-danger-bg-hover: #8a1729;

  /* Danger as TEXT (#1759). Dark's danger reads AA on the three flat surfaces
   * (5.26 bg-0 / 4.90 bg-1 / 4.55 bg-2) but FAILS on hover and selection —
   * 4.03:1 on bg-3, 4.13:1 on accent-bg — so it is not a safe text role here
   * either. #1759 copied the value from the dark cveblocked-fg of the day
   * (Nightfall's); the revert moved cveblocked-fg to v0's #f57a8b and kept
   * this (ADR 2026-09-06-001, verdict 3), as in light:
   *   #ff8a9b -> 8.01:1 on bg-1, 8.60 bg-0, 7.44 bg-2, 6.76 accent-bg,
   *              6.59:1 worst case (bg-3, hover)
   * Comfortably clear in every row state. */
  --wep-color-danger-text: #ff8a9b;

  --wep-shadow-sm:   0 1px 0 0 rgb(0 0 0 / 0.4);
  --wep-shadow-pop:  0 8px 24px -8px rgb(0 0 0 / 0.6),
                     0 2px 4px -1px rgb(0 0 0 / 0.3);
  --wep-shadow-drawer: -16px 0 32px -16px rgb(0 0 0 / 0.6);
}

/* ─────────── Density: comfortable (opt-in via [data-density="comfortable"]) ─────────── */
/* Plan 2026-07-28-001 U4 (OQ4, R6/AE4) — the density cookie's visible effect.
 * App.razor writes data-density="comfortable"|"default" on <html> from the
 * wep_density cookie (DensityPreferenceService); this block is the token-level
 * consumer. Same override pattern as [data-theme="dark"] above: a values-only
 * re-declaration after :root (attribute selector ties :root's specificity and
 * wins by source order), zero selector edits — with the attribute absent or
 * "default", the dense rendering is exactly what it was before this block.
 *
 * OQ4 DECISION — the dimensional meaning of "comfortable", anchored on
 * design.md §26 (dense = the 28px row module) and AMPLIFIED by U4b: the
 * operator live-tested §26's literal deltas (+2px vertical padding, +4px
 * row height) and they were invisible in a side-by-side, so comfortable
 * moves the step by one full 4px scale step instead:
 *
 *   --wep-space-2  8px -> 12px   The row-rhythm step. It is the VERTICAL
 *     cell padding of every .wep-table / .wep-diff row (padding: space-2
 *     space-3), so +4px top and bottom delivers +8px of row height on
 *     every padded row — clearly visible at a glance. Intra-row gaps on
 *     the same step loosen by the same 4px.
 *
 * Deliberately NOT moved (design.md prizes density over whitespace — the
 * console must loosen, never turn airy):
 *   --wep-space-3           horizontal cell padding — §26 loosens the
 *                           vertical axis only.
 *   --wep-space-4..16       page gutters, card padding, section gaps —
 *                           macro layout stays dense in both modes. This
 *                           also keeps the component-scoped comfortable
 *                           blocks (e.g. WepSettingsList's --wep-set-* knobs)
 *                           at their authored values instead of compounding.
 *   type scale              comfortable is never bigger text. U4b loosens
 *                           LEADING on the dense text surfaces (tables /
 *                           settings prose) — the strongest side-by-side
 *                           lever — but does it in the scoped comfortable
 *                           rules (_shell.css, WepSettingsList.razor.css),
 *                           never by moving the global line tokens here.
 * The fixed-height 28px row surfaces that cannot follow a spacing token get
 * the same +8px in _shell.css under this attribute (fleet-table rows); the
 * 28px control chrome (buttons, inputs, selects — chevron geometry is tuned
 * to the 28px box) deliberately stays dense. */
[data-density="comfortable"] {
  --wep-space-2: 12px;
}

/* ─────────── Reset / base ─────────── */
* { box-sizing: border-box; }
html, body { margin: 0; padding: 0; }

html {
  background: var(--wep-color-bg-0);
  color: var(--wep-color-fg-1);
  font-family: var(--wep-font-sans);
  font-size: var(--wep-size-text-md);
  line-height: var(--wep-line-snug);
  text-rendering: optimizeLegibility;
  -webkit-font-smoothing: antialiased;
}

/* Numbers and timestamps — always tabular mono, right-aligned in cells. */
.wep-num,
.wep-mono {
  font-family: var(--wep-font-mono);
  font-variant-numeric: tabular-nums;
  letter-spacing: 0;
}
.wep-num { text-align: right; }

/* Focus is always visible, never removed. The ring is a box-shadow, and
   Windows High Contrast (forced colours) sets box-shadow to none. The
   transparent outline paints nothing here, and forced colours paints it in a
   system colour, so focus stays visible when the ring cannot (#2053). Every
   rule that paints --wep-focus-ring carries the same outline, enforced by
   ForcedColorsFocusIndicatorTests. */
:focus-visible {
  outline: 2px solid transparent;
  box-shadow: var(--wep-focus-ring);
  border-radius: var(--wep-radius-xs);
}

/* Programmatic focus targets (keyboard-nav page containers with tabindex="-1")
   receive .focus() on load; they must NOT paint the focus ring, which would
   box the whole page-head/content. Real interactive controls are unaffected. */
[tabindex="-1"]:focus-visible,
[tabindex="-1"]:focus {
  box-shadow: none;
  outline: none;
}

/* Reduced-motion: every transition collapses to instant. */
@media (prefers-reduced-motion: reduce) {
  * {
    animation-duration: 1ms !important;
    transition-duration: 1ms !important;
  }
}
