/* MaverickWX — the operator console's own stylesheet.
 *
 * Every value here comes from `site/assets/tokens.css`, which this service serves alongside
 * it. There is not one colour literal, one font-size literal, one radius or one shadow in
 * this file, and `test/tokens.parity.test.ts` fails if that stops being true — from either
 * side. That test is the same device as `tests/render/test_ramp_parity.py`: two things that
 * cannot import from each other, held together by an assertion that reads both.
 *
 * Why a stylesheet of its own at all, rather than the site's `shell.css`: `CLAUDE.md` says
 * no module reaches into another, and the console is a third module. It serves the site's
 * *tokens* — the design system, which is shared vocabulary and the thing a redesign changes
 * once — and writes its own small layer of structure on top. That is also why this file is
 * short: the Transit Board is carried by the tokens, not by rules restated here.
 *
 * `DESIGN.md`, applied to an admin surface without exception:
 *   · one interface colour, the enamel, carrying structure and affordance
 *   · state by a printed rank marker (· / ! / !!) and reversal — never a status hue
 *   · flat and square; the route bullet is the only round thing, and this page has none
 *   · Manrope for language, IBM Plex Mono for anything read as a value — `--font-body` and
 *     `--font-mono`; this line said "IBM Plex Sans" for three packages after the tokens
 *     stopped naming that face anywhere, and the preload in `layout.ts` believed it
 */

*,
*::before,
*::after {
  box-sizing: border-box;
}

html {
  -webkit-text-size-adjust: 100%;
}

body {
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font-family: var(--font-body);
  font-size: var(--fs-remarks);
  line-height: var(--lh-remarks);
}

a {
  color: var(--link);
  text-decoration: underline;
}

a:hover {
  color: var(--enamel-strong);
}

:focus-visible {
  outline: var(--focus-ring);
  outline-offset: var(--focus-offset);
}

/* `--wrap-wide`, not `--wrap`.
 *
 * At 1440px the console was a 1120px column holding a 68ch measure, which left roughly 500px
 * of empty page beside every table while `/feeds`'s `Last error` — the column an operator is
 * actually reading during an incident — clipped mid-word inside its own scroller. Prose is
 * capped at `--wrap-prose` and the dense field grid at `--wrap` regardless, so widening the
 * shell spends the extra width only on the things that wanted it: the tables. */
.shell-inner {
  margin: 0 auto;
  max-width: var(--wrap-wide);
  padding-inline: var(--gutter);
  width: 100%;
}

/* ---- The station header -----------------------------------------------------------------
   The masthead is the enamel field the whole diagram hangs from. It is a band and an edge,
   not a bar with an elevation: the separation below it is a rule, per the One Plane Rule. */
.masthead {
  background: var(--enamel);
  border-bottom: 1px solid var(--enamel-strong);
  color: var(--on-enamel);
}

.masthead .shell-inner {
  align-items: baseline;
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-3);
  justify-content: space-between;
  padding-block: var(--sp-4);
}

.brand {
  align-items: center;
  color: var(--on-enamel);
  display: inline-flex;
  gap: var(--sp-2);
  font-size: var(--fs-lead);
  font-weight: 700;
  line-height: var(--lh-lead);
  text-decoration: none;
}

/* The portrait sits beside the name; its size is on the element, so the bar never reflows. */
.brand img {
  display: block;
  flex: none;
}

/* The second register of the wordmark, as on the site: one word, two weights. */
.brand .wx {
  color: var(--on-enamel-muted);
}

.station {
  color: var(--on-enamel-muted);
  font-size: var(--fs-micro);
  font-weight: 700;
  letter-spacing: var(--track-micro);
  line-height: var(--lh-micro);
  text-transform: uppercase;
}

/* ---- The title strip ---------------------------------------------------------------------
   A labelled region rather than a div: it sits between <header> and <main>, so as a plain
   div the page's own H1 would be content inside no landmark at all. That was a real axe
   finding on eight of the site's nine pages before the strip became a <section>. */
.pagehead {
  border-bottom: var(--hairline);
}

.pagehead .shell-inner {
  padding-block: var(--sp-6) var(--sp-5);
}

.pagehead-title {
  font-size: var(--fs-title);
  font-weight: 700;
  letter-spacing: var(--track-title);
  line-height: var(--lh-title);
  margin: 0;
}

.pagehead-sub {
  color: var(--ink-2);
  margin: var(--sp-2) 0 0;
  max-width: var(--wrap-prose);
}

/* ---- The work surface ---- */
main {
  display: block;
}

main .shell-inner {
  padding-block: var(--sp-6) var(--sp-8);
}

.prose {
  max-width: var(--wrap-prose);
}

.prose p {
  margin: 0 0 var(--sp-4);
}

.lead {
  font-size: var(--fs-lead);
  font-weight: 700;
  line-height: var(--lh-lead);
  margin: 0 0 var(--sp-3);
}

/* ---- The field line: LABEL...VALUE --------------------------------------------------------
   The narrow-screen form of every table, and the source's own key/value shape. Label in
   muted sans, leader in the hairline colour, value in the mono register at a fixed rate so
   two readings can be compared down a column. */
.fields {
  border-top: var(--hairline);
  list-style: none;
  margin: var(--sp-5) 0 0;
  max-width: var(--wrap-prose);
  padding: 0;
}

/* Four readings or more become two columns of rows at desk width, and one column below it.
   Ten instants and counts stacked in a 68ch column is ten rows an operator scrolls past; side
   by side it is five, and the pair of them still reads left to right. The container widens
   past the prose measure because that measure belongs to sentences, and these are values. */
.fields--dense {
  column-gap: var(--sp-7);
  display: grid;
  grid-template-columns: 1fr;
  max-width: var(--wrap);
}

@media (min-width: 60rem) {
  .fields--dense {
    grid-template-columns: 1fr 1fr;
  }
}

.field {
  align-items: baseline;
  border-bottom: var(--hairline);
  display: flex;
  gap: var(--sp-2);
  padding-block: var(--sp-2);
}

.field-label {
  color: var(--muted);
  font-size: var(--fs-micro);
  font-weight: 700;
  letter-spacing: var(--track-micro);
  line-height: var(--lh-micro);
  text-transform: uppercase;
}

/* The leader dots. A border rather than a row of characters, so a screen reader is not
   handed thirty full stops between a label and its value. */
.field-lead {
  align-self: baseline;
  border-bottom: 1px dotted var(--line);
  flex: 1 1 auto;
  min-width: var(--sp-4);
}

.field-value {
  font-family: var(--font-mono);
  font-size: var(--fs-bulletin);
  font-variant-numeric: tabular-nums;
  line-height: var(--lh-bulletin);
  /* A flex item's default `min-width: auto` refuses to shrink below its content, so a long
     value pushes the row — and with it the page — wider than the viewport. This is the
     declaration that lets the leader collapse instead. */
  min-width: 0;
  overflow-wrap: anywhere;
  text-align: right;
}

/* ---- The rank marker ---------------------------------------------------------------------
   State is a printed mark and a word, never a hue: the Saffir-Simpson ramp already spans the
   spectrum, so any status colour would collide with a category. `·` is ordinary and takes no
   emphasis; `!` is caution and takes weight; `!!` is escalation and reverses — paper on ink,
   which survives greyscale, a colour-vision difference and forced-colours mode identically.
   A17.2 is the first package to use all three: a refused sign-in takes `!` inside a reversed
   notice, and an account with no recovery codes left takes `!!` on its own field line. */
.mark {
  font-family: var(--font-mono);
  font-size: var(--fs-bulletin);
  padding-inline: var(--sp-1);
}

/* The gap between the mark and the word beside it.
 *
 * `padding-inline` alone looks right on `·` and `!`, which have no background, and wrong on
 * `!!`, which does — its padding is inside the reversed box, so the box ended flush against the
 * next letter and the page read `!!ACT NOW` and `!!NOT CONFIGURED`.
 *
 * On the mark itself rather than on `.mark + *`, because in a status cell and a record head the
 * state word is a bare **text node** and a sibling selector cannot reach it — which is exactly
 * the half the first attempt at this missed, and it missed it in the table. A margin sits
 * outside the reversed box and works for all three marks and both shapes. Zeroed inside the two
 * reversed blocks, which lay their children out with a flex `gap`. */
.mark {
  margin-inline-end: var(--sp-2);
}

.mark-ordinary {
  color: var(--muted);
}

/* Caution takes a neutral ground, not just weight.
 *
 * `!` in body colour at body weight is a character an eye slides over: measured against the
 * column beside it, `! DEGRADED` scanned *weaker* than `· CURRENT`, which is backwards. The
 * ladder now steps properly — ordinary is muted and ungrounded, caution sits on a wash of the
 * ink, escalation reverses fully. `--wash-strong` is a tint of `--ink`, not a hue, so this is
 * still the printed-rank system and not a status colour. */
.mark-caution {
  background: var(--wash-strong);
  color: var(--ink);
  font-weight: 700;
}

.mark-escalation {
  background: var(--ink);
  color: var(--paper);
  font-weight: 700;
}

/* ---- Two utilities the whole console reaches for -------------------------------------------
 * `.muted` is written on forty-odd spans across these routes — every "not recorded", "none
 * stored", "could not be read" — and had no rule at all, so each one rendered at full ink
 * weight, indistinguishable from a value that was actually there. This console's whole posture
 * is that an absence is the finding; it has to look like one.
 *
 * `.value` is the mono register outside a table cell, which `td.value` already had inside one. */
.muted {
  color: var(--muted);
}

.value {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
}

/* ---- Rules ---- */
.rule {
  background: var(--line);
  border: 0;
  height: 1px;
  margin: var(--sp-4) 0 var(--sp-3);
}

footer .shell-inner {
  padding-block: 0 var(--sp-7);
}

footer p {
  color: var(--muted);
  font-size: var(--fs-bulletin);
  line-height: var(--lh-bulletin);
  margin: var(--sp-2) 0 0;
  max-width: var(--wrap-prose);
}

/* ---- The reversed notice ------------------------------------------------------------------
   DESIGN.md's Reversed Alarm Rule, and the only place this console has to say something
   badly: a refused sign-in. Paper on ink plus a printed rank marker plus the state word —
   never a red, because the Saffir-Simpson ramp already spans the spectrum and a status hue on
   an operator surface would collide with a category. Reversal also survives greyscale, a
   colour-vision difference and forced-colours mode identically, which a red does not. */
.notice {
  background: var(--ink);
  color: var(--paper);
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-2);
  margin: 0 0 var(--sp-5);
  max-width: var(--wrap-prose);
  padding: var(--sp-2) var(--sp-3);
}

/* The marker's own rules paint ink on paper; inside a reversed bar that is ink on ink. */
.notice .mark {
  background: none;
  color: var(--paper);
  padding-inline: 0;
}

/* The flex `gap` on `.notice` and `.danger` already separates these; the rule above would add a
   second gap on top of it. */
.notice .mark,
.danger .mark {
  margin-inline-end: 0;
}

.notice-state {
  font-size: var(--fs-micro);
  font-weight: 700;
  letter-spacing: var(--track-micro);
  line-height: var(--lh-micro);
  text-transform: uppercase;
}

.notice-message {
  flex: 1 1 var(--sp-8);
  font-size: var(--fs-bulletin);
  line-height: var(--lh-bulletin);
}

/* ---- Controls ------------------------------------------------------------------------------
   DESIGN.md, Inputs: paper ground, ink text, a 1px Edge boundary, square, and a minimum
   height that is also a touch target. Focus takes the boundary to 2px ink and adds the
   standard ring; the caret is the enamel — as `--link`, which is the enamel's readable
   register in both themes rather than the field colour, which in the dark palette sits almost
   on top of the paper.

   `border-radius: 0` is the square rule being *enforced*, not broken. A user agent rounds its
   own control chrome — a button on iOS most of all — and `appearance: none` does not
   reliably take that with it. `site/assets/radar.css` writes the same declaration for the
   same reason. */
.control-block {
  margin: 0 0 var(--sp-4);
  max-width: var(--wrap-prose);
}

.control-label {
  color: var(--muted);
  display: block;
  font-size: var(--fs-micro);
  font-weight: 700;
  letter-spacing: var(--track-micro);
  line-height: var(--lh-micro);
  margin: 0 0 var(--sp-1);
  text-transform: uppercase;
}

.control {
  appearance: none;
  background: var(--paper);
  border: 1px solid var(--edge);
  border-radius: 0;
  caret-color: var(--link);
  color: var(--ink);
  font-family: var(--font-mono);
  font-size: var(--fs-bulletin);
  line-height: var(--lh-bulletin);
  min-height: var(--sp-8);
  padding-inline: var(--sp-3);
  width: 100%;
}

.control:focus-visible {
  border-color: var(--ink);
  border-width: 2px;
  outline: var(--focus-ring);
  outline-offset: var(--focus-offset);
}

.control-hint {
  color: var(--muted);
  font-size: var(--fs-bulletin);
  line-height: var(--lh-bulletin);
  margin: var(--sp-2) 0 0;
  /* The one prose class that had no measure, so it inherited the whole shell: 150 characters
     a line on `/ai`, 110 on `/flags`. Every other prose class here is capped. */
  max-width: var(--wrap-prose);
}

/* ---- A group of controls that belongs to one choice ----------------------------------------
 * The run form carries inputs that apply to `reprocess` and inputs that apply to `restamp`, and
 * there is no JavaScript here to hide the ones that do not apply. A `— reprocess only` appended
 * to each label said so and nobody read it; a fieldset with a legend says it once, structurally,
 * and a screen reader announces the legend with every control inside it.
 *
 * Square and ruled, per the One Plane Rule: a left rule and the legend, not a box with a
 * rounded corner. */
.fieldgroup {
  border: 0;
  border-inline-start: 1px solid var(--line);
  margin: var(--sp-5) 0 0;
  max-width: var(--wrap-prose);
  padding: 0 0 0 var(--sp-4);
}

.fieldgroup-legend {
  color: var(--ink-2);
  font-size: var(--fs-micro);
  font-weight: 700;
  letter-spacing: var(--track-micro);
  line-height: var(--lh-micro);
  padding: 0;
  text-transform: uppercase;
}

/* A disclosure that follows a record or a table is about the block above it, and needs the
   separation a rule would otherwise supply. */
.record + .why,
.table-scroll + .why,
.bars + .why {
  margin-top: var(--sp-6);
}

/* The primary action, which is where DESIGN.md says the interface's one colour is spent. */
.button {
  align-items: center;
  appearance: none;
  background: var(--enamel);
  border: 1px solid var(--enamel);
  border-radius: 0;
  color: var(--on-enamel);
  cursor: pointer;
  /* `inline-flex`, not the initial `inline`, and this is load-bearing on the two `.button`s
     that are anchors rather than buttons (`/backups`'s state export, `/notices`'s New notice).
     `min-height` does nothing to an inline non-replaced box, so those two collapsed to their
     line box and measured **19px** where every real <button> measured 44. That is under the
     24px floor WCAG 2.2 SC 2.5.8 sets; axe passes them under the inline-text exception, which
     is why nothing caught it. A flex box takes the height and centres the label in it. */
  display: inline-flex;
  font-family: var(--font-body);
  font-size: var(--fs-micro);
  font-weight: 700;
  justify-content: center;
  letter-spacing: var(--track-micro);
  line-height: var(--lh-micro);
  min-height: var(--sp-8);
  padding-inline: var(--sp-4);
  text-align: center;
  text-decoration: none;
  text-transform: uppercase;
}

/* `color` is restated here and must stay.
 *
 * `a:hover { color: var(--enamel-strong) }` at the top of this file has specificity (0,1,1)
 * and beats `.button`'s (0,1,0), while `.button:hover` sets the *background* to that same
 * `--enamel-strong`. On the two anchors that wear this class the label and its ground landed
 * on the identical value and the control measured **1.0:1** — the text vanished on hover.
 * axe never saw it: axe does not evaluate hover states. */
.button:hover {
  background: var(--enamel-strong);
  border-color: var(--enamel-strong);
  color: var(--on-enamel);
}

.signin,
.signout {
  margin: var(--sp-5) 0 0;
}

/* A path, a command, an identifier: the mono register, per the Two Registers Rule.
 *
 * `overflow-wrap: anywhere` because these carry values with no spaces in them — an object
 * key, a run id, a sixty-four-character digest — and an unbreakable token is wider than a
 * 320px viewport. Without it the *page* scrolls sideways to accommodate one `<code>`, which
 * is the failure the whole `.table-scroll` arrangement exists to keep off the body: measured
 * in Chromium at 320px on /feeds, where `ops/runs/latest.json` pushed the document to 352px.
 * It breaks only where a break is needed, so a short identifier still reads as one word. */
code {
  background: var(--code-bg);
  font-family: var(--font-mono);
  font-size: var(--fs-bulletin);
  overflow-wrap: anywhere;
  padding: 0 var(--sp-1);
}

/* ---- The station header's navigation ------------------------------------------------------
   DESIGN.md, Navigation: an enamel band, on-enamel links in Micro, uppercase, tracked. The
   current section takes three signals and none of them is the only colour — weight, a 2px
   underline rule, and `aria-current`, which is the one a screen reader gets. It is also not a
   link, because a link to the page you are on is a control that does nothing.

   The row wraps rather than scrolls. Five short words fit a 320px viewport in two lines, and
   a horizontally scrolling navigation is a navigation whose last item nobody finds. */
.nav {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-3);
  width: 100%;
}

.nav-item {
  border-bottom: 2px solid transparent;
  color: var(--on-enamel);
  font-size: var(--fs-micro);
  letter-spacing: var(--track-micro);
  line-height: var(--lh-micro);
  padding-block: var(--sp-1);
  text-decoration: none;
  text-transform: uppercase;
}

.nav-item:hover {
  border-bottom-color: var(--on-enamel-muted);
  color: var(--on-enamel);
}

.nav-item--current {
  border-bottom-color: var(--on-enamel);
  font-weight: 700;
}

/* ---- Records ------------------------------------------------------------------------------
   The console's table, which is not a <table>. DESIGN.md: below 720px every table becomes a
   stack of LABEL...VALUE field lines, and a feed row is six columns of prose — as a table
   that is a horizontal scroller at 320px on the surface an operator opens first. Separation
   is a rule and space, per the One Plane Rule; there is no card here and no elevation. */
.record {
  border-top: var(--hairline);
  margin: var(--sp-6) 0 0;
  padding-top: var(--sp-4);
}

.record:first-of-type {
  margin-top: var(--sp-4);
}

/* The state leads and the title follows it. Ranged right against a variable-length heading
   the state words never formed a column, so finding the one bad record meant reading every
   title down the page; given the first position and a minimum width they stack under each
   other and the page can be scanned in one pass. The row still wraps on a narrow screen. */
.record-head {
  align-items: baseline;
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-1) var(--sp-4);
}

.record-title {
  font-size: var(--fs-lead);
  font-weight: 700;
  line-height: var(--lh-lead);
  margin: 0;
}

/* The state word, and the printed marker beside it. Never the marker alone.
 *
 * In the reading register rather than the label one: this is the first thing on the record and
 * the thing the record is read for, and at Micro it was the same size as the word `COMMAND`
 * four lines below it. */
.record-state {
  flex: 0 0 auto;
  font-size: var(--fs-bulletin);
  font-weight: 700;
  letter-spacing: var(--track-micro);
  line-height: var(--lh-lead);
  min-width: calc(var(--sp-8) * 3);
  text-transform: uppercase;
}

.record-lead,
.note {
  color: var(--ink-2);
  font-size: var(--fs-bulletin);
  line-height: var(--lh-bulletin);
  margin: var(--sp-2) 0 0;
  max-width: var(--wrap-prose);
}

.note {
  color: var(--muted);
}

.note-label {
  color: var(--ink-2);
  font-size: var(--fs-micro);
  font-weight: 700;
  letter-spacing: var(--track-micro);
  text-transform: uppercase;
}

/* A field line's value is a fixed column of mono on a table; in a record it is often a
   sentence, so it wraps and stops being right-aligned once it is long enough to need to.
 *
 * The basis used to be `var(--wrap-prose)` — 68 characters — which is wider than the column
 * these sit in, so *every* prose value wrapped onto a line of its own whether it was a
 * paragraph or the word `completed`. Every record was twice as tall as its content. The basis
 * is now a third of the row: a short value sits beside its label and a long one still takes
 * the second line it needs. */
.fields--prose .field {
  flex-wrap: wrap;
}

.fields--prose .field-value {
  flex: 1 1 33%;
  font-family: var(--font-body);
  min-width: 0;
  text-align: left;
}

.fields--prose .field-lead {
  display: none;
}

/* ---- Tables -------------------------------------------------------------------------------
   For content that genuinely is tabular and cannot narrow: the CSP buckets, a snapshot diff,
   a run's per-storm dispositions. Wide content scrolls inside its own box, never taking the
   page body with it — the same rule and the same pattern as the site's API reference and the
   upstream-advisory table on /status. `tabindex` is what makes the scroller reachable from
   the keyboard when nothing inside it can take focus (axe: scrollable-region-focusable). */
.table-scroll {
  border: 1px solid var(--divider);
  margin: var(--sp-4) 0 0;
  max-width: 100%;
  overflow-x: auto;
}

.table-scroll:focus-visible {
  outline: var(--focus-ring);
  outline-offset: var(--focus-offset);
}

table {
  border-collapse: collapse;
  min-width: var(--panel-w);
  width: 100%;
}

th,
td {
  border-bottom: var(--hairline);
  padding: var(--sp-2) var(--sp-3);
  text-align: left;
  vertical-align: top;
}

thead th {
  background: var(--enamel-wash);
  color: var(--ink);
  font-size: var(--fs-micro);
  font-weight: 700;
  letter-spacing: var(--track-micro);
  line-height: var(--lh-micro);
  text-transform: uppercase;
  white-space: nowrap;
}

tbody td {
  font-size: var(--fs-bulletin);
  line-height: var(--lh-bulletin);
}

tbody tr:last-child td {
  border-bottom: 0;
}

/* A value read down a column is mono with tabular figures, per the Two Registers Rule.
 *
 * Not `white-space: nowrap`. These cells carry a forty-character commit SHA and a
 * sixty-four-character digest, and a cell that refuses to wrap holds its whole column open —
 * measured in Chromium at 1440px, where one hash pushed the version table wide enough to clip
 * the column beside it inside its own scroller. */
td.value {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
}

/* `break-word` inside a table, `anywhere` everywhere else, and the difference is the whole
   reason both appear in this file.
 *
 * `anywhere` lets a word break when the *minimum* width is computed, so a column's min-content
 * becomes one character and the browser will happily squeeze `connect-src` down to `connec` /
 * `t-src` rather than letting the box scroll — measured at 375px on the CSP table, which is
 * where a scroller should have engaged instead. `break-word` breaks only what still overflows
 * after the natural widths are chosen, so the table keeps its columns and `.table-scroll` does
 * its job. Outside a scroller there is nothing to scroll and `anywhere` is what keeps a long
 * identifier from taking the whole page sideways. */
.table-scroll code,
.table-scroll td,
.table-scroll th {
  overflow-wrap: break-word;
}

/* ---- Lists and links ---------------------------------------------------------------------- */
.picker {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-2) var(--sp-3);
  list-style: none;
  margin: var(--sp-4) 0 0;
  padding: 0;
}

.picker a {
  font-family: var(--font-mono);
  font-size: var(--fs-bulletin);
  line-height: var(--lh-bulletin);
}

.picker li {
  border-left: 1px solid var(--line);
  padding-left: var(--sp-3);
}

/* A block of JSON or a long error, kept inside its own scroller for the same reason a wide
   table is. `tabindex` for the same reason too. */
.pre-scroll {
  border: 1px solid var(--divider);
  margin: var(--sp-4) 0 0;
  max-height: 60vh;
  overflow: auto;
}

.pre-scroll:focus-visible {
  outline: var(--focus-ring);
  outline-offset: var(--focus-offset);
}

.pre-scroll pre {
  font-family: var(--font-mono);
  font-size: var(--fs-bulletin);
  line-height: var(--lh-bulletin);
  margin: 0;
  padding: var(--sp-3);
}

/* ---- The page's own verdict line ----------------------------------------------------------
   A summary a reader meets before the records. Ruled off rather than boxed. */
.verdict {
  border-bottom: var(--hairline);
  margin: 0 0 var(--sp-4);
  padding-bottom: var(--sp-4);
}

.verdict-line {
  font-size: var(--fs-lead);
  font-weight: 700;
  line-height: var(--lh-lead);
  margin: 0;
}

.verdict-detail {
  color: var(--ink-2);
  margin: var(--sp-2) 0 0;
  max-width: var(--wrap-prose);
}

/* ---- The one-line verdict, and the reasoning behind it -------------------------------------
   What `.verdict` became. The word, its clause, and a row of short facts; the account of how
   the word is reached sits in the disclosure below and no longer stands between an operator
   and the rows. Ruled off rather than boxed, per the One Plane Rule. */
.summary {
  border-bottom: var(--hairline);
  margin: 0 0 var(--sp-5);
  padding-bottom: var(--sp-4);
}

.summary-line {
  font-size: var(--fs-lead);
  line-height: var(--lh-lead);
  margin: 0;
  max-width: var(--wrap-prose);
}

/* The verdict itself. Bold and uppercase where the clause after it is neither, so the word
   carries and the sentence explains. */
.summary-state {
  font-weight: 700;
  letter-spacing: var(--track-micro);
  margin-inline-end: var(--sp-2);
  text-transform: uppercase;
}

.summary-meta {
  color: var(--muted);
  font-size: var(--fs-bulletin);
  line-height: var(--lh-bulletin);
  margin: var(--sp-2) 0 0;
}

/* ---- The disclosure -------------------------------------------------------------------------
   `<details>`, not a script: this console has no JavaScript, and the element is its own
   keyboard-operable control. The summary takes the label register and the enamel's readable
   link colour, because it is a thing you press. The marker is the user agent's own triangle —
   drawing one would be inventing an icon system for a single glyph. */
.why {
  margin: var(--sp-3) 0 0;
  max-width: var(--wrap-prose);
}

.why-summary {
  color: var(--link);
  cursor: pointer;
  font-size: var(--fs-micro);
  font-weight: 700;
  letter-spacing: var(--track-micro);
  line-height: var(--lh-micro);
  padding-block: var(--sp-1);
  text-transform: uppercase;
  width: fit-content;
}

.why-summary:hover {
  color: var(--enamel-strong);
}

.why-summary:focus-visible {
  outline: var(--focus-ring);
  outline-offset: var(--focus-offset);
}

.why-body {
  border-inline-start: 1px solid var(--line);
  color: var(--ink-2);
  font-size: var(--fs-bulletin);
  line-height: var(--lh-bulletin);
  margin: var(--sp-2) 0 0;
  padding-inline-start: var(--sp-3);
}

.why-body p {
  margin: 0 0 var(--sp-3);
}

.why-body p:last-child {
  margin-bottom: 0;
}

/* ---- The overview table ----------------------------------------------------------------------
   Every subject of a room, one row each, state first. It is what `/versions` always had and
   what every other room lacked: a column an operator can run an eye down instead of a stack of
   records whose state words sat six hundred pixels apart. */
.statustable .statecell {
  font-size: var(--fs-micro);
  font-weight: 700;
  letter-spacing: var(--track-micro);
  text-transform: uppercase;
  white-space: nowrap;
}

.statustable tbody th {
  font-weight: 700;
  white-space: nowrap;
}

/* An instant, a duration or a budget is one token and must not be broken across lines: a
   column of `2026-09-\n06T12:05:04Z` is a column nothing can be compared down. The table is
   already inside `.table-scroll`, which is the device that carries the extra width. */
.statustable .value {
  white-space: nowrap;
}

/* Below 48rem the overview table stops being a table.
 *
 * `.table-scroll` is the right device for a wide *value* table — a digest, an instant, a
 * column of ages — where the width is real and a sideways nudge is a fair price. It is the
 * wrong device here, and measurably so: at 390px `/` showed STATE and ROOM and put the column
 * carrying the answer off the side of the screen, so the page that exists to answer one
 * question at 3am did not answer it on the device it gets read on. `/feeds`, `/flags`, `/ai`
 * and `/versions` all did the same.
 *
 * This is the rule `record`'s own doc comment has stated since A17.3 — *"below 720px every
 * table becomes a stack of LABEL...VALUE field lines"* — finally applied to the one table that
 * was exempting itself from it.
 *
 * `statusTable` writes out every ARIA role explicitly, because `display: block` strips a
 * table's implicit roles from the accessibility tree; `data-label` carries each column name
 * into its cell, and `::before` prints it where the header row used to be. */
@media (max-width: 48rem) {
  .statustable,
  .statustable thead,
  .statustable tbody,
  .statustable tr,
  .statustable th,
  .statustable td {
    display: block;
    min-width: 0;
    white-space: normal;
  }

  /* The header row's job is done by the per-cell labels below. */
  .statustable thead {
    display: none;
  }

  .statustable tbody tr {
    border-bottom: var(--hairline);
    padding-block: var(--sp-3);
  }

  .statustable tbody tr:last-child {
    border-bottom: 0;
  }

  .statustable th,
  .statustable td {
    border-bottom: 0;
    padding: 0;
  }

  /* State and the row's name lead, as they do on a record: the mark and the word, then the
     thing they are about. Neither needs a printed label. */
  .statustable .statecell {
    font-size: var(--fs-bulletin);
  }

  .statustable tbody th {
    font-size: var(--fs-lead);
    line-height: var(--lh-lead);
    margin-block: var(--sp-1) var(--sp-2);
  }

  .statustable td:not(.statecell)::before {
    color: var(--muted);
    content: attr(data-label);
    display: block;
    font-size: var(--fs-micro);
    font-weight: 700;
    letter-spacing: var(--track-micro);
    line-height: var(--lh-micro);
    text-transform: uppercase;
  }

  .statustable td:not(.statecell) {
    margin-bottom: var(--sp-2);
  }

  /* A cell with nothing in it prints a label over a blank. Hidden rather than left hanging. */
  .statustable td:empty {
    display: none;
  }

  .statustable tbody th {
    min-width: 0;
  }
}

/* The last column of the console strip carries a sentence rather than a value.
 *
 * `table`'s `min-width` is 300px, so at phone width the browser squeezed that column to a few
 * characters and wrapped the sentence to roughly one word a line — a row four hundred pixels
 * tall, for text that was off the side of the scroller and unreadable anyway. Measured at 390px
 * in Chromium, where five rows ran to about two thousand.
 *
 * Given a real minimum the sentence keeps its own shape, each row is a line or two, and the
 * width goes where width is supposed to go on this console: into `.table-scroll`. The state and
 * the room — the half that answers the question — stay on screen without scrolling. */
@media (min-width: 48.01rem) {
  .statustable td:last-child {
    min-width: 24rem;
  }
}

/* ---- The console's four rooms --------------------------------------------------------------
   A ruled list, not a grid of cards: the One Plane Rule says nothing floats, and a card drawn
   round each of four links is the reflex this system breaks. Each entry carries the question
   it answers, because an operator arrives with a question rather than with a taxonomy. */
.rooms {
  border-top: var(--hairline);
  list-style: none;
  margin: var(--sp-5) 0 0;
  max-width: var(--wrap-prose);
  padding: 0;
}

.room {
  border-bottom: var(--hairline);
  padding-block: var(--sp-3);
}

.room-link {
  display: block;
  font-size: var(--fs-lead);
  font-weight: 700;
  line-height: var(--lh-lead);
}

.room-question {
  color: var(--ink-2);
  display: block;
  font-size: var(--fs-bulletin);
  line-height: var(--lh-bulletin);
  margin-top: var(--sp-1);
}

/* A heading that introduces a run of records rather than sitting inside one. */
.section-title {
  font-size: var(--fs-lead);
  font-weight: 700;
  line-height: var(--lh-lead);
  margin: var(--sp-7) 0 0;
}

/* Prose that follows a run of records is a note about them, not another record. It takes the
   space a rule would otherwise have to supply, per the One Plane Rule. */
.record + .prose,
.table-scroll + .prose {
  margin-top: var(--sp-6);
}

/* ---- The job runner (A17.4) ----------------------------------------------------------------
   The form, and the run log.

   The log is the one surface on this console that arrives while it is being drawn: it is a
   chunked HTTP response with no JavaScript on it, so the browser lays out each line as it
   comes off the socket (see src/runs/stream.ts). Two consequences shape the rules below.

   First, nothing here may depend on knowing the log's final size — no fixed-height scroller,
   no sticky footer. The list simply grows down the page, which is also what gets a reader the
   browser's own "stay at the bottom" behaviour for free, where a box with its own scrollbar
   would need a script to follow.

   Second, a log line is arbitrary text with no spaces in the places that matter — a JSON
   record, a filesystem path, a traceback frame — so it *wraps* rather than scrolling. The
   `.table-scroll` device is right for a table with columns and wrong here: a horizontal
   scroller on the one view somebody watches for minutes is a scroller they have to keep
   dragging back. `overflow-wrap: anywhere` plus `white-space: pre-wrap` keeps the leading
   indentation a traceback carries while never taking the page sideways. */
.runform {
  margin: var(--sp-5) 0 0;
  max-width: var(--wrap-prose);
}

/* The advisory bulletin. A textarea rather than a file input: what is pasted here is the raw
   product text, and it goes to the child on stdin without ever becoming an argument. */
.control--text {
  min-height: calc(var(--sp-8) * 4);
  padding-block: var(--sp-2);
  resize: vertical;
}

/* A checkbox is the one control a user agent draws itself, and `appearance: none` on it would
   mean drawing a tick. It keeps the platform control and takes only the square corner the
   system asks for everywhere else. */
.control-check {
  accent-color: var(--enamel);
  border-radius: 0;
  margin-inline-end: var(--sp-2);
}

.log {
  border-top: var(--hairline);
  list-style: none;
  margin: var(--sp-4) 0 0;
  padding: 0;
}

.log-line {
  border-bottom: var(--hairline);
  display: grid;
  gap: 0 var(--sp-3);
  grid-template-columns: auto auto 1fr;
  padding-block: var(--sp-1);
}

/* Below the fold of a phone the three columns become two rows: a line number and a stream
   beside each other, and the text under them at full measure. A 4,000-character line in a
   1fr column beside two others is four characters wide. */
@media (max-width: 40rem) {
  .log-line {
    grid-template-columns: auto 1fr;
  }

  .log-text {
    grid-column: 1 / -1;
  }
}

/* The line number is a link to itself, so a line can be pointed at in a message to somebody
   else. Tabular so the column does not shift as the log passes 1000 lines. */
.log-seq {
  color: var(--muted);
  font-family: var(--font-mono);
  font-size: var(--fs-micro);
  font-variant-numeric: tabular-nums;
  line-height: var(--lh-bulletin);
  text-align: end;
  min-width: calc(var(--sp-5) + var(--sp-3));
}

.log-stream {
  color: var(--ink-2);
  font-family: var(--font-body);
  font-size: var(--fs-micro);
  letter-spacing: var(--track-micro);
  line-height: var(--lh-bulletin);
  min-width: calc(var(--sp-8) + var(--sp-4));
  text-transform: uppercase;
}

.log-text {
  font-family: var(--font-mono);
  font-size: var(--fs-bulletin);
  line-height: var(--lh-bulletin);
  overflow-wrap: anywhere;
  white-space: pre-wrap;
}

/* This console's own lines — the command it spawned, the lock it took, the exit it saw. They
   are set in the language register rather than the value one, because they are sentences this
   service wrote and everything around them is output it merely relayed. Weight and face carry
   that, not a colour: there is one interface colour and it is spent on the primary action. */
.log-line--runner .log-text {
  font-family: var(--font-body);
  font-weight: 700;
}

/* A line the child wrote to stderr. Emphasised by the rule beside it — the one plane, ruled —
   rather than by a tint, which on this system would collide with a Saffir-Simpson tier. */
.log-line--stderr .log-text {
  border-inline-start: 2px solid var(--line-strong);
  padding-inline-start: var(--sp-2);
}

/* ---- Maintenance notices (A17.5) ----------------------------------------------------------
   Two additions the notices room needs beyond what the runs room already has: a preview of the
   site banner, and a secondary button for the actions that are not the primary one on a page.

   The preview mirrors the site's `.databanner` family, which this console cannot import — so
   it is rebuilt here from the shared tokens and held equivalent by
   `test/notices/banner.parity.test.ts`. DESIGN.md's Reversed Alarm Rule decides the three:
   `incident` inverts to paper-on-ink and carries a rank marker because it is the alarm;
   `maintenance` is the ruled caution and `info` the informational bar, neither of which
   reverses. None is a status hue — all three are the enamel family, so none can be mistaken
   for a Saffir-Simpson category. */
.notice-preview {
  border: 1px solid var(--divider);
  font-family: var(--font-body);
  font-size: var(--fs-remarks);
  margin: 0 0 var(--sp-5);
  max-width: var(--wrap-prose);
  padding: var(--sp-3) var(--sp-4);
}

.notice-preview-line {
  margin: 0;
}

/* The printed rank marker on the reversed bar, in the value register so it reads as a mark
   rather than a word. It carries no meaning on its own — the reversal and the title do — which
   is why it is aria-hidden in the markup. */
.notice-preview-mark {
  font-family: var(--font-mono);
  font-weight: 700;
  margin-inline-end: var(--sp-2);
}

.notice-preview-body {
  margin-inline-start: var(--sp-2);
}

/* Informational: the quiet bar. Paper on paper, ruled — the site's `.databanner--note`. */
.notice-preview--info {
  background: var(--paper);
  border-color: var(--divider);
  color: var(--ink);
}

/* Maintenance: the ruled caution — the site's `.databanner--warn`, wash on the plane. */
.notice-preview--maintenance {
  background: var(--wash);
  border-color: var(--edge);
  color: var(--ink);
}

/* Incident: the reversed alarm — paper on ink, the site's default `.databanner`. */
.notice-preview--incident {
  background: var(--ink);
  border-color: var(--ink);
  color: var(--paper);
}

/* The secondary action on a page — edit beside publish, republish beside the record. Outlined
   rather than filled, so the one filled button stays the primary one the eye lands on. It is
   still the enamel doing the work, in its readable `--link` register, not a second colour. */
.button--secondary {
  background: var(--paper);
  border-color: var(--edge);
  color: var(--link);
}

.button--secondary:hover {
  background: var(--wash);
  border-color: var(--link);
  color: var(--link);
}

/* A form that is one button — publish, withdraw, republish. It sits tight to the record above
   it rather than opening the `--sp-5` gap a full form does. */
.runform--inline {
  margin: 0;
}

/* ---- Storm Mode's curated events (SM-6) ---------------------------------------------------
   Three things the events room needs beyond the notices': the hazard checkboxes, the operators'
   summary set apart as theirs, and the outline of a drawn area.

   The checkboxes are labels at least `--sp-6` (24px) tall, so the whole line is the target and
   WCAG 2.2's 2.5.8 minimum is met by the label rather than by the platform's small box. */
.event-hazards {
  display: grid;
  gap: 0 var(--sp-4);
  grid-template-columns: repeat(auto-fill, minmax(12rem, 1fr));
  margin: var(--sp-2) 0 0;
}

.event-hazard {
  align-items: center;
  cursor: pointer;
  display: flex;
  min-height: var(--sp-6);
  padding-block: var(--sp-1);
}

/* The operators' words, set apart by the ruled edge a quotation takes, never by a tint: the
   page labels them as theirs, and the rule says "quoted" without saying "alarm". */
.event-summary {
  border-inline-start: 2px solid var(--line-strong);
  margin: 0 0 var(--sp-5);
  max-width: var(--wrap-prose);
  padding-inline-start: var(--sp-4);
  white-space: pre-line;
}

.event-summary p {
  margin: 0;
}

.event-codes {
  margin: 0 0 var(--sp-2);
  max-width: var(--wrap-prose);
  overflow-wrap: anywhere;
}

.event-figure {
  margin: 0 0 var(--sp-5);
  max-width: var(--wrap-prose);
}

/* The outline, with no map under it. The SVG carries no colour of its own — the console's CSP
   is `style-src 'self'`, so every fill and stroke is here — and it scales to the column with its
   aspect kept. The ground is the plane's wash; the area is the enamel's wash, ruled in the
   enamel, so it reads as "the selection" and never as a hazard or a category. */
.event-area {
  border: 1px solid var(--line);
  display: block;
  height: auto;
  max-height: 28rem;
  width: 100%;
}

.event-area-ground {
  fill: var(--wash);
}

.event-area-shape {
  fill: var(--enamel-wash);
  stroke: var(--enamel);
  stroke-linejoin: round;
  stroke-width: 2;
}

/* ---- Analytics (A17.6): in-house CSS/SVG bars ---------------------------------------------
   No charting dependency and no JavaScript, per the brief. Each bar is an SVG <rect> whose
   width is computed server-side in a 0–100 coordinate space; the fill is the one interface
   colour and the track is its wash. The label and the exact count sit beside the bar as text,
   so the chart reads identically in greyscale, to a screen reader, and under this console's
   no-inline-style policy — the SVG itself is aria-hidden decoration. Flat and square: the rect
   takes no radius, nothing floats, and severity is not a concept here, so no tier colour. */
.bars {
  list-style: none;
  margin: var(--sp-4) 0 0;
  max-width: var(--wrap-prose);
  padding: 0;
}

.barrow {
  align-items: center;
  border-bottom: var(--hairline);
  display: grid;
  gap: var(--sp-2) var(--sp-3);
  grid-template-columns: minmax(0, 18rem) 1fr auto;
  padding-block: var(--sp-2);
}

/* Below a phone's width the three columns become two rows: the label at full measure, then
   the bar and its count under it. A path in an 18rem column beside two others is a column of
   wrapped fragments; given the whole width it reads. */
@media (max-width: 40rem) {
  .barrow {
    grid-template-columns: 1fr auto;
  }

  .barrow-label {
    grid-column: 1 / -1;
  }
}

/* A label is a value read down a column — a path, a host, a country code, a date — so it is
   the mono register. `overflow-wrap: anywhere` because a path has no spaces and would
   otherwise hold its column open past the viewport. */
.barrow-label {
  font-family: var(--font-mono);
  font-size: var(--fs-bulletin);
  line-height: var(--lh-bulletin);
  min-width: 0;
  overflow-wrap: anywhere;
}

/* The track is the bar's ground — the enamel's wash, the same tint the table header uses — and
   it stretches the SVG's 0–100 space to whatever width the column has (preserveAspectRatio is
   "none" on the element). `display: block` so it takes the row height rather than baseline. */
.barrow-track {
  background: var(--enamel-wash);
  display: block;
  height: var(--sp-4);
  width: 100%;
}

/* The filled part: the one interface colour. A zero-width rect is simply invisible, which is
   the honest drawing of a zero beside its printed 0. */
.barrow-bar {
  fill: var(--enamel);
}

.barrow-value {
  font-family: var(--font-mono);
  font-size: var(--fs-bulletin);
  font-variant-numeric: tabular-nums;
  line-height: var(--lh-bulletin);
  text-align: right;
}

/* ---- The reversed destructive control (A17.8) ----------------------------------------------
   DESIGN.md's Reversed Alarm Rule, applied to the one thing this package adds that an operator
   can regret: turning a feature off, and removing the destination alerts go to. *"Escalation
   inverts, it does not tint... a destructive action gets reversal and `!!`, never a red
   button."* So the block is paper on ink, carries the printed `!!`, and states its consequence
   in the block rather than in a confirm dialog — this console has no JavaScript to draw one,
   and a consequence you can read before you press is better than one you dismiss afterwards.

   Not a red, and it is worth restating why on a console rather than on the public site: the
   Saffir-Simpson ramp already spans the spectrum, so a status hue anywhere in this system
   collides with a category. Reversal also survives greyscale, a colour-vision difference and
   forced-colours mode identically, which a red does not. */
.danger {
  background: var(--ink);
  color: var(--paper);
  margin: var(--sp-4) 0 0;
  max-width: var(--wrap-prose);
  padding: var(--sp-3);
}

.danger-head {
  align-items: baseline;
  display: flex;
  gap: var(--sp-2);
  margin: 0;
}

/* Ink on ink inside a reversed block; the marker's own rule paints the other way. */
.danger .mark {
  background: none;
  color: var(--paper);
  padding-inline: 0;
}

.danger-state {
  font-size: var(--fs-micro);
  font-weight: 700;
  letter-spacing: var(--track-micro);
  line-height: var(--lh-micro);
  text-transform: uppercase;
}

.danger-body {
  font-size: var(--fs-bulletin);
  line-height: var(--lh-bulletin);
  margin: var(--sp-2) 0 0;
}

/* A control block inside a reversed block still needs the separation the ruled surface outside
   one gets from a border. Without it the field's label sits on the consequence it follows. */
.danger .control-block {
  margin-top: var(--sp-4);
}

/* The label, hint and field inside a reversed block take the reversed ground with them. The
   input itself stays paper-on-ink — a text field drawn in ink on ink is a field a reader
   cannot see what they are typing in — and takes the paper edge so its boundary still reads. */
.danger .control-label,
.danger .control-hint,
.danger .danger-body {
  color: var(--paper);
}

.danger .control {
  border-color: var(--paper);
}

/* The action inside a reversed block. Paper ground, ink text: the inverse of `.button`, so the
   one filled enamel button on a page is still the ordinary primary action and this is visibly
   not it. The `!!` above carries the rank; this carries the affordance. */
.button--reversed {
  background: var(--paper);
  border-color: var(--paper);
  color: var(--ink);
}

.button--reversed:hover {
  background: var(--wash-strong);
  border-color: var(--wash-strong);
  color: var(--ink);
}

/* A `<pre>` inside a record — the restore commands. The scroller and its focus ring are
   `.pre-scroll`'s already; this only stops the block adding a second top margin to a record
   that has just closed its field lines. */
.record .pre-scroll {
  margin-top: var(--sp-4);
}
