/*
 * The console's entire stylesheet, deliberately one file.
 *
 * WHY IT LIVES IN public/ RATHER THAN BEING IMPORTED FROM TSX. An imported
 * stylesheet reaches the page through an injected <style> element in dev,
 * which cannot be served under `style-src 'self'` without adding
 * 'unsafe-inline'. Serving it as a static same-origin asset keeps the
 * Content-Security-Policy identical in development and in the built artifact,
 * and it means Vite's CSS pipeline (and therefore lightningcss) never runs
 * over console source at all.
 *
 * WHY ONE FILE. Every token that distinguishes one decision class from another
 * must be reviewable in one place because "these two outcomes are visually
 * distinct" is a claim about this file. Utility classes scattered through
 * markup would make that unreviewable.
 *
 * ── THE RULE THAT USED TO BE HERE, AND THE RULE THAT REPLACED IT ────────────
 *
 * IMAGERY AND MOTION ARE PERMITTED HERE, AND NEITHER MAY EVER CARRY A STATUS
 * ALONE. This replaces the "no imagery of any kind" rule this file carried until
 * 2026-08-05, which the owner lifted on that date: "we can have imagery,
 * animation sprites and flashy aesthetic thats fine". ROADMAP §8 records the
 * same decision and the same date.
 *
 * (The superseded rule's own wording is deliberately not repeated verbatim in
 * this file. The Go gate named below scans for it, because a stylesheet still
 * stating it would be describing a console that no longer exists.)
 *
 * EXACTLY ONE CONSTRAINT SURVIVED THE LIFT, and it is the part that was ever
 * load-bearing:
 *
 *   AN ICON, A SPRITE, A GRADIENT, A PULSE OR A COLOUR MAY NEVER BE THE ONLY
 *   CARRIER OF A STATUS. It layers on top of a text label that already says the
 *   same thing.
 *
 * EVD-02-A07 is about not asserting a state the evidence does not support. A
 * bare green tick meaning "verified" is exactly that assertion, made by a glyph
 * that no markup assertion and no screen reader can interrogate. A pulsing dot
 * beside the sentence "Showing data received at 2026-08-05T09:14:22Z" asserts
 * nothing on its own, and that is the whole difference.
 *
 * ENFORCEMENT IS ON BOTH SIDES OF THE RULE AND IN BOTH SUITES, AND ALL OF IT
 * CAN FAIL. Two of these live in Go deliberately: CI does not run web/console's
 * vitest suite, so a rule that lived only there would redden on a developer's
 * machine and nowhere else.
 *   - web/console/src/evidence/decision.test.tsx scans THIS FILE and rejects any
 *     `content:` declaration that is not the empty string. A shape is allowed; a
 *     glyph is not, because a ::before carrying "✓" is imagery no markup
 *     assertion would ever see. The same file's markup gate requires every
 *     element carrying a status data-attribute to contain readable text, and its
 *     `EverySeverityTreatmentIsALabelTheDetectionEngineEmits` binds the severity
 *     ramp below to the Go array that declares it.
 *   - web/console/src/incidents/screens.test.tsx strips every <svg> out of the
 *     rendered markup and fails if the status is no longer stated in words. That
 *     needs a render, which is why it is there rather than here.
 *   - tests/architecture/console_imagery_and_style_test.go fails on any <svg> in
 *     console source that is not `aria-hidden="true"` and `focusable="false"`,
 *     and fails in BOTH directions if this header's account of the rule and the
 *     console's practice disagree — a stale prohibition here, or marks drawn
 *     with no rule stated.
 *   - the same Go file fails on any unrecorded inline `style` attribute, for the
 *     Content-Security-Policy reason set out below.
 *
 * NO ASSET IS FETCHED BY THIS FILE. There is no url(), no @import, no remote
 * font and no remote origin, so `img-src 'self'` needs no widening on account of
 * anything here: every visual is a gradient, a border, a shadow or an inline
 * <svg> in markup, and an inline <svg> element is not subject to img-src at all.
 * If a future screen introduces a data: URI sprite, THAT is the change that must
 * widen img-src — and it must widen it in web/console/index.html and in
 * internal/httpapi/console.go together, or a named test reddens.
 *
 * NO ICON PACKAGE IS INSTALLED, and that half of the old rule still binds rather
 * than being a leftover: a glyph vocabulary that cannot be enumerated cannot be
 * reviewed, and lucide-react ships Lock, ShieldCheck and BadgeCheck.
 *
 * INLINE `style` ATTRIBUTES DO NOT WORK IN THIS CONSOLE. `style-src 'self'`
 * carries no 'unsafe-inline', and that blocks style ATTRIBUTES as well as
 * <style> elements — the browser drops them, nothing throws, nothing logs, and
 * the element simply draws wrong. Anything data-driven that would want a
 * computed length — the incident swimlane's bars — is quantised to a class
 * enumerated at the foot of this file instead.
 *
 * ── SINGLE COLOUR SCHEME, DELIBERATELY ─────────────────────────────────────
 *
 * This file was light-first with light-dark() tokens until 2026-08-05. It is now
 * DARK ONLY, by the same owner decision of that date — "match Darktrace more
 * closely", dark and dense — and the light theme is DROPPED, not deprecated and
 * not half-kept. `color-scheme` is `dark` rather than `light dark`, so form
 * controls and scrollbars follow rather than rendering as light widgets on a
 * near-black ground, and every token below is a single value rather than a
 * light-dark() pair.
 *
 * The reason is the one in the header above: the claim this file exists to
 * support is "these outcomes are visually distinct", and that claim has to be
 * re-made for every scheme shipped. Ten decision kinds, five severities and
 * four entity hues in two schemes is two review surfaces, and the second one is
 * the one nobody opens — and a light half that can never resolve is a value a
 * reader could spend time tuning for nothing. ROADMAP §8's superseded sentence
 * asked for the viewer's preference to be matched; the owner overrode it on
 * 2026-08-05, and a light theme kept alive without an owner asking for it is
 * exactly the half-dead theme that is worse than no theme.
 */

:root {
  color-scheme: dark;

  /* ── Ground and panels ──────────────────────────────────────────────────
     Three steps and no more. Near-black ground, panel one step up, a raised
     step for the thing under review. Matte: no gloss, no inner highlight.

     THE INCIDENT CONSOLE'S PANELS USE THESE THREE RATHER THAN A FOURTH. That
     screen arrived on 2026-08-05 with its own `--surface-panel` between the
     ground and the raised step; it is the same role this scale already names,
     so it maps onto `--surface` rather than adding a step nobody could tell
     from its neighbours. "Three steps and no more" is a claim about this
     block, and a fourth token would have quietly made it false. */
  --ground: #04060a;
  --surface: #0a0e15;
  --surface-raised: #111621;
  --surface-inset: #070a10;

  --border: #1b2231;
  --border-strong: #2c3648;
  --border-quiet: #141a26;

  --text: #e8ecf4;
  --text-quiet: #8b94a8;
  /* #5b6478 until 2026-08-19 — measured at ~3.2:1 against --surface at the
     0.72rem sizes it labels, below the 4.5:1 floor small text needs. Raised one
     careful step: still clearly quieter than --text-quiet, now readable at the
     sizes this file actually sets it. */
  --text-faint: #78829b;

  /* Absence is styled as absence: quiet, italic, and never mistakable for a
     value. It is deliberately NOT the same colour as text. */
  --absent-text: #737d91;

  /* ── The house accent ──────────────────────────────────────────────────
     One gradient, used for focus, for the active tab and for the incident
     window's edge glow. It is decoration and it never means anything.

     IT IS THE BRAND PHOSPHOR AS OF 2026-08-18, and it used to be violet. The
     mark in web/brand is seven redaction bars in this gradient, and a console
     whose chrome shared no hue with its own logo read as a different product.

     THE CONSTRAINT THAT COMES WITH THAT CHANGE, and it is load-bearing rather
     than taste. This hue family now overlaps `--severity-high` (#f0873c) and
     sits near `--severity-critical`. The block below says conflating severity
     with anything else "would let a colour imply an outcome", and an accent
     that looked like a severity would do exactly that.

     So the two are separated by WEIGHT, not by hue:

       SEVERITY owns full chroma. Filled chips, solid dots, the wash behind a
       row. If a thing is saturated orange, it is a severity.

       THE ACCENT owns everything under about 25% -- washes, hairline borders,
       focus rings, edge glows, the active tab's underline. It never fills a
       chip and never draws a solid dot.

     A future edit that gives the accent a filled treatment has not made the
     console prettier; it has made a decoration indistinguishable from a
     severity, on a screen where severity is the thing being looked for.

     THE VENDOR PLANE DOES NOT USE THIS ACCENT. ~~It runs violet~~ IT RUNS
     CRIMSON ON NEAR-BLACK WITH BONE TYPE, and it does not share this file:
     web/vendor-console/public/vendor-console.css is its whole stylesheet.
     Invariant 11 separates the platform and tenant planes, and an operator
     should not have to read a breadcrumb to know which one is on screen.

     The violet is struck rather than overwritten because this line named a
     colour nothing had drawn: no vendor stylesheet existed when it was written,
     so it described an intention. It became FALSE on 2026-08-18, when
     web/vendor-console shipped in crimson, and it is corrected here in the same
     commit rather than left to be read as current state.

     CRIMSON WOULD BE A DEFECT IN THIS FILE AND IS NOT ONE IN THAT ONE, which is
     the part worth carrying across: this console renders the five-level
     severity ramp below, whose top two levels are red and orange, so a crimson
     chrome here would be a decoration in the hue family the reader is scanning
     for. That plane renders consent states, counts and bands and has no
     severity at all -- and
     tests/architecture/vendor_console_test.go's
     TestNoVendorConsoleModuleRendersASeverity reads `severityLabels` out of
     internal/detection/risk.go and reddens if one of those words reaches a
     vendor screen, so the condition that makes its palette safe is checked
     rather than assumed. */
  --accent: #ff7a3c;
  --accent-deep: #c43b14;
  --accent-hot: #ffc08a;

  /* The brand phosphor itself, for the mark and the wall motif. Same family as
     the accent and named separately because the mark is not chrome: it is the
     product's signature and must not drift when the accent is retuned. */
  --phos: #ff5a2b;
  --phos-2: #ff9152;
  --phos-3: #ffc08a;

  /* A cool counterweight, for measurement rather than status: coverage
     denominators, counts, the "this is what we can see" register. It is off the
     severity ramp on purpose, so a figure can be emphasised without being
     mistaken for a level. */
  --signal: #4de0e8;

  /* ── Severity ───────────────────────────────────────────────────────────
     FIVE LEVELS, AND THEY ARE THE PRODUCT'S OWN. `internal/detection/risk.go`
     declares `severityLabels = {"none","low","medium","high","critical"}` and
     `SeverityLabelOf()` emits exactly those; this ramp is that array and
     nothing else. `EverySeverityTreatmentIsALabelTheDetectionEngineEmits`
     reads that Go array at test time and binds the two together in BOTH
     directions, so a level the engine drops and a level the stylesheet invents
     each redden by name.

     CORRECTED 2026-08-05 AFTER AN ADVERSARIAL REVIEW. This ramp previously
     read `critical / suspicious / informational`, and nothing checked it
     against anything. Two of those three were real words from two DIFFERENT
     scales that are never combined — `critical` from the detection engine's,
     `informational` from the review schemas' — and `suspicious` was not a
     severity anywhere in this repository. It is the reference vendor's word,
     read off their screenshots. The owner's instruction of 2026-08-05 was
     that we follow the design philosophy and not the vocabulary, and this was
     the precise failure that instruction was given to prevent. Worse, the four
     labels the engine really emits below `critical` had no treatment at all,
     so a real severity would have rendered as the default.

     There IS a second severity scale in this repository — the review schemas'
     `critical / high / medium / low / informational`
     (`conformance/security-review-summary.schema.json` and the RC1, pilot and
     disclosure records). THE CONSOLE DOES NOT RENDER IT. It describes reviews
     OF this codebase, is consumed by release engineering, and appears on no
     console screen and in no console decoder. Styling it here would put two
     scales behind one attribute and let a colour mean two things.

     These are NOT the decision-kind tokens below: severity says how loudly to
     look, a decision kind says what the policy did. Conflating them would let
     a colour imply an outcome.

     `none` is styled rather than left to fall through. An unstyled level is
     indistinguishable from a level nobody thought about, and "no severity" is
     a real answer the engine returns for empty input. */
  --severity-critical: #f2565f;
  --severity-critical-wash: rgba(242, 86, 95, 0.13);
  --severity-high: #f0873c;
  --severity-high-wash: rgba(240, 135, 60, 0.13);
  --severity-medium: #e2a33c;
  --severity-medium-wash: rgba(226, 163, 60, 0.13);
  --severity-low: #57a9e8;
  --severity-low-wash: rgba(87, 169, 232, 0.13);
  --severity-none: #7f8896;
  --severity-none-wash: rgba(127, 136, 150, 0.13);

  /* ── Inline entity hues, for log rows ──────────────────────────────────
     An enrolment, a policy rule and a network address each get their own hue
     inside a sentence so the eye can find the same kind of thing twice. The
     hue is redundant with the word it colours — the word is still there and
     still says what it is. */
  --entity-enrolment: #74d3a6;
  --entity-rule: #8fb5ff;
  --entity-address: #f0a868;
  --entity-connector: #c79ff0;

  /* Outcome tokens are keyed to what happened to the content, not to any
     assurance about it. There is no "good" colour here on purpose: an
     allow/log outcome means raw content left the surface, which is not a
     success state to be coloured green. */
  --raw-egress-edge: #e0a53c;
  --interaction-edge: #b58ce0;
  --transformation-edge: #6cb6e6;
  --mandatory-edge: #ec7ad8;
  --hold-edge: #d4b45c;
  --blocked-edge: #ef7c81;

  --font: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  --mono: ui-monospace, "Cascadia Mono", "Segoe UI Mono", monospace;

  /* ── Density ────────────────────────────────────────────────────────────
     Density comes from consistent small type, tight leading and restrained
     padding — not from shrinking things until they are cramped. One row
     rhythm, used by every table and every list on every screen. */
  --row-y: 0.3rem;
  --row-x: 0.55rem;
  --gutter: 0.85rem;
  --radius: 9px;
  --radius-small: 5px;
}

* {
  box-sizing: border-box;
}

body {
  margin: 0;
  background: var(--ground);
  color: var(--text);
  font-family: var(--font);
  font-size: 13.5px;
  line-height: 1.45;
  -webkit-font-smoothing: antialiased;
}

/* Anything numeric lines up. Applied at the root rather than per-table so a new
   screen inherits it instead of remembering it. */
body,
table,
time,
code,
.identifier,
[data-severity],
[data-live-status] {
  font-variant-numeric: tabular-nums;
}

/* ── The app frame ──────────────────────────────────────────────────────────
   THE SHELL WAS A HORIZONTAL HEADER BAR UNTIL 2026-08-19, SECOND PASS OF THAT
   DATE. The owner's design sheet — the "RedactWall Phosphor" page — frames the
   console as a chamfered two-column app instead: a 212px rail carrying the
   brand row and the grouped navigation, and a pane whose top bar names the
   screen. This block is that frame; the rail and top bar follow it.

   THE CHAMFER IS A clip-path AND NOT AN IMAGE, so nothing here fetches and
   nothing here widens img-src. Two corners are cut — top-left and
   bottom-right, the design sheet's own asymmetry — and the cut is a fixed
   10px whatever the frame's size.

   ONE DEVIATION FROM THE SHEET, RECORDED RATHER THAN SLIPPED IN: the sheet's
   page wrapper is 1360px, sized for a specimen page showing one grid of
   demonstration cards. This console's evidence and fleet tables are real and
   dense, so the frame runs to 1560px before it stops growing — the sheet's
   frame at the sheet's width would put the evidence list's fixed columns
   into permanent horizontal scroll. */
.console-frame {
  max-width: 1560px;
  margin: 0.9rem auto 3rem;
  display: grid;
  grid-template-columns: 212px minmax(0, 1fr);
  background: var(--surface);
  border: 1px solid var(--border-strong);
  box-shadow:
    0 0 0 1px rgba(255, 90, 43, 0.07),
    0 30px 80px -30px rgba(0, 0, 0, 0.9);
  clip-path: polygon(
    10px 0,
    100% 0,
    100% calc(100% - 10px),
    calc(100% - 10px) 100%,
    0 100%,
    0 10px
  );
}

/* The rail: the darkest surface in the frame, one hairline from the pane. It
   is sticky rather than fixed so the frame's own border and chamfer stay one
   box; on a page taller than the viewport the rail holds still and scrolls
   its own overflow. */
.console-rail {
  background: var(--surface-inset);
  border-right: 1px solid var(--border);
  padding: 0.9rem 0 1.1rem;
  position: sticky;
  top: 0;
  align-self: start;
  max-height: 100vh;
  overflow-y: auto;
  min-width: 0;
}

.console-pane {
  min-width: 0;
  display: flex;
  flex-direction: column;
}

/* The top bar: the pane's own header, raised one step, carrying the crumb on
   the left and the session control on the right. position anchors the wall
   motif drawn by ::after below. */
.console-topbar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.4rem 1rem;
  padding: 0.6rem 1.1rem 0.8rem;
  background: var(--surface-raised);
  border-bottom: 1px solid var(--border);
  position: relative;
}

.console-crumb {
  font-size: 0.8rem;
  color: var(--text-quiet);
  margin-right: auto;
}

.console-crumb b {
  color: var(--text);
  font-weight: 600;
}

/* The reader's clock, mono and quiet, honest by its second line. */
.console-clock {
  font-family: var(--mono);
  font-size: 0.7rem;
  color: var(--text-faint);
  text-align: right;
  line-height: 1.3;
  display: flex;
  flex-direction: column;
}

.console-clock time {
  color: var(--text-quiet);
}

.console-clock-label {
  font-size: 0.58rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}

/* The design sheet's segmented control: one pill group, the pressed member
   filled at wash weight. The pressed state is aria-pressed in the markup;
   the words that say what it changed are the panel's own qualifiers, which
   derive their interval from the response. */
.window-selection {
  display: inline-flex;
  border: 1px solid var(--border-strong);
  border-radius: 999px;
  overflow: hidden;
  margin: 0.2rem 0 0.4rem;
}

.window-selection button {
  background: transparent;
  border: 0;
  border-radius: 0;
  color: var(--text-quiet);
  font-size: 0.74rem;
  padding: 0.24rem 0.75rem;
}

.window-selection button:hover:not(:disabled) {
  background: rgba(255, 255, 255, 0.03);
  border-color: transparent;
}

.window-selection button[aria-pressed='true'] {
  background: rgba(255, 90, 43, 0.16);
  color: var(--phos-3);
}

.console-topbar [data-render-faults] {
  margin: 0;
  font-size: 0.75rem;
  color: var(--text-quiet);
}

main {
  padding: 1rem 1.1rem 2.5rem;
  min-width: 0;
}

/* Every screen's top-level sections read as the design sheet's cards: raised
   one step off the pane, one hairline border, and their own inner padding.
   The selector is deliberately shallow — the incident console and the
   overview arrange their OWN cards inside a div and are not re-carded here,
   and panels nested inside a card (the inventory's estates, the coverage
   well) keep their existing surface-and-inset steps, which now read as wells
   inside the card rather than boxes on a bare page. The sign-in forms are
   not re-carded: their own rules already draw them as cards. */
main > section {
  background: var(--surface-raised);
  border: 1px solid var(--border);
  padding: 0.9rem 1.1rem 1.1rem;
  margin: 0 0 0.8rem;
  min-width: 0;
}

main > section > h2:first-child {
  margin-top: 0;
}

/* Nesting must stay legible: a card's FIRST h2 is its title; any later h2 on
   the same card (the evidence detail's Decision / Derivation / Findings) is
   a section of it and steps down half a register. */
main > section > h2:not(:first-child) {
  font-size: 0.88rem;
}

/* The note register: every loose paragraph of prose at a card's top level.
   Status lines keep their own rule and notices their own box; everything
   else that explains rather than measures drops one register, so the data
   above it leads. The words are all still here. */
main > section > p:not([data-live-status]):not(.notice) {
  font-size: 0.78rem;
  color: var(--text-quiet);
  line-height: 1.5;
  max-width: 74ch;
}

/* ── Fine print: progressive disclosure, not deletion ──────────────────────
   The third tier of the design sheet's hierarchy — summary, context, detail
   on demand — carried by the NATIVE <details> element: no script under this
   CSP, keyboard and screen-reader semantics for free, and every collapsed
   word still in the markup, where the word-scanning tests and any reader
   who opens it find the same sentences. The summary line is always a
   headline of what is inside ("what that denominator leaves out"), so the
   collapsed state still states THAT a caveat exists beside the figure it
   qualifies — only the enumeration waits for the click. */
details.fine-print {
  margin: 0.15rem 0 0.55rem;
}

details.fine-print summary {
  cursor: pointer;
  width: fit-content;
  font-size: 0.7rem;
  font-weight: 600;
  letter-spacing: 0.03em;
  color: var(--text-faint);
  padding: 0.1rem 0.2rem 0.1rem 0;
}

details.fine-print summary::marker {
  color: var(--accent);
}

details.fine-print summary:hover {
  color: var(--text-quiet);
}

details.fine-print[open] summary {
  color: var(--text-quiet);
  margin-bottom: 0.2rem;
}

details.fine-print p {
  font-size: 0.75rem;
  color: var(--text-quiet);
  line-height: 1.5;
  max-width: 74ch;
  margin: 0.2rem 0 0.3rem;
}

/* ── The brand row, 2026-08-19 (moved into the rail the same day) ──────────
   The shell's identity was a bare <h1> until this date; the mark reached the
   browser tab and nothing on the page. It is now the rail's brand row — the
   same construction the vendor plane's rail carries (mark, mono-uppercase
   wordmark) in a deliberately different hue, so the two planes stay
   tellable-apart in a cropped screenshot, which is the argument both
   stylesheets already record against sharing chrome. The plane's NAME moved
   to the top bar's crumb, where the design sheet reads it out beside the
   screen's own name.

   THE HUE QUESTION IS ANSWERED HERE RATHER THAN ASSUMED, because the vendor
   stylesheet's own header says a signature hue is safe only where the severity
   ramp cannot be confused with it, and THIS console renders that ramp, whose
   top two levels are red and orange. The answer is the accent block's standing
   weight rule, unchanged: severity keeps full chroma, and everything the
   brand row draws stays under the ~25% ceiling — the wordmark is TEXT in the
   phosphor's pale step, the wall motif is a wash, and the one full-strength
   phosphor on screen is the mark itself, which is the product's signature
   (the tokens block already separates --phos from the accent for exactly
   this reason: "the mark is not chrome"). */
.console-identity {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  padding: 0 0.9rem 0.85rem;
  border-bottom: 1px solid var(--border-quiet);
  margin-bottom: 0.7rem;
}

.console-mark {
  display: block;
  flex: none;
  /* The served favicon carries the gradient-and-bloom original; the inline
     chrome mark flattens each bar to one fill so it needs no defs, and takes
     the phosphor glow from a drop shadow instead of a filter element. */
  filter: drop-shadow(0 0 5px rgba(255, 90, 43, 0.35));
}

.console-mark .mark-bar {
  fill: var(--phos);
  opacity: 0.82;
}

.console-mark .mark-bar-hot {
  fill: var(--phos-2);
}

.console-identity h1 {
  font-family: var(--mono);
  text-transform: uppercase;
  letter-spacing: 0.14em;
  font-size: 0.7rem;
  line-height: 1.2;
  /* The sheet's wordmark treatment at rail scale: the phosphor gradient
     clipped to the glyphs, with a soft cast behind them. The colour line
     under it is the fallback for a renderer without clipped backgrounds —
     the gradient paints over it where supported. */
  color: var(--phos-2);
  background: linear-gradient(160deg, var(--phos-3), var(--phos));
  -webkit-background-clip: text;
  background-clip: text;
  -webkit-text-fill-color: transparent;
  text-shadow: 0 0 18px rgba(255, 90, 43, 0.28);
}

/* ── The wall motif, drawn the sheet's way ──────────────────────────────────
   The tokens block has named "the mark and the wall motif" as the phosphor's
   two jobs since 2026-08-18, and until 2026-08-19 no rule drew a wall — the
   documentation audit recorded it as a stated intention with zero
   implementation. The first draw put a dashed strip along the top bar's
   bottom edge, and the frame-pass review measured what that had become: the
   same position and rhythm as the VENDOR plane's hazard rule, on the one
   edge the two planes must never share a device. So the wall now stands
   where the design sheet builds it — a strip of redaction bricks at the foot
   of the rail, gapped 2px, wash-weight phosphor with every third brick
   heavier and every fourth fainter. It is decoration, it carries nothing,
   and no severity register touches it. */
.console-wall {
  display: flex;
  gap: 2px;
  height: 16px;
  align-items: flex-end;
  margin: 1.1rem 0.9rem 0;
}

.console-wall span {
  flex: 1;
  background: linear-gradient(180deg, var(--phos), rgba(196, 59, 20, 0.9));
  opacity: 0.28;
}

.console-wall span:nth-child(3n) {
  opacity: 0.5;
}

.console-wall span:nth-child(4n) {
  opacity: 0.16;
}

/* ── The scanline and the phosphor haze ─────────────────────────────────────
   The design sheet's CRT treatment, in its own words "purely decorative,
   carries no status": a one-pixel-in-three scanline sits over the whole
   viewport at 1.4% white, and a phosphor haze breathes up from the top-right
   corner. Both are fixed, both ignore the pointer, and both sit ABOVE the
   content deliberately — a scanline behind the page is a scanline nothing
   shows through, and at these alphas nothing underneath loses a step of
   contrast. Until this second 2026-08-19 pass the atmosphere was a single
   radial BEHIND the content; the sheet's version replaced it rather than
   joining it. The haze here is phosphor only: the sheet's page also carried
   a crimson corner because it demonstrated both planes on one page, and this
   plane must never wear the vendor's colour. */
body::before {
  content: '';
  position: fixed;
  inset: 0;
  pointer-events: none;
  z-index: 99;
  background: repeating-linear-gradient(
    to bottom,
    rgba(255, 255, 255, 0.014) 0 1px,
    transparent 1px 3px
  );
}

body::after {
  content: '';
  position: fixed;
  inset: 0;
  pointer-events: none;
  z-index: 98;
  background: radial-gradient(
    120% 60% at 82% 0%,
    rgba(255, 90, 43, 0.09),
    transparent 60%
  );
}

#console-root {
  position: relative;
  z-index: 1;
}

h1 {
  font-size: 0.95rem;
  margin: 0;
  font-weight: 650;
  letter-spacing: 0.01em;
}

/* ── The type registers, retuned 2026-08-20 ────────────────────────────────
   The owner read the finished screens and named the remaining weight problem:
   everything spoke at one volume, so the pages felt cumbersome even with the
   instruments in. The fix is REGISTERS, not deletion — the same words, at
   the loudness their job earns:

     TITLES read as titles: sentence case, bright, largest thing on the card.
     (h2 was quiet uppercase chrome until this date — a title dressed as a
     label is why every screen led with a whisper.)
     DATA leads: counts mono and cyan, labels small caps, tables unchanged.
     EXPLANATIONS recede: the note register — small, quiet, measured line —
     for every loose paragraph of prose. Nothing is removed and nothing is
     clamped; a sentence a reader must be able to find is still on the page,
     one register down from the figure it qualifies.
     METADATA is mono and faint: source operations, freshness lines — true,
     present, and visibly not the content. */
h2 {
  font-size: 1.02rem;
  margin: 1.1rem 0 0.35rem;
  font-weight: 700;
  letter-spacing: 0.005em;
  color: var(--text);
}

h3 {
  font-size: 0.85rem;
  margin: 0.9rem 0 0.3rem;
  font-weight: 650;
}

p {
  margin: 0.35rem 0;
}

a {
  color: inherit;
}

/* ── The rail navigation ────────────────────────────────────────────────────
   The pills the nav wore until 2026-08-19's second pass are gone with the
   horizontal header that carried them; the design sheet's rail treatment
   replaces both. Rows, not pills: each destination is a full-width row with
   a two-pixel left edge, and the one the operator is ON fills with a phosphor
   gradient that fades to nothing and lights that edge. The active row is
   selected by `aria-current`, which react-router's NavLink puts in the
   MARKUP, so the treatment renders a fact the accessibility tree already
   carries rather than making a second, independent claim about it — and the
   active row's text is also the only one at full contrast, so the fill never
   does the work alone.

   THE COUNT LESSON FROM THE OLD COMMENT SURVIVES ITS SUBJECT: this prose
   states no destination count, because two corrections (2026-08-05,
   2026-08-19) each went stale the same way. The nav renders every route the
   table declares, and the table is the count.

   The group labels between rows are the design sheet's reading order —
   evidence, discovery, control, settings — mono, uppercase, faint, and not
   links. The icons are sixteen-unit geometry in currentColor beside each
   row's words; they are aria-hidden in the markup and they carry nothing. */
.console-rail nav {
  display: flex;
  flex-direction: column;
  gap: 1px;
}

.console-rail nav a {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  padding: 0.42rem 0.9rem;
  border-left: 2px solid transparent;
  color: var(--text-quiet);
  text-decoration: none;
  font-size: 0.82rem;
  font-weight: 550;
  transition: background-color 140ms ease, color 140ms ease, border-color 140ms ease;
}

.console-rail nav a:hover {
  background: rgba(255, 255, 255, 0.025);
  color: var(--text);
}

.console-rail nav a[aria-current] {
  background: linear-gradient(90deg, rgba(255, 90, 43, 0.14), transparent);
  border-left-color: var(--phos);
  color: var(--text);
}

.console-nav-icon {
  width: 14px;
  height: 14px;
  flex: none;
  opacity: 0.85;
}

.console-nav-group {
  display: block;
  font-family: var(--mono);
  font-size: 0.6rem;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--text-faint);
  padding: 0.9rem 0.9rem 0.3rem;
}

/* Below the sheet's own breakpoint the frame stacks: the rail becomes a top
   block and its navigation wraps. The sheet HIDES the rail at this width,
   which a specimen page can afford and a real console cannot — the rail is
   the only navigation there is. */
@media (max-width: 960px) {
  .console-frame {
    grid-template-columns: minmax(0, 1fr);
    margin: 0;
  }

  .console-rail {
    position: static;
    max-height: none;
    border-right: none;
    border-bottom: 1px solid var(--border);
    padding: 0.7rem 0 0.5rem;
  }

  .console-rail nav {
    flex-direction: row;
    flex-wrap: wrap;
    gap: 0.1rem 0.35rem;
    padding: 0 0.6rem;
  }

  .console-rail nav a {
    border-left: none;
    padding: 0.3rem 0.55rem;
  }

  .console-nav-group {
    width: 100%;
    padding: 0.55rem 0.35rem 0.15rem;
  }
}

:focus-visible {
  /* The hot step of the accent, consumed at last (it was declared 2026-08-18
     and used by nothing): a focus ring is the accent family's job under the
     weight rule, and the pale step reads over every surface this file draws. */
  outline: 2px solid var(--accent-hot);
  outline-offset: 2px;
}

button {
  font: inherit;
  font-size: 0.83rem;
  color: var(--text);
  background: var(--surface-raised);
  border: 1px solid var(--border);
  border-radius: var(--radius-small);
  padding: 0.25rem 0.65rem;
  cursor: pointer;
  transition: background-color 140ms ease, border-color 140ms ease;
}

button:hover:not(:disabled) {
  background: #23232f;
  border-color: var(--border-strong);
}

button:disabled {
  cursor: not-allowed;
  color: var(--text-faint);
  background: var(--surface);
}

/* ── Tables ────────────────────────────────────────────────────────────────
   One geometry for every table on the console. The evidence list, the hold
   queue and the audit tally are the same kind of object — rows of identifiers
   and counts — and a reader who has learned one must not have to learn another.
   Zebra striping is deliberately absent: a striped row and a severity wash
   would compete, and severity has to win. */
table.evidence-list,
table.hold-queue,
table.audit-tally,
table.fleet-agents,
table.fleet-operations,
table.module-entitlements,
table.policy-resolutions,
table.tool-rollup,
table.server-rollup,
table.scans,
section.observed-running table,
section.mcp-server-estate table,
section.guarded-invocations table {
  width: 100%;
  border-collapse: collapse;
  font-size: 0.83rem;
}

table.audit-tally {
  margin-top: 0.6rem;
}

table.evidence-list th,
table.evidence-list td,
table.hold-queue th,
table.hold-queue td,
table.audit-tally th,
table.audit-tally td,
table.fleet-agents th,
table.fleet-agents td,
table.fleet-operations th,
table.fleet-operations td,
table.module-entitlements th,
table.module-entitlements td,
table.policy-resolutions th,
table.policy-resolutions td,
table.tool-rollup th,
table.tool-rollup td,
table.server-rollup th,
table.server-rollup td,
table.scans th,
table.scans td,
section.observed-running table th,
section.observed-running table td,
section.mcp-server-estate table th,
section.mcp-server-estate table td,
section.guarded-invocations table th,
section.guarded-invocations table td {
  text-align: left;
  border-bottom: 1px solid var(--border-quiet);
  padding: var(--row-y) var(--row-x);
  vertical-align: top;
}

table.evidence-list th,
table.hold-queue th,
table.audit-tally thead th,
table.fleet-agents thead th,
table.fleet-operations thead th,
table.module-entitlements thead th,
table.policy-resolutions thead th,
table.tool-rollup thead th,
table.server-rollup thead th,
table.scans thead th,
section.observed-running table thead th,
section.mcp-server-estate table thead th,
section.guarded-invocations table thead th {
  font-weight: 600;
  color: var(--text-faint);
  font-size: 0.72rem;
  text-transform: uppercase;
  letter-spacing: 0.07em;
  background: var(--surface);
  border-bottom-color: var(--border);
  position: sticky;
  top: 0;
}

table.evidence-list tbody tr,
table.hold-queue tbody tr,
table.fleet-agents tbody tr,
table.fleet-operations tbody tr,
table.module-entitlements tbody tr,
table.policy-resolutions tbody tr,
table.tool-rollup tbody tr,
table.server-rollup tbody tr,
table.scans tbody tr,
section.observed-running table tbody tr,
section.mcp-server-estate table tbody tr,
section.guarded-invocations table tbody tr {
  transition: background-color 120ms ease;
}

table.evidence-list tbody tr:hover,
table.hold-queue tbody tr:hover,
table.fleet-agents tbody tr:hover,
table.fleet-operations tbody tr:hover,
table.module-entitlements tbody tr:hover,
table.policy-resolutions tbody tr:hover,
table.tool-rollup tbody tr:hover,
table.server-rollup tbody tr:hover,
table.scans tbody tr:hover,
section.observed-running table tbody tr:hover,
section.mcp-server-estate table tbody tr:hover,
section.guarded-invocations table tbody tr:hover {
  background: var(--surface);
}

/* The content-match cell carries a SENTENCE rather than a token, and it is
   bounded rather than shortened. The sentence is the whole reason the column is
   permissible: it is what stops "no match on this page" being read as "no
   duplicate", when the reference is keyed per installation and rotates. Cutting
   it to fit a column would be deciding the limit matters less than the layout.

   NO COLOUR CARRIES THE MATCH STATE. `data-content-match` is in the console's
   STATUS_ATTRIBUTES list, and every element carrying it encloses the words that
   say which state it is in — EVD-02-A07, checked by
   `statusesWithoutTextCarrier` over the rendered markup. */
table.evidence-list td [data-content-match] {
  display: block;
  max-width: 44ch;
  color: var(--text-quiet);
}

table.evidence-list td [data-content-match] strong {
  color: var(--text);
}

/* THE CAPTION IS VISIBLE AND STAYS VISIBLE. It carries the sentence that stops
   the numbers being read as coverage — "a count over the entries examined here,
   not over the chain" — and a caption hidden for layout would be a caveat
   removed by a stylesheet. */
table.audit-tally caption,
table.tool-rollup caption,
table.server-rollup caption,
table.scans caption,
table.fleet-agents caption,
table.fleet-operations caption,
table.policy-resolutions caption,
table.module-entitlements caption,
section.observed-running table caption,
section.mcp-server-estate table caption,
section.guarded-invocations table caption {
  text-align: left;
  color: var(--text-quiet);
  font-size: 0.78rem;
  padding-bottom: 0.4rem;
}

/* The selection column is bounded so a settled row's absence sentence cannot
 * widen it past the identifiers beside it. It wraps instead. */
table.hold-queue th:first-child,
table.hold-queue td:first-child {
  width: 9rem;
  vertical-align: top;
}

table.hold-queue td[colspan] {
  vertical-align: top;
}

/* Absence is styled as absence everywhere it appears. Neither a checkpoint this
   run could not read nor a break with no comparison may look like a value. */
.absent,
[data-absent='true'] {
  color: var(--absent-text);
  font-style: italic;
}

code,
.identifier {
  font-family: var(--mono);
  font-size: 0.85em;
  overflow-wrap: anywhere;
  color: #c9cddd;
}

/* ── Decision classes ──────────────────────────────────────────────────────
   One visual treatment per decision class. This is the block a design review
   reads when checking the classes are distinguishable; §5.2 of the console plan
   is explicit that types cannot enforce this and it stays a review obligation.

   THE MIX THIS IS TUNED FOR IS THE ONE THAT IS COMING, NOT TODAY'S. The browser
   sensor currently refuses every warn instead of asking, and those refusals are
   never recorded, so today's evidence stream is almost entirely hard stops. Once
   the acknowledgement affordance lands, measured populations put warn refusals
   at 33% of ordinary prose and 70% of source code, which makes mandatory stops
   roughly 1-2% of rows. At a fixed page size of 50 that is zero or one
   interesting row per page.

   There is no filter and no sort to compensate with, deliberately: the request
   schema has none, because each would be a restricted-disclosure surface
   needing its own review, and a client-side filter over one already-fetched
   page is worse than none -- it hides rows from a count the user can see
   without saying so. So the ROW has to carry it, which is why
   `mandatory_transformation` gets the loudest treatment in this file and does
   NOT share one with an ordinary configured redaction. Those two differ by
   exactly the field a careless renderer drops.

   EDGE WIDTH, EDGE STYLE AND WEIGHT ARE ALL IN PLAY, and colour is never alone:
   every one of these rows also spells its decision kind in words in a cell. */
[data-decision-kind] {
  border-left: 4px solid var(--border-strong);
  padding-left: 0.6rem;
}
[data-decision-kind='raw_release_permitted_by_allow'],
[data-decision-kind='raw_release_recorded_by_log'] {
  border-left-color: var(--raw-egress-edge);
  border-left-style: double;
  border-left-width: 6px;
}
/* The user declined. The interaction fired and stopped there, so this is the
   quiet member of the pair. */
[data-decision-kind='warn_blocked_by_user'],
[data-decision-kind='justify_blocked_by_user'] {
  border-left-color: var(--interaction-edge);
  border-left-style: dashed;
}
/* The user proceeded, and the raw value left BY THEIR CHOICE. It shares the
   raw-egress edge with allow and log rather than the interaction edge, because
   what a reviewer needs to find in a page of fifty rows is "raw content left",
   not "which control produced it". Grouping these two with the declined pair --
   which is what one `interaction_required` treatment did -- puts a raw release
   and a refusal behind the same visual mark. */
[data-decision-kind='warn_sent_by_user'],
[data-decision-kind='justified_by_user'] {
  border-left-color: var(--raw-egress-edge);
  border-left-style: double;
  border-left-width: 6px;
}
[data-decision-kind='transformation_required_by_redact'] {
  border-left-color: var(--transformation-edge);
  border-left-style: solid;
}
/* The needle. Widest edge, its own colour, a wash behind the row and the only
   rule in this file that also weights the text, so one mandatory stop is
   findable in a page of fifty warns without a filter. */
[data-decision-kind='mandatory_transformation'] {
  border-left-color: var(--mandatory-edge);
  border-left-style: solid;
  border-left-width: 10px;
  font-weight: 600;
  background: rgba(236, 122, 216, 0.07);
}
/* The second needle, and it is deliberately NOT the first one's rule and not
   the ordinary hold's either. It carries the mandatory edge and the mandatory
   wash, because a reviewer scanning for hard stops must find this row among
   them; it keeps the hold's dotted style, because the operation is also sitting
   in a queue with a decision owed by a person, and a reviewer scanning for
   holds must find it there too. Sharing a rule with either neighbour would put
   one of those two facts behind the other's visual mark. */
[data-decision-kind='mandatory_hold_required'] {
  border-left-color: var(--mandatory-edge);
  border-left-style: dotted;
  border-left-width: 10px;
  font-weight: 600;
  background: rgba(236, 122, 216, 0.07);
}
[data-decision-kind='hold_required'] {
  border-left-color: var(--hold-edge);
  border-left-style: dotted;
  border-left-width: 6px;
}
[data-decision-kind='blocked'] {
  border-left-color: var(--blocked-edge);
  border-left-style: solid;
  border-left-width: 6px;
}

/* ── Live status: the one animated indicator ───────────────────────────────
   Five states, each already rendered as a complete sentence by
   src/evidence/status-line.tsx. This adds a dot in front of the sentence and
   makes exactly one of the five breathe.

   THE DOT NEVER CARRIES THE STATE. `content: ''` is an empty box with a
   background — there is no glyph in it and there is no text in it, by design,
   and the stylesheet gate rejects a `content:` that is anything else. Remove
   every rule below and the five states still read correctly, because the
   sentence beside the dot is the carrier and always was.

   ONLY `healthy` PULSES, and it pulses because it is the one state that is
   making a live claim. A failed read does not get an attention-seeking
   animation: it gets a red dot that sits still, because motion on a failure is
   decoration pretending to be urgency. */
[data-live-status] {
  /* The metadata register: freshness is a fact ABOUT the data, so it reads
     as a stamp — mono, small, faint — rather than competing with the data.
     0.82rem sans until the 2026-08-20 register pass. */
  font-size: 0.7rem;
  font-family: var(--mono);
  color: var(--text-faint);
  /* display: flex until 2026-08-19 — flex made every text node its own item,
     so the sentence rendered with the gap-width space before its own full stop
     on every screen. Block flow puts the dot inline and lets the sentence be a
     sentence. */
  display: block;
  padding: 0.28rem 0.55rem;
  border: 1px solid var(--border-quiet);
  border-radius: var(--radius-small);
  background: var(--surface);
}

/* A failed or stale freshness line is a warning, not a stamp: it keeps the
   quiet sans register and its stronger border so it cannot be skimmed past
   as metadata. */
[data-live-status='failed'],
[data-live-status='stale'] {
  font-family: var(--font);
  font-size: 0.8rem;
  color: var(--text-quiet);
}

[data-live-status]::before {
  content: '';
  display: inline-block;
  width: 7px;
  height: 7px;
  border-radius: 50%;
  vertical-align: middle;
  margin-right: 0.45rem;
  background: var(--text-faint);
}

[data-live-status='healthy']::before {
  /* The signal register, not a green: the design sheet's "ok" hue is the
     measurement cyan, and green appears nowhere in its palette. This dot wore
     --entity-enrolment until 2026-08-19's frame pass — a hue from the entity
     register doing a status-adjacent job — and the sheet's own assignment is
     the better one: freshness is a measurement, not an entity. */
  background: var(--signal);
  animation: rw-breathe 2.4s ease-in-out infinite;
}

[data-live-status='stale']::before {
  background: var(--severity-medium);
}

[data-live-status='failed']::before {
  background: var(--severity-critical);
}

[data-live-status='failed'] {
  border-color: rgba(242, 86, 95, 0.35);
  background: var(--severity-critical-wash);
  color: var(--text);
}

[data-live-status='unavailable']::before,
[data-live-status='disabled']::before {
  background: transparent;
  border: 1px solid var(--text-faint);
}

@keyframes rw-breathe {
  0%,
  100% {
    opacity: 1;
    box-shadow: 0 0 0 0 rgba(116, 211, 166, 0.45);
  }
  50% {
    opacity: 0.55;
    box-shadow: 0 0 0 4px rgba(116, 211, 166, 0);
  }
}

/* ── Notices ───────────────────────────────────────────────────────────────*/
.notice {
  border: 1px solid var(--border);
  border-left-width: 3px;
  border-left-color: var(--border-strong);
  background: var(--surface);
  padding: 0.55rem 0.75rem;
  margin: 0.7rem 0;
  border-radius: var(--radius-small);
  /* The note register: a notice explains, so it speaks one step down and
     lets its own <strong> phrases carry the load-bearing words at full
     contrast. */
  font-size: 0.78rem;
  color: var(--text-quiet);
  line-height: 1.5;
}

.notice strong {
  color: var(--text);
}

.note {
  color: var(--text-quiet);
  font-size: 0.82rem;
}

/* ── Definition grids ──────────────────────────────────────────────────────*/
dl.record-fields {
  display: grid;
  grid-template-columns: minmax(9rem, 13rem) 1fr;
  gap: 0.25rem 0.9rem;
  margin: 0.4rem 0 0;
  font-size: 0.85rem;
}

dl.record-fields dt {
  color: var(--text-faint);
  font-size: 0.72rem;
  text-transform: uppercase;
  letter-spacing: 0.07em;
  padding-top: 0.12rem;
}

dl.record-fields dd {
  margin: 0;
}

ul.not-on-this-surface {
  margin: 0.2rem 0 0;
  padding-left: 1rem;
  font-size: 0.85rem;
}

/* Findings render as the design sheet's chips: a wrapped row of bordered
   mono tokens, each carrying the finding type and its count in text — the
   chip is the row's own words boxed, never a colour standing in for them.
   They were a bulleted list until 2026-08-19's frame pass; the chip
   vocabulary this file has declared since 2026-08-05 finally has a wearer. */
ul.findings {
  margin: 0.2rem 0 0;
  padding: 0;
  list-style: none;
  display: flex;
  flex-wrap: wrap;
  gap: 0.25rem;
}

ul.findings li {
  border: 1px solid var(--border-strong);
  background: rgba(255, 255, 255, 0.02);
  padding: 0.1rem 0.4rem;
  font-size: 0.7rem;
  font-family: var(--mono);
  letter-spacing: 0.03em;
  color: var(--text-quiet);
  /* nowrap until the review caught long detector names pushing their counts
     through the chip border and into the next column: a chip wraps inside
     itself before it overflows its cell. */
  max-width: 100%;
  overflow-wrap: anywhere;
}

ul.findings li code {
  font-size: inherit;
  color: var(--text);
}

/* ── Hold state ────────────────────────────────────────────────────────────
   THE HOLD STATE CARRIES DECIDABILITY, not just a name. `approved`, `denied`
   and `expired` are settled; `pending` and `pending_justification` are not, and
   that single fact is what a reviewer acts on. The two treatments differ in
   weight rather than in colour, because a settled hold is not a failure and an
   open one is not an alarm -- there is no red/green here for the same reason
   there is none on a decision. */
[data-hold-decidable='true'] {
  font-weight: 650;
  color: var(--text);
}

[data-hold-decidable='false'] {
  color: var(--text-quiet);
}

/* The hold under review, marked in the queue so the row and the panel below are
   visibly the same hold. */
tr[data-hold-open='true'] {
  outline: 1px solid var(--accent);
  /* rgba(180, 108, 240, …) until 2026-08-19 — the old violet accent, left
     behind when the 2026-08-18 pass retuned the accent to the brand phosphor.
     The one wash in the file that still answered to a hue nothing else drew. */
  background: rgba(255, 122, 60, 0.07);
}

ul.bulk-outcome-tally {
  list-style: none;
  padding: 0;
  margin: 0.3rem 0 0.7rem;
  display: flex;
  flex-wrap: wrap;
  gap: 0.3rem 1.2rem;
  font-size: 0.82rem;
}

/* A derived count, styled quietly on purpose: it is arithmetic over the rows
 * below rather than a figure the server reported, and it must not read as the
 * headline of the pass. */
ul.bulk-outcome-tally li {
  color: var(--text-quiet);
}

dl.hold-routing {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: 0.1rem 0.55rem;
  margin: 0;
  font-size: 0.82rem;
}

dl.hold-routing dt {
  color: var(--text-faint);
}

dl.hold-routing dd {
  margin: 0;
}

/* ── Forms ─────────────────────────────────────────────────────────────────*/
fieldset.hold-state-selection,
form.hold-decision fieldset {
  border: 1px solid var(--border);
  border-radius: var(--radius-small);
  padding: 0.5rem 0.75rem;
  margin: 0.7rem 0;
  background: var(--surface);
}

fieldset legend {
  color: var(--text-faint);
  font-size: 0.72rem;
  text-transform: uppercase;
  letter-spacing: 0.07em;
  padding: 0 0.3rem;
}

fieldset.hold-state-selection label,
form.hold-decision label {
  display: block;
  margin: 0.15rem 0;
  font-size: 0.85rem;
}

form.sign-in,
form.federated-sign-in,
form.hold-decision {
  display: grid;
  gap: 0.5rem;
  max-width: 30rem;
  margin: 0.8rem 0;
}

form.sign-in,
form.federated-sign-in {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 1.1rem 1.2rem;
}

form.sign-in label,
form.federated-sign-in label {
  display: grid;
  gap: 0.15rem;
  font-size: 0.78rem;
  color: var(--text-quiet);
  text-transform: uppercase;
  letter-spacing: 0.06em;
}

form.sign-in input,
form.federated-sign-in input,
form.hold-decision select,
form.hold-decision textarea {
  font: inherit;
  font-size: 0.88rem;
  color: var(--text);
  background: var(--surface-inset);
  border: 1px solid var(--border);
  border-radius: var(--radius-small);
  padding: 0.3rem 0.45rem;
  transition: border-color 140ms ease;
}

form.sign-in input:focus,
form.federated-sign-in input:focus,
form.hold-decision select:focus {
  border-color: var(--accent);
}

/* ══════════════════════════════════════════════════════════════════════════
   SHARED INCIDENT PRIMITIVES — DECLARED AHEAD OF THEIR MARKUP, AND STILL
   WAITING FOR IT AS OF THE 2026-08-05 MERGE
   ──────────────────────────────────────────────────────────────────────────
   Styled here, as primitives, rather than as rules for one module's screen, so
   that whichever screen ships them, every screen gets the same object.

   WHAT THIS BLOCK CLAIMED, AND WHAT IS ACTUALLY TRUE. It said the incident
   module and the screens that already exist "share these". They do not, yet.
   These rules were written on claude/console-theme for an incident screen that
   branch did not contain; the incident console arrived from
   claude/console-incident in the same merge, and it renders its own vocabulary
   — `.incident-console`, `.incident-rail`, `.incident-chips`, and an
   `<article class="incident-window">` — at the foot of this file. NO SHIPPED
   CONSOLE MODULE RENDERS `.severity-dot`, `.finding-card`, `.chip`, `.entity`,
   `.extent-bar`, `ol.event-log` OR `ol.finding-trace`, and none carries a
   `data-severity` attribute at all. `section.incident-window` below does not
   match the shipped window either, because that window is an `<article>`.

   THE RULES ARE KEPT RATHER THAN DELETED, and the reason is not sentiment. The
   severity ramp they apply is bound to `internal/detection/risk.go` by
   `EverySeverityTreatmentIsALabelTheDetectionEngineEmits`, and it is the ramp
   that is load-bearing; the treatments are the reviewed answer to "what does a
   severity look like" and the next screen to render one should get them rather
   than invent a third vocabulary. Which of the two incident treatments the
   product keeps is a design decision for the owner, not one to settle inside a
   merge — so the divergence is recorded here rather than resolved by whichever
   branch happened to be merged second.

   EVERY PRIMITIVE BELOW IS SHAPE AND COLOUR ONLY. None of them supplies a
   word. `.severity-dot` is an empty span whose sibling text says "Critical";
   `.entity` colours a word that is already written; `.extent-bar` draws a span
   whose start and end instants are printed beside it. Delete this whole block
   and every screen still says everything it said before, less prettily.
   ══════════════════════════════════════════════════════════════════════════ */

/* The incident as a WINDOW: a titled object with an identifier, sitting on the
   ground rather than being the page. The accent edge is decoration; the title
   bar spells the severity and the identifier in words. (Unmatched today — see
   the block comment above.) */
section.incident-window {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  margin: 0.7rem 0;
  overflow: hidden;
}

section.incident-window > header {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem 0.9rem;
  padding: 0.4rem 0.75rem;
  background: var(--surface-raised);
  border-bottom: 1px solid var(--border);
  font-size: 0.84rem;
  font-weight: 600;
}

section.incident-window[data-severity='critical'] {
  border-color: rgba(242, 86, 95, 0.4);
  box-shadow: 0 0 24px -8px rgba(242, 86, 95, 0.5);
}

section.incident-window > .incident-body {
  padding: 0.7rem 0.85rem;
}

/* A severity dot. Empty by construction — see the block comment above.
 *
 * IT IS COLOURED ONLY AS A DESCENDANT, NEVER BY AN ATTRIBUTE ON ITSELF, and
 * that is the whole design rather than a stylistic preference.
 *
 * FOUND BY INJECTING THE FAULT on 2026-08-05. The first draft of this block
 * also carried `.severity-dot[data-severity='critical']`, which let a caller
 * write `<span class="severity-dot" data-severity="critical"></span>` and get a
 * red dot out of an element containing no text at all. The text-carrier gate in
 * decision.test.tsx flagged exactly that shape while it was catching a
 * different injected fault, which is the gate doing its job on its own author.
 *
 * With the self-form gone, the ONLY way to colour a dot is to put the severity
 * on an ancestor — and an ancestor carrying `data-severity` must enclose text,
 * or the gate reddens. So the stylesheet now makes the accessible shape the
 * only shape that works: you cannot get the colour without writing the word. */
.severity-dot {
  display: inline-block;
  width: 7px;
  height: 7px;
  border-radius: 50%;
  margin-right: 0.35rem;
  vertical-align: baseline;
  background: var(--text-faint);
}

[data-severity='critical'] .severity-dot {
  background: var(--severity-critical);
}
[data-severity='high'] .severity-dot {
  background: var(--severity-high);
}
[data-severity='medium'] .severity-dot {
  background: var(--severity-medium);
}
[data-severity='low'] .severity-dot {
  background: var(--severity-low);
}
[data-severity='none'] .severity-dot {
  background: var(--severity-none);
}

/* A card in the bottom strip or the left rail: coloured left edge by severity,
   name, a severity row, then metadata count rows. The edge is redundant with
   the severity row directly beneath it. */
article.finding-card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-left: 3px solid var(--border-strong);
  border-radius: var(--radius-small);
  padding: 0.4rem 0.6rem;
  font-size: 0.82rem;
  transition: background-color 140ms ease, transform 140ms ease;
}

article.finding-card:hover {
  background: var(--surface-raised);
}

article.finding-card[data-severity='critical'] {
  border-left-color: var(--severity-critical);
}
article.finding-card[data-severity='high'] {
  border-left-color: var(--severity-high);
}
article.finding-card[data-severity='medium'] {
  border-left-color: var(--severity-medium);
}
article.finding-card[data-severity='low'] {
  border-left-color: var(--severity-low);
}
article.finding-card[data-severity='none'] {
  border-left-color: var(--severity-none);
}

article.finding-card > .card-meta {
  display: grid;
  gap: 1px;
  margin-top: 0.3rem;
  color: var(--text-quiet);
  font-size: 0.78rem;
}

article.finding-card > .card-meta > * {
  background: var(--surface-inset);
  padding: 0.15rem 0.35rem;
  border-radius: 3px;
}

/* Chips: a classification that fired, a tag, a scope. Text first, always. */
.chip {
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
  padding: 0.08rem 0.45rem;
  border-radius: 999px;
  border: 1px solid var(--border-strong);
  background: var(--surface-raised);
  color: var(--text);
  font-size: 0.75rem;
  font-weight: 550;
  white-space: nowrap;
}

.chip[data-severity='critical'] {
  border-color: rgba(242, 86, 95, 0.5);
  background: var(--severity-critical-wash);
}
.chip[data-severity='high'] {
  border-color: rgba(240, 135, 60, 0.5);
  background: var(--severity-high-wash);
}
.chip[data-severity='medium'] {
  border-color: rgba(226, 163, 60, 0.5);
  background: var(--severity-medium-wash);
}
.chip[data-severity='low'] {
  border-color: rgba(87, 169, 232, 0.5);
  background: var(--severity-low-wash);
}
.chip[data-severity='none'] {
  border-color: rgba(127, 136, 150, 0.5);
  background: var(--severity-none-wash);
}

ul.chip-row {
  display: flex;
  flex-wrap: wrap;
  gap: 0.3rem;
  list-style: none;
  padding: 0;
  margin: 0.4rem 0;
}

/* Time drawn as EXTENT. The bar is a proportion of the incident's own span; the
   two instants it spans are printed as <time> elements beside it, so the bar
   adds shape to a fact already stated. Width is set by the inline `style` the
   markup cannot use — so it is set with a CSS custom property applied through a
   class the renderer picks from a bounded set, or the bar renders full-width
   and says so. See the note in the incident module. */
.extent-bar {
  display: block;
  height: 5px;
  border-radius: 3px;
  /* The TRACK is the incident's whole span and has to be visible, or the fill
     reads as an underline rather than as a proportion of something. Found by
     looking at it: at --surface-inset the track vanished into the ground. */
  background: var(--surface-raised);
  border: 1px solid var(--border);
  margin-top: 0.25rem;
  overflow: hidden;
}

.extent-bar > .extent-fill {
  display: block;
  height: 100%;
  background: linear-gradient(90deg, var(--accent-deep), var(--accent));
  transition: width 220ms ease;
}

/* The chronological trace. A date gutter groups the day; each row prints a
   title and a start→end range. */
ol.finding-trace {
  list-style: none;
  padding: 0;
  margin: 0.4rem 0;
  display: grid;
  gap: 0.35rem;
}

ol.finding-trace > li {
  display: grid;
  grid-template-columns: 4.5rem 1fr;
  gap: 0.6rem;
  padding: 0.3rem 0;
  border-bottom: 1px solid var(--border-quiet);
}

ol.finding-trace .trace-gutter {
  color: var(--text-faint);
  font-size: 0.75rem;
  text-align: right;
  padding-top: 0.1rem;
}

ol.finding-trace .trace-range {
  color: var(--text-quiet);
  font-size: 0.78rem;
}

/* Event log rows: timestamp, severity dot, sentence with entities coloured
   inline. The entity classes colour a word; they never replace one. */
ol.event-log {
  list-style: none;
  padding: 0;
  margin: 0.4rem 0;
  font-size: 0.82rem;
}

ol.event-log > li {
  display: grid;
  grid-template-columns: 12rem 1fr;
  gap: 0.6rem;
  padding: 0.22rem 0.4rem;
  border-bottom: 1px solid var(--border-quiet);
  align-items: baseline;
}

ol.event-log > li:hover {
  background: var(--surface);
}

ol.event-log .log-instant {
  color: var(--text-faint);
  font-family: var(--mono);
  font-size: 0.75rem;
}

.entity {
  font-weight: 550;
}
.entity[data-entity='enrolment'] {
  color: var(--entity-enrolment);
}
.entity[data-entity='rule'] {
  color: var(--entity-rule);
}
.entity[data-entity='address'] {
  color: var(--entity-address);
}
.entity[data-entity='connector'] {
  color: var(--entity-connector);
}

/* A count with its population. ROADMAP §8 keeps percentile context and rejects
   a confidence percentage, so this primitive takes a count and the population
   it was taken over, and there is no rule here that styles a bare percentage.

   WHAT "NOT A FOOTNOTE" MEANS HERE, MEASURED IN A BROWSER RATHER THAN
   ASSERTED. This comment said "the population is the same size as the count on
   purpose" until 2026-08-17, and the two rules below never made it so. `rem` is
   root-relative and the root is 16px, so the count resolves to 16.8px (1.05rem)
   and the population to 13.12px (0.82rem) — the population is 78% of the count,
   and a shade under this file's own `body { font-size: 13.5px }`. The claim was
   false and the RULE it was trying to state is this: the population is set at
   the size the page is read at, give or take a fraction of a pixel, while the
   count is set larger than reading size. That is what stops it being a
   footnote — fine print is text set materially below the body, and this is not
   — and it is the floor a later edit may not go under. It went unnoticed
   because nothing rendered this primitive until the incident console's scope
   population, and a rule with no markup cannot be looked at. */
.count-with-context {
  display: inline-flex;
  align-items: baseline;
  gap: 0.35rem;
}

.count-with-context > .count-value {
  font-size: 1.05rem;
  font-weight: 650;
}

.count-with-context > .count-population {
  color: var(--text-quiet);
  font-size: 0.82rem;
}

/* Decorative inline SVG. Every <svg> in this console is aria-hidden and sits
   beside text that says the same thing; this rule stops one being sized
   differently on each screen. */
svg.glyph {
  width: 0.85em;
  height: 0.85em;
  flex: none;
  vertical-align: -0.08em;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.6;
  stroke-linecap: round;
  stroke-linejoin: round;
  opacity: 0.75;
}

/* The unavailable panel. A screen with no route to read from renders THIS,
   which says what is missing, rather than a plausible number. It is styled to
   look explicitly unfinished — hatched, quiet, italic — so a screenshot of it
   cannot be mistaken for data. */
.unavailable-panel {
  border: 1px dashed var(--border-strong);
  border-radius: var(--radius-small);
  background: var(--surface-inset);
  color: var(--absent-text);
  font-style: italic;
  padding: 0.7rem 0.85rem;
  margin: 0.5rem 0;
  font-size: 0.85rem;
}

/* ==========================================================================
   The incident console.

   DENSITY IS THE POINT OF THIS BLOCK. The rest of this console is a set of
   tables read one row at a time; this screen is read at a glance, which is why
   it is tighter, why the panels sit on a slightly lifted ground rather than
   floating, and why every rule below is a small step rather than a large one.

   NO RULE HERE CARRIES A STATUS ON ITS OWN. Each mark, edge and hue sits beside
   a sentence that says the same thing. That is checkable rather than promised:
   web/console/src/incidents/screens.test.tsx fails if a status mark loses its
   words.
   ========================================================================== */

.incident-console {
  display: grid;
  gap: 0.6rem;
}

.incident-caveat {
  color: var(--text-quiet);
  font-size: 0.85rem;
  margin: 0.2rem 0 0.6rem;
}

.incident-console section {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 9px;
  padding: 0.75rem 1rem;
}

.incident-console h3 {
  font-size: 0.95rem;
  margin: 0 0 0.3rem;
  font-weight: 600;
}

.incident-console h4 {
  font-size: 0.85rem;
  margin: 0.9rem 0 0.3rem;
  color: var(--text-quiet);
  text-transform: uppercase;
  letter-spacing: 0.05em;
}

/* The band mark. Three concentric rings for a mandatory stop, two for a
   permitted raw egress, one for a refusal - decoration over the sentence in
   `.incident-band`, never instead of it. The rings inherit their colour from
   the same tokens the decision edges use, so a reader who has learned the
   evidence list has already learned this. */
.incident-mark {
  vertical-align: -0.2em;
  margin-right: 0.4rem;
}

.incident-mark-ring,
.incident-mark-core {
  stroke: var(--border-strong);
  fill: none;
}

.incident-mark-core {
  fill: var(--border-strong);
}

[data-incident-mark='mandatory_stop'] .incident-mark-ring,
[data-incident-mark='mandatory_stop'] .incident-mark-core {
  stroke: var(--mandatory-edge);
}

[data-incident-mark='mandatory_stop'] .incident-mark-core {
  fill: var(--mandatory-edge);
  animation: incident-core-pulse 2.4s ease-in-out infinite;
}

[data-incident-mark='raw_egress_permitted'] .incident-mark-ring,
[data-incident-mark='raw_egress_permitted'] .incident-mark-core {
  stroke: var(--raw-egress-edge);
}

[data-incident-mark='raw_egress_permitted'] .incident-mark-core {
  fill: var(--raw-egress-edge);
}

[data-incident-mark='egress_refused'] .incident-mark-ring,
[data-incident-mark='egress_refused'] .incident-mark-core {
  stroke: var(--hold-edge);
}

[data-incident-mark='egress_refused'] .incident-mark-core {
  fill: var(--hold-edge);
}

/* One of the two animations in this console, and it decorates a sentence that
   is already on screen. The `prefers-reduced-motion` block at the FOOT of this
   file — below every @keyframes, which is what makes it win — turns it off, so
   nothing is lost when it is. (This comment said "the block ABOVE this one, it
   sits with the form rules" until the 2026-08-05 merge, which was true of the
   branch and false of the merged file; the block moved, and a comment pointing
   a reader at the wrong end of the file is worse than no comment.) */
@keyframes incident-core-pulse {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.35; }
}

.incident-band {
  margin: 0.3rem 0 0.6rem;
  display: flex;
  align-items: baseline;
  gap: 0.1rem;
  border-left: 4px solid var(--border-strong);
  padding-left: 0.6rem;
}

[data-incident-band='mandatory_stop'] { border-left-color: var(--mandatory-edge); }
[data-incident-band='raw_egress_permitted'] { border-left-color: var(--raw-egress-edge); }
[data-incident-band='egress_refused'] { border-left-color: var(--hold-edge); }

/* The incident as a window. The title bar carries the identifier, because the
   identifier is the object - it is deliberately NOT in the address bar. */
.incident-window {
  border: 1px solid var(--border);
  border-radius: 9px;
  background: var(--surface-raised);
  padding: 0 0.9rem 0.9rem;
  margin-top: 0.6rem;
}

.incident-window-bar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem;
  border-bottom: 1px solid var(--border);
  margin: 0 -0.9rem 0.7rem;
  padding: 0.55rem 0.9rem;
}

.incident-window-title {
  margin: 0;
  font-size: 0.95rem;
  font-weight: 600;
}

.incident-window-controls {
  margin: 0;
}

/* The rail of incidents. Numbered and timestamped, densest thing on the page. */
ol.incident-rail {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: 0.35rem;
}

li.incident-card button {
  display: grid;
  gap: 0.15rem;
  width: 100%;
  text-align: left;
  background: var(--surface-raised);
  border: 1px solid var(--border);
  border-left-width: 4px;
  border-radius: 6px;
  padding: 0.45rem 0.6rem;
}

li.incident-card[data-incident-decidable='true'] button { border-left-color: var(--hold-edge); }
li.incident-card[data-incident-decidable='false'] button { border-left-color: var(--border-strong); }
li.incident-card[data-incident-open='true'] button { outline: 2px solid var(--border-strong); }

.incident-card-when,
.incident-card-state,
.incident-card-findings {
  color: var(--text-quiet);
  font-size: 0.8rem;
}

.incident-card-title { font-weight: 600; }

/* Chips. The label is a token; the sentence beside it is what carries the
   meaning, and it is not hidden - a chip whose reading was tucked into a
   tooltip would be a token nobody can check. */
ul.incident-chips {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: 0.3rem;
}

ul.incident-chips li {
  border: 1px solid var(--border);
  border-left-width: 4px;
  border-radius: 6px;
  padding: 0.3rem 0.55rem;
  background: var(--surface-raised);
}

ul.incident-chips li[data-chip-group='finding'] { border-left-color: var(--transformation-edge); }
ul.incident-chips li[data-chip-group='decision'] { border-left-color: var(--mandatory-edge); }
ul.incident-chips li[data-chip-group='lifecycle'] { border-left-color: var(--hold-edge); }
ul.incident-chips li[data-chip-group='custody'] { border-left-color: var(--interaction-edge); }
ul.incident-chips li[data-chip-group='routing'] { border-left-color: var(--border-strong); }

.incident-chip-reading {
  display: block;
  color: var(--text-quiet);
  font-size: 0.8rem;
}

/* Two columns once there is room for them. The chips are the densest thing on
   this screen in the reference and the thinnest here, because every one of ours
   carries its own sentence rather than a token a reader is assumed to know —
   so the density has to come from the layout instead of from the copy. */
@media (min-width: 60rem) {
  ul.incident-chips {
    grid-template-columns: 1fr 1fr;
  }
}

/* The shown work. "Did not answer" is a WORD in the markup as well as a colour
   here; the rule below is the second carrier, not the first. */
ol.incident-trace-steps {
  margin: 0;
  padding-left: 1.2rem;
  display: grid;
  gap: 0.45rem;
}

ol.incident-trace-steps li {
  border-left: 3px solid var(--border);
  padding-left: 0.6rem;
}

ol.incident-trace-steps li[data-trace-reached='answered'] { border-left-color: var(--transformation-edge); }
ol.incident-trace-steps li[data-trace-reached='did_not_answer'] { border-left-color: var(--blocked-edge); }
ol.incident-trace-steps li[data-trace-reached='not_attempted'] { border-left-color: var(--border-strong); }

.incident-trace-examined { margin: 0; }
.incident-trace-outcome { margin: 0.1rem 0 0; color: var(--text-quiet); font-size: 0.85rem; }
.incident-trace-verdict { color: var(--text); font-weight: 600; }

/* The funnel. The shape narrows because a funnel narrows; it does not encode a
   figure, and it must not - two of the four stages have no figure at all. */
ol.incident-funnel-stages {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: 0.35rem;
}

ol.incident-funnel-stages li {
  display: grid;
  grid-template-columns: 40px 10rem 1fr;
  align-items: baseline;
  gap: 0.5rem;
  border-bottom: 1px solid var(--border);
  padding-bottom: 0.35rem;
}

.incident-funnel-shape { fill: var(--border-strong); }
li[data-funnel-measure='unavailable'] .incident-funnel-shape { fill: var(--border); }
.incident-funnel-label { font-weight: 600; }
.incident-funnel-figure { color: var(--text-quiet); font-size: 0.85rem; }
.incident-funnel-ratio { color: var(--text-quiet); font-size: 0.85rem; margin: 0.5rem 0 0; }

/* The scope-figure arm of the funnel. It is the one stage whose count is NOT
   over a page this session loaded, so it is drawn a shade stronger than the
   page arms — but only a shade, and never as the only difference: the row
   carries `data-funnel-measure="over_committed_scope"` and the qualifier says
   which population it counted, so the treatment is decoration over a statement
   that already stands on its own. */
li[data-funnel-measure='over_committed_scope'] .incident-funnel-shape {
  fill: var(--text-quiet);
}

/* ══════════════════════════════════════════════════════════════════════════
   THE SCOPE POPULATION
   ──────────────────────────────────────────────────────────────────────────
   The first thing in this console to render `.count-with-context`, which was
   declared for ROADMAP §8's "percentile context beside any score" and had no
   figure to hold until POST /v1/event-evidence/trends published one.

   THE POPULATION IS NOT A FOOTNOTE AND THIS IS WHERE THAT IS ENFORCED
   VISUALLY. `.count-with-context > .count-population` is already sized to sit
   with the count rather than under it; nothing here may shrink it further. A
   count whose population is set as fine print is a count that gets read alone,
   which is the whole defect §8's bullet is about.

   THE EXCLUSIONS ARE PART OF THE FIGURE. They are laid out as a definition
   list at readable size rather than behind a disclosure control, for the reason
   inventory/estate.tsx gives: a coverage figure read without them is read as a
   statement about the institution, and it is a statement about this platform's
   own enrolment records.
   ══════════════════════════════════════════════════════════════════════════ */
.incident-population-exclusions {
  margin-top: 0.6rem;
  border-top: 1px solid var(--border);
  padding-top: 0.5rem;
}

.incident-population-exclusions h4 {
  margin: 0 0 0.35rem;
  font-size: 0.85rem;
  color: var(--text-quiet);
}

.incident-population-exclusions dl {
  margin: 0;
  display: grid;
  gap: 0.3rem;
}

.incident-population-exclusions dt {
  font-family: var(--mono);
  font-size: 0.8rem;
}

.incident-population-exclusions dd {
  margin: 0 0 0.2rem;
  color: var(--text-quiet);
  font-size: 0.82rem;
}

.incident-population-absent {
  margin: 0;
  font-size: 0.9rem;
}

/* ══════════════════════════════════════════════════════════════════════════
   THE INCIDENT FILTER
   ──────────────────────────────────────────────────────────────────────────
   The only panel on the incident screen that takes input, and the only one
   whose figure has a denominator. `.incident-filter-figure` is styled to sit
   with the funnel's figure rather than louder than it, because the number in it
   is a page length and the surrounding sentence is what makes it mean anything
   — a treatment that made the numeral the loudest thing on the panel would be
   the stylesheet undoing what the markup is careful about.

   NOTHING HERE CARRIES A STATUS BY COLOUR ALONE. The one state-dependent rule
   is `[data-filter-narrowing='true']`, and it thickens a border beside a
   sentence that already says whether anything was narrowed. */
.incident-filter {
  border: 1px solid var(--border);
  border-radius: 6px;
  padding: 0.85rem 1rem;
  margin: 1rem 0;
}

.incident-filter-controls {
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem;
  align-items: flex-start;
}

.incident-filter-field {
  border: 1px solid var(--border);
  border-radius: 4px;
  padding: 0.5rem 0.75rem;
  min-width: 14rem;
  flex: 1 1 14rem;
}

.incident-filter-field legend {
  font-weight: 600;
  font-size: 0.85rem;
  padding: 0 0.35rem;
}

.incident-filter-operator {
  display: flex;
  gap: 0.5rem;
  align-items: baseline;
  flex-wrap: wrap;
  font-size: 0.85rem;
}

.incident-filter-reading {
  color: var(--text-quiet);
  font-size: 0.8rem;
  margin: 0.3rem 0 0.5rem;
}

ul.incident-filter-values,
ul.incident-filter-limits {
  list-style: none;
  margin: 0;
  padding: 0;
}

ul.incident-filter-values li {
  display: inline-block;
  margin: 0 0.6rem 0.25rem 0;
  font-size: 0.85rem;
}

ul.incident-filter-limits li {
  color: var(--text-quiet);
  font-size: 0.85rem;
  margin: 0 0 0.45rem;
  padding-left: 0.75rem;
  border-left: 2px solid var(--border);
}

.incident-filter-presence + .incident-filter-presence {
  margin-top: 0.5rem;
  padding-top: 0.5rem;
  border-top: 1px solid var(--border);
}

.incident-filter-figure {
  color: var(--text-quiet);
  font-size: 0.85rem;
  margin: 0.75rem 0 0;
  padding-left: 0.6rem;
  border-left: 2px solid var(--border);
}

.incident-filter-figure[data-filter-narrowing='true'] {
  border-left-color: var(--border-strong);
}

/* The swimlane. */
ol.incident-gutter,
ol.incident-lanes {
  list-style: none;
  margin: 0;
  padding: 0;
}

/* THE DATES ARE STAGGERED ACROSS TWO LINES AND EVERY ONE OF THEM IS DRAWN.
   Labels are positioned at the column their date begins at, so two dates
   landing within a couple of columns of each other used to overprint and read
   as one mangled string — measured as "2026-072026-07-03" on a five-incident
   page. Alternating them between two lines doubles the horizontal room without
   dropping a date, moving one, or letting a later label paint over an earlier
   one; the alternative fixes all hide a value that was read from the page. */
ol.incident-gutter {
  position: relative;
  height: 2.2rem;
  border-bottom: 1px solid var(--border);
  margin-bottom: 0.3rem;
}

ol.incident-gutter li {
  position: absolute;
  color: var(--text-quiet);
  font-size: 0.75rem;
  white-space: nowrap;
}

.lane-gutter-tier-0 { top: 0; }
.lane-gutter-tier-1 { top: 1.1rem; }

ol.incident-lanes { display: grid; gap: 0.7rem; }
.incident-lane-name { margin: 0 0 0.15rem; font-size: 0.85rem; }

/* The shared scale, drawn once above the lanes rather than behind each bar.
   Every line below divides the same window into the same columns, so one ruler
   is the honest number of rulers. */
ol.incident-lane-rule {
  list-style: none;
  margin: 0 0 0.3rem;
  padding: 0;
  position: relative;
  height: 0.5rem;
}

/* ONE LINE PER INCIDENT, NEVER TWO BARS IN ONE TRACK. Bars are positioned
   absolutely, so two incidents quantising to the same start column would
   overlap and the later one would cover the earlier — a lane naming two
   incidents while one rectangle was on screen. Giving each bar its own line
   makes that unrepresentable rather than unlikely. */
ol.incident-lane-lines { list-style: none; margin: 0; padding: 0; display: grid; gap: 0.35rem; }
li.incident-lane-line { display: grid; gap: 0.1rem; }

.incident-lane-track {
  display: block;
  position: relative;
  height: 0.9rem;
  background: var(--surface-raised);
  border: 1px solid var(--border);
  border-radius: 4px;
}

.lane-tick {
  position: absolute;
  top: 0;
  bottom: 0;
  width: 1px;
  background: var(--border);
}

.incident-bar {
  display: block;
  position: absolute;
  top: 0.15rem;
  bottom: 0.15rem;
  min-width: 0.3rem;
  border-radius: 3px;
  background: var(--hold-edge);
}

[data-bar-decidable='false'] .incident-bar { background: var(--border-strong); }

/* A bar drawn at the floor is drawn hollow. IT IS NOT THE CARRIER OF THAT FACT —
   the bar's own sentence says "shorter than one column" in words, and this is
   the same statement in a second channel for a reader scanning the shapes. */
.incident-bar[data-bar-floor='true'] {
  background: transparent;
  border: 1px solid var(--hold-edge);
}

/* The bar's own words, beside the bar rather than clipped inside it. They are IN
   the markup rather than in a title attribute, because a status a reader has to
   hover to read is a status carried by the shape — and every limit of the
   drawing that can only be said per bar is said here: the repeat across rows,
   the resolution floor, and an incident that never moved. */
.incident-bar-text {
  display: block;
  font-size: 0.7rem;
  line-height: 1.15rem;
  color: var(--text-quiet);
}

table.incident-rollup-table {
  width: 100%;
  border-collapse: collapse;
  font-variant-numeric: tabular-nums;
}

table.incident-rollup-table caption {
  text-align: left;
  color: var(--text-quiet);
  font-size: 0.8rem;
  padding-bottom: 0.4rem;
}

table.incident-rollup-table th,
table.incident-rollup-table td {
  text-align: left;
  border-bottom: 1px solid var(--border);
  padding: 0.3rem 0.5rem;
  vertical-align: top;
}

table.incident-rollup-table th {
  font-weight: 600;
  color: var(--text-quiet);
  font-size: 0.8rem;
  text-transform: uppercase;
  letter-spacing: 0.04em;
}

/* Each row's own sentence saying what it counts, so a row is never a bare token
   whose meaning a reader has to already know — and so the one row that is a
   BUCKET rather than a group says which it is where it is read. */
.incident-rollup-reading {
  display: block;
  color: var(--text-quiet);
  font-size: 0.75rem;
  max-width: 42ch;
  margin-top: 0.15rem;
}

/* The swimlane's twenty-four column positions and extents.
 *
 * WHY THESE ARE CLASSES RATHER THAN COMPUTED LENGTHS. The console is served
 * under `style-src 'self'` with no 'unsafe-inline', which blocks style
 * ATTRIBUTES as well as <style> elements — so a bar positioned by an inline
 * percentage would be dropped by the browser and would draw at zero width with
 * nothing on screen to say it had. Every position a bar can take is therefore
 * enumerated here, and the number of them is SWIMLANE_COLUMNS in
 * web/console/src/incidents/incident.ts. The screen states the resolution beside
 * the lanes, because a bar drawn to the nearest twenty-fourth is a position and
 * not an instant, and the difference is the kind a reader would otherwise assume
 * away. */
.lane-start-0 { left: 0.000000%; }
.lane-start-1 { left: 4.166667%; }
.lane-start-2 { left: 8.333333%; }
.lane-start-3 { left: 12.500000%; }
.lane-start-4 { left: 16.666667%; }
.lane-start-5 { left: 20.833333%; }
.lane-start-6 { left: 25.000000%; }
.lane-start-7 { left: 29.166667%; }
.lane-start-8 { left: 33.333333%; }
.lane-start-9 { left: 37.500000%; }
.lane-start-10 { left: 41.666667%; }
.lane-start-11 { left: 45.833333%; }
.lane-start-12 { left: 50.000000%; }
.lane-start-13 { left: 54.166667%; }
.lane-start-14 { left: 58.333333%; }
.lane-start-15 { left: 62.500000%; }
.lane-start-16 { left: 66.666667%; }
.lane-start-17 { left: 70.833333%; }
.lane-start-18 { left: 75.000000%; }
.lane-start-19 { left: 79.166667%; }
.lane-start-20 { left: 83.333333%; }
.lane-start-21 { left: 87.500000%; }
.lane-start-22 { left: 91.666667%; }
.lane-start-23 { left: 95.833333%; }
.lane-span-1 { width: 4.166667%; }
.lane-span-2 { width: 8.333333%; }
.lane-span-3 { width: 12.500000%; }
.lane-span-4 { width: 16.666667%; }
.lane-span-5 { width: 20.833333%; }
.lane-span-6 { width: 25.000000%; }
.lane-span-7 { width: 29.166667%; }
.lane-span-8 { width: 33.333333%; }
.lane-span-9 { width: 37.500000%; }
.lane-span-10 { width: 41.666667%; }
.lane-span-11 { width: 45.833333%; }
.lane-span-12 { width: 50.000000%; }
.lane-span-13 { width: 54.166667%; }
.lane-span-14 { width: 58.333333%; }
.lane-span-15 { width: 62.500000%; }
.lane-span-16 { width: 66.666667%; }
.lane-span-17 { width: 70.833333%; }
.lane-span-18 { width: 75.000000%; }
.lane-span-19 { width: 79.166667%; }
.lane-span-20 { width: 83.333333%; }
.lane-span-21 { width: 87.500000%; }
.lane-span-22 { width: 91.666667%; }
.lane-span-23 { width: 95.833333%; }
.lane-span-24 { width: 100.000000%; }

/* ── The overview ──────────────────────────────────────────────────────────
   NP-31a's screen, and the one place a stylesheet could undo its whole point.

   THE "NOTHING HERE IS A TILE AND NOTHING HERE IS BIG" RULE THIS HEADER
   CARRIED FROM 2026-08-10 TO 2026-08-19 IS SUPERSEDED, by the owner's design
   sheet and by the owner's direct reading of what the rule produced: walls
   of sentences where the sheet shows instruments. The figures are now the
   sheet's stat tiles — the count large, mono and in the measurement cyan,
   over its label and over the SAME qualifier-and-operation sentence the old
   layout carried inline. What the old rule protected survives the layout it
   forbade: the qualifier and the source still render inside the one
   `overview-figure` element the Go gate splits this module on, so a large
   number without its honest caption is still unbuildable — the caption IS
   the markup the count cannot be printed without.

   EVERY PANEL CARRIES ITS SCOPE ABOVE ITS FIGURES and the rule below keeps it
   visible rather than collapsing it into a caption. The screen has no
   page-level scope claim, so the per-panel one is the whole mechanism.

   NO COLOUR CARRIES A STATUS HERE. `data-overview-measure` is used only to
   quiet an absence's presentation; what an absence MEANS is the sentence
   AbsentValue renders, exactly as EVD-02-A07 requires — and the measurement
   cyan on a count is the figure register the registry tiles already use,
   not a state. */
section.overview-panel {
  background: var(--surface-raised);
  border: 1px solid var(--border);
  border-radius: 6px;
  padding: 0.75rem 1rem;
  margin: 0 0 1rem;
  /* Flex so the exclusions register can be ORDERED after the figures: the
     markup states the denominator's caveats before the counts (reading
     order for a screen reader stays caveats-first, deliberately), and the
     sheet's visual order is instrument first, register after. `order` moves
     presentation only. */
  display: flex;
  flex-direction: column;
}

section.overview-panel > .overview-exclusions {
  order: 2;
}

.overview-scope-note,
.overview-panel-scope {
  color: var(--text-quiet);
  font-size: 0.75rem;
  line-height: 1.5;
  margin: 0.2rem 0 0.7rem;
  max-width: 72ch;
}

/* THE SELECTOR IS `overview-figures` AND IT WAS `overview-rows` UNTIL THIS WAS
   MEASURED. screen.tsx renders `<ul className="overview-figures">` holding
   `<li className="overview-row">` children; the list rule was written against
   the child's name and matched nothing, so the figures drew as a default
   bulleted list. Nothing throws when a selector matches nothing, which is why
   this is the class of defect that survives a green suite. */
ul.overview-figures {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 12.5rem), 1fr));
  gap: 0.5rem;
  align-items: stretch;
}

/* Each figure is a tile: label in small caps, the count large over its own
   qualifier sentence, and the proportion meter along the tile's foot. The
   markup order is label / meter / figure; `order` moves the meter to the
   foot so the number leads, which is presentation and changes nothing about
   what is stated. */
li.overview-row {
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
  background: var(--surface-inset);
  border: 1px solid var(--border-quiet);
  padding: 0.55rem 0.65rem 0.6rem;
  min-width: 0;
}

.overview-row-label {
  font-weight: 600;
  font-size: 0.66rem;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--text-quiet);
}

.overview-figure {
  font-size: 0.75rem;
  color: var(--text-quiet);
  line-height: 1.45;
  display: block;
  flex: 1;
}

.overview-figure strong[data-overview-count] {
  display: block;
  font-family: var(--mono);
  font-size: 1.4rem;
  font-weight: 650;
  letter-spacing: -0.01em;
  line-height: 1.2;
  color: var(--signal);
  margin: 0.05rem 0 0.2rem;
}

.overview-figure-bar {
  order: 3;
  margin-top: 0.45rem;
}

li[data-overview-measure='unavailable'] .overview-figure { color: var(--text-quiet); }

/* Source operations are metadata: mono, quiet, visibly not the content.
   0.64rem faint until the review measured it as below the small-text floor
   for these colours on a raised card; one step up on both axes. */
.overview-figure-source {
  color: var(--text-quiet);
  font-family: var(--mono);
  font-size: 0.7rem;
  letter-spacing: 0.02em;
  display: block;
  margin-top: 0.3rem;
}
.overview-share { margin: 0 0 0.75rem; font-size: 0.9rem; }

/* The coverage exclusions and the gaps read as compact registers rather than
   a page of prose: two columns where the pane affords them, the small note
   register, and no pair split across a column break. */
.overview-exclusions dl,
.overview-gaps dl {
  columns: 2 22rem;
  column-gap: 1.6rem;
  margin: 0.5rem 0 0;
}

.overview-exclusions div,
.overview-gaps div {
  break-inside: avoid;
}

.overview-exclusions dt,
.overview-exclusions dd,
.overview-gaps dt,
.overview-gaps dd {
  break-inside: avoid;
}

/* ── The NOC arrangement, 2026-08-20 ───────────────────────────────────────
   The overview is the wall-display page, and its sections follow the
   category leader's dashboard order in our own words and theme (the standing
   §4a default: philosophy, never vocabulary): the time series leads at full
   width with its window control, the live wall-and-radar row follows, then
   the coverage pair, the remediation pair, and the opt-in audit tally. */
section.overview-panel[data-overview-panel='received_over_time'] {
  grid-column: 1 / -1;
}

section.overview-panel[data-overview-panel='received_over_time'] .overview-spark {
  height: 5.5rem;
}

.overview-live {
  grid-column: 1 / -1;
  min-width: 0;
}

.overview-exclusions dt,
.overview-gaps dt {
  font-weight: 600;
  font-size: 0.75rem;
  margin-top: 0.5rem;
}

.overview-exclusions dd,
.overview-gaps dd {
  margin: 0.15rem 0 0;
  color: var(--text-quiet);
  font-size: 0.75rem;
  max-width: 74ch;
}

/* ══════════════════════════════════════════════════════════════════════════
   THE REGISTRY SCREENS, STYLED AT LAST — 2026-08-19
   ──────────────────────────────────────────────────────────────────────────
   Fleet health, agent builds, fleet operations, the AI inventory, policy
   inheritance, the settings pair and the audit chain all shipped after the
   2026-08-05 rulings and rendered on element defaults inside the dark shell —
   raw definition lists, browser-default tables, no panels. The documentation
   audit of 2026-08-19 measured the gap (every class below existed in markup
   and bound to nothing here), and this section closes it IN THE EXISTING
   VOICE: the one table geometry (those tables joined the shared selectors
   above), the one panel treatment, the one absence register. Nothing below
   invents a second vocabulary.

   TWO RESERVED TOKENS ARE CONSUMED HERE, on the jobs their declarations named:
   --signal carries the measurement figures (fleet counts, coverage percent) —
   "a figure can be emphasised without being mistaken for a level" — and the
   accent stays at hairline-and-wash weight throughout, per the standing rule
   in the tokens block. */

/* The fleet's whole-estate figures: one strip of quiet tiles, the figure in
   the measurement register, the label in words above it. The tile's top rule
   is an accent wash, not a chroma fill — a saturated orange tile would read
   as a severity on the one screen that is mostly numbers. */
dl.fleet-counts,
dl.fleet-current-build {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(11rem, 1fr));
  gap: 0.6rem;
  margin: 0.8rem 0;
}

dl.fleet-counts > div,
dl.fleet-current-build > div {
  background: var(--surface);
  border: 1px solid var(--border);
  border-top: 2px solid rgba(255, 122, 60, 0.22);
  border-radius: var(--radius-small);
  padding: 0.5rem 0.7rem;
  /* Column flex with the figure pinned to the bottom, so a strip of tiles
     keeps one numeral baseline whether a label runs one line or two. */
  display: flex;
  flex-direction: column;
}

dl.fleet-counts dt,
dl.fleet-current-build dt {
  font-size: 0.68rem;
  text-transform: uppercase;
  letter-spacing: 0.07em;
  color: var(--text-faint);
  font-weight: 600;
}

dl.fleet-counts dd,
dl.fleet-current-build dd {
  margin: 0.15rem 0 0;
  margin-top: auto;
  font-family: var(--mono);
  font-size: 1.25rem;
  font-weight: 650;
  color: var(--signal);
  font-variant-numeric: tabular-nums;
  overflow-wrap: anywhere;
}

/* The figure's qualifier text inside a tile stays at reading size and reading
   colour — the count-with-context floor, applied here too. */
dl.fleet-counts dd span,
dl.fleet-counts dd small,
dl.fleet-current-build dd code,
dl.fleet-current-build dd small {
  font-size: 0.78rem;
  font-weight: 400;
  color: var(--text-quiet);
}

/* Coverage's headline figures take the same measurement register. */
section.coverage strong[data-coverage-percent],
section.coverage strong[data-coverage-gap] {
  font-family: var(--mono);
  color: var(--signal);
  font-variant-numeric: tabular-nums;
}

/* The inventory's four estates and the coverage statement are panels — the
   incident console's section treatment, restated here because those rules are
   scoped to .incident-console. Same surface, same border, same radius. */
section.coverage,
section.observed-running,
section.mcp-server-estate,
section.guarded-invocations {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 0.75rem 1rem;
  margin: 0.9rem 0;
}

/* The browser-origins statement nests inside coverage, so it steps DOWN to the
   inset well rather than stacking a second panel on the first — the three-step
   rule holds because inset is the well, not a fourth step up. */
section.coverage section.browser-origins {
  background: var(--surface-inset);
  border: 1px solid var(--border-quiet);
  border-radius: var(--radius-small);
  padding: 0.6rem 0.8rem;
  margin: 0.7rem 0;
}

ul.ungoverned-origins {
  list-style: none;
  margin: 0.5rem 0;
  padding: 0;
  display: grid;
  gap: 0.35rem;
}

ul.ungoverned-origins > li {
  border-left: 3px solid var(--border-strong);
  padding: 0.15rem 0 0.15rem 0.6rem;
  font-size: 0.83rem;
  color: var(--text-quiet);
}

ul.ungoverned-origins > li > strong {
  color: var(--text);
  font-family: var(--mono);
  font-size: 0.8rem;
  font-weight: 600;
}

/* The exclusions register — fleet, inventory and the incident population all
   print the populations a number does NOT contain, and the honest list is the
   product's signature move, so it gets a signature treatment: a quiet ruled
   register on the inset well, the lead-in word in the label voice. */
ul.exclusions {
  list-style: none;
  margin: 0.6rem 0;
  padding: 0;
  border: 1px solid var(--border-quiet);
  border-radius: var(--radius-small);
  overflow: hidden;
}

ul.exclusions > li {
  background: var(--surface-inset);
  padding: 0.45rem 0.7rem;
  font-size: 0.82rem;
  color: var(--text-quiet);
}

ul.exclusions > li + li {
  border-top: 1px solid var(--border-quiet);
}

ul.exclusions > li > strong {
  color: var(--text);
  font-size: 0.7rem;
  font-family: var(--mono);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  margin-right: 0.35em;
}

/* Paging is a toolbar row, separated from the data it pages by a hairline. */
p.paging {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.6rem;
  border-top: 1px solid var(--border-quiet);
  padding-top: 0.7rem;
  margin-top: 1rem;
}

/* The policy inheritance chain: a rung ladder, hung off one rail. The rail is
   structure, so it is a border colour and not the accent. */
div[data-chain-for] {
  margin: 0.75rem 0;
}

div[data-chain-for] > h4 {
  margin: 0 0 0.25rem;
  font-size: 0.8rem;
}

ol.chain {
  list-style: none;
  margin: 0.3rem 0 0.3rem 0.3rem;
  padding: 0 0 0 0.85rem;
  border-left: 2px solid var(--border-strong);
}

ol.chain > li {
  padding: 0.22rem 0;
  font-size: 0.83rem;
}

/* Settings: the tenant's declared branding as a record, and the mark strip the
   recorded inline-style defect lives on. The strip's base treatment comes from
   here; the arbitrary per-tenant accent is the style attribute the CSP drops,
   recorded in the Go gate's exception list, and this rule deliberately does
   not try to fake it with a colour of its own. */
dl.branding {
  display: grid;
  /* The grid items are the dt/dd PAIR WRAPPERS (dl > div), not the dt and dd
     themselves — so a narrow first column here caps a whole value's width.
     The 13rem cap this rule carried until 2026-08-19 broke the support email
     mid-token ("…exampl / e"), which for the one value a person copies when
     something is wrong is a misread risk, not a nit. Each pair now gets at
     least 20rem, enough for any address the grammar admits on one line. */
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 20rem), 1fr));
  gap: 0.3rem 0.8rem;
  margin: 0.7rem 0;
}

dl.branding dt {
  font-size: 0.72rem;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--text-faint);
  font-weight: 600;
  align-self: baseline;
}

dl.branding dd {
  margin: 0;
  font-size: 0.85rem;
  overflow-wrap: break-word;
}

p.brand-mark {
  background: var(--surface-raised);
  border: 1px solid var(--border);
  border-radius: var(--radius-small);
  padding: 0.5rem 0.75rem;
  font-size: 0.95rem;
}

/* An entitled module is read at weight, not colour — the words "On" and "Off"
   are the state, and weight is how this file says "look here first". */
table.module-entitlements td[data-entitled='true'] {
  font-weight: 650;
}

/* The overview's panels sit two-up where the pane is wide enough for two
   honest columns — the review measured the single column leaving half the
   screen empty at desktop widths, which is the opposite of the density the
   2026-08-05 ruling asked for. The threshold was 42rem per column while the
   content ran the viewport's full width; inside the frame the pane gives the
   rail 212px first, so the column floor drops to 34rem or the two-up would
   never fire on the width it was measured for. */
div.overview {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 34rem), 1fr));
  align-items: start;
  gap: 0.9rem;
}

/* The heading-and-scope-note section spans the row rather than sitting in a
   cell: paired against a tall panel it left a column of void, which is the
   layout the two-up grid exists to remove. */
div.overview > section[aria-labelledby='overview-heading'] {
  grid-column: 1 / -1;
}

/* Grid items keep min-width:auto by default, and a panel whose inner rows
   declare fixed label columns can therefore blow out its track and run past
   the viewport instead of wrapping. Zero it, the standard grid fix. */
div.overview > * {
  min-width: 0;
}

/* ── Form controls, globally in the palette ────────────────────────────────
   Until 2026-08-19 only the sign-in and hold-decision forms styled their
   controls, so the policy authoring surfaces rendered UA-default grey boxes
   and every checkbox checked in the browser's own blue — a colour from no
   register this file declares. One base rule for every control, and the
   UA-drawn glyphs keyed to the accent. */
input,
select,
textarea {
  font: inherit;
  font-size: 0.88rem;
  color: var(--text);
  background: var(--surface-inset);
  border: 1px solid var(--border);
  border-radius: var(--radius-small);
  padding: 0.25rem 0.45rem;
  transition: border-color 140ms ease;
}

input:focus,
select:focus,
textarea:focus {
  border-color: var(--accent);
}

input[type='checkbox'],
input[type='radio'] {
  accent-color: var(--accent);
  width: 0.95rem;
  height: 0.95rem;
  padding: 0;
}

/* ── Identifier and timestamp flow in tables ───────────────────────────────
   The review found every screen breaking fixed vocabulary mid-token —
   CREDIT_CARD_NU / MBER, 2026-08- / 19T…, riverbend_c / u — because narrow
   auto-layout columns wrap wherever they must. A finding type, a timestamp
   and a target node are tokens: they widen their column or they stay whole,
   and only the deliberately-anywhere identifiers (.identifier) keep breaking. */
td time {
  white-space: nowrap;
}

ul.findings code,
table.fleet-operations td:first-child code {
  overflow-wrap: normal;
  word-break: keep-all;
}

/* The operation column's tokens keep whole words when they fit and break
   only on overflow: keep-all here let clipboard_paste_preflight run out of
   its fixed column and collide with the decision prose beside it, which the
   review caught. break-word wraps a token only when no line can hold it. */
table.evidence-list td:nth-child(3) code {
  overflow-wrap: break-word;
  word-break: normal;
}

/* Bare-digit columns follow the design sheet's numeric-cell register: mono,
   tabular, right-aligned, quiet. Prose counts ("41 of 90 (45.6%)") stay
   left-aligned sentences; these three columns are numerals and nothing else —
   the audit tally's entry counts, the hold queue's version, the policy
   table's override count. */
table.audit-tally td:nth-child(2),
table.hold-queue td:nth-child(4),
table.policy-resolutions td:nth-child(4) {
  font-family: var(--mono);
  font-variant-numeric: tabular-nums;
  text-align: right;
}

table.audit-tally th:nth-child(2),
table.hold-queue th:nth-child(4),
table.policy-resolutions th:nth-child(4) {
  text-align: right;
}

/* The hold queue's fixed-purpose columns take content width; the identifier
   column gets what they give back, so a hold id wraps in two lines rather
   than five. */
table.hold-queue th:nth-child(1) { width: 3.4rem; }
table.hold-queue th:nth-child(4) { width: 4.5rem; }

/* A module's description is a note, not an emphasis: the row-header cell it
   sits in is bold because it names the module, and the note under the name
   drops back to regular weight. */
table.module-entitlements th[scope='row'] .note {
  font-weight: 400;
}

/* The bulk-selection toolbar: buttons lead, and the summary sentence is its
   own flex item so a wrapped line stays under the sentence's own left edge
   instead of sliding beneath the buttons. */
p:has(> [data-bulk-selected-count]) {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.35rem 0.6rem;
}

[data-bulk-selected-count] {
  flex: 1;
  min-width: 18rem;
  font-size: 0.8rem;
  color: var(--text-quiet);
}

/* The evidence list is the one table whose sixth column carries a sentence
   per row, so it gets fixed geometry: the identifier, instant and token
   columns take what a token needs, the decision keeps the slack, and the
   same-content sentences wrap in a column wide enough to read. */
table.evidence-list {
  table-layout: fixed;
}

/* Rebalanced at the frame pass: the pane is 212px narrower than the page the
   first widths were cut for, and the review measured the slack landing in the
   wrong column — the decision sentences (the remainder column) squeezed to a
   fifteen-line ribbon while the same-content prose repeated at full width.
   The identifier, instant and token columns give back what a token needs and
   no more; the decision keeps the slack. */
table.evidence-list th:nth-child(1) { width: 10rem; }
table.evidence-list th:nth-child(2) { width: 10rem; }
table.evidence-list th:nth-child(3) { width: 10.5rem; }
table.evidence-list th:nth-child(5) { width: 11.5rem; }
table.evidence-list th:nth-child(6) { width: 15rem; }

/* Fleet's stacked gap tables repeat one header set, so they share one
   geometry — fixed columns at the same fractions in every stack. */
table.fleet-agents {
  table-layout: fixed;
}

table.fleet-agents th:nth-child(1) { width: 38%; }
table.fleet-agents th:nth-child(2) { width: 24%; }

/* The policy table's full-digest annotation drops to its own bounded block
   instead of shouldering into the neighbouring column. */
table.policy-resolutions small.absent {
  display: block;
  max-width: 36ch;
  overflow-wrap: anywhere;
}

/* The audit chain's figures take the measurement register — the review found
   this screen, which is nothing but counts, was the one screen with no
   figures in it. Scoped by the screen's own data attributes. */
dd[data-head-sequence],
dd[data-entries-walked],
dd[data-verified-through],
dd[data-checkpoint-sequence],
dd[data-uncovered-count],
table.audit-tally td[data-entry-count] {
  font-family: var(--mono);
  color: var(--signal);
  font-variant-numeric: tabular-nums;
}

/* Fleet operations' closed-vocabulary list joins the register treatment
   instead of rendering as a browser-default definition list. */
div[data-operation-vocabulary] {
  background: var(--surface-inset);
  border: 1px solid var(--border-quiet);
  border-radius: var(--radius-small);
  padding: 0.5rem 0.7rem;
  margin: 0.4rem 0;
}

div[data-operation-vocabulary] dt {
  font-family: var(--mono);
  font-size: 0.78rem;
  font-weight: 600;
}

div[data-operation-vocabulary] dd {
  margin: 0.15rem 0 0;
  font-size: 0.82rem;
  color: var(--text-quiet);
}

/* ── Motion ────────────────────────────────────────────────────────────────
   THIS BLOCK IS NOT OPTIONAL AND IT IS LAST SO IT WINS. Every transition and
   every animation above is decoration over a state that text already carries,
   so removing all of them removes nothing but the flourish. A reader who has
   asked their operating system for less motion gets a console that is
   completely intact and completely still.

   IT MOVED TO THE FOOT OF THE FILE IN THE 2026-08-05 MERGE, and the move is the
   point rather than tidying. It used to sit with the form rules, and the
   incident console's `@keyframes incident-core-pulse` was appended after it —
   which is a real defect at equal specificity, not a stylistic one, and
   `MotionIsNeutralisedForAReaderWhoAsksForLessOfIt` in
   web/console/src/evidence/decision.test.tsx is the gate that says so: it
   requires this query to appear after the LAST @keyframes in the file. Anything
   animated declared below this block outranks it. */
/* ══════════════════════════════════════════════════════════════════════════
   OVERVIEW MARKS — the proportion bar, the share ring and the trend sparkline
   ══════════════════════════════════════════════════════════════════════════

   ADDED 2026-08-18 on the owner's instruction that the overview should convey
   its information visually rather than as a wall of text.

   THE WALL OF TEXT WAS NOT PADDING, which is why none of it was deleted. Every
   sentence on that screen is a QUALIFIER — the population a count is a count
   over — and `TestNoOverviewFigureIsPrintedWithoutItsQualifier` requires one
   beside every figure. A count without it is read as a count over the whole
   scope, which is the defect the gate exists to prevent.

   So the qualifier became the mark's CAPTION rather than a paragraph. Same
   sentence, same gate, less wall: the eye lands on the bar, and the sentence is
   there for the reader who needs to know what the bar is a bar of.

   EVERY MARK HERE IS DECORATIVE AND CARRIES NO STATUS ALONE. That is the one
   constraint that survived the 2026-08-05 lift of the old no-imagery rule, and
   it is satisfied structurally rather than by inspection: none of these marks
   is rendered on a branch where the figure is absent, so there is no state a
   mark can report that the words beside it do not.

   THE GEOMETRY IS AN SVG ATTRIBUTE AND NOT CSS, which is what lets these carry
   real data. `style-src 'self'` drops a style ATTRIBUTE silently, so every
   data-driven length elsewhere in this file is quantised to a class — but `d`,
   `width`, `stroke-dasharray` and friends on an SVG element are presentation
   attributes, outside style-src entirely. See src/overview/charts.tsx. */

.overview-figure-bar {
  width: 100%;
  /* The design sheet's bar is a bordered inset TRACK, not a hairline: 4px
     until 2026-08-19's frame pass, now a track a reader can see has an inside.
     The background and border are on the svg's own box, which CSS may paint;
     the fill rect stays an SVG attribute-driven length, outside style-src. */
  height: 12px;
  display: block;
  align-self: center;
  background: var(--surface-inset);
  border: 1px solid var(--border-quiet);
}
.overview-figure-bar-track { fill: var(--surface-inset); }
.overview-figure-bar-fill { fill: var(--accent); fill-opacity: 0.7; }

/* The ring sits inline with the sentence that prints its numerator and
   denominator, so it is never the only place the ratio appears. The design
   sheet draws it at 104px with the percentage large beside it; 2.6rem until
   2026-08-19's frame pass, which the review measured as the sheet's ring
   demoted to a bullet. The stroke is the phosphor's mid step at full
   strength: the accent weight rule names rings as one of the accent
   family's own jobs, so this is the register spending its allowance, not
   severity's chroma leaking. */
.overview-ring {
  width: 5.75rem;
  height: 5.75rem;
  vertical-align: -2.15rem;
  margin-right: 0.8rem;
  transform: rotate(-90deg);
}
.overview-ring-track {
  fill: none;
  stroke: var(--surface-inset);
  stroke-width: 10;
}
.overview-ring-value {
  fill: none;
  stroke: var(--phos-2);
  stroke-linecap: round;
  stroke-width: 10;
}

/* The share sentence carries the sheet's .ringnum register: the percentage
   large, bold and tabular, the rest of the sentence at body size — the words
   still state numerator, denominator and source, so the size is emphasis on
   a figure the sentence already owns. The ring is absolutely placed in a
   reserved left column so every line of the sentence keeps ONE left edge —
   the review caught the inline ring giving line two a different margin. */
p.overview-share {
  /* Block flow, deliberately: a flex here would make every text node its own
     item and shatter the sentence — the exact defect the status line's
     comment records. The reserved padding column is what gives every line
     one left edge. */
  position: relative;
  padding-left: 6.7rem;
  min-height: 6.2rem;
  padding-top: 1.1rem;
}

p.overview-share .overview-ring {
  position: absolute;
  left: 0;
  top: 50%;
  transform: translateY(-50%) rotate(-90deg);
  margin: 0;
}

p.overview-share strong[data-overview-share-percent] {
  font-size: 1.55rem;
  font-weight: 750;
  letter-spacing: -0.02em;
}

/* The same hero-numeral register wherever a derived share leads a sentence:
   the inventory's coverage figure was the metadata size while the overview's
   identical statement led at display size — one job, one volume. */
section.coverage p[data-coverage-determinate='true'] {
  font-size: 0.78rem;
  color: var(--text-quiet);
  position: relative;
  padding-left: 6.7rem;
  min-height: 6.2rem;
  padding-top: 1rem;
  max-width: 66ch;
}

section.coverage p[data-coverage-determinate='true'] .overview-ring {
  position: absolute;
  left: 0;
  top: 50%;
  transform: translateY(-50%) rotate(-90deg);
  margin: 0;
}

section.coverage strong[data-coverage-percent] {
  font-size: 1.55rem;
  font-weight: 750;
  letter-spacing: -0.02em;
  color: var(--text);
}

/* An absent measure is still a tile's answer: it keeps the absence
   register's voice (italic, never mistakable for a numeral) one step up in
   size and tone, so the tile does not read as empty beside its counted
   siblings. */
li.overview-row .overview-figure .absent {
  font-size: 0.85rem;
  color: var(--text-quiet);
}

.overview-spark {
  width: 100%;
  height: 4rem;
  display: block;
  margin: 0 0 0.6rem;
  border: 1px solid var(--border-quiet);
  background: var(--surface-inset);
}
.overview-spark-area { fill: var(--accent); fill-opacity: 0.13; stroke: none; }
.overview-spark-line {
  fill: none;
  stroke: var(--accent);
  stroke-opacity: 0.85;
  stroke-width: 1.5;
  vector-effect: non-scaling-stroke;
}

/* Motion is permitted and is kept to the one place it says something a static
   mark cannot: the trend line draws left to right, in reading order, so the
   shape of the window arrives the way it is read. It runs once, not on a loop —
   a looping animation on a dashboard is a thing the eye keeps returning to
   after it has stopped carrying information. */
@media (prefers-reduced-motion: no-preference) {
  .overview-spark-line {
    stroke-dasharray: 2000;
    stroke-dashoffset: 2000;
    animation: overview-spark-draw 1.4s ease-out forwards;
  }
  .overview-ring-value { transition: stroke-dashoffset 0.9s cubic-bezier(0.22, 1, 0.36, 1); }
}

@keyframes overview-spark-draw {
  to { stroke-dashoffset: 0; }
}


/* ── The sheet's two instruments over the evidence page ────────────────────
   The design sheet's egress graph and category radar, drawn over the one
   population this console can draw them over honestly: the records of the
   loaded page, which the table below renders row by row. The picture is
   aria-hidden decoration; the source and exit lists beside it carry every
   count in words with the page-scoped qualifier, and src/evidence/flow.test.tsx
   holds both figures to it. The motion rules live in the tail with the other
   animations, under the terminal reduced-motion block's authority. */
.evidence-figures {
  display: grid;
  grid-template-columns: 2fr minmax(min(100%, 17rem), 1fr);
  gap: 0.75rem;
  margin: 0.9rem 0 1.1rem;
}

@media (max-width: 68rem) {
  .evidence-figures {
    grid-template-columns: 1fr;
  }
}

.egress-wall,
.finding-radar {
  background: var(--surface-raised);
  border: 1px solid var(--border);
  border-radius: 6px;
  padding: 0.75rem 1rem 0.9rem;
  margin: 0;
  min-width: 0;
}

.egress-wall-scope,
.finding-radar-scope {
  color: var(--text-quiet);
  font-size: 0.75rem;
  margin: 0.25rem 0 0.7rem;
  max-width: 72ch;
}

.egress-wall-figure {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  gap: 0.9rem;
  align-items: center;
}

.egress-wall-sources,
.egress-wall-exits {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  justify-content: space-around;
  align-self: stretch;
  font-size: 0.72rem;
  color: var(--text-quiet);
}

.egress-wall-exits li {
  max-width: 11rem;
}

.egress-wall-sources strong,
.egress-wall-exits strong,
.finding-radar-axes strong {
  font-family: var(--mono);
  font-variant-numeric: tabular-nums;
  color: var(--signal);
  font-size: 0.9rem;
}

.egress-wall-canvas {
  width: 100%;
  height: auto;
  display: block;
}

.egress-lanes line {
  stroke: var(--border-strong);
  stroke-width: 1;
}

.egress-wall-bars rect {
  fill: var(--phos);
}

/* The wall glows the way the mark's bloom filter glows in the favicon: a soft
   cast, not a status. */
.egress-wall-bars {
  filter: drop-shadow(0 0 7px rgba(255, 90, 43, 0.5));
}

.egress-pulse-in {
  fill: var(--phos-3);
}

/* The exit hues repeat the outcome registers this file already assigns:
   measurement cyan for raw egress the policy permitted, phosphor for a
   transformation, and the severity ramp's own two for traffic that ended at
   the wall — beside labels that say the same thing in words. */
.egress-exit-passed_raw {
  fill: var(--signal);
}

.egress-exit-passed_redacted {
  fill: var(--phos-2);
}

.egress-exit-held_at_wall {
  fill: var(--severity-medium);
}

.egress-exit-stopped_at_wall {
  fill: var(--severity-critical);
}

.finding-radar-canvas {
  width: 100%;
  max-width: 14.5rem;
  height: auto;
  display: block;
  margin: 0 auto;
}

.finding-radar-web polygon {
  fill: none;
  stroke: var(--border-strong);
  stroke-width: 1;
}

.finding-radar-plot {
  fill: rgba(255, 90, 43, 0.2);
  stroke: var(--phos-2);
  stroke-width: 1.5;
}

.finding-radar-few {
  color: var(--text-quiet);
  font-size: 0.78rem;
}

.finding-radar-axes {
  list-style: none;
  margin: 0.5rem 0 0;
  padding: 0;
  display: flex;
  flex-wrap: wrap;
  gap: 0.3rem 0.9rem;
  font-size: 0.72rem;
  color: var(--text-quiet);
}

/* The flow meters under the wall figure's counts, and the rollup meters under
   the inventory's counts: the sheet's decision-flow track at list scale. The
   fill width is an SVG attribute; the exit fills reuse the exit hues declared
   above, so the meter and the pulse on the same lane agree. */
.egress-flow-meter,
.rollup-meter {
  display: block;
  width: 100%;
  max-width: 10.5rem;
  height: 6px;
  margin-top: 0.25rem;
  border: 1px solid var(--border-quiet);
}

.egress-flow-meter-track,
.rollup-meter-track {
  fill: var(--surface-inset);
}

.egress-flow-meter-in {
  fill: var(--phos-2);
  fill-opacity: 0.6;
}

.rollup-meter-fill {
  fill: var(--accent);
  fill-opacity: 0.6;
}

.rollup-meter {
  max-width: 9rem;
}

/* ══════════════════════════════════════════════════════════════════════════
   THE REDACTION TRANSITION — screen changes, and waiting
   ══════════════════════════════════════════════════════════════════════════

   ADDED 2026-08-18. The mark in web/brand is seven redaction bars struck across
   a line of text, drawn in phosphor with a bloom. This is that gesture in
   motion: a screen arrives the way a redaction is applied — struck across, left
   to right, then the phosphor settles.

   IT CARRIES NO STATUS AND CANNOT. A transition runs on every navigation, to
   every screen, whatever the screen says; there is no state it could report,
   which is the cleanest way to satisfy the one rule that survived the
   2026-08-05 lift. It also runs ONCE per navigation rather than looping: a
   dashboard that keeps moving after it has stopped saying anything is a
   dashboard the eye keeps going back to for no reason.

   THE SWEEP IS A PSEUDO-ELEMENT WITH AN EMPTY `content`, not a glyph.
   `decision.test.tsx` rejects any `content:` declaration that is not the empty
   string, and it is right to: a ::before carrying a character is imagery no
   markup assertion and no screen reader can interrogate. A shape is fine. */

@keyframes redaction-strike {
  0%   { clip-path: inset(0 100% 0 0); }
  100% { clip-path: inset(0 0 0 0); }
}

@keyframes phosphor-settle {
  0%   { opacity: 0.55; filter: brightness(1.5) saturate(0.6); }
  40%  { opacity: 1; filter: brightness(1.08) saturate(1); }
  100% { opacity: 1; filter: none; }
}

@keyframes redaction-sweep {
  0%   { transform: translateX(-100%); opacity: 0; }
  12%  { opacity: 1; }
  88%  { opacity: 1; }
  100% { transform: translateX(100%); opacity: 0; }
}

/* The screen itself. `key` on the routed element in app.tsx remounts it per
   navigation, which is what replays these. */
.screen-transition {
  animation:
    redaction-strike 0.34s cubic-bezier(0.22, 1, 0.36, 1) both,
    phosphor-settle 0.5s ease-out both;
  position: relative;
}

/* The bar that strikes across, in the mark's own gradient. */
.screen-transition::before {
  content: '';
  position: absolute;
  inset: 0 0 auto 0;
  height: 2px;
  background: linear-gradient(90deg, transparent, var(--phos-2), var(--phos-3), transparent);
  box-shadow: 0 0 12px var(--phos);
  animation: redaction-sweep 0.62s ease-in-out both;
  pointer-events: none;
  z-index: 2;
}

/* ── Waiting ────────────────────────────────────────────────────────────────
   The loading state is the mark's seven bars, redacting and clearing in
   sequence. It says "a read is in flight" and nothing about what will come
   back — which matters here, because a spinner that resolves into an empty
   screen has told the reader a read succeeded when it may not have.

   The live-status line beside it carries the actual state in words. */

@keyframes bar-redact {
  0%, 100% { transform: scaleX(0.15); opacity: 0.28; }
  50%      { transform: scaleX(1); opacity: 0.9; }
}

.redaction-loader {
  display: flex;
  flex-direction: column;
  gap: 3px;
  width: 8.5rem;
  padding: 0.35rem 0;
}
.redaction-loader span {
  display: block;
  height: 5px;
  background: linear-gradient(90deg, var(--phos), var(--phos-2));
  transform-origin: left center;
  animation: bar-redact 1.5s ease-in-out infinite;
}
.redaction-loader span:nth-child(1) { width: 100%; animation-delay: 0s; }
.redaction-loader span:nth-child(2) { width: 72%;  animation-delay: 0.09s; }
.redaction-loader span:nth-child(3) { width: 90%;  animation-delay: 0.18s; }
.redaction-loader span:nth-child(4) { width: 100%; animation-delay: 0.27s; }
.redaction-loader span:nth-child(5) { width: 64%;  animation-delay: 0.36s; }
.redaction-loader span:nth-child(6) { width: 86%;  animation-delay: 0.45s; }
.redaction-loader span:nth-child(7) { width: 46%;  animation-delay: 0.54s; }

/* A row-level skeleton for a table that is still filling. */
@keyframes skeleton-scan {
  0%   { background-position: -180% 0; }
  100% { background-position: 280% 0; }
}
.redaction-skeleton {
  height: 0.85rem;
  background: linear-gradient(
    90deg,
    var(--surface-inset) 0%,
    var(--surface-raised) 45%,
    rgba(255, 145, 82, 0.16) 50%,
    var(--surface-raised) 55%,
    var(--surface-inset) 100%
  );
  background-size: 220% 100%;
  animation: skeleton-scan 1.6s linear infinite;
}

/* The brand row settles the way a screen does: the identity reuses the
   phosphor-settle the navigation transition already declares, and the wall
   motif draws in once, left to right, to its standing wash weight — then its
   bricks breathe on the sheet's slow cycle, each lifting toward its heavier
   step and settling back. All of it is decoration, and the terminal
   reduced-motion block below turns all of it off — under reduce the wall
   simply stands at its static opacity, which is the finished state. */
@keyframes wall-settle {
  0% {
    transform: scaleX(0);
    opacity: 0;
  }
  100% {
    transform: scaleX(1);
    opacity: 1;
  }
}

@keyframes wall-brick {
  45% {
    opacity: 0.85;
  }
}

.console-identity {
  animation: phosphor-settle 0.5s ease-out both;
}

.console-wall {
  animation: wall-settle 0.55s ease-out both;
  transform-origin: left center;
}

.console-wall span {
  animation: wall-brick 3.4s ease-in-out infinite;
}

.console-wall span:nth-child(odd) {
  animation-delay: 0.4s;
}

/* The egress pulses: traffic runs the lanes that had traffic, dies at the
   wall where the decision ended there, and continues past it where the
   decision let it through — which is the product's own mechanism, looped as
   decoration. The counts are the words beside the picture; the terminal
   block below stops every pulse for a reader who asked for less motion. */
@keyframes egress-pulse-run {
  0% {
    transform: translateX(0);
    opacity: 0;
  }
  8% {
    opacity: 0.9;
  }
  86% {
    opacity: 0.9;
  }
  100% {
    transform: translateX(282px);
    opacity: 0;
  }
}

@keyframes egress-pulse-continue {
  0% {
    transform: translateX(0);
    opacity: 0;
  }
  10% {
    opacity: 0.95;
  }
  88% {
    opacity: 0.95;
  }
  100% {
    transform: translateX(266px);
    opacity: 0;
  }
}

@keyframes egress-flare-absorb {
  0%,
  100% {
    opacity: 0.15;
    transform: scale(0.7);
  }
  45% {
    opacity: 0.95;
    transform: scale(1.35);
  }
}

@keyframes finding-radar-pop {
  from {
    transform: scale(0.4);
    opacity: 0;
  }
}

.egress-pulse-in {
  animation: egress-pulse-run 3.2s linear infinite;
}

.egress-pulse-in.pulse-trail {
  animation-delay: 1.7s;
}

.egress-pulse-out {
  animation: egress-pulse-continue 3.2s linear infinite;
  animation-delay: 1.35s;
}

.egress-pulse-out.pulse-trail {
  animation-delay: 3.05s;
}

.egress-flare {
  animation: egress-flare-absorb 2.6s ease-in-out infinite;
  transform-origin: center;
  transform-box: fill-box;
}

.finding-radar-plot {
  animation: finding-radar-pop 0.9s 0.25s cubic-bezier(0.22, 1, 0.36, 1) both;
  transform-origin: center;
  transform-box: fill-box;
}

/* ── The NOC motion, 2026-08-20 ────────────────────────────────────────────
   The overview is what a wall display shows, so its instruments ARRIVE: the
   rings draw themselves closed, every meter grows in from the left, and the
   cards settle in sequence after the navigation strike — the same phosphor
   gesture the screen change already makes, echoed one tier down. All of it
   is one-shot decoration over values already stated in words, and the
   terminal block below stands everything still for a reader who asked for
   less motion. */
@keyframes ring-draw {
  /* 283 is the circumference of the ring's r=45 circle (2π·45 ≈ 282.74),
     the same constant charts.tsx computes for the dasharray attribute; the
     keyframe only needs the FROM, so the draw ends on each ring's own
     attribute-set value. */
  from {
    stroke-dashoffset: 283;
  }
}

@keyframes meter-grow {
  from {
    transform: scaleX(0);
  }
}

.overview-ring-value {
  animation: ring-draw 1.4s cubic-bezier(0.22, 1, 0.36, 1);
}

/* Scoped to the meter FILLS by position, never by the shared exit classes —
   the exit classes also colour the pulses, whose own animations a broader
   selector would silently cancel. */
.overview-figure-bar-fill,
.rollup-meter-fill,
.egress-flow-meter rect + rect {
  animation: meter-grow 0.9s cubic-bezier(0.22, 1, 0.36, 1) both;
  transform-origin: left center;
  transform-box: fill-box;
}

div.overview > * {
  animation: phosphor-settle 0.5s ease-out both;
}

div.overview > *:nth-child(2) {
  animation-delay: 0.06s;
}

div.overview > *:nth-child(3) {
  animation-delay: 0.12s;
}

div.overview > *:nth-child(4) {
  animation-delay: 0.18s;
}

div.overview > *:nth-child(5) {
  animation-delay: 0.24s;
}

div.overview > *:nth-child(6) {
  animation-delay: 0.3s;
}

div.overview > *:nth-child(7) {
  animation-delay: 0.36s;
}

div.overview > *:nth-child(n + 8) {
  animation-delay: 0.42s;
}

/* The mark's bars sweep, the design sheet's idle signature: each bar lifts
   from its standing 82% to full and back, staggered down the seven, on a
   cycle slow enough to read as a glow rather than a blink. It is the brand
   breathing, it carries nothing, and the terminal block below stills it. */
@keyframes mark-sweep {
  0%, 72%, 100% { opacity: 0.82; }
  80% { opacity: 1; }
}

.console-mark .mark-bar,
.console-mark .mark-bar-hot {
  animation: mark-sweep 4.2s ease-in-out infinite;
}

.console-mark rect:nth-child(2) { animation-delay: 0.09s; }
.console-mark rect:nth-child(3) { animation-delay: 0.18s; }
.console-mark rect:nth-child(4) { animation-delay: 0.27s; }
.console-mark rect:nth-child(5) { animation-delay: 0.36s; }
.console-mark rect:nth-child(6) { animation-delay: 0.45s; }
.console-mark rect:nth-child(7) { animation-delay: 0.54s; }

@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    transition: none !important;
    animation: none !important;
    scroll-behavior: auto !important;
  }
}
