/* CANON EVENT wall.
   Light only. Dark theme is cut. There is no green token anywhere in this file,
   and that absence is stated on screen in the legend.

   Two typefaces, one rule: if a machine produced it (identifier, GTIN, hash,
   timestamp, collector id, template version, rate, count) it is IBM Plex Mono.
   If a human or a regulator wrote it, it is Public Sans. Public Sans is the US
   Web Design System typeface, the type CPSC itself publishes in, so the wall
   visually cites the regulator it audits.

   Type scale, no exceptions:  10 11 12 14 15 16 20 28
   Spacing scale:              4 8 12 16 24 32 48
   Weights:                    400 and 700 only
   Radius:                     0 everywhere except buttons at 4px
   Elevation:                  none. Separation is 1px hairlines and one step
                               of surface lightness. Zero box-shadow except focus.
*/

@import url("contract/tokens.css");

* { margin: 0; padding: 0; box-sizing: border-box; }

body {
  background: var(--ground);
  color: var(--ink);
  font-family: var(--font-prose);
  font-size: 14px;
  line-height: 1.45;
  -webkit-font-smoothing: antialiased;
}

.mono { font-family: var(--font-machine); font-variant-numeric: tabular-nums; }
.shell { max-width: 1440px; margin: 0 auto; padding: 0 32px 96px; }

:focus-visible { outline: none; box-shadow: inset 0 0 0 2px var(--focus); }

/* ---------------------------------------------------------------- masthead */

.masthead {
  display: flex; align-items: baseline; gap: 16px;
  padding: 32px 0 16px;
  border-bottom: 1px solid var(--rule-strong);
}
.wordmark { font-size: 20px; font-weight: 700; letter-spacing: -0.01em; }
.wordmark span { color: var(--hazard); }
.tagline { font-size: 12px; color: var(--ink-3); }
.masthead .spacer { flex: 1; }
.affiliation {
  font-size: 10px; color: var(--ink-3); text-align: right; max-width: 280px;
  line-height: 1.35;
}

/* ----------------------------------------------------------- verdict band */

.verdict {
  padding: 32px 0 24px;
  border-bottom: 1px solid var(--rule);
  transition: background-color 420ms ease, color 420ms ease, padding 420ms ease;
}
.verdict h1 {
  font-size: 28px; font-weight: 700; line-height: 1.25; max-width: 26ch;
  letter-spacing: -0.015em;
}
.verdict .sub { margin-top: 12px; font-size: 14px; color: var(--ink-2); max-width: 60ch; }
.verdict .clause {
  margin-top: 12px; font-size: 12px; color: var(--ink-3);
  font-family: var(--font-machine);
}

/* The one motion moment in the entire interface. Nothing else animates. */
.act-finding.is-withheld {
  background: var(--void); color: var(--void-ink);
  margin: 0 -32px; padding: 48px 32px;
}
.act-finding.is-withheld h1 { color: var(--void-ink); }
.act-finding.is-withheld .sub { color: #C9CDD1; }
.act-finding.is-withheld .clause { color: #868E95; }
.act-finding.is-withheld .withheld-mark {
  display: block; font-family: var(--font-machine); font-size: 12px;
  /* On --void, not on paper: 12px --hazard measures 4.32:1 there and fails.
     This label is on screen in ?state=v1, which is a filmed state. */
  letter-spacing: 0.14em; margin-bottom: 16px; color: var(--hazard-on-dark);
}

/* --------------------------------------------------------- instrument line */

.instruments {
  display: flex; gap: 0; border-bottom: 1px solid var(--rule);
  flex-wrap: wrap;
}
.instrument {
  padding: 16px 24px 16px 0; margin-right: 24px;
  border-right: 1px solid var(--rule); min-width: 150px;
}
.instrument:last-child { border-right: none; }
.instrument .label {
  font-family: var(--font-machine); font-size: 10px; letter-spacing: 0.1em;
  color: var(--ink-3); text-transform: uppercase;
}
.instrument .value {
  font-family: var(--font-machine); font-size: 16px; font-weight: 700;
  margin-top: 4px;
}
.instrument .qualifier { font-size: 11px; color: var(--ink-3); margin-top: 4px; }
.instrument.is-struck .value { color: var(--unknown); text-decoration: line-through; }
.instrument.is-struck .qualifier::after {
  content: " contaminated by a withheld arm"; color: var(--hazard-deep);
}
.instrument.is-pending .value { color: var(--unknown); }

/* -------------------------------------------------------------- arm rail */

.arm-rail { display: grid; grid-template-columns: repeat(3, 1fr); gap: 1px;
            background: var(--rule); border-bottom: 1px solid var(--rule); }
.arm {
  background: var(--surface); padding: 16px 20px; min-height: 132px;
  display: flex; flex-direction: column; gap: 8px;
}
.arm-head { display: flex; align-items: baseline; gap: 8px; }
.arm-code { font-family: var(--font-machine); font-size: 16px; font-weight: 700; }
.arm-host { font-family: var(--font-machine); font-size: 11px; color: var(--ink-3); }
.arm-state {
  margin-left: auto; font-family: var(--font-machine); font-size: 10px;
  letter-spacing: 0.08em; padding: 2px 6px; border: 1px solid var(--rule-strong);
  color: var(--ink-2);
}
.arm-copy { font-size: 12px; color: var(--ink-2); line-height: 1.4; }
.arm-attest {
  font-family: var(--font-machine); font-size: 10px; color: var(--ink-3);
  margin-top: auto; padding-top: 8px; border-top: 1px solid var(--rule);
}

.arm.state-WITHHELD { background: var(--void); color: var(--void-ink); }
.arm.state-WITHHELD .arm-host,
.arm.state-WITHHELD .arm-attest { color: #868E95; }
.arm.state-WITHHELD .arm-copy { color: #C9CDD1; }
.arm.state-WITHHELD .arm-state { border-color: var(--hazard); color: var(--hazard); }

.arm.state-DEGRADED .arm-state { border-color: var(--hazard); color: var(--hazard-deep); }

/* A country with no collector behind it. Every other card is a surface carrying a
   result, so this one is not a surface: it stays paper, inside a dashed edge, and
   its telemetry line reports that there is no exit to attest. */
.arm.state-NOT_SWEPT {
  background: var(--paper); border: 1px dashed var(--rule-strong);
  color: var(--ink-3);
}
.arm.state-NOT_SWEPT .arm-code,
.arm.state-NOT_SWEPT .arm-copy { color: var(--ink-3); }
.arm.state-NOT_SWEPT .arm-state { border-style: dashed; color: var(--ink-3); }
.arm.state-NOT_SWEPT .arm-attest { border-top-color: var(--rule-strong); }
/* The hatch is added by is-stale as well as by state-STALE, because staleness
   is decided in the browser at render time while the arm state was decided at
   sweep time. They are different facts and an arm can be both: DEGRADED because
   its join coverage fell short, AND stale because a judge is reading it two days
   later. The class is added on top of the state, never in place of it, so the
   card keeps saying why it degraded. */
.arm.state-STALE, .arm.is-stale { background: repeating-linear-gradient(45deg,
  var(--surface), var(--surface) 2px, var(--hatch) 2px, var(--hatch) 4px); }
.arm.state-STALE .arm-copy, .arm.state-STALE .arm-attest,
.arm.is-stale .arm-copy, .arm.is-stale .arm-attest { color: var(--ink-3); }

.coverage { height: 4px; background: var(--surface-sunk); }
.coverage > i { display: block; height: 100%; background: var(--hazard); }

/* 7-cell heal step track replaces telemetry while healing. */
.steps { display: flex; gap: 2px; }
.steps i {
  flex: 1; height: 10px; background: var(--surface-sunk);
  border: 1px solid var(--rule);
}
.steps i.done { background: var(--flux); border-color: var(--flux); }
.steps i.active { background: var(--surface); border-color: var(--flux); }

/* --------------------------------------------------------------- the wall */

.wall-head {
  display: grid; grid-template-columns: 28px 300px minmax(340px, 1fr) 108px minmax(260px, 1fr) 96px;
  gap: 16px; padding: 24px 0 8px; align-items: end;
  border-bottom: 1px solid var(--rule-strong);
  /* "Data is only useful once someone can read it" is the judging criterion, and
     this ledger is 207 rows deep with two columns nobody can infer from their
     contents: "Found in" renders as a row of chips, "Days since recall" as a
     bare number. Ten rows down, with the head gone, a reader is looking at chips
     and numbers with no labels.

     The cost was already paid: the comment at the overflow rule says clip was
     chosen over hidden precisely because hidden would make a scroll container
     and break position: sticky inside it. The infrastructure was there, unused.

     z-index 2 sits under .mark (3) and .rail (4), so the corner stamp and the
     act rail still paint above it. --paper because the rows beneath are
     --surface and would otherwise ghost through. */
  position: sticky; top: 0; z-index: 2; background: var(--paper);
}
/* Both sit between the sticky head and the rows, so they need their own ground
   or they show through it as it passes over them. */
.sort-note, .historical-note { position: relative; z-index: 1; background: var(--paper); }

/* The Bright Data act's table gave th and td the same 1px --rule, so its header
   read as another data row while the ledger three acts above offsets its own on
   --rule-strong. Two tables on one page disagreeing about their own grammar is
   the kind of thing a judge reads as unfinished without being able to name it.
   Both tokens are 1px, so nothing reflows. */
.platform-table th { border-bottom-color: var(--rule-strong); }
.wall-head span {
  font-family: var(--font-machine); font-size: 10px; letter-spacing: 0.1em;
  color: var(--ink-3); text-transform: uppercase;
}
.wall-head .num { text-align: right; }

.row {
  display: grid; grid-template-columns: 28px 300px minmax(340px, 1fr) 108px minmax(260px, 1fr) 96px;
  gap: 16px; height: 64px; align-items: center;
  border-bottom: 1px solid var(--rule);
  background: var(--surface); cursor: pointer;
  transition: background-color 120ms linear;
}
.row:hover { background: var(--hazard-wash); }
.row.is-amber { background: var(--ground); }
.row.is-amber:hover { background: var(--surface-sunk); }

.c-gutter { font-family: var(--font-machine); font-size: 11px; color: var(--ink-3); text-align: right; }

.c-id .name {
  font-weight: 700; font-size: 15px; line-height: 1.2;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.c-id .ident {
  font-family: var(--font-machine); font-size: 12px; color: var(--ink-2);
  margin-top: 2px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

.c-hazard .quote {
  font-size: 14px; color: var(--ink-2); line-height: 1.3;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
  overflow: hidden;
}
.c-hazard .src { font-family: var(--font-machine); font-size: 11px; color: var(--ink-3); margin-top: 2px; }

.chips { display: flex; gap: 4px; }
.chip {
  width: 26px; height: 18px; display: grid; place-items: center;
  font-family: var(--font-machine); font-size: 10px; font-weight: 700;
  border: 1px solid var(--rule-strong); color: var(--ink-3); background: var(--surface);
}
.chip.v-RED { background: var(--hazard); border-color: var(--hazard); color: #fff; }
.chip.v-WITHHELD { background: var(--void); border-color: var(--void); color: #fff; }
/* Hollow IS the rendering for NOT_FOUND, and this rule exists to say so rather
   than to change anything. The base .chip is already correct: we looked in that
   country and found nothing, which is a measurement and should not shout. It is
   written out because the neighbouring NOT_SWEPT rule turns entirely on the
   difference between "hollow" and "never ran", and an intent carried only by the
   absence of a rule is one edit away from becoming the bug that shipped once
   already, when WITHHELD rendered identically to healthy. */
.chip.v-NOT_FOUND { background: var(--surface); border-color: var(--rule-strong); }
.chip.v-STALE { background: repeating-linear-gradient(45deg,
  var(--surface), var(--surface) 2px, var(--hatch) 2px, var(--hatch) 4px); }
/* An arm that never ran is not the hollow chip. Hollow already means we looked in
   that country and found nothing, which is a measurement. This one is a cell
   nobody filled in: a dashed edge and unweighted type, with the country code left
   legible so a reader sees which country is missing rather than which verdict. */
.chip.v-NOT_SWEPT {
  background: transparent; border: 1px dashed var(--hatch);
  color: var(--unknown); font-weight: 400;
}

/* The wall's shape. Bars share a left edge and are sorted descending, so the
   silhouette is a staircase. Red is length, never fill. */
.c-bar { display: flex; align-items: center; gap: 12px; }
.track { flex: 1; height: 10px; background: var(--surface-sunk); }
.track > i { display: block; height: 100%; background: var(--hazard); }
.row.is-amber .track > i { background: var(--hatch); }
.days {
  font-family: var(--font-machine); font-size: 16px; font-weight: 700;
  min-width: 56px; text-align: right;
}
.days.frozen::after { content: " ❄"; font-size: 10px; color: var(--unknown); }

.c-tier { text-align: right; }
.tier-box {
  display: inline-block; font-family: var(--font-machine); font-size: 10px;
  /* 10px, so it needs 4.5:1. --hazard is 4.03:1 on paper; --hazard-deep is
     6.07:1. The border keeps the signature red, where thresholds do not
     apply, so the box still reads as the same colour. */
  padding: 1px 5px; border: 1px solid var(--hazard); color: var(--hazard-deep);
  letter-spacing: 0.06em;
}
.tier-box.t-AMBER { border-color: var(--amber); color: var(--amber); }
.c-tier .cap { font-family: var(--font-machine); font-size: 10px; color: var(--ink-3); margin-top: 3px; }

/* ------------------------------------------------------ two-receipt card */

.receipt {
  border-bottom: 1px solid var(--rule-strong);
  border-top: 1px solid var(--rule-strong);
  background: var(--surface); padding: 24px 0;
}
.receipt-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 1px; background: var(--rule); }
.receipt-pane { background: var(--surface); padding: 20px 24px; }
.receipt-pane h3 {
  font-family: var(--font-machine); font-size: 10px; letter-spacing: 0.1em;
  color: var(--ink-3); text-transform: uppercase; margin-bottom: 12px;
}
.field { margin-bottom: 10px; }
.field .k {
  font-family: var(--font-machine); font-size: 10px; color: var(--ink-3);
  text-transform: uppercase; letter-spacing: 0.06em;
}
.field .v { font-size: 14px; margin-top: 2px; }
.field .v.mono { font-family: var(--font-machine); font-size: 12px; }

/* Bright Data omits absent keys rather than nulling them. Everyone else treats
   that as a parser annoyance. Rendering it deliberately is how you show absence
   without implying zero. Never 0, never null, never blank. */
.field.is-missing .k { text-decoration: line-through; color: var(--unknown); }
.field.is-missing .v { color: var(--unknown); font-family: var(--font-machine); font-size: 12px; }

.assertion {
  border-left: 2px solid var(--hazard); padding: 10px 14px; margin: 12px 0;
  background: var(--ground); font-family: var(--font-machine); font-size: 12px;
  line-height: 1.5; word-break: break-word;
}
.assertion mark { background: var(--hazard-wash); color: var(--hazard-deep); font-weight: 700; padding: 0 2px; }
.assertion .why { display: block; margin-top: 8px; font-family: var(--font-prose);
  font-size: 11px; color: var(--ink-3); line-height: 1.4; }

.attest-chip {
  display: inline-flex; align-items: center; gap: 6px; margin-top: 8px;
  border: 1px solid var(--rule-strong); padding: 4px 8px;
  font-family: var(--font-machine); font-size: 10px; color: var(--ink-2);
}
.attest-chip b { color: var(--ink); }

.buy {
  display: inline-block; border: 1px solid var(--hazard); color: var(--hazard);
  font-size: 12px; padding: 4px 10px; margin-top: 8px; border-radius: 4px;
}

/* -------------------------------------------------------------- panels */

.panels { display: grid; grid-template-columns: 1fr 1fr; gap: 24px; margin-top: 48px; }
.panel { background: var(--surface); border: 1px solid var(--rule); padding: 20px 24px; }
.panel h2 {
  font-family: var(--font-machine); font-size: 11px; letter-spacing: 0.12em;
  text-transform: uppercase; margin-bottom: 4px;
}
.panel .lede { font-size: 12px; color: var(--ink-3); margin-bottom: 16px; line-height: 1.4; }
.panel dl { display: grid; grid-template-columns: 1fr auto; gap: 6px 16px; }
.panel dt { font-size: 13px; color: var(--ink-2); }
.panel dd { font-family: var(--font-machine); font-size: 13px; font-weight: 700; text-align: right; }

.budget { margin-top: 12px; }
.budget .meter { height: 8px; background: var(--surface-sunk); }
.budget .meter > i { display: block; height: 100%; background: var(--flux); }
.budget .cap { font-family: var(--font-machine); font-size: 11px; color: var(--ink-3); margin-top: 4px; }

.heal-entry { border-top: 1px solid var(--rule); padding-top: 12px; margin-top: 12px; }
.heal-entry.rejected { border-top-color: var(--hazard); }
.heal-verdict {
  font-family: var(--font-machine); font-size: 11px; letter-spacing: 0.08em;
  color: var(--hazard); margin-bottom: 6px;
}
.prompt-box {
  font-family: var(--font-machine); font-size: 11px; background: var(--ground);
  border: 1px solid var(--rule); padding: 10px 12px; line-height: 1.5;
  white-space: pre-wrap; margin-top: 8px; max-height: 180px; overflow: auto;
}
.btn-approve {
  margin-top: 10px; font-family: var(--font-machine); font-size: 11px;
  padding: 6px 12px; border: 1px solid var(--rule-strong); background: var(--surface-sunk);
  color: var(--unknown); cursor: not-allowed;
}
.btn-reason { font-size: 11px; color: var(--hazard-deep); margin-top: 6px; }

/* -------------------------------------------------------------- legend */

.legend { margin-top: 48px; border-top: 1px solid var(--rule-strong); padding-top: 24px; }
.legend ul { list-style: none; display: grid; gap: 8px; max-width: 78ch; }
.legend li { display: flex; gap: 12px; align-items: flex-start; font-size: 13px; color: var(--ink-2); }
.legend .swatch { flex: none; width: 26px; height: 18px; border: 1px solid var(--rule-strong); }
.legend .final {
  margin-top: 20px; font-size: 16px; font-weight: 700; color: var(--ink);
  max-width: 60ch; line-height: 1.4;
}

.provenance {
  margin-top: 32px; padding-top: 16px; border-top: 1px solid var(--rule);
  font-family: var(--font-machine); font-size: 10px; color: var(--ink-3);
  display: flex; flex-wrap: wrap; gap: 16px;
}
.fixture-stamp {
  border: 1px solid var(--amber); color: var(--amber); padding: 1px 6px;
}

details.machinery { margin-top: 24px; }
details.machinery summary {
  font-family: var(--font-machine); font-size: 11px; letter-spacing: 0.1em;
  text-transform: uppercase; cursor: pointer; color: var(--ink-3);
  padding: 8px 0; border-top: 1px solid var(--rule);
}
details.machinery table { width: 100%; border-collapse: collapse; margin-top: 8px; }
details.machinery td, details.machinery th {
  font-family: var(--font-machine); font-size: 11px; text-align: left;
  padding: 6px 12px 6px 0; border-bottom: 1px solid var(--rule); color: var(--ink-2);
}
details.machinery th { color: var(--ink-3); font-weight: 400; }

.pager { padding: 16px 0; font-family: var(--font-machine); font-size: 11px; color: var(--ink-3); }

/* Skeleton: full chrome, real headers, correct 64px geometry, no spinner and no
   shimmer. Nothing shifts when data lands because the geometry is already right. */
.row.skeleton { pointer-events: none; }
.row.skeleton .name, .row.skeleton .ident, .row.skeleton .quote, .row.skeleton .src {
  color: transparent; background: var(--surface-sunk);
}
.row.skeleton .days::after { content: "–––"; color: var(--ink-3); }

@media (max-width: 1279px) {
  .shell::before {
    content: "This wall is built for 1280px and wider. Below that, columns are dropped rather than reflowed, because a hazard ledger that reflows stops being a ledger.";
    display: block; padding: 16px; margin: 16px 0; border: 1px solid var(--amber);
    color: var(--amber); font-size: 12px;
  }
  /* The grid for this range is set in the later 1279px block, which declares
     five columns and reserves 84px for the verdict. This block used to declare
     four AND hide .c-tier; the later grid won and the hide stayed, so between
     768px and 1279px the header printed "Verdict" over a reserved column with
     nothing drawn in it.

     That range is not exotic. It is a browser window that is not maximised, or
     110 to 125 percent zoom on a 1440 or 1536 screen, which is exactly what
     people do when filming so the text is legible on video.

     The verdict is the one column this ledger exists to publish. Dropping the
     hazard quote to make room is the right trade; dropping the verdict is not. */
  .c-hazard { display: none; }
  .panels, .receipt-grid { grid-template-columns: 1fr; }
}

/* ------------------------------------------------------- survival curve */

.curve { margin-top: 48px; border-top: 1px solid var(--rule-strong); padding-top: 24px; }
.curve h2 {
  font-family: var(--font-machine); font-size: 11px; letter-spacing: 0.12em;
  text-transform: uppercase; margin-bottom: 4px;
}
.curve .lede { font-size: 12px; color: var(--ink-3); max-width: 70ch; line-height: 1.45; }
.curve-plot { display: flex; align-items: flex-end; gap: 1px; height: 180px; margin: 20px 0 0; }
.curve-col { flex: 1; display: flex; flex-direction: column; justify-content: flex-end; height: 100%; }
.curve-band {
  width: 100%; background: var(--hazard-wash); position: relative;
  border-top: 2px solid var(--hazard); border-bottom: 1px solid var(--rule);
}
.curve-col.is-thin .curve-band {
  background: repeating-linear-gradient(45deg,
    var(--surface), var(--surface) 2px, var(--hatch) 2px, var(--hatch) 4px);
  border-top-color: var(--unknown);
}
.curve-col .v {
  font-family: var(--font-machine); font-size: 12px; font-weight: 700;
  text-align: center; padding-bottom: 4px;
}
.curve-col.is-thin .v { color: var(--unknown); }
.curve-axis { display: flex; gap: 1px; margin-top: 6px; }
.curve-axis span {
  flex: 1; text-align: center; font-family: var(--font-machine);
  font-size: 10px; color: var(--ink-3);
}
.curve-note {
  margin-top: 14px; font-size: 11px; color: var(--ink-3);
  border-left: 2px solid var(--rule-strong); padding-left: 12px; max-width: 78ch;
  line-height: 1.5;
}

/* ============================================================================
   DESIGN LAYER — five acts, not one plane
   ----------------------------------------------------------------------------
   Everything above this line is the contract's visual states: which colour a
   withheld arm is, how a stale row hatches, what MISSING looks like. Those are
   decisions the data makes and they do not change.

   Everything below is the editorial treatment: what a reader meets first, what
   they meet only if they ask, and how the page moves while they arrive.

   THE PROBLEM THIS SOLVES
   The page was written for its author. Every field was on screen because it was
   defensible, not because it was legible, so three audiences were flattened onto
   one plane at once: a stranger who needs one sentence, a judge who needs the
   finding and the method, and an auditor who needs a DOM path and a content
   hash. The auditor's fields were arriving first, unexplained.

   So the page is now five acts. Nothing is deleted. Everything that was on the
   first screen is still in the document, further down, behind a heading that
   says who it is for.
   ========================================================================== */

/* --- shell, rhythm ------------------------------------------------------- */

.shell { max-width: 1380px; margin: 0 auto; padding: 0 40px 120px; }

.act { padding: 96px 0 0; }

/* GOV.UK section-break: one hairline, equal air both sides, never a box and
   never a heavier rule. Eight acts were separated by 96px of nothing, so a fast
   scroller met the act head's 2px rule with no way to tell whether it CLOSED the
   last section or OPENED the next one. This arrives first and answers that.

   Drawn absolutely inside padding that is already empty, so it adds no height
   and cannot shift anything. --rule-strong so it reads as a different class of
   mark from the 207 row separators, but still 1px so it does not compete with
   the 2px rule below it. Weight is carried by the silence around it, not by ink.

   .act-finding lives outside .shell, so the graphite hero is never touched.
   :not(:empty) guards the three acts wall.js fills at runtime: if a render ever
   fails there is no orphan rule. */
.shell > .act { position: relative; --act-mark: 48px; }
.shell > .act + .act:not(.act-foot):not(:empty)::before {
  content: "";
  position: absolute; left: 0; right: 0; top: var(--act-mark);
  height: 1px; background: var(--rule-strong); pointer-events: none;
}
@media (max-width: 1279px) { .shell > .act { --act-mark: 32px; } }
@media (max-width: 767px)  { .shell > .act { --act-mark: 24px; } }
.act-head {
  display: flex; align-items: baseline; gap: 16px;
  font-family: var(--font-display); font-variation-settings: "wdth" 104;
  font-size: 20px; font-weight: 700; letter-spacing: -0.01em;
  padding-bottom: 20px; border-bottom: 2px solid var(--rule-ink);
}
/* The act numbers are a real sequence: a reader moves through finding, ledger,
   method, gaps in that order, and each one presumes the one before it. */
.act-num {
  font-family: var(--font-machine); font-size: 11px; font-weight: 500;
  /* Was --hazard. Red on this wall marks an accusation and nothing else, which
     the legend states in as many words: "Red. Still on sale." Acts 01 through 08
     accuse nobody, and eight red numbers is more red than there are red verdicts
     in most renderings, so the page was contradicting its own legend in its
     section furniture. */
  color: var(--ink-2); letter-spacing: 0.1em;
}
.act-lede {
  margin: 20px 0 0; max-width: 62ch; font-size: 15px; line-height: 1.55;
  color: var(--ink-2);
}
.act-jump {
  display: inline-block; margin-top: 32px; font-family: var(--font-machine);
  font-size: 11px; letter-spacing: 0.1em; text-transform: uppercase;
  color: var(--ink-2); text-decoration: none;
  border-bottom: 1px solid var(--rule-strong); padding-bottom: 3px;
  transition: color var(--t-state) var(--ease), border-color var(--t-state) var(--ease);
}
.act-jump:hover { color: var(--hazard); border-color: var(--hazard); }

/* --- the corner mark ----------------------------------------------------- */

.mark {
  position: absolute; top: 0; left: 0; right: 0; z-index: 3;
  display: grid; grid-template-columns: auto 1fr auto; align-items: start;
  gap: 20px; padding: 28px 40px; color: var(--ink-inv);
  mix-blend-mode: difference;   /* legible over the black act and the paper */
}
.mark-name {
  font-family: var(--font-display); font-variation-settings: "wdth" 118;
  font-size: 14px; font-weight: 700; letter-spacing: 0.06em;
}
.mark-name span { color: var(--hazard); }
.mark-role, .mark-independence {
  font-family: var(--font-machine); font-size: 10px; letter-spacing: 0.08em;
  text-transform: uppercase; opacity: 0.7; line-height: 1.5;
}
.mark-independence { text-align: right; max-width: 34ch; text-transform: none; letter-spacing: 0; }

/* A judge selects a figure to check it is real, and this page invites exactly
   that: tokens.css argues the mono is there "because a figure a reader may
   retype has to be unambiguous". Left undefined, that gesture paints the OS
   default blue, which is the one moment the three-hue discipline visibly breaks.
   Both pairs are existing tokens. Deliberately not hazard: selecting a number is
   not an accusation. */
::selection { background: var(--ink); color: var(--paper); text-shadow: none; }
.act-finding ::selection {
  background: var(--ink-inv); color: var(--hero-ground); text-shadow: none;
}

/* --- ACT I: the finding -------------------------------------------------- */

.act-finding {
  /* A vignette rather than a flat fill, so the type sits in a space instead of
     on a swatch. It only DARKENS toward the edges: the measured contrast of
     white on --hero-ground is a floor here rather than an average, so no text
     can end up on a lighter ground than check_contrast.py verified. */
  background:
    radial-gradient(120% 90% at 50% 38%,
      rgba(255, 255, 255, 0.035) 0%,
      rgba(255, 255, 255, 0) 55%),
    radial-gradient(140% 120% at 50% 50%,
      rgba(0, 0, 0, 0) 40%,
      rgba(0, 0, 0, 0.35) 100%),
    var(--hero-ground);
  color: var(--ink-inv);
  min-height: 92vh; padding: 0;
  display: grid; align-content: center;
}
.act-finding > .finding-inner {
  max-width: 1380px; margin: 0 auto; padding: 0 40px; width: 100%;
}
.withheld-mark {
  display: inline-flex; align-items: center; gap: 10px;
  font-family: var(--font-machine); font-size: 11px; font-weight: 500;
  letter-spacing: 0.16em; color: var(--hazard); text-transform: uppercase;
}
/* A live indicator that is not green, because nothing here is ever green. The
   pulse says the page is reporting; it does not say the news is good. */
.withheld-mark::before {
  content: ""; width: 7px; height: 7px; background: var(--hazard);
  animation: pulse 2.4s var(--ease) infinite;
}
@keyframes pulse { 0%,100% { opacity: 1 } 50% { opacity: 0.25 } }

.act-finding h1 {
  font-family: var(--font-display); font-variation-settings: "wdth" 100;
  font-size: var(--type-hero); font-weight: 700; letter-spacing: -0.03em;
  line-height: 1.02; margin: 28px 0 0; max-width: 26ch;
  text-wrap: balance;
}
/* --type-hero is sized for the RED sentence, which is two short clauses. The
   fallback sentence is 176 characters, and at the 82px cap it ran to five lines
   that filled the viewport on their own: the sub, the clause and the hunt line
   were all below the fold on a screen whose entire job is to state the finding.

   heroH1() in wall.js picks the band from the raw character count. Each band
   drops the size and widens the measure together, so all three land near four
   lines instead of near a fixed size. Leading tightens as the size falls
   because large display type needs the air and 40px type does not. */
.act-finding h1.is-long {
  font-size: clamp(30px, 3.8vw, 56px); max-width: 34ch; line-height: 1.05;
}
.act-finding h1.is-xlong {
  font-size: clamp(26px, 2.9vw, 40px); max-width: 46ch; line-height: 1.12;
  letter-spacing: -0.02em;
}
/* The day count inside the sentence is the finding. It gets the width axis and
   the only red on this screen. */
.act-finding h1 .count {
  font-variant-numeric: tabular-nums;
  color: var(--hazard); font-variation-settings: "wdth" 118;
  font-variant-numeric: tabular-nums;
}
.act-finding .sub {
  margin: 32px 0 0; max-width: 56ch; font-size: 17px; line-height: 1.5;
  color: #C9CDD1;
}
.act-finding .clause {
  margin: 16px 0 0; font-family: var(--font-machine); font-size: 11px;
  line-height: 1.6; color: #7C858D; max-width: 72ch;
}
.scroll-cue {
  margin-top: 56px; font-family: var(--font-machine); font-size: 10px;
  letter-spacing: 0.16em; text-transform: uppercase; color: #6B747C;
  display: flex; align-items: center; gap: 10px;
}
.scroll-cue::after {
  content: ""; width: 1px; height: 28px; background: #3A4148;
  animation: drop 2.6s var(--ease) infinite;
}
@keyframes drop {
  0% { transform: scaleY(0); transform-origin: top }
  55% { transform: scaleY(1); transform-origin: top }
  56% { transform-origin: bottom }
  100% { transform: scaleY(0); transform-origin: bottom }
}

/* --- ACT II: two figures, plain English ---------------------------------- */

.figures {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(380px, 1fr));
  gap: 1px; background: var(--rule); margin-top: 48px;
}
.figure {
  background: var(--surface); padding: 44px 40px 40px;
  display: flex; flex-direction: column; gap: 0;
}
.figure-value {
  font-family: var(--font-display); font-variation-settings: "wdth" 112;
  font-size: var(--type-display); font-weight: 700; line-height: 0.94;
  letter-spacing: -0.035em;
  /* Tabular figures, because these count up. Proportional digits change width
     as they cycle, so a 56px number jitters for the whole animation and the
     layout settles a fraction late. It reads as a page still loading. */
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum" 1;
}
/* The sentence, not the label. A reader should be able to read the figure and
   the line beneath it aloud and have said something true. */
.figure-claim {
  margin-top: 20px; font-size: 19px; line-height: 1.35; max-width: 26ch;
  color: var(--ink); font-weight: 600; letter-spacing: -0.01em;
}
.figure-basis {
  margin-top: auto; padding-top: 24px;
  /* In print a rule is how you say "this is apparatus, not argument". The
     interval and the method name were hanging unattached under a 56px number;
     with it, each card reads as a plate with a caption. padding-top already
     existed, so nothing moves. */
  border-top: 1px solid var(--rule); font-family: var(--font-machine);
  font-size: 11px; line-height: 1.6; color: var(--ink-3);
}
/* A withheld figure is redacted, never greyed. The number was computed and we
   decline to stand behind it, which is a decision and not a missing value. */
.figure.is-withheld .figure-value {
  position: relative; color: transparent; background: var(--void);
  display: inline-block; padding: 0 16px; user-select: none;
  font-size: var(--type-figure);
}
.figure.is-withheld .figure-value::after {
  content: "WITHHELD"; position: absolute; inset: 0; display: grid;
  place-items: center; font-family: var(--font-machine); font-size: 13px;
  font-weight: 500; letter-spacing: 0.16em; color: var(--void-ink);
}
.figure.is-pending .figure-value { color: var(--unknown); font-size: var(--type-figure); }

/* --- ACT III: the ledger ------------------------------------------------- */

/* Five columns, not ten. The model number, the GTIN, the notice reference and
   the publication date were on every collapsed row; a reader scanning for a
   hazard does not need any of them, and all four are one click away. */
.wall-head, .row {
  grid-template-columns: 34px minmax(220px, 1fr) minmax(320px, 1.5fr) 108px 190px 92px;
  gap: 20px;
}
.wall-head {
  padding: 32px 0 10px; border-bottom: 1px solid var(--rule-strong);
  align-items: end;
}
.wall-head span { font-size: 10px; letter-spacing: 0.1em; color: var(--ink-3); }

.row {
  height: auto; min-height: 84px; padding: 18px 0;
  border-bottom: 1px solid var(--rule); background: var(--surface);
  transition: background-color var(--t-state) var(--ease);
}
.row:hover, .row.is-amber:hover { background: var(--surface-raised); }
.row.is-amber { background: var(--surface); }
/* An excluded row is quieter than a finding, but it is not hidden: the AMBER
   rows are the ones carrying "we could not confirm this". */
.row.is-amber .name { color: var(--ink-2); }

.c-id .name { font-size: 16px; line-height: 1.25; white-space: normal; overflow: visible; }
.c-id .ident { margin-top: 5px; font-size: 11px; color: var(--ink-3); }
.c-hazard .quote { font-size: 14px; line-height: 1.45; color: var(--ink-2); }
.c-hazard .src { display: none; }   /* moved into the open row */

.chips { gap: 5px; }
.chip { width: 30px; height: 20px; font-size: 10px; }

.c-bar { gap: 18px; }
.track { height: 4px; background: var(--rule); }
.row.is-amber .track > i { background: var(--rule-strong); }
.days {
  font-family: var(--font-display); font-variation-settings: "wdth" 112;
  font-size: var(--type-figure); font-weight: 700; line-height: 1;
  min-width: 92px; letter-spacing: -0.03em;
}
.days::after {
  content: " d"; font-family: var(--font-machine); font-size: 11px;
  font-weight: 400; color: var(--ink-3); letter-spacing: 0;
}
.days.frozen::after { content: " d ❄"; }

.tier-box { font-size: 10px; padding: 2px 6px; letter-spacing: 0.08em; }

/* The row says that it opens. Without this the evidence card — the strongest
   thing on the page — was reachable only by guessing. */
.c-gutter { position: relative; font-size: 11px; }
.open-mark {
  display: block; width: 7px; height: 7px; margin: 8px 0 0 auto;
  border-right: 1.5px solid var(--rule-strong);
  border-bottom: 1.5px solid var(--rule-strong);
  transform: rotate(45deg);
  transition: transform var(--t-state) var(--ease), border-color var(--t-state) var(--ease);
}
.row:hover .open-mark, .row:focus-visible .open-mark { border-color: var(--ink); }
.row.is-open .open-mark { transform: rotate(-135deg); border-color: var(--hazard); }
.row.is-open { background: var(--surface-raised); border-bottom-color: var(--rule-ink); }
.row:focus-visible { outline: 2px solid var(--focus); outline-offset: -2px; }

.sort-note {
  font-family: var(--font-machine); font-size: 10px; letter-spacing: 0.08em;
  text-transform: uppercase; color: var(--ink-3); margin: 0; padding: 10px 0 14px;
}

/* --- ACT IV: apparatus --------------------------------------------------- */

/* Six apparatus cells, three across, so the grid fills exactly two rows. An
   auto-fit track left empty cells showing the 1px divider ground through, which
   reads as a rendering fault rather than as space. */
.instruments {
  display: grid; grid-template-columns: repeat(3, 1fr);
  gap: 1px; background: var(--rule); margin-top: 48px; border: none;
}
@media (max-width: 1023px) { .instruments { grid-template-columns: repeat(2, 1fr) } }
.instrument {
  background: var(--surface); padding: 24px 24px 26px; margin: 0;
  border: none; min-width: 0;
}
.instrument .label {
  font-family: var(--font-machine); font-size: 10px; letter-spacing: 0.1em;
  color: var(--ink-3); text-transform: uppercase;
}
.instrument .value {
  font-family: var(--font-display); font-variation-settings: "wdth" 108;
  font-size: 24px; font-weight: 700; margin-top: 10px; letter-spacing: -0.02em;
}
.instrument .qualifier {
  margin-top: 10px; font-family: var(--font-machine); font-size: 10px;
  line-height: 1.6; color: var(--ink-3);
}
.instrument.is-struck .value {
  position: relative; color: transparent; text-decoration: none;
  background: var(--void); display: inline-block; padding: 0 10px;
  font-size: 18px; user-select: none;
}
.instrument.is-struck .value::after {
  content: "WITHHELD"; position: absolute; inset: 0; display: grid;
  place-items: center; font-family: var(--font-machine); font-size: 10px;
  letter-spacing: 0.14em; color: var(--void-ink);
}
/* A redaction whose caption still prints "0 of 104" underneath it has redacted
   nothing. The qualifier is replaced rather than appended to. */
.instrument.is-struck .qualifier { font-size: 0; }
.instrument.is-struck .qualifier::after {
  content: "Withheld: a collector this figure depends on was too broken to trust. "
           "The count exists and is not published.";
  font-size: 10px; color: var(--ink-3); line-height: 1.6;
}
.instrument.is-pending .value { color: var(--unknown); font-size: 18px; }

.arm-rail { margin-top: 40px; border: none; }
.arm { padding: 24px 24px 22px; min-height: 148px; }

/* --- ACT V: gaps -------------------------------------------------------- */

.panels { grid-template-columns: 1.05fr 1fr; gap: 64px; margin-top: 48px; }
.panel {
  border: none; background: transparent; padding: 0;
  border-top: 2px solid var(--rule-ink); padding-top: 22px;
}
.panel h2 { font-family: var(--font-display); letter-spacing: 0.04em; }
.panel .lede { max-width: 46ch; }

/* --- foot ---------------------------------------------------------------- */

.act-foot { border-top: 1px solid var(--rule); margin-top: 96px; }
.legend { margin-top: 40px; border: none; padding: 0; }
.legend .final { font-family: var(--font-display); font-variation-settings: "wdth" 104; }

/* --- arrival ------------------------------------------------------------- */

/* One orchestrated sequence rather than an effect per element. The hero states
   the finding, the figures resolve, the ledger arrives. Everything below the
   fold reveals on approach, once, and never animates again. */
.reveal { opacity: 0; transform: translateY(14px); }
.reveal.is-in {
  opacity: 1; transform: none;
  transition: opacity var(--t-arrive) var(--ease), transform var(--t-arrive) var(--ease);
}
.act-finding .withheld-mark,
.act-finding h1,
.act-finding .sub,
.act-finding .clause,
.act-finding .found-anyway,
.act-finding .scroll-cue { animation: rise var(--t-arrive) var(--ease) both; }
.act-finding h1 { animation-delay: 90ms }
.act-finding .sub { animation-delay: 220ms }
.act-finding .clause { animation-delay: 300ms }
/* Added to the stagger, not left behind it. This line arrived on the first
   screen today and was the only element there that did not rise with the rest,
   so the hero assembled itself in sequence and then the finding was abruptly
   already present. It lands after the clause and before the cue, which is the
   order a reader needs: the measurement, then the exception to it. */
.act-finding .found-anyway { animation-delay: 380ms }
.act-finding .scroll-cue { animation-delay: 480ms }
@keyframes rise {
  from { opacity: 0; transform: translateY(18px) }
  to { opacity: 1; transform: none }
}

/* The redaction wipes across the figure it covers, so a reader sees the decision
   being taken rather than finding it already made. */
.figure.is-withheld .figure-value::before {
  content: ""; position: absolute; inset: 0; background: var(--void);
  transform-origin: left; animation: wipe 520ms var(--ease) both;
  animation-delay: 260ms;
}
@keyframes wipe { from { transform: scaleX(0) } to { transform: scaleX(1) } }

@media (prefers-reduced-motion: reduce) {
  .reveal { opacity: 1; transform: none }
  .withheld-mark::before, .scroll-cue::after { animation: none }
}

/* --- narrow ------------------------------------------------------------- */

@media (max-width: 1279px) {
  .shell { padding: 0 24px 96px }
  .mark { padding: 20px 24px; grid-template-columns: auto 1fr; }
  .mark-independence { display: none }
  .act { padding-top: 64px }
  .panels { grid-template-columns: 1fr; gap: 48px }
  .wall-head, .row { grid-template-columns: 28px minmax(180px,1fr) 108px 150px 84px; gap: 14px }
  .wall-head span:nth-child(3), .c-hazard { display: none }
  .days { font-size: 28px; min-width: 74px }
}

/* A redaction covers a value, so it is the width of that value. Stretching to
   the column made it read as a banner rather than as something laid over a
   number. */
.figure.is-withheld .figure-value,
.instrument.is-struck .value { align-self: flex-start; }
.figure { align-items: flex-start; }
.figure-claim, .figure-basis { align-self: stretch; }

/* --- the audit trail, behind a door ------------------------------------- */

/* The technical fields are not hidden, they are second. A reader who wants the
   claim gets sentences; a reader who wants to reproduce it opens this. */
.audit { margin-top: 24px; border-top: 1px solid var(--rule); padding-top: 14px; }
.audit > summary {
  cursor: pointer; list-style: none;
  font-family: var(--font-machine); font-size: 10px; letter-spacing: 0.12em;
  text-transform: uppercase; color: var(--ink-3);
  display: flex; align-items: center; gap: 8px;
  transition: color var(--t-state) var(--ease);
}
.audit > summary::-webkit-details-marker { display: none }
.audit > summary::before {
  content: ""; width: 6px; height: 6px;
  border-right: 1.5px solid currentColor; border-bottom: 1.5px solid currentColor;
  transform: rotate(-45deg);
  transition: transform var(--t-state) var(--ease);
}
.audit[open] > summary::before { transform: rotate(45deg) }
.audit > summary:hover { color: var(--ink) }
.audit-why {
  margin: 12px 0 16px; font-size: 12px; line-height: 1.55; color: var(--ink-3);
  max-width: 52ch;
}

.receipt-pane h3 {
  font-family: var(--font-display); font-variation-settings: "wdth" 104;
  font-size: 13px; letter-spacing: 0.02em;
}
.attest-chip { line-height: 1.6 }

/* The verdict badge is right-aligned in the last grid column, and the row had no
   horizontal padding, so it sat hard against the edge of its own surface with the
   timestamp underneath it. Both the header and the rows take the same inset or
   the columns stop lining up. */
.wall-head, .row { padding-left: 22px; padding-right: 22px; }
.sort-note { padding-left: 22px; padding-right: 22px; }
.receipt { padding-left: 22px; padding-right: 22px; }

/* --- the act rail -------------------------------------------------------- */

/* Wayfinding for a document that is deliberately one page. Fixed to the left
   edge, hidden while ACT I is on screen because the finding should arrive with
   nothing beside it, and hidden again in the footer where there is nowhere left
   to go. Labels appear on hover so the resting state is four marks and a number,
   which is orientation without furniture. */
.rail {
  position: fixed; left: 0; top: 50%; transform: translateY(-50%);
  z-index: 4; display: flex; flex-direction: column; gap: 2px;
  padding: 12px 0 12px 20px;
  opacity: 0; pointer-events: none;
  transition: opacity 320ms var(--ease);
}
body.rail-on .rail { opacity: 1; pointer-events: auto; }

.rail a {
  display: flex; align-items: center; gap: 10px;
  padding: 7px 14px 7px 0; text-decoration: none; color: var(--ink-3);
  transition: color var(--t-state) var(--ease);
}
.rail a:hover { color: var(--ink); }
.rail-dot {
  flex: none; width: 7px; height: 7px; border: 1.5px solid currentColor;
  transition: background-color var(--t-state) var(--ease),
              transform var(--t-state) var(--ease);
}
.rail-num {
  font-family: var(--font-machine); font-size: 10px; letter-spacing: 0.08em;
}
.rail-label {
  font-family: var(--font-machine); font-size: 10px; letter-spacing: 0.06em;
  text-transform: uppercase; white-space: nowrap;
  max-width: 0; overflow: hidden; opacity: 0;
  transition: max-width 260ms var(--ease), opacity 200ms var(--ease);
}
.rail:hover .rail-label { max-width: 180px; opacity: 1; }

/* The act you are reading. Red, because it is the only place on the page where
   red means "here" rather than "hazard" — and it is a 7px square, which is not
   competing with anything. */
.rail a.is-here { color: var(--ink); }
.rail a.is-here .rail-dot { background: var(--hazard); border-color: var(--hazard); }
.rail a:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; }

@media (max-width: 1279px) { .rail { display: none } }

/* --- the finding fills the screen exactly ------------------------------- */

/* 92vh left an eighth of paper showing under the black, which reads as a layout
   fault rather than as a hint that there is more below. The scroll cue does that
   job, so the section takes the whole viewport and nothing else.

   svh rather than vh because on a phone vh is measured against the largest
   possible viewport, so the section sits taller than the screen it is on and the
   cue is pushed under the browser chrome — the exact problem in reverse. */
.act-finding {
  min-height: 100vh;
  min-height: 100svh;
  /* The sentence is optically centred; the cue is pinned to the bottom edge. That
     is what the leftover space is for, rather than trailing under the text. */
  grid-template-rows: 1fr auto;
  align-content: stretch;
  padding-bottom: 40px;
}
.act-finding > .finding-inner { align-self: center; }
.act-finding > .scroll-cue {
  align-self: end; margin: 0 auto; padding: 0 40px; width: 100%;
  max-width: 1380px;
}

/* A section that could not be rendered says so, in place. Quiet, because it is a
   fault report rather than a finding, and red is reserved for accusations. */
.render-fail {
  font-family: var(--font-machine); font-size: 11px; line-height: 1.6;
  color: var(--ink-2); padding: 20px 22px; background: var(--surface-sunk);
  border-left: 2px solid var(--rule-strong); max-width: 72ch;
}

/* ------------------------------------------------- act 04, the platform */

.platform-table { width: 100%; border-collapse: collapse; margin: 24px 0 16px; }
.platform-table th, .platform-table td {
  font-family: var(--font-machine); font-size: 12px; text-align: left;
  padding: 8px 16px 8px 0; border-bottom: 1px solid var(--rule);
}
.platform-table th {
  font-size: 10px; letter-spacing: 0.1em; text-transform: uppercase;
  color: var(--ink-3); font-weight: 400;
}
.cli-strip { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 32px; }
.cli-strip code {
  font-family: var(--font-machine); font-size: 11px;
  border: 1px solid var(--rule-strong); padding: 4px 10px; color: var(--ink-2);
}
.platform-sub {
  font-family: var(--font-machine); font-size: 12px; letter-spacing: 0.08em;
  text-transform: uppercase; margin: 32px 0 8px;
}
.heal-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 16px; margin-top: 16px; }
.heal-card { border: 1px solid var(--rule); padding: 14px 16px; }
/* A refusal is louder than a success. Nobody fakes a rejection, so the two
   refused heals get the hazard rule and the approved one does not. */
.heal-card.is-refused { border-color: var(--hazard); border-left-width: 3px; }
.heal-verdict {
  font-family: var(--font-machine); font-size: 11px; letter-spacing: 0.08em;
  color: var(--ink-2); margin-bottom: 8px;
}
.heal-card.is-refused .heal-verdict { color: var(--hazard); }
.heal-why { font-size: 13px; color: var(--ink-2); line-height: 1.45; margin: 8px 0; }
.heal-meta { font-family: var(--font-machine); font-size: 10px; color: var(--ink-3); }
.platform-foot {
  font-size: 13px; color: var(--ink-2); line-height: 1.5; max-width: 74ch;
  border-left: 2px solid var(--rule-strong); padding-left: 14px; margin-top: 24px;
}
@media (max-width: 1279px) { .heal-grid { grid-template-columns: 1fr; } }

/* ============================================================================
   PHONE
   ----------------------------------------------------------------------------
   The existing narrow rules stop at 1279px, which is a tablet. On a 390px phone
   the page overflowed horizontally by 309px: the arm rail held three columns at
   231px each, the ledger grid still asked for 550px of tracks, and the machinery
   table laid out at 491px with nothing to scroll it.

   The README says coverage is 1280px and up, because a hazard ledger that
   reflows stops being a ledger. That remains true of the LEDGER and is why the
   hazard column is dropped here rather than wrapped. It was never a licence for
   the rest of the page to overflow, and a judge opening the deployed link on a
   phone sees the overflow first.
   ========================================================================== */

@media (max-width: 767px) {
  .shell { padding: 0 18px 72px; }
  .act { padding-top: 48px; }
  .act-head { font-size: 16px; gap: 10px; padding-bottom: 14px; }
  .act-lede, .panel .lede { font-size: 14px; max-width: none; }

  .mark { padding: 14px 18px; grid-template-columns: 1fr; gap: 4px; }
  .mark-role, .mark-independence { display: none; }

  /* The hero keeps the full screen and loses only the leading it cannot spare. */
  .act-finding > .finding-inner { padding: 0 18px; }
  .act-finding h1 { max-width: none; letter-spacing: -0.02em; }
  .act-finding .sub { font-size: 15px; }
  .act-finding > .scroll-cue { padding: 0 18px; }

  /* One figure at a time. minmax(380px) cannot fit and was forcing the overflow. */
  .figures { grid-template-columns: 1fr; margin-top: 28px; }
  .figure { padding: 28px 22px; }
  .figure-value { font-size: 40px; }
  .figure-claim { font-size: 17px; max-width: none; }

  /* Three arm cards abreast is what broke the page. They stack, and each keeps
     its own black or hatched ground, so the withheld one still reads as withheld
     rather than as a smaller card. */
  .arm-rail { grid-template-columns: 1fr; margin-top: 28px; }
  .arm { min-height: 0; padding: 18px 20px; }

  .instruments { grid-template-columns: 1fr; margin-top: 28px; }
  .instrument { padding: 18px 20px; }

  /* The ledger keeps identity, verdict and duration. The hazard sentence is the
     one column that cannot survive 390px without becoming two words a line, and
     it is one tap away in the open row, quoted in full. */
  /* Not zero. Zeroing the inset put the verdict badge and the day count exactly
     on the row's right edge, which reads as cropped whether or not anything is
     cut — and nothing is: measured at 430, 390, 360, 320, 300 and 280, the
     furthest content in any row stops short of the viewport at every width.
     It looked cut because it was flush, which is the same complaint that was
     fixed on desktop and then reintroduced here. */
  .wall-head, .row {
    grid-template-columns: 22px minmax(0, 1fr) auto;
    gap: 10px; padding-left: 18px; padding-right: 18px;
  }
  .wall-head span:nth-child(3),
  .wall-head span:nth-child(4),
  .wall-head span:nth-child(5),
  .c-hazard { display: none; }
  .wall-head span:nth-child(6) { text-align: right; }

  /* Two lines rather than one, placed explicitly. Left to flow, the verdict
     wrapped onto a row of its own and left the badge stranded under the gutter.
     Name and verdict lead; the countries and the day count sit beneath, where
     there is width for them. */
  .row {
    min-height: 0; padding-top: 14px; padding-bottom: 14px;
    align-items: start; row-gap: 8px;
  }
  .row .c-gutter { grid-column: 1; grid-row: 1; }
  .row .c-id     { grid-column: 2; grid-row: 1; }
  .row .chips    { grid-column: 2; grid-row: 2; }
  /* The narrow breakpoint above hides the verdict entirely. On a tablet that is
     a column-count decision; on a phone it removes the answer to the row and
     leaves a ledger that says what was searched and never what was found. It
     comes back, and it is the only thing sharing the top line with the name. */
  .row .c-tier   { grid-column: 3; grid-row: 1; display: block; }
  .wall-head span:nth-child(6) { display: block; }
  /* The badge already says AMBER. The caption under it repeats that in eleven
     characters this column does not have, and it was the last pixel of overflow
     on the page. The open row still carries the reason in full. */
  .row .c-tier .cap { display: none; }
  .row .c-bar    { grid-column: 3; grid-row: 2; justify-content: flex-end; }
  /* The day suffix is the last glyph on the line and was the one sitting on the
     edge. It gets its own space rather than relying on the row inset alone. */
  .days::after { margin-right: 2px; }
  /* The duration bar is a silhouette read down a column of forty rows. At one
     row per screen it encodes nothing, so the number stays and the bar goes. */
  .row .c-bar .track { display: none; }
  .c-id .name { font-size: 15px; }
  .days { font-size: 24px; min-width: 0; }

  .sort-note, .historical-note { padding-left: 18px; padding-right: 18px; font-size: 9px; }
  .receipt { padding-left: 18px; padding-right: 18px; }
  .receipt-grid { grid-template-columns: 1fr; }
  .receipt-pane { padding: 20px 18px; }

  .panels { grid-template-columns: 1fr; gap: 36px; margin-top: 28px; }

  /* A wide table on a phone scrolls inside its own box rather than dragging the
     document sideways with it. */
  details.machinery > div {
    overflow-x: auto; -webkit-overflow-scrolling: touch;
    /* Without this a grid or flex child refuses to shrink below its content, so
       overflow-x never engages and the table pushes the document instead of
       scrolling inside its own box. */
    min-width: 0; max-width: 100%;
  }
  details.machinery table { min-width: 440px; }
  .curve, .platform-table { max-width: 100%; }

  .curve { overflow-x: auto; }
  .legend li { font-size: 12px; }
}


/* --- nothing scrolls sideways -------------------------------------------- */

/* Measured at 1440, 1280, 1024, 768, 430, 390 and 360: the document is within a
   pixel of the viewport at every one, and the only element wider than the screen
   is the machinery table, which scrolls inside its own box.

   This is the backstop, not the fix. Sub-pixel rounding on a fractional layout
   is enough to arm a horizontal scrollbar, and a page that can be dragged
   sideways by one pixel reads as broken however small the number is. `clip`
   rather than `hidden` because hidden makes the element a scroll container and
   would break `position: sticky` inside it.

   If a real overflow is ever introduced this will hide it, so the widths above
   are checked by measuring the document rather than by looking at the page. */
html { overflow-x: clip; }
body { overflow-x: clip; max-width: 100%; }


/* ------------------------------------- the withheld hero, and why it is black

   The hero ground is graphite. This is the only element on the page allowed to
   be #000000, and it earns it: a collector is broken, so the figures it feeds
   are not shown at all rather than shown empty. An empty hazard wall reads as
   safe, which is the failure the whole project is built to refuse.

   The transition is the one motion moment in the interface. Nothing else
   animates, so when this fires it is the only thing that has moved. */
.act-finding.is-withheld {
  background: var(--void);
  transition: background-color 420ms ease;
}
.act-finding.is-withheld h1,
.act-finding.is-withheld .sub { color: var(--void-ink); }
.act-finding.is-withheld .clause { color: #868E95; }
.act-finding.is-withheld .withheld-mark {
  display: block; font-family: var(--font-machine); font-size: 12px;
  letter-spacing: 0.16em; text-transform: uppercase;
  color: var(--hazard); margin-bottom: 20px;
}
/* A hairline of hazard along the top edge, so the state is legible in a
   greyscale screenshot and to a reader who cannot separate the two darks. */
.act-finding.is-withheld { border-top: 3px solid var(--hazard); }


/* ------------------------------------------------------ act 05, the detectors */

.det-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 16px; margin-top: 28px; }
.det { border: 1px solid var(--rule); padding: 16px 18px; }
/* A firing detector is louder than a quiet one, but a quiet one still speaks.
   Rendering only the failures would make a board with nothing watching it look
   identical to a board where nothing is wrong. */
.det.is-fired { border-color: var(--hazard); border-left-width: 3px; }
.det-head { display: flex; justify-content: space-between; align-items: baseline; gap: 8px; }
.det-name {
  font-family: var(--font-machine); font-size: 13px; font-weight: 700;
  letter-spacing: 0.02em;
}
.det-state {
  font-family: var(--font-machine); font-size: 10px; letter-spacing: 0.12em;
  color: var(--ink-3);
}
.det.is-fired .det-state { color: var(--hazard); }
.det-scope {
  font-family: var(--font-machine); font-size: 10px; color: var(--ink-3);
  text-transform: uppercase; letter-spacing: 0.1em; margin-top: 6px;
}
.det-note { font-size: 13px; color: var(--ink-2); line-height: 1.45; margin-top: 10px; }
@media (max-width: 1279px) { .det-grid { grid-template-columns: 1fr 1fr; } }


/* ------------------------------------------------------------------ act 06
   THE HUNT. Hand-found rows, kept unmistakably apart from the sweep.

   The separation is done three ways at once, because one way is a caption a
   reader can skip: the act sits below the sweep, its whole body is inset behind
   a heavy left rule, and it opens with a banner that says what these rows are
   not. A judge who reads only the banner still cannot mistake them for sweep
   output.

   Red is rationed on this wall to accusations. One row here is an accusation
   and earns it. The rest are amber and take amber. */
.act-hunt { margin-top: 96px; }
.hunt-banner {
  margin-top: 24px; padding: 14px 18px;
  border: 1px solid var(--rule-strong); border-left: 3px solid var(--ink);
  background: var(--paper-hero);
  font-family: var(--font-machine); font-size: 11px; line-height: 1.6;
  letter-spacing: 0.04em; text-transform: uppercase; color: var(--ink-2);
}
.hunt-banner b { color: var(--ink); }
.hunt-list { margin-top: 28px; border-left: 3px solid var(--rule-strong); padding-left: 28px; }
.hunt-item { padding: 24px 0; border-bottom: 1px solid var(--rule); }
.hunt-item:first-child { padding-top: 4px; }
.hunt-item:last-child { border-bottom: none; }

.hunt-top { display: flex; align-items: baseline; gap: 14px; flex-wrap: wrap; }
.hunt-chip {
  font-family: var(--font-machine); font-size: 10px; font-weight: 700;
  letter-spacing: 0.14em; padding: 4px 9px; color: var(--ink-inv);
  background: var(--unknown);
}
.hunt-chip.is-red   { background: var(--hazard); }
.hunt-chip.is-amber { background: var(--amber); }
.hunt-ref { font-family: var(--font-machine); font-size: 11px; color: var(--ink-3); letter-spacing: 0.06em; }
.hunt-name { font-size: 17px; font-weight: 700; letter-spacing: -0.01em; }
.hunt-hazard { margin: 10px 0 0; max-width: 62ch; font-size: 14px; line-height: 1.5; color: var(--ink-2); }
.hunt-item.is-red .hunt-hazard { color: var(--hazard-deep); font-weight: 600; }

.hunt-facts {
  display: grid; grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 12px 20px; margin-top: 16px;
}
.hunt-facts dt {
  font-family: var(--font-machine); font-size: 9px; letter-spacing: 0.12em;
  text-transform: uppercase; color: var(--ink-3);
}
.hunt-facts dd {
  margin: 3px 0 0; font-family: var(--font-machine); font-size: 12px;
  color: var(--ink); overflow-wrap: anywhere;
}

/* A caveat is not a footnote. Where a row is weaker than it looks, the reason
   sits in the row at reading size, not under an asterisk. */
.hunt-caveat {
  margin-top: 16px; padding: 12px 16px; background: var(--paper-hero);
  border-left: 2px solid var(--amber);
  font-size: 13px; line-height: 1.55; color: var(--ink-2); max-width: 74ch;
}
.hunt-caveat b {
  display: block; font-family: var(--font-machine); font-size: 9px;
  letter-spacing: 0.12em; text-transform: uppercase; color: var(--amber);
  margin-bottom: 5px;
}
.hunt-evidence {
  display: inline-block; margin-top: 12px; font-family: var(--font-machine);
  font-size: 10px; letter-spacing: 0.08em; text-transform: uppercase;
  color: var(--ink-2); text-decoration: none;
  border-bottom: 1px solid var(--rule-strong); padding-bottom: 2px;
}
.hunt-evidence:hover { color: var(--flux); border-color: var(--flux); }
.hunt-close {
  margin-top: 32px; padding-top: 20px; border-top: 2px solid var(--rule-ink);
  max-width: 68ch; font-size: 15px; line-height: 1.6;
}
.hunt-close .hunt-punch { font-weight: 700; color: var(--ink); }
@media (max-width: 1279px) { .hunt-facts { grid-template-columns: 1fr 1fr; } }
@media (max-width: 719px)  { .hunt-facts { grid-template-columns: 1fr; }
                             .hunt-list { padding-left: 16px; } }

/* --------------------------------------------------- platform telemetry
   Their number beside ours. The card is deliberately NOT styled as an alarm:
   an 80% success rate on an 8-page job is not a crisis, it is a disagreement
   between two instruments, and dressing it in hazard red would overclaim it in
   exactly the direction this project spends its whole time refusing. */
.pj-grid { display: grid; gap: 16px; margin-top: 24px; }
.pj-card { border: 1px solid var(--rule-strong); border-left: 3px solid var(--flux); padding: 18px 20px; }
.pj-head { display: flex; justify-content: space-between; align-items: baseline; gap: 12px; }
.pj-name { font-family: var(--font-machine); font-size: 13px; font-weight: 700; }
.pj-rate { font-family: var(--font-machine); font-size: 13px; font-weight: 700; color: var(--flux); }
.pj-facts { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 12px 20px; margin: 16px 0 0; }
.pj-facts dt {
  font-family: var(--font-machine); font-size: 9px; letter-spacing: 0.12em;
  text-transform: uppercase; color: var(--ink-3);
}
.pj-facts dd { margin: 3px 0 0; font-family: var(--font-machine); font-size: 12px; color: var(--ink); overflow-wrap: anywhere; }
.pj-reconcile { margin: 16px 0 0; font-size: 13px; line-height: 1.55; color: var(--ink-2); max-width: 74ch; }
.pj-limit { margin-top: 16px; font-size: 13px; line-height: 1.55; color: var(--ink-2); max-width: 74ch; }
.pj-limit b { color: var(--ink); }
@media (max-width: 719px) { .pj-facts { grid-template-columns: 1fr 1fr; } }

/* ------------------------------------------------------- the matcher proof
   Two cards, side by side, because the argument IS the pairing. Neither card
   means anything alone and the layout should make that hard to forget: a
   matcher that discards everything passes the right-hand card perfectly, and
   one that reddens everything passes the left-hand card perfectly.

   Green does not exist in this palette, on purpose, so a holding card is
   marked by weight and a rule rather than by colour. Only a BROKEN card
   spends hazard, because a broken control set is the one condition under
   which nothing else on this page may be read as a measurement. */
.mp { margin-top: 40px; padding-top: 28px; border-top: 2px solid var(--rule-ink); }
.mp h3 {
  margin: 0; font-family: var(--font-display); font-variation-settings: "wdth" 104;
  font-size: 18px; font-weight: 700; letter-spacing: -0.01em;
}
.mp-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 18px; margin-top: 20px; }
.mp-card { border: 1px solid var(--rule-strong); border-top: 3px solid var(--ink); padding: 18px 20px; }
.mp-card.is-broken { border-top-color: var(--hazard); background: var(--hazard-wash); }
.mp-head { display: flex; justify-content: space-between; align-items: baseline; gap: 10px; }
.mp-title { font-size: 15px; font-weight: 700; letter-spacing: -0.01em; }
.mp-state {
  font-family: var(--font-machine); font-size: 10px; font-weight: 700;
  letter-spacing: 0.14em; color: var(--ink-3);
}
.mp-card.is-broken .mp-state { color: var(--hazard); }
.mp-count {
  font-family: var(--font-machine); font-size: 30px; font-weight: 700;
  margin-top: 12px; line-height: 1;
}
.mp-count span {
  display: block; font-size: 10px; font-weight: 500; letter-spacing: 0.1em;
  text-transform: uppercase; color: var(--ink-3); margin-top: 6px;
}
.mp-result { margin-top: 12px; font-size: 14px; font-weight: 700; }
.mp-card.is-broken .mp-result { color: var(--hazard-deep); }
.mp-note { margin: 10px 0 0; font-size: 12px; line-height: 1.5; color: var(--ink-2); }
.mp-close { margin-top: 20px; max-width: 74ch; font-size: 14px; line-height: 1.6; color: var(--ink); }
@media (max-width: 719px) { .mp-grid { grid-template-columns: 1fr; } }

/* ------------------------------------------------------------ query coverage
   The one block on this page that checks a method instead of arguing for it.
   Set in the flux accent rather than hazard: it is a calibration result, not an
   accusation, and this palette spends red on accusations only. */
.qc { margin-top: 40px; padding: 24px 26px; border: 1px solid var(--rule-strong); border-left: 3px solid var(--flux); }
.qc h3 {
  margin: 0; font-family: var(--font-display); font-variation-settings: "wdth" 104;
  font-size: 17px; font-weight: 700; letter-spacing: -0.01em;
}
.qc-figs { display: grid; grid-template-columns: repeat(4, 1fr); gap: 18px; margin-top: 20px; }
.qc-figs > div { border-top: 1px solid var(--rule); padding-top: 10px; }
.qc-n { display: block; font-family: var(--font-machine); font-size: 26px; font-weight: 700; line-height: 1; }
.qc-figs span + span {
  display: block; margin-top: 7px; font-size: 11px; line-height: 1.4; color: var(--ink-3);
}
.qc-note { margin: 16px 0 0; font-size: 13px; line-height: 1.55; color: var(--ink-2); max-width: 76ch; }
@media (max-width: 1279px) { .qc-figs { grid-template-columns: 1fr 1fr; } }
@media (max-width: 719px)  { .qc-figs { grid-template-columns: 1fr; } }

/* Outbound links inside an evidence receipt. Underlined and in the flux accent
   because a reader has to be able to SEE that a claim is checkable; a link
   styled to look like text is a link nobody clicks. */
.field.is-link .v a { color: var(--flux); text-decoration: underline; text-underline-offset: 2px; }
.field.is-link .v a:hover { text-decoration-thickness: 2px; }
.link-why { margin-top: 5px; font-size: 11px; line-height: 1.45; color: var(--ink-3); max-width: 46ch; }
.heal-meta a { color: var(--flux); text-decoration: underline; text-underline-offset: 2px; }
.hunt-links { display: flex; flex-wrap: wrap; gap: 18px; margin-top: 12px; }
.hunt-links .hunt-evidence { margin-top: 0; }

/* ------------------------------------------------------------- repo links
   The deployed wall is one file. Everything that substantiates it is in the
   repository, and a judge arriving from a submission form has minutes, so the
   path from claim to proof has to be one click rather than a search. */
.repo-links { margin-top: 40px; padding-top: 24px; border-top: 1px solid var(--rule); }
.repo-links h3 {
  margin: 0 0 16px; font-family: var(--font-machine); font-size: 10px;
  letter-spacing: 0.14em; text-transform: uppercase; color: var(--ink-3);
}
.repo-links ul { list-style: none; margin: 0; padding: 0;
  display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 16px 28px; }
.repo-links li { font-size: 13px; line-height: 1.5; }
.repo-links a {
  color: var(--flux); text-decoration: underline; text-underline-offset: 2px;
  font-weight: 700;
}
.repo-links span { display: block; margin-top: 3px; color: var(--ink-2); }
@media (max-width: 719px) { .repo-links ul { grid-template-columns: 1fr; } }

/* A detector that could not run is not a detector that passed.
   Two states drew three meanings: "ran and found nothing" and "had nothing to
   run on" both rendered as a quiet card, so identity_reassertion looked like an
   all-clear while having examined zero rows. The third state is drawn in the
   unknown grey this palette already reserves for withheld verdicts, with a
   dashed edge, so it reads as absence rather than as either pass or fail. */
.det.is-inconclusive { border-style: dashed; border-color: var(--unknown); }
.det.is-inconclusive .det-state { color: var(--unknown); font-weight: 700; }
.det.is-inconclusive .det-name { color: var(--ink-2); }
.det-measured {
  font-family: var(--font-machine); font-size: 11px; color: var(--ink);
  margin-top: 8px; padding-top: 8px; border-top: 1px solid var(--rule);
  line-height: 1.5;
}
.det.is-fired .det-measured { color: var(--hazard-deep); font-weight: 700; }

/* ------------------------------------------------- the raw row, as returned
   The field names are the evidence, so they are set as data and given room.
   A judge scanning this act should be able to see, without reading a sentence,
   that these names were chosen by something looking at a page rather than
   handed down by a library. */
.rawf-grid { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 20px; }
.rawf {
  font-family: var(--font-machine); font-size: 12px; padding: 5px 9px;
  border: 1px solid var(--rule-strong); background: var(--paper-hero); color: var(--ink);
}
.raw-lede { margin: 22px 0 0; max-width: 68ch; font-size: 14px; line-height: 1.55; color: var(--ink); }
.raw-shows { margin: 12px 0 0; padding-left: 22px; max-width: 74ch; }
.raw-shows li { font-size: 13px; line-height: 1.55; color: var(--ink-2); margin-bottom: 8px; }

/* --------------------------------------------------- the two regulators
   Set at figure scale because it earns it: this is the only measurement on the
   page that survives every collector failing at once, and in a sweep whose
   headline is a zero, a finding that cannot be broken by a scraper fault is
   worth more than one that can. */
.regulators { margin-top: 48px; padding-top: 28px; border-top: 2px solid var(--rule-ink); }
.regulators h3 {
  margin: 0; font-family: var(--font-display); font-variation-settings: "wdth" 104;
  font-size: 20px; font-weight: 700; letter-spacing: -0.015em; max-width: 32ch;
}
.regulators .lede { margin: 14px 0 0; max-width: 64ch; font-size: 15px; line-height: 1.55; color: var(--ink-2); }
.reg-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 24px; margin-top: 28px; }
.reg-card { border-top: 3px solid var(--ink); padding-top: 16px; }
.reg-name {
  font-family: var(--font-machine); font-size: 11px; letter-spacing: 0.1em;
  text-transform: uppercase; color: var(--ink-3);
}
.reg-figure {
  font-family: var(--font-display); font-variation-settings: "wdth" 108;
  font-size: var(--type-figure); font-weight: 700; line-height: 1; margin-top: 8px;
  letter-spacing: -0.02em;
}
.reg-basis { font-family: var(--font-machine); font-size: 11px; color: var(--ink-2); margin-top: 8px; }
.reg-kinds { list-style: none; margin: 12px 0 0; padding: 0; }
.reg-kinds li {
  font-family: var(--font-machine); font-size: 11px; color: var(--ink-3);
  display: flex; justify-content: space-between; gap: 12px;
  padding: 4px 0; border-top: 1px solid var(--rule);
}
.reg-kinds b { color: var(--ink); }
.reg-sep { margin-top: 24px; max-width: 68ch; font-size: 15px; line-height: 1.6; font-weight: 700; color: var(--ink); }
@media (max-width: 719px) { .reg-grid { grid-template-columns: 1fr; } }

/* ------------------------------------------------- the three-refusal pattern
   The status-field sentence is the load-bearing one in the whole self-healing
   argument, so it is set louder than the prose around it. It is the only place
   this project can say what would have happened if it had trusted a passing
   status, and the answer is that a working production collector would have been
   emptied two days before the deadline. */
.pattern-table td:last-child { color: var(--hazard-deep); font-weight: 700; }
.pattern-punch { margin: 20px 0 0; max-width: 72ch; font-size: 14px; line-height: 1.6; color: var(--ink); }
.pattern-punch.is-loud {
  font-weight: 700; font-size: 15px; border-left: 3px solid var(--hazard);
  padding: 12px 0 12px 16px; background: var(--hazard-wash);
}
.pattern-rule {
  margin: 20px 0 0; max-width: 60ch; font-family: var(--font-machine);
  font-size: 13px; line-height: 1.6; color: var(--ink); font-weight: 700;
  border-top: 1px solid var(--rule-strong); padding-top: 14px;
}

/* The cross-reference into the sweep's own ledger. Not styled as a caveat and
   not styled as an accusation: it is the join between the two halves of the
   page, so it reads as a fact with a rule under it. */
.hunt-xref-lede { margin: 22px 0 0; max-width: 72ch; font-size: 15px; line-height: 1.6; color: var(--ink-2); }
.hunt-xref-lede b { color: var(--ink); }
.xref {
  margin-top: 14px; padding: 10px 14px; background: var(--paper-hero);
  border-left: 2px solid var(--ink); font-size: 13px; line-height: 1.5;
  color: var(--ink); max-width: 74ch;
}
.xref.is-absent { border-left-color: var(--unknown); color: var(--ink-2); }

/* The one line on the first screen that is not about the sweep.
   Set apart by a hazard rule because it IS the accusation, and marked in its own
   words as coming from outside the sweep, so the separation the whole act rests
   on survives being quoted out of context on the very first screen. */
/* This paragraph sits on the dark finding act and was the one thing in it still
   styled with the light-ground ink tokens: --ink-2 on #14171A is about 2.2:1 and
   --ink is about 1.05:1, so the sentence was murky and the link was legible only
   as an underline. The cursor was the only thing telling a reader it could be
   clicked, which is the same failure as a control with no label. The act already
   sets color: var(--ink-inv) and this rule was overriding it.

   The link is now the brightest thing in the paragraph, because it is the only
   control in this act. Hover thickens the rule rather than colouring it: red is
   an accusation on this wall and a hover state is not one. */
.found-anyway {
  margin: 32px 0 0; max-width: 62ch; font-size: 15px; line-height: 1.6;
  color: #C9CDD1; border-left: 3px solid var(--hazard);
  padding: 12px 0 12px 18px;
}
/* --hazard measures 3.70:1 on the graphite hero and this text is 15px bold,
   which needs 4.5:1. The dark-ground variant is the same hue at the minimum
   lift that passes. This is the loudest phrase on the first screen. */
.found-anyway b { color: var(--hazard-on-dark); }
.found-anyway a {
  color: var(--ink-inv); text-decoration: underline; text-underline-offset: 3px;
  text-decoration-thickness: 1px;
}
.found-anyway a:hover { text-decoration-thickness: 2px; }
.act-finding.is-withheld .found-anyway { color: var(--void-ink); border-left-color: var(--void-ink); }
.act-finding.is-withheld .found-anyway b,
.act-finding.is-withheld .found-anyway a { color: var(--void-ink); }

/* ---------------------------------------------------------- global blackout
   The state that exists to say we do not know, and it had no styling at all.

   boot() adds .is-blackout to <body> when implausible_cleanliness fires, and
   there was no rule for it, so the hero went black, every figure struck out, and
   then 101 RED rows rendered underneath exactly as they do on a good day. That
   is the same failure as the withheld verdict drawing in the healthy black: a
   class emitted with nothing behind it.

   Red is an accusation on this wall. In a blackout the entire claim is that we
   cannot stand behind any accusation, so red is the one thing these rows must
   not be. They stay on the page, because deleting them would be a different lie
   and a reader needs to see what the last trustworthy sweep found, but they are
   drawn as history: hatched, desaturated, and with the hazard bar reduced to the
   same unknown grey the withheld verdicts use. */
body.is-blackout .row .track > i { background: var(--unknown); }
body.is-blackout .row.is-red .track > i { background: var(--unknown); }
body.is-blackout .chip.v-RED {
  background: var(--unknown); border-color: var(--unknown); color: var(--void-ink);
}
body.is-blackout .c-verdict, body.is-blackout .c-hazard .quote { color: var(--ink-2); }
body.is-blackout #wall {
  background: repeating-linear-gradient(45deg,
    transparent, transparent 3px, var(--hatch) 3px, var(--hatch) 4px);
}
body.is-blackout .row { background: var(--paper); }
body.is-blackout .days { color: var(--unknown); }
/* The note above the rows is the only thing in this state that should read at
   full strength, because it is the sentence doing the work. */
body.is-blackout .historical-note { color: var(--ink); font-weight: 700; }

/* ------------------------------------------------------------------ act 08
   The ask. A measurement with no addressee is a curiosity, and the page ended
   on a legend and a collapsed machinery drawer, so a reader got the finding and
   never the point of it. Set plainly: this is the one part of the page that is
   a position rather than a measurement, and it should not borrow the authority
   of the figures above it by looking like them. */
.act-ask { margin-top: 96px; }
.ask-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 20px; margin-top: 28px; }
.ask-card { border-top: 3px solid var(--ink); padding-top: 14px; }
.ask-k {
  font-family: var(--font-machine); font-size: 10px; letter-spacing: 0.12em;
  text-transform: uppercase; color: var(--ink-3);
}
.ask-v { margin-top: 8px; font-size: 15px; line-height: 1.5; color: var(--ink); }
.ask-v code, .ask-body code {
  font-family: var(--font-machine); font-size: 13px;
  background: var(--paper-hero); padding: 1px 4px;
}
.ask-body { margin: 24px 0 0; max-width: 70ch; font-size: 15px; line-height: 1.6; color: var(--ink-2); }
.ask-body b { color: var(--ink); }
.ask-repro {
  margin: 16px 0 0; padding: 16px 18px; background: var(--paper-hero);
  border-left: 3px solid var(--flux); overflow-x: auto;
}
.ask-repro code {
  font-family: var(--font-machine); font-size: 12px; line-height: 1.6;
  white-space: pre; color: var(--ink);
}
@media (max-width: 1279px) { .ask-grid { grid-template-columns: 1fr; } }
/* The answer to "your controls are synthetic", set apart from the two cards
   because it is a different kind of evidence: not a set we designed to pass, but
   a single page we did not write, that the same classifier reddened. */
.mp-real {
  margin: 18px 0 0; padding: 12px 16px; max-width: 74ch;
  border-left: 3px solid var(--hazard); background: var(--hazard-wash);
  font-size: 14px; line-height: 1.55; color: var(--ink);
}
