/* Slice 1 styling only. The v5 mockup CSS lands when the real screens do. */

:root {
  color-scheme: light;
  --plane: #f4f6f8;
  --surface: #ffffff;
  --surface-sunk: #eef1f4;
  --rail: #1b2228;
  --rail-ink: #e8edf1;
  --rail-muted: #8d9aa4;
  --rail-active: #2b353d;
  --teal: #35857a;
  --good: #0ca30c;
  --good-ink: #0a6b0a;
  --good-wash: rgba(12, 163, 12, 0.10);
  --ink: #14181b;
  --ink-2: #545f68;
  --ink-muted: #7d8890;
  --line: #e2e7ec;
  /* Two weights either side of --line. Both were in use from 2026-09-02 and neither was ever
     defined, so every declaration referencing them was invalid and dropped -- the row rules
     on the clause and age tables have never rendered, in either theme. A var() with no
     fallback and no definition fails silently and looks like a design choice. */
  --line-soft: #edf0f3;
  --line-strong: #c7d0d8;
  --accent-ink: #23557f;
  --critical-ink: #a72c2c;
  --serious-ink: #9c4a25;
  --moderate-ink: #8a6000;
  --minor-ink: #5f6b74;
  --critical-wash: rgba(208, 59, 59, 0.11);
  --serious-wash: rgba(236, 131, 90, 0.14);
  --moderate-wash: rgba(250, 178, 25, 0.16);
  --minor-wash: rgba(95, 107, 116, 0.11);
  --sans: system-ui, -apple-system, "Segoe UI", sans-serif;
  --serif: Georgia, "Iowan Old Style", "Times New Roman", serif;
  --mono: ui-monospace, "Cascadia Mono", Consolas, monospace;
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    color-scheme: dark;
    --plane: #101315;
    --surface: #191d20;
    --surface-sunk: #22272b;
    --rail: #0b0e10;
    --rail-ink: #e8edf1;
    --rail-muted: #7d8890;
    --rail-active: #1e252a;
    --teal: #58ab9e;
    --good: #0ca30c;
    --good-ink: #4cc74c;
    --good-wash: rgba(12, 163, 12, 0.16);
    --ink: #f2f5f7;
    --ink-2: #aab5bd;
    --ink-muted: #8d99a1;
    --line: #2b3238;
    --line-soft: #232a2f;
    --line-strong: #3f4952;
    --accent-ink: #8cbde9;
    --critical-ink: #e06a6a;
    --serious-ink: #f0a082;
    --moderate-ink: #fac54f;
    --minor-ink: #9aa7b1;
    --critical-wash: rgba(208, 59, 59, 0.18);
    --serious-wash: rgba(236, 131, 90, 0.15);
    --moderate-wash: rgba(250, 178, 25, 0.14);
    --minor-wash: rgba(154, 167, 177, 0.14);
  }
}

* { box-sizing: border-box; }

body {
  margin: 0;
  background: var(--plane);
  color: var(--ink);
  font-family: var(--sans);
  font-size: 15px;
  line-height: 1.55;
  -webkit-font-smoothing: antialiased;
}

.well { max-width: 880px; margin: 0 auto; padding: 40px 24px 80px; display: flex; flex-direction: column; gap: 24px; }

.page-head { display: flex; flex-direction: column; gap: 6px; }

.eyebrow {
  font-size: 0.7rem; font-weight: 650; letter-spacing: 0.13em;
  text-transform: uppercase; color: var(--ink-muted);
}

/* ---------- the standing card ---------- */

/* Two columns: the number on the left, what moved on the right. The card supplies no
   padding of its own — each block inside pads itself, so the divider can run the full
   height between them.

   Written as .card.verdict-card rather than .verdict-card because .card is defined
   further down this file, and at equal specificity it would win on source order. */
.card.verdict-card { padding: 0; }

.verdict {
  display: grid;
  grid-template-columns: minmax(0, 268px) 1px minmax(0, 1fr);
  gap: 26px;
  align-items: stretch;
  padding: 24px 26px;
}

.score-block { display: flex; flex-direction: column; gap: 4px; min-width: 0; }

.score-eyebrow {
  font-size: 0.68rem; font-weight: 700; letter-spacing: 0.11em;
  text-transform: uppercase; color: var(--ink-muted);
}

.score-row { display: flex; align-items: baseline; gap: 4px; }
.score { font-family: var(--serif); font-size: 4.2rem; line-height: 0.98; letter-spacing: -0.02em; }

.verdict-word { font-family: var(--serif); font-size: 1.3rem; font-weight: 700; margin-top: 2px; }
.verdict-word.good       { color: var(--good-ink); }
.verdict-word.fair       { color: var(--moderate-ink); }
.verdict-word.needs-work { color: var(--serious-ink); }
.verdict-word.poor       { color: var(--critical-ink); }

/* The two caveats the number cannot be quoted without. Quiet, but on the card itself. */
.score-caveat { margin: 8px 0 0; font-size: 0.78rem; line-height: 1.45; color: var(--ink-muted); max-width: 34ch; }

.verdict-sub { margin: 6px 0 0; font-size: 0.85rem; color: var(--ink-2); max-width: 34ch; }
.verdict-sub b { font-weight: 650; color: var(--ink); }

/* Which run produced the number. Last, and quiet — it is provenance, not a headline. */
.verdict-meta { margin: 10px 0 0; font-size: 0.76rem; color: var(--ink-muted); overflow-wrap: anywhere; }

.rule { background: var(--line); }

.verdict-right { display: flex; flex-direction: column; min-width: 0; }

/* Severity chips sit under both columns, inside the same card — they count the same
   distinct problems the score above is made of, and a separate card made them look like
   a second, unrelated measurement. */
.chips-block { padding: 18px 26px 22px; border-top: 1px solid var(--line); }
.chips-block h3 { font-family: var(--serif); font-weight: 400; font-size: 1.1rem; margin: 0 0 4px; }

.sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

h1 { font-family: var(--serif); font-weight: 400; font-size: 2rem; margin: 0; }
h2 { font-family: var(--serif); font-weight: 400; font-size: 1.25rem; margin: 0 0 4px; }

.sub { margin: 0; color: var(--ink-2); font-size: 0.92rem; }
.note { margin: 0 0 14px; color: var(--ink-muted); font-size: 0.87rem; max-width: 68ch; }

/* Moved here from help.css on 2026-09-09, because it was never only a help style. /Rules
   and /Documents both used .callout while it was defined in a stylesheet loaded on /help
   routes alone, so both rendered an unstyled div and had done since they shipped. app.css
   is loaded on every route and help.css is additive, so moving it fixes the product screens
   and leaves the help pages exactly as they were. */
.callout {
  border-left: 3px solid var(--teal); background: var(--surface);
  border: 1px solid var(--line); border-left: 3px solid var(--teal);
  border-radius: 0 8px 8px 0; padding: 14px 18px; margin: 0 0 14px;
}
.callout.warn { border-left-color: var(--critical-ink); }
.callout p:last-child { margin-bottom: 0; }
/* Direct child only. `:first-child` ignores text nodes, so a descendant selector here also
   matched a <strong> that merely opened a sentence inside a callout's <p> — which broke the
   sentence onto its own line and left the rest of it starting with a comma. */
.callout > strong:first-child { display: block; margin-bottom: 3px; }

.card {
  background: var(--surface); border: 1px solid var(--line);
  border-radius: 10px; padding: 22px 24px;
}

.table-scroll { overflow-x: auto; }

table { width: 100%; border-collapse: collapse; font-size: 0.88rem; }
th, td { text-align: left; padding: 8px 10px; border-bottom: 1px solid var(--line); }
th { font-size: 0.7rem; letter-spacing: 0.08em; text-transform: uppercase; color: var(--ink-muted); font-weight: 650; }
.num { text-align: right; font-variant-numeric: tabular-nums; }
tfoot td { border-bottom: 0; border-top: 2px solid var(--line); font-weight: 650; }
code { font-family: var(--mono); font-size: 0.86em; color: var(--ink-2); }

.sev {
  display: inline-block; font-size: 0.7rem; font-weight: 700;
  letter-spacing: 0.05em; text-transform: uppercase;
  padding: 2px 8px; border-radius: 5px; white-space: nowrap;
}
.sev.critical { color: var(--critical-ink); background: var(--critical-wash); }
.sev.serious  { color: var(--serious-ink);  background: var(--serious-wash); }
.sev.moderate { color: var(--moderate-ink); background: var(--moderate-wash); }
.sev.minor    { color: var(--minor-ink);    background: var(--minor-wash); }
.sev.unknown  { color: var(--ink-muted);    background: var(--minor-wash); }

/* ---------- surfaces ---------- */

/* Pages, documents, monitoring. auto-fit rather than three fixed columns: the row
   reflows to two and then one without a media query of its own, and the cards keep a
   readable measure at every width. */
.surfaces { display: grid; grid-template-columns: repeat(auto-fit, minmax(230px, 1fr)); gap: 20px; }
.surface-card { display: flex; flex-direction: column; gap: 12px; }
.surface-top { display: flex; align-items: center; gap: 9px; }
.surface-top h3 { margin: 0; font-family: var(--serif); font-size: 0.95rem; font-weight: 700; }
.surface-top svg { color: var(--ink-muted); flex: none; }
.surface-num { display: flex; align-items: baseline; gap: 8px; margin: 0; }
.surface-num strong { font-size: 2.1rem; font-weight: 700; letter-spacing: -0.02em; line-height: 1; }
.surface-num span { font-size: 0.83rem; color: var(--ink-muted); }

.meter { height: 6px; border-radius: 3px; background: var(--surface-sunk); overflow: hidden; }
.meter i { display: block; height: 100%; border-radius: 3px; }
/* Banded by the same VerdictBands the score word uses, so the bar and the word on the
   card above it can never disagree about whether a number is good. */
.meter i.good       { background: var(--good-ink); }
.meter i.fair       { background: var(--moderate-ink); }
.meter i.needs-work { background: var(--serious-ink); }
.meter i.poor       { background: var(--critical-ink); }

.surface-foot { font-size: 0.82rem; color: var(--ink-2); margin: 0; }
.surface-foot b { font-weight: 650; color: var(--ink); }

/* The card grows to fill its grid cell, so the footnote sits at the bottom and the three
   numbers stay on one line across the row however long the prose under them runs. */
.surface-card .surface-foot { margin-top: auto; }

/* ---------- rows about one rule ---------- */

/* Shared by the priority card and the alerts screen, which show the same shape of thing:
   a severity badge, what the problem is, and one figure on the right. Two copies of this
   grid would drift, and the two screens are read side by side by the same person. */
.finding-list { list-style: none; margin: 0; padding: 0; }
.finding-row {
  display: grid; grid-template-columns: auto 1fr auto; gap: 4px 14px;
  align-items: start; padding: 14px 0; border-top: 1px solid var(--line);
}
.finding-row:first-child { border-top: 0; padding-top: 4px; }
.finding-row .sev { margin-top: 2px; }
.finding-body { min-width: 0; }
.finding-name { margin: 0; font-weight: 650; font-size: 0.95rem; overflow-wrap: anywhere; }
.finding-why { margin: 4px 0 0; font-size: 0.87rem; color: var(--ink-2); max-width: 68ch; }
.finding-rule { margin: 5px 0 0; font-size: 0.8rem; }
.finding-aside {
  margin: 2px 0 0; font-size: 0.83rem; color: var(--ink-muted);
  white-space: nowrap; text-align: right;
}
.finding-aside b { color: var(--ink); font-weight: 700; font-size: 0.95rem; }
.finding-aside .sub { display: block; font-size: 0.76rem; color: var(--ink-muted); }

/* ---------- what to fix next ---------- */

.priorities-foot { margin: 16px 0 0; }
.priorities-foot b { color: var(--ink); font-weight: 650; }
.priorities-foot a { margin-left: 6px; }

/* ---------- alerts ---------- */

.coverage { margin: 0; }
.alerts-count { margin: 0; }
.alerts-count b { color: var(--ink); font-weight: 650; }
.alert-site-head { margin-bottom: 4px; }
.alert-site-head h2 { margin: 0; }
.alert-site-head h2 a { color: inherit; text-decoration: none; }
.alert-site-head h2 a:hover { text-decoration: underline; }
.alert-site-head .note { margin: 2px 0 0; font-size: 0.8rem; }
/* A regression that costs nothing is the one a score-watcher misses, so it is marked
   rather than left blank where the points would be. */
.alert-free { color: var(--ink-muted); font-style: italic; }
.alert-pages-head { margin: 6px 0 2px; font-size: 0.85rem; color: var(--ink-muted); }
.alert-pages { margin: 0; padding-left: 18px; font-size: 0.85rem; }
.alert-pages li { margin: 1px 0; overflow-wrap: anywhere; }
.alert-pages-more { margin: 2px 0 0; font-size: 0.85rem; color: var(--ink-muted); }
.alerts-clear h2 { margin-bottom: 6px; }
.alerts-clear .note { margin: 0; }

/* Scans that were cut short. Sits above the regressions because it qualifies them, so it
   is deliberately quieter than an error and louder than a note. */
.alerts-cut-short h2 { margin-bottom: 6px; }
.alerts-cut-short ul { margin: 10px 0; padding-left: 20px; }
.alerts-cut-short li { margin-bottom: 4px; }
.alerts-server-errors h2 { margin-bottom: 6px; }
.alerts-server-errors h3 { margin: 14px 0 4px; font-size: 1rem; }
.alerts-server-errors ul { margin: 6px 0 10px; padding-left: 20px; }
.alerts-server-errors li { margin-bottom: 4px; }
.alerts-server-errors .alerts-url { overflow-wrap: anywhere; }

/* ---------- the publish feed (/Publishes) ---------- */
.publish-figures { display: grid; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); gap: 12px; margin-bottom: 18px; }
.publish-figure { background: var(--surface); border: 1px solid var(--line); border-radius: 10px; padding: 14px 16px; display: grid; gap: 2px; align-content: start; }
.publish-figure b { font-family: var(--serif); font-weight: 400; font-size: 1.9rem; line-height: 1.1; font-variant-numeric: tabular-nums; }
.publish-figure .k { font-size: 0.7rem; letter-spacing: 0.05em; text-transform: uppercase; color: var(--ink-muted); }
.publish-figure b.good { color: var(--good-ink); }
.publish-queue { font-size: 0.86rem; }
.publish-queue td { vertical-align: top; }
.publish-queue .sub { display: block; font-size: 0.76rem; color: var(--ink-muted); }
.publish-path { display: block; font-family: var(--mono); font-size: 0.78rem; overflow-wrap: anywhere; }
.publish-chips { min-width: 220px; }
.publish-chip { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; margin-bottom: 4px; }
.publish-live { white-space: nowrap; font-weight: 650; }
.publish-live.long { color: var(--critical-ink); }
.publish-feed-line { margin: 4px 0 8px; font-size: 0.9rem; }
.publish-feed-line .good { color: var(--good-ink); }
.publish-feed-line .bad { color: var(--critical-ink); }
/* All sites: the publish feed's two columns, set apart from the scan's as the mockup does. */
.publishing-col { background: var(--surface-sunk); }
/* Edit target: the publish feed panel. */
.feed-panel h2 { margin: 0 0 6px; font-size: 1.05rem; }
.feed-panel-line { margin: 0 0 6px; font-size: 0.88rem; }
.feed-panel-ok { color: var(--good-ink); font-weight: 650; margin-right: 4px; }
.feed-panel .sev { margin-right: 4px; }
.publish-figure span { color: var(--ink-2); font-size: 0.84rem; }
.publish-warning { margin: 0 0 14px; padding: 10px 14px; border-radius: 8px; background: var(--critical-wash); color: var(--ink); font-size: 0.88rem; }
.publish-empty { margin: 8px 0 0; color: var(--ink-2); }
.publish-unchecked { margin: 6px 0 0; padding-left: 20px; display: grid; gap: 4px; font-size: 0.88rem; }
.publish-unchecked li { overflow-wrap: anywhere; }
.alerts-published-pointer { margin: 0; }
.published-row { padding: 14px 0; border-top: 1px solid var(--line); }
.published-row:first-child { border-top: 0; padding-top: 4px; }
.published-row > .note { margin: 4px 0 0; font-size: 0.84rem; }
.published-head { display: flex; justify-content: space-between; gap: 14px; align-items: baseline; }
.published-head .finding-name a { font-weight: 500; margin-left: 4px; overflow-wrap: anywhere; }
.published-problems { list-style: none; margin: 8px 0 0; padding: 0; }
.published-problems li { margin: 4px 0; font-size: 0.87rem; overflow-wrap: anywhere; }
.published-problems .sev { margin-right: 6px; }
.published-problems .alerts-url { margin-left: 6px; font-family: var(--mono, monospace); font-size: 0.8rem; color: var(--ink-2); }
.published-log { margin-top: 8px; font-size: 0.84rem; }
.published-log td { vertical-align: top; }
.published-log .sub { display: block; font-size: 0.74rem; color: var(--ink-muted); }
.published-log-when { white-space: nowrap; font-variant-numeric: tabular-nums; }
.published-log-flag td:last-child { font-weight: 650; }
.alerts-cut-short .note:last-child { margin-bottom: 0; }
.alerts-overnight h2 { margin-bottom: 6px; }
.alerts-overnight .note:last-child { margin-bottom: 0; }
.alerts-overnight-exclusions code { margin-right: 4px; }
.overnight-list { list-style: none; margin: 12px 0; padding: 0; display: grid; gap: 12px; }
.overnight-row { border-top: 1px solid var(--line); padding-top: 10px; }
.overnight-head { display: flex; flex-wrap: wrap; justify-content: space-between; gap: 4px 16px; font-weight: 600; }
.overnight-head a { color: inherit; }
.overnight-score { font-variant-numeric: tabular-nums; font-weight: 400; }
.overnight-score b { color: var(--ink); font-weight: 650; }
.overnight-split { margin: 4px 0; font-variant-numeric: tabular-nums; }
.overnight-split b { color: var(--ink); font-weight: 650; }
.overnight-reasons { margin: 4px 0; padding-left: 20px; }
.overnight-row .note { margin: 2px 0 0; font-size: 0.8rem; }
.alerts-ops { margin: 0; }
.alerts-ops b { color: var(--ink); font-weight: 650; }

/* ---------- severity chips ---------- */

.chips { display: flex; flex-wrap: wrap; gap: 10px; margin: 0; padding: 0; list-style: none; }
.chip {
  display: flex; align-items: center; gap: 10px;
  padding: 9px 16px 9px 11px; border-radius: 9px;
  border: 1px solid var(--line); background: var(--surface); min-width: 128px;
}
.chip-icon { width: 26px; height: 26px; flex: none; border-radius: 7px; display: grid; place-items: center; }
.chip-icon.critical { background: var(--critical-wash); color: var(--critical-ink); }
.chip-icon.serious  { background: var(--serious-wash);  color: var(--serious-ink); }
.chip-icon.moderate { background: var(--moderate-wash); color: var(--moderate-ink); }
.chip-icon.minor    { background: var(--minor-wash);    color: var(--minor-ink); }
.chip-icon.unknown  { background: var(--minor-wash);    color: var(--ink-muted); }
.chip-text { display: flex; flex-direction: column; line-height: 1.25; min-width: 0; }
.chip-num { font-size: 1.2rem; font-weight: 700; font-variant-numeric: tabular-nums; }
.chip-label { font-size: 0.76rem; color: var(--ink-muted); }

.chips-foot { margin: 14px 0 0; }

/* ---------- what changed ---------- */

.changed-head { display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; margin-bottom: 14px; }
.changed-head h3 { font-family: var(--serif); font-weight: 400; font-size: 1.1rem; margin: 0; }
.changed-head .since { font-size: 0.78rem; font-weight: 400; color: var(--ink-muted); }

.changes { display: flex; flex-direction: column; gap: 9px; margin: 0; padding: 0; list-style: none; }

.change { display: flex; align-items: flex-start; gap: 10px; font-size: 0.885rem; line-height: 1.45; }
.change-icon {
  width: 20px; height: 20px; flex: none; border-radius: 5px;
  display: grid; place-items: center; margin-top: 2px;
}
.change-icon.up   { background: var(--good-wash);     color: var(--good); }
.change-icon.down { background: var(--critical-wash); color: var(--critical-ink); }
.change-icon.flat { background: var(--surface-sunk);  color: var(--ink-muted); }
.change b { font-weight: 650; }
.change em { font-style: normal; color: var(--ink-muted); }
.change code { color: inherit; }

/* The running build, in the rail foot. Quiet by default — it is a thing you go looking
   for, not a thing that should compete with the score. */
.rail-build {
  margin: 0 0 8px; font-size: 0.7rem; color: var(--ink-muted);
  font-variant-numeric: tabular-nums; cursor: help;
}

/* A timestamp whose tooltip is filled in client-side with the reader's own timezone.
   Dotted underline is the long-standing convention for "there is more on hover", and the
   text still reads correctly without ever hovering. */
time[data-local-time] {
  text-decoration: underline dotted; text-underline-offset: 2px; cursor: help;
}

/* The rule's WCAG position, on its own line beneath the headline. Deliberately uniform
   across all four sentences: "Best practice — not a WCAG criterion" and "Not yet
   classified" mean different things, and the words are what say so. Colouring one of
   them would make the distinction depend on seeing the colour. */
.change .attribution {
  display: block;
  font-size: 0.79rem;
  color: var(--ink-muted);
  margin-top: 1px;
}

/* The comparison's own limits, kept visible rather than tucked into a tooltip. A run that
   could not check a page is the difference between a fix and a guess. */
.caveats { margin-top: 18px; padding-top: 14px; border-top: 1px solid var(--line); }
.caveats h4 {
  margin: 0 0 8px; font-family: var(--sans); font-size: 0.7rem; font-weight: 650;
  letter-spacing: 0.08em; text-transform: uppercase; color: var(--ink-muted);
}
.caveats ul { margin: 0; padding-left: 18px; display: flex; flex-direction: column; gap: 5px; }
.caveats li { color: var(--ink-2); font-size: 0.83rem; line-height: 1.5; }

.rule-names { margin: 14px 0 0; font-size: 0.78rem; }

.first-scan { margin: 0; }

.method {
  margin: 0; padding: 16px 18px; border-radius: 9px;
  background: var(--surface); border: 1px solid var(--line);
  color: var(--ink-2); font-size: 0.85rem; max-width: 78ch;
}
/* A rule excluded by agreement keeps its row in the breakdown and loses the arithmetic.
   Muted rather than hidden, and muted rather than flagged: it is disclosed because the
   client should not find a gap we did not mention, not because it is a warning. */
#points .excluded { color: var(--ink-muted); }
#points .excluded code { opacity: 0.75; }

/* ---------- rules ---------- */

/* The operator view of exclusions and folds. Louder than anything client-facing on
   purpose: the client's version of this subject is one muted table row, and this one has
   to make a number somebody should argue with hard to scroll past. */
.lede { margin: 0 0 14px; font-size: 1.02rem; color: var(--ink); max-width: 70ch; }

.exclusion-stats {
  display: grid; gap: 10px; margin: 0 0 18px;
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
}
.exclusion-stat {
  padding: 12px 14px; border-radius: 9px;
  border: 1px solid var(--line); background: var(--surface);
}
.exclusion-stat strong { display: block; font-size: 1.5rem; line-height: 1.1; }
.exclusion-stat span { font-size: 0.82rem; color: var(--ink-2); }
/* Only the two counts that should prompt an action are tinted, so the tint keeps meaning
   something. A count of best-practice exclusions is information, not a problem. */
.exclusion-stat.warn { border-color: var(--serious-ink); }
.exclusion-stat.warn strong { color: var(--serious-ink); }

.attr {
  display: inline-block; font-size: 0.76rem; font-weight: 600;
  padding: 2px 8px; border-radius: 999px; white-space: nowrap;
}
.attr-criterion { color: var(--serious-ink); background: var(--serious-wash); }
.attr-practice  { color: var(--ink-2);      background: var(--minor-wash); }
.attr-unknown   { color: var(--ink-muted);  background: var(--minor-wash); }
.attr-missing   { color: var(--critical-ink); background: var(--critical-wash); }

#links .excluded code { opacity: 0.75; }
#links .link-reason td { padding-top: 0; color: var(--ink-2); }
.muted { color: var(--ink-muted); }

/* ---------- trend ---------- */

.sites { display: flex; gap: 8px; flex-wrap: wrap; }
.site-pill {
  font-size: 0.83rem; font-weight: 600; text-decoration: none;
  padding: 5px 12px; border-radius: 999px;
  border: 1px solid var(--line); background: var(--surface); color: var(--ink-2);
}
.site-pill:hover { border-color: var(--ink-muted); }
.site-pill.on { background: var(--accent-ink); border-color: var(--accent-ink); color: #fff; }

/* The chart pads itself, so the plot can run wider than the card's prose measure and the
   horizontal scroll appears at the card edge rather than inside a gutter. */
.card.chart-card { padding: 0; }
.chart-card .card-head { padding: 20px 24px 4px; }
.chart-card .card-head h2 { margin: 0; }
.chart-card .card-head p { margin: 2px 0 0; font-size: 0.83rem; color: var(--ink-muted); }
.chart-scroll { overflow-x: auto; padding: 6px 16px 4px; }
.chart-foot { margin: 0; padding: 0 24px 20px; }
.chart-empty { margin: 0; padding: 4px 24px 22px; }

svg.trend { width: 100%; min-width: 620px; height: auto; display: block; }
svg.trend .grid { stroke: var(--line); stroke-width: 1; }
svg.trend .axis { fill: var(--ink-muted); font-size: 10px; font-family: var(--sans); }
svg.trend .axis.num { font-variant-numeric: tabular-nums; }
svg.trend .wash { fill: url(#trend-wash); }
svg.trend .series { fill: none; stroke: var(--accent-ink); stroke-width: 2; stroke-linejoin: round; stroke-linecap: round; }
svg.trend .dot { fill: var(--accent-ink); }
svg.trend .dot.last { fill: var(--accent-ink); stroke: var(--surface); stroke-width: 2.5; }

/* The target line is the one thing on the chart the client agreed to, so it gets its own
   colour rather than borrowing the muted grey the axis uses. */
svg.trend .target { stroke: var(--teal); stroke-width: 1.5; stroke-dasharray: 5 4; opacity: 0.8; }
svg.trend .target-label { fill: var(--teal); font-weight: 600; }

/* The steepest fall, called out on the chart. Muted rather than alarming — it is a label
   on something that already happened, not a live warning. */
svg.trend .annot-strong { fill: var(--ink); font-size: 10px; font-family: var(--sans); font-weight: 650; }
svg.trend .annot text { fill: var(--ink-2); font-size: 10px; font-family: var(--sans); }
/* Restated inside .annot: the rule above it is one selector less specific, so without
   this the emphasised first line is drawn in the same ink as the second. */
svg.trend .annot text.annot-strong { fill: var(--ink); font-weight: 650; }
svg.trend .annot-line { stroke: var(--ink-muted); stroke-width: 1; opacity: 0.55; }
svg.trend .dot.flagged { fill: var(--critical-ink); stroke: var(--surface); stroke-width: 2; }
svg.trend .latest-label { font-variant-numeric: tabular-nums; }

td a { color: var(--accent-ink); font-weight: 600; }

/* ---------- all sites ---------- */

/* The verdict word in a table cell. The same four palette classes the standing card uses,
   at table weight rather than headline weight — one decision, two sizes. */
.verdict-cell { font-weight: 650; }
.verdict-cell.good       { color: var(--good-ink); }
.verdict-cell.fair       { color: var(--moderate-ink); }
.verdict-cell.needs-work { color: var(--serious-ink); }
.verdict-cell.poor       { color: var(--critical-ink); }

/* Nothing to report in this cell, drawn as an em dash rather than left blank so a reader
   can tell "we have no value" from "the page failed to render one". */
.none { color: var(--ink-muted); }

/* ---------- the chips, as filters ---------- */

/* Each chip states a figure and is the way to see the sites behind it. The lit one is the
   view you are in — marked by weight and a border as well as colour, because a filter you
   cannot tell is on is a table you will misread. */
.filters { align-items: stretch; }
.filters li { display: flex; }
a.chip {
  text-decoration: none; color: inherit; width: 100%;
  transition: border-color 0.12s ease, background 0.12s ease;
}
a.chip:hover { border-color: var(--ink-muted); background: var(--surface-sunk); }
a.chip:focus-visible { outline: 2px solid var(--accent-ink); outline-offset: 2px; }
a.chip[aria-current="true"] {
  border-color: var(--accent-ink); box-shadow: inset 0 0 0 1px var(--accent-ink);
}
a.chip[aria-current="true"] .chip-label { color: var(--accent-ink); font-weight: 650; }

/* The median. A statistic rather than a slice, so it is deliberately not a control. */
.chip.is-stat { cursor: default; }

.site-search {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  margin-top: 16px; font-size: 0.85rem;
}
.site-search label { color: var(--ink-muted); }
.site-search input[type="search"] {
  padding: 6px 10px; font: inherit; min-width: 180px;
  color: var(--ink); background: var(--surface-sunk);
  border: 1px solid var(--line); border-radius: 7px;
}
.site-search button {
  padding: 6px 14px; font: inherit; font-weight: 600; cursor: pointer;
  color: #fff; background: var(--accent-ink);
  border: 1px solid var(--accent-ink); border-radius: 7px;
}
.site-search a { color: var(--accent-ink); font-weight: 600; }
.site-search input:focus-visible, .site-search button:focus-visible {
  outline: 2px solid var(--accent-ink); outline-offset: 2px;
}

/* How much of the estate the table is currently showing. Beside the heading rather than
   above the table, so it reads as part of what the heading is naming. */
.showing {
  margin-left: 10px; font-family: var(--sans); font-size: 0.75rem; font-weight: 650;
  letter-spacing: 0.04em; text-transform: uppercase; color: var(--accent-ink);
}

.empty-slice { margin: 0; }

/* ---------- movement ---------- */

/* Direction carries a sign as well as a colour, so the column reads the same to someone
   who cannot tell the green from the red. "level" is spelled out rather than shown as 0:
   measured twice and unchanged is a result, and an em dash — nothing to compare against —
   is a different statement again. */
.moved { font-weight: 650; white-space: nowrap; }
.moved.up   { color: var(--good-ink); }
.moved.down { color: var(--critical-ink); }
.moved.flat { color: var(--ink-muted); font-weight: 500; }
.moved-since { display: block; font-size: 0.72rem; color: var(--ink-muted); white-space: nowrap; }

td .num small, td small.over, td small.under { margin-left: 6px; font-size: 0.78rem; font-weight: 600; }
small.over  { color: var(--good-ink); }
small.under { color: var(--critical-ink); }

/* The failed-attempt line, under the date of the run that was actually scored. Its own
   line rather than beside the date: the two are different runs, and side by side they read
   as one date range. */
.attempt {
  display: flex; align-items: center; gap: 6px; flex-wrap: wrap;
  margin-top: 3px; font-size: 0.74rem; color: var(--ink-muted);
}

/* ---------- shell ---------- */

.skip {
  position: absolute; left: -9999px;
  background: var(--surface); color: var(--ink);
  padding: 10px 16px; border-radius: 0 0 8px 0; z-index: 10;
}
.skip:focus { left: 0; top: 0; }

.shell { display: grid; grid-template-columns: 224px minmax(0, 1fr); min-height: 100vh; }

/* Pinned to the viewport rather than stretched down the document. Without the sticky
   height the rail grows with .shell, and margin-top:auto on .rail-foot drops the sign-in
   block to the bottom of a long page — so you had to scroll past the whole score
   breakdown to find out who you were signed in as. */
.rail {
  background: var(--rail);
  color: var(--rail-ink);
  padding: 20px 14px;
  display: flex;
  flex-direction: column;
  gap: 26px;
  position: sticky;
  top: 0;
  align-self: start;
  height: 100vh;
  overflow-y: auto;
}

.brand { display: flex; align-items: center; gap: 9px; padding: 0 6px; }
.brand-mark {
  width: 26px; height: 26px; flex: none; border-radius: 6px;
  background: var(--teal); display: grid; place-items: center; color: #08110f;
}
.brand-name { font-size: 0.76rem; font-weight: 700; letter-spacing: 0.14em; text-transform: uppercase; }

.rail-group { display: flex; flex-direction: column; gap: 3px; }
.rail-label {
  font-size: 0.66rem; font-weight: 600; letter-spacing: 0.11em;
  text-transform: uppercase; color: var(--rail-muted); padding: 0 6px 6px;
}

.rail-item {
  display: flex; align-items: center; gap: 10px;
  padding: 8px; border-radius: 7px;
  text-decoration: none; color: var(--rail-ink);
  font-size: 0.875rem; transition: background 0.12s ease;
}
a.rail-item:hover { background: var(--rail-active); }
.rail-item[aria-current="page"] { background: var(--rail-active); font-weight: 600; box-shadow: inset 2px 0 0 var(--teal); }
.rail-item svg { flex: none; opacity: 0.85; }

/* Named but not yet routable. Shown so the rail stays the map of the product. */
.rail-item.is-unbuilt { color: var(--rail-muted); cursor: default; }
.rail-item.is-unbuilt svg { opacity: 0.45; }
.rail-soon {
  margin-left: auto; font-size: 0.62rem; font-weight: 700;
  letter-spacing: 0.08em; text-transform: uppercase;
  color: var(--rail-muted); border: 1px solid var(--rail-active);
  border-radius: 4px; padding: 1px 5px;
}

.rail-foot { margin-top: auto; padding: 0 6px; display: flex; flex-direction: column; gap: 8px; }
.rail-user { display: flex; align-items: center; gap: 9px; font-size: 0.8rem; color: var(--rail-muted); }
.rail-user small { font-size: 0.72rem; }
.rail-user-name { min-width: 0; overflow-wrap: anywhere; color: var(--rail-ink); }

.rail-signout {
  display: flex; align-items: center; gap: 8px; width: 100%;
  padding: 7px 8px; border-radius: 7px;
  border: 1px solid transparent; background: none; cursor: pointer;
  color: var(--rail-muted); font: inherit; font-size: 0.8rem; text-align: left;
}
.rail-signout:hover { background: var(--rail-active); color: var(--rail-ink); }
.avatar {
  width: 26px; height: 26px; flex: none; border-radius: 50%;
  background: var(--rail-active); display: grid; place-items: center;
  font-size: 0.7rem; font-weight: 700; color: var(--rail-ink);
}

.main { min-width: 0; }

.topbar {
  display: flex; align-items: center; gap: 14px; flex-wrap: wrap;
  padding: 14px 30px; background: var(--surface); border-bottom: 1px solid var(--line);
  min-height: 57px;
}

/* Wraps and shrinks rather than pushing the page sideways. The full URL beside the host
   is long enough to overflow a phone on its own, and a topbar that forces a horizontal
   scrollbar moves every screen under it. */
.site-switch {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  min-width: 0; max-width: 100%;
  padding: 6px 10px 6px 12px;
  border: 1px solid var(--line); border-radius: 8px;
  background: var(--surface); color: var(--ink);
  font-weight: 600; font-size: 0.9rem;
}
.site-switch small {
  font-weight: 400; color: var(--ink-muted); font-size: 0.8rem;
  min-width: 0; overflow-wrap: anywhere;
}

/* ---------- the site switcher ---------- */

/* A <details> rather than a select. The panel below holds a filter box, which is the part
   that makes thirty-three sites usable, and <summary> is a real button — a select that
   navigated on change would fire on every option a keyboard user arrowed past. */
details.site-switch { padding: 0; position: relative; border-color: transparent; background: none; }
details.site-switch > summary {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  min-width: 0; max-width: 100%;
  padding: 6px 10px 6px 12px;
  border: 1px solid var(--line); border-radius: 8px;
  background: var(--surface); color: var(--ink);
  font-weight: 600; font-size: 0.9rem;
  cursor: pointer; list-style: none;
}
details.site-switch > summary::-webkit-details-marker { display: none; }
details.site-switch > summary:hover { border-color: var(--ink-muted); }
details.site-switch > summary:focus-visible { outline: 2px solid var(--accent-ink); outline-offset: 2px; }

.site-switch-name { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; min-width: 0; }
.site-switch-name small {
  font-weight: 400; color: var(--ink-muted); font-size: 0.8rem;
  min-width: 0; overflow-wrap: anywhere;
}

/* How many there are to switch between, on the closed control. It is the number that
   explains why the control exists. */
.site-switch-count {
  font-size: 0.7rem; font-weight: 700; letter-spacing: 0.06em; text-transform: uppercase;
  color: var(--ink-muted); background: var(--surface-sunk);
  border-radius: 5px; padding: 2px 7px; white-space: nowrap;
}

.site-switch-chevron { color: var(--ink-muted); transition: transform 0.14s ease; }
details.site-switch[open] .site-switch-chevron { transform: rotate(180deg); }

.site-menu {
  position: absolute; z-index: 30; top: calc(100% + 6px); left: 0;
  width: max(260px, 100%); max-width: min(380px, 92vw);
  padding: 10px; border: 1px solid var(--line); border-radius: 10px;
  background: var(--surface); box-shadow: 0 12px 34px rgba(0, 0, 0, 0.16);
}

.site-filter {
  width: 100%; padding: 7px 10px; font: inherit; font-size: 0.85rem;
  color: var(--ink); background: var(--surface-sunk);
  border: 1px solid var(--line); border-radius: 7px;
}
.site-filter:focus-visible { outline: 2px solid var(--accent-ink); outline-offset: 1px; }

/* Capped and scrolled rather than allowed to run the height of the screen. The cap is
   what keeps the panel a control instead of a page. */
.site-list {
  list-style: none; margin: 8px 0 0; padding: 0;
  max-height: min(52vh, 340px); overflow-y: auto;
}

.site-option {
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
  padding: 7px 9px; border-radius: 7px;
  text-decoration: none; color: var(--ink); font-size: 0.86rem; font-weight: 500;
}
.site-option:hover { background: var(--surface-sunk); }
.site-option:focus-visible { outline: 2px solid var(--accent-ink); outline-offset: -2px; }
.site-option.on { font-weight: 700; }
/* The name over the host, as one column, so the flex row above stays two-part. */
.site-option-text { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
.site-option-host { min-width: 0; overflow-wrap: anywhere; }
/* Truncated rather than wrapped: the host is the secondary line, and a hyphenless domain
   broken mid-word ("hatfieldborough.co / m") is harder to read than an elided one. */
.site-option-sub {
  font-size: 0.72rem; font-weight: 400; color: var(--ink-muted);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 100%;
}
.site-option-mark {
  flex: none; font-size: 0.66rem; font-weight: 700; letter-spacing: 0.07em;
  text-transform: uppercase; color: var(--teal);
}

.site-menu-none { margin: 10px 4px 4px; font-size: 0.83rem; color: var(--ink-muted); }

.scanned { font-size: 0.82rem; color: var(--ink-muted); display: flex; align-items: center; gap: 7px; }

/* The priority card's names became links when Layer 3 shipped. Underline on hover
   only: four underlined lines in a row reads as a list of links rather than as a list
   of problems. */
.rule-link { color: inherit; text-decoration: none; }
.rule-link:hover code, .rule-link:focus-visible code { text-decoration: underline; }

.finding-name a { color: inherit; text-decoration: none; }
.finding-name a:hover, .finding-name a:focus-visible { text-decoration: underline; }

/* The monthly report. The only screen with an audience beyond the person looking at it,
   which is why it prints: it is forwarded to a council, and "print to PDF" is how that
   actually happens. The build order's instruction was to reach for a stylesheet before a
   renderer, and a renderer is still not needed. */
.report-actions { margin: 0 0 18px; display: flex; flex-wrap: wrap; align-items: center; gap: 8px; }
.report-actions a.act { text-decoration: none; display: inline-block; }
.report-issue-form { display: inline; margin: 0; }

/* Which report this is. An issued one is the record of what a client was sent; a live one
   for a past month is today's reading of it, and the two can differ. Both say so above the
   report rather than in its footnotes, because the number is read first. */
.report-issued-note, .report-live-note {
  max-width: 62rem; margin: 0 0 16px; padding: 10px 14px; border-radius: 8px;
  border: 1px solid var(--line); background: var(--surface-sunk); line-height: 1.55;
}
.report-archive, .report-staff { max-width: 62rem; margin-bottom: 18px; }
.report-staff .report-actions { margin: 10px 0 0; }
.report-archive small.note { display: inline; margin-left: 6px; }
.report { max-width: 62rem; }

.report-head { display: flex; justify-content: space-between; align-items: flex-start; gap: 26px; padding-bottom: 18px; border-bottom: 2px solid var(--ink); }
.report-head h1 { margin: 0; font-size: 1.7rem; }
.report-sub { margin: 8px 0 0; color: var(--ink-muted); max-width: 46ch; line-height: 1.55; }
.report-meta { flex: none; text-align: right; font-size: 0.84rem; }
.report-meta > div { margin-bottom: 7px; }
.report-meta .k { display: block; font-size: 0.68rem; letter-spacing: 0.04em; text-transform: uppercase; color: var(--ink-muted); }
.report-meta .v { font-weight: 600; }

.report-score { display: flex; gap: 30px; align-items: flex-start; padding: 22px 0; border-bottom: 1px solid var(--line); }
.report-score-num { flex: none; text-align: center; }
.report-score-num b { font-size: 3.4rem; line-height: 1; font-variant-numeric: tabular-nums; }
.report-score-body { padding-top: 6px; }
.report-score-body p { margin: 0 0 8px; line-height: 1.6; max-width: 58ch; }
.report-score-body .report-caveat { font-size: 0.85rem; color: var(--ink-muted); }
.report-score-num .verdict-word { max-width: 11ch; margin-inline: auto; }
.report-delta strong { font-variant-numeric: tabular-nums; }

.report-section { padding: 22px 0; border-bottom: 1px solid var(--line); }
.report-section:last-child { border-bottom: 0; }
.report-section h2 { margin: 0 0 4px; font-size: 1.15rem; }
.report-section h3 { margin: 16px 0 6px; font-size: 0.92rem; }
.report-list { margin: 0; padding-left: 20px; }
.report-list li { margin-bottom: 7px; line-height: 1.55; }
.report-asof { margin: 16px 0 0; padding: 11px 14px; border-radius: 9px; background: var(--minor-wash); font-size: 0.86rem; line-height: 1.6; }
.report-warn { margin: 10px 0 0; padding: 11px 14px; border-radius: 9px; border: 1px solid var(--line); font-size: 0.86rem; line-height: 1.6; max-width: 62ch; }
.report-projection { margin: 14px 0 0; padding: 12px 14px; border-radius: 9px; background: var(--minor-wash); font-size: 0.88rem; line-height: 1.6; }
.report-method p { line-height: 1.65; max-width: 68ch; }

/* Paged. Everything that is navigation on screen is furniture on paper, and a heading
   stranded at the foot of a page is the commonest way a printed report looks careless. */
@media print {
  @page { margin: 16mm; }

  .rail, .topbar, .report-actions, .report-archive, .report-staff, .crumb, .site-switch, .rail-foot { display: none !important; }
  .shell, .main, .well { display: block !important; margin: 0 !important; padding: 0 !important; max-width: none !important; }

  body { background: #fff !important; color: #000 !important; font-size: 10.5pt; }
  .report { max-width: none; }

  a { color: #000 !important; text-decoration: none; }
  /* The reader has paper and cannot click. Rule documentation URLs are long and would
     wrap badly, so only in-report links spell themselves out — and none currently do. */

  .report-section { break-inside: auto; page-break-inside: auto; }
  .report-section h2, .report-section h3 { break-after: avoid; page-break-after: avoid; }
  .report-head, .report-score { break-inside: avoid; page-break-inside: avoid; }
  table { break-inside: avoid; page-break-inside: avoid; }
  tr, li { break-inside: avoid; page-break-inside: avoid; }

  .report-method { break-before: page; page-break-before: always; }

  .sev { border: 1px solid #999 !important; background: none !important; color: #000 !important; }
  .report-projection, .report-asof, .report-warn { border: 1px solid #999; background: none !important; }
  .table-scroll { overflow: visible !important; }
}

/* Layer 2 — the fix list. The same problems as the breakdown, arranged by the part of
   the site they live in, because "who do I hand this to" is the question somebody has
   when they sit down to do the work. */
.fix-group-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 18px; }
.fix-group-head h2 { margin: 0; display: flex; align-items: center; gap: 9px; flex-wrap: wrap; }
.fix-group-head .note { margin: 5px 0 0; max-width: 56ch; }
.fix-group-cost { text-align: right; flex: none; }
.fix-group-cost .k { font-size: 0.68rem; letter-spacing: 0.04em; text-transform: uppercase; color: var(--ink-muted); }
.fix-group-cost .v { font-size: 1.2rem; font-weight: 650; }

.fix-bar { height: 5px; border-radius: 999px; background: var(--minor-wash); margin: 14px 0 4px; overflow: hidden; }
.fix-bar span { display: block; height: 100%; background: var(--ink-muted); border-radius: 999px; }

.fix-rules { list-style: none; margin: 10px 0 0; padding: 0; }
.fix-rules li { display: flex; gap: 16px; align-items: flex-start; justify-content: space-between; padding: 11px 0; border-top: 1px solid var(--line); }
.fix-rule-body { min-width: 0; }
.fix-rule-name { margin: 0; font-weight: 550; }
.fix-rule-name a { color: inherit; text-decoration: none; }
.fix-rule-name a:hover, .fix-rule-name a:focus-visible { text-decoration: underline; }
.fix-rule-why { margin: 3px 0 0; font-size: 0.84rem; color: var(--ink-muted); max-width: 62ch; line-height: 1.5; }
.fix-rule-aside { margin: 0; flex: none; display: flex; align-items: center; gap: 10px; font-size: 0.8rem; color: var(--ink-muted); }
.fix-rule-cost { font-variant-numeric: tabular-nums; font-weight: 600; color: var(--ink); }

@media (max-width: 720px) {
  .fix-group-head { flex-direction: column; gap: 8px; }
  .fix-group-cost { text-align: left; }
  .fix-rules li { flex-direction: column; gap: 6px; }
  .fix-rule-aside { flex-wrap: wrap; }
}

/* Layer 3 — one problem's own page. The cause list is the screen: a rule with two
   hundred occurrences is usually a handful of elements, and naming them turns a number
   into a job of work. */
.crumb { margin: 0 0 14px; font-size: 0.84rem; }
.crumb a { color: var(--ink-muted); text-decoration: none; }
.crumb a:hover { color: var(--ink); text-decoration: underline; }

.item-head h1 { margin: 6px 0 0; font-size: 1.45rem; line-height: 1.3; }
.item-sev { display: flex; align-items: center; gap: 10px; margin: 0; }
.item-sev code { font-size: 0.76rem; color: var(--ink-muted); }
.item-why { margin: 10px 0 0; color: var(--ink-muted); max-width: 62ch; line-height: 1.55; }
.item-source { margin: 9px 0 0; font-size: 0.82rem; }

.facts { display: flex; flex-wrap: wrap; gap: 26px; margin-top: 18px; padding-top: 16px; border-top: 1px solid var(--line); }
.facts .k { font-size: 0.7rem; letter-spacing: 0.04em; text-transform: uppercase; color: var(--ink-muted); }
.facts .v { font-size: 1.05rem; font-weight: 600; margin-top: 3px; }

.insight { margin: 0 0 16px; padding: 11px 13px; border-radius: 9px; background: var(--minor-wash); font-size: 0.87rem; line-height: 1.55; }
.item-caveat { margin: 0 0 16px; }

.cause-list { list-style: none; margin: 0; padding: 0; display: grid; gap: 12px; counter-reset: cause; }
.cause { border: 1px solid var(--line); border-radius: 10px; overflow: hidden; }
.cause-head { display: flex; flex-wrap: wrap; align-items: center; gap: 9px; padding: 9px 13px; background: var(--minor-wash); }
.cause-head::before { counter-increment: cause; content: counter(cause); font-weight: 700; font-size: 0.8rem; color: var(--ink-muted); min-width: 1.1em; }
.cause-pages { font-weight: 600; font-size: 0.87rem; }
.cause-tag, .cause-engine { font-size: 0.72rem; padding: 2px 8px; border-radius: 999px; border: 1px solid var(--line); color: var(--ink-muted); background: var(--surface-sunk); }
.cause-engine { margin-left: auto; }
.cause-body { padding: 12px 13px; display: grid; gap: 9px; }

.kv { display: grid; grid-template-columns: 108px minmax(0, 1fr); gap: 12px; align-items: start; font-size: 0.84rem; }
.kv .k { color: var(--ink-muted); }
.kv .v { min-width: 0; overflow-wrap: anywhere; }
/* Markup is quoted as the engine recorded it, so it wraps rather than stretching the
   card. A horizontal scrollbar inside a card reads as a broken layout. */
.cause-markup { display: block; white-space: pre-wrap; line-height: 1.45; }
/* Engine failure summaries arrive with newlines in them ("Fix any of the following:"
   then an indented list). Preserved rather than collapsed — the shape is the message. */
.cause-message { white-space: pre-line; line-height: 1.5; }
/* The scanner's photograph of the element. Capped rather than shown at natural size: these
   are clips of a 1280px viewport and a full-width header would tower over the text that
   explains it. The border matters — a screenshot of a white page on a white card has no
   edge, and the reader cannot tell where the evidence stops and the product starts. */
.cause-shot { display: inline-block; line-height: 0; border: 1px solid var(--line); border-radius: 8px; overflow: hidden; background: var(--minor-wash); }
.cause-shot img { display: block; max-width: 100%; max-height: 220px; width: auto; height: auto; }
.cause-shot:hover { border-color: var(--ink-muted); }

@media (max-width: 640px) {
  .kv { grid-template-columns: 1fr; gap: 3px; }
  .facts { gap: 18px; }
}

/* The retired notice, above every site-scoped screen. Deliberately not a severity colour:
   a retired site is not a problem with the site, and painting it amber would put it in
   the same visual language as the findings underneath it. */
.retired-note {
  display: flex; gap: 12px; align-items: flex-start;
  padding: 12px 14px; margin: 0 0 18px;
  border: 1px solid var(--line); border-radius: 10px;
  background: var(--minor-wash);
}
.retired-note p { margin: 0; font-size: 0.84rem; line-height: 1.5; color: var(--ink-muted); }
.retired-note strong { color: var(--ink); font-weight: 600; }
.retired-note .sev { flex: none; margin-top: 1px; }
.dot-live { width: 7px; height: 7px; border-radius: 50%; background: var(--good); box-shadow: 0 0 0 3px var(--good-wash); }

/* Centred within .main, not within the viewport. Pinned left it left a wide gutter on
   the right of any large monitor; auto margins split the remaining space instead. The
   cap stays: prose past ~1080px stops being comfortable to read, and .note and .method
   hold their own tighter measures inside it. */
.well { max-width: 1080px; margin: 0 auto; padding: 28px 30px 80px; display: flex; flex-direction: column; gap: 24px; }

@media (max-width: 820px) {
  .shell { grid-template-columns: 1fr; }
  .rail {
    flex-direction: row; align-items: center; gap: 14px; padding: 12px 16px; flex-wrap: wrap;
    position: static; height: auto; overflow-y: visible;
  }
  .rail-group { flex-direction: row; align-items: center; flex-wrap: wrap; }
  .rail-label, .rail-foot { display: none; }
  .well { padding: 20px 16px 60px; }
  .topbar { padding: 12px 16px; }

  /* One column, and the divider becomes a horizontal rule between the two halves. */
  .verdict { grid-template-columns: 1fr; gap: 20px; padding: 20px 18px; }
  .rule { height: 1px; }
  .chips-block { padding: 16px 18px 20px; }
  .verdict-sub { max-width: none; }

  /* The right-hand figure moves under the problem rather than fighting it for width. */
  .finding-row { grid-template-columns: auto 1fr; }
  .finding-aside { grid-column: 2; text-align: left; margin-top: 6px; }
}

/* ---------- signed out ---------- */

.auth-body { display: grid; place-items: center; min-height: 100vh; padding: 24px; }
.auth-well { width: 100%; max-width: 380px; display: flex; flex-direction: column; gap: 20px; }
.auth-brand { display: flex; align-items: center; gap: 10px; justify-content: center; color: var(--ink); }

.auth-card {
  background: var(--surface); border: 1px solid var(--line);
  border-radius: 12px; padding: 28px 26px;
}
.auth-card h1 { font-family: var(--serif); font-size: 1.5rem; margin: 0 0 4px; }
.auth-sub { margin: 0 0 20px; color: var(--ink-muted); font-size: 0.88rem; }

.field { display: flex; flex-direction: column; gap: 5px; margin-bottom: 14px; }
.field label { font-size: 0.8rem; font-weight: 600; color: var(--ink-2); }
.field input {
  padding: 9px 11px; border-radius: 8px; font: inherit; font-size: 0.92rem;
  border: 1px solid var(--line); background: var(--plane); color: var(--ink);
}
.field input:focus-visible { outline: 2px solid var(--teal); outline-offset: 1px; border-color: var(--teal); }

.auth-submit {
  display: block; width: 100%; margin-top: 6px; padding: 10px 14px;
  border: 0; border-radius: 8px; cursor: pointer;
  background: var(--accent-ink); color: #fff;
  font: inherit; font-size: 0.92rem; font-weight: 600;
  text-align: center; text-decoration: none;
}
.auth-submit:hover { filter: brightness(1.08); }

/* One message for every kind of failure, so the box is always the same shape. */
.auth-errors:not(:empty) {
  margin: 0 0 16px; padding: 10px 12px; border-radius: 8px;
  background: var(--critical-wash); color: var(--critical-ink); font-size: 0.85rem;
}
.auth-errors ul { margin: 0; padding-left: 18px; }
.auth-errors ul:empty { display: none; }

.auth-foot { margin: 18px 0 0; color: var(--ink-muted); font-size: 0.78rem; line-height: 1.5; }

/* ---------- /Targets: the first screen that writes ---------- */

/* The outcome of the last press. A status region rather than a colour alone, because the
   difference between "queued" and "not queued, it already had one" is the sentence, and a
   green box with no words in it says neither. */
.banner {
  border: 1px solid var(--line); border-left-width: 3px; border-radius: 8px;
  padding: 11px 15px; font-size: 0.86rem; background: var(--surface);
}
.banner.good { border-left-color: var(--good-ink); }
.banner.bad { border-left-color: var(--critical-ink); }

.sev.on { color: var(--good-ink); background: var(--good-wash); }

.target-filter { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; }
.target-filter input {
  font: inherit; font-size: 0.84rem; padding: 6px 10px; min-width: 220px;
  border: 1px solid var(--line); border-radius: 7px;
  background: var(--surface); color: var(--ink);
}
.target-filter button {
  font: inherit; font-size: 0.82rem; font-weight: 600; cursor: pointer;
  padding: 6px 13px; border: 1px solid var(--line); border-radius: 7px;
  background: var(--surface-sunk); color: var(--ink);
}

.target-card table { font-size: 0.85rem; }
.target-card td { vertical-align: top; }
.target-scanner, .target-when {
  display: block; font-size: 0.74rem; color: var(--ink-muted);
}
.target-url { word-break: break-all; max-width: 26ch; }
.target-run { font-weight: 600; }

/* Two forms side by side in one cell, so each button posts to its own action. Inline-flex
   rather than a grid: the pair should stay together on a narrow table and wrap as a unit. */
.target-actions { white-space: nowrap; }
.target-actions form { display: inline-block; margin: 0 4px 0 0; }
.act {
  font: inherit; font-size: 0.79rem; font-weight: 600; cursor: pointer;
  padding: 5px 11px; border: 1px solid var(--accent-ink); border-radius: 7px;
  background: var(--surface); color: var(--accent-ink);
}
.act:hover:not(:disabled) { background: var(--surface-sunk); }

/* A disabled Scan now is the normal state for a target that already has work queued, not
   an error — so it recedes rather than shouting. The reason is in the row beside it. */
.act:disabled { opacity: 0.42; cursor: default; border-color: var(--line); color: var(--ink-muted); }
.act.subtle { border-color: var(--line); color: var(--ink-2); }
.act:focus-visible { outline: 2px solid var(--teal); outline-offset: 1px; }
.target-filter input:focus-visible,
.target-filter button:focus-visible { outline: 2px solid var(--teal); outline-offset: 1px; }

/* ---------- /Users ---------- */

.user-you {
  font-size: 0.7rem; font-weight: 650; letter-spacing: 0.06em; text-transform: uppercase;
  color: var(--accent-ink); border: 1px solid var(--line); border-radius: 999px; padding: 1px 7px;
}
.user-sites { font-size: 0.82rem; color: var(--ink-2); max-width: 34ch; }
.user-never { color: var(--ink-muted); font-style: italic; }
.user-failed, .user-note, .user-nomatch { display: block; font-size: 0.74rem; }
.user-failed { color: var(--critical-ink); }
.user-note { color: var(--ink-muted); }
.user-nomatch { color: var(--critical-ink); font-weight: 600; }

.user-form .card { margin-bottom: 14px; }
.field { margin-bottom: 16px; }
.field label, .check { display: block; font-size: 0.86rem; font-weight: 600; margin-bottom: 5px; }
.field input[type="text"], .field input[type="email"], .field select {
  font: inherit; font-size: 0.88rem; padding: 8px 10px; width: 100%; max-width: 420px;
  border: 1px solid var(--line); border-radius: 7px;
  background: var(--surface); color: var(--ink);
}
.field input:disabled, .field select:disabled { opacity: 0.55; }
.hint { font-size: 0.78rem; color: var(--ink-muted); margin: 5px 0 0; max-width: 62ch; }

/* A value shown on a form that cannot be changed. Deliberately not a disabled input: a
   greyed-out field reads as "you may not edit this right now", and this one is never
   editable by anyone. */
.static-field { font-size: 0.88rem; font-weight: 600; margin: 0 0 2px; }
.check { font-weight: 500; display: flex; align-items: flex-start; gap: 8px; }
.check input { margin-top: 3px; flex: none; }

/* One column per grant on a wide screen, wrapping to one long list on a narrow one. Thirty
   sites is a lot of ticking either way, which is why the filter on /Targets exists and why
   this list is sorted with the retired ones last. */
/* The picker's own header: filter, count, clear. The count is not decoration — with the
   list scrolling, it is the only thing that says how many grants this account has without
   scrolling back to the top of it. */
.grants-head {
  display: flex; flex-wrap: wrap; align-items: center; gap: 8px 12px;
  padding: 4px 24px 10px;
}
.grants-head input[type="search"] {
  font: inherit; font-size: 0.84rem; padding: 6px 10px; min-width: 220px;
  border: 1px solid var(--line); border-radius: 7px;
  background: var(--surface); color: var(--ink);
}
.grants-head input:focus-visible { outline: 2px solid var(--teal); outline-offset: 1px; }
.grants-count { font-size: 0.8rem; color: var(--ink-muted); }
.grants-count b { color: var(--ink); font-variant-numeric: tabular-nums; }

/* Capped and scrollable rather than thirty-five rows pushing the save button off the page.
   The granted ones are sorted to the top server-side, so the part that matters is the part
   visible without scrolling. */
.site-grants {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
  gap: 8px 18px; padding: 4px 24px 12px;
  max-height: 340px; overflow-y: auto;
}

/* A ticked grant is tinted, so the ones this account holds are findable at a glance in a
   long list rather than by hunting for a ticked box. */
.site-grants .check:has(input:checked) {
  background: var(--good-wash); border-radius: 6px;
  margin: -3px -6px; padding: 3px 6px;
}
.site-grants .check { font-size: 0.84rem; margin-bottom: 0; }
.grant-host { display: block; font-size: 0.74rem; color: var(--ink-muted); }
.user-form-actions { display: flex; align-items: center; gap: 12px; }

/* The one value on the site that must be readable character by character, because somebody
   is about to retype it. Monospace, spaced out, and large. */
.credential-value {
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 1.45rem; letter-spacing: 0.08em; font-weight: 600;
  background: var(--surface-sunk); border: 1px solid var(--line); border-radius: 8px;
  padding: 14px 18px; margin: 0 24px 14px; user-select: all; overflow-wrap: anywhere;
}
.credential h2 { margin-bottom: 10px; }
.credential-actions { padding: 0 24px 4px; }

/* ---------- /Runs: the batch swimlane ---------- */

/* Chart-only tokens, declared here rather than up in the main :root block so the whole
   screen arrives in one piece. Same two-state pattern as the palette above: bare :root for
   light, and the guarded media query for dark. The wait colour is deliberately neither of
   the two scanner hues — contention is a warning about the machine, not a third kind of
   work, and giving it a scanner-like colour made it read as one. */
:root {
  --c-page: var(--accent-ink);
  --c-pdf: var(--teal);
  --c-wait: #d9a23a;
  --c-fail: var(--critical-ink);
  --c-slot: #b7c1c9;
  --c-band: rgba(35, 85, 127, 0.13);
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --c-wait: #d8a63d;
    --c-slot: #49535b;
    --c-band: rgba(140, 189, 233, 0.15);
  }
}

/* The counter strip. One-pixel gaps over a --line ground rather than borders per cell,
   so the dividers stay hairlines at any zoom. */
.stats {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(132px, 1fr));
  gap: 1px; background: var(--line); border: 1px solid var(--line);
  border-radius: 10px; overflow: hidden;
}
.stat { background: var(--surface); padding: 14px 16px; display: flex; flex-direction: column; gap: 2px; }
.stat .k { font-size: 0.68rem; font-weight: 650; letter-spacing: 0.1em; text-transform: uppercase; color: var(--ink-muted); }
.stat .v { font-size: 1.42rem; font-weight: 620; font-variant-numeric: tabular-nums; line-height: 1.1; }
.stat .n { font-size: 0.76rem; color: var(--ink-muted); }
.stat .v.bad { color: var(--critical-ink); }
.stat .v.good { color: var(--good-ink); }
.ink-bad { color: var(--critical-ink); }

/* One select and two arrows, not one chip per night. A control whose size grows by one
   every morning stops being a control; this one is the same size at ten batches and at a
   thousand, and the arrows are what the chips were actually used for. No JavaScript --
   it is a GET form, which is also why it keeps working with the app shipping none. */
.batch-nav { display: flex; flex-wrap: wrap; align-items: center; gap: 8px 10px; }
.batch-nav form { display: flex; gap: 6px; align-items: center; margin: 0; }
.batch-nav select {
  font: inherit; font-size: 0.84rem; padding: 6px 9px; max-width: 100%;
  border: 1px solid var(--line); border-radius: 7px;
  background: var(--surface); color: var(--ink);
}
.batch-nav button {
  font: inherit; font-size: 0.82rem; font-weight: 600; cursor: pointer;
  padding: 6px 13px; border: 1px solid var(--line); border-radius: 7px;
  background: var(--surface-sunk); color: var(--ink);
}
.batch-nav button:hover { border-color: var(--accent-ink); }
.batch-nav .step {
  text-decoration: none; font-size: 0.82rem; color: var(--accent-ink);
  padding: 6px 10px; border-radius: 7px; border: 1px solid transparent;
}
.batch-nav .step:hover { border-color: var(--line); background: var(--surface); }
.batch-nav .step.disabled { color: var(--ink-muted); pointer-events: none; }
.batch-nav .step.latest { color: var(--ink-2); }
.batch-nav select:focus-visible,
.batch-nav button:focus-visible,
.batch-nav .step:focus-visible { outline: 2px solid var(--teal); outline-offset: 1px; }
.note-inline { font-size: 0.8rem; color: var(--ink-muted); }

/* The live strip. A table rather than a grid of spans, because it is tabular — eight
   facts about each of a handful of runs — and a table gives the header association for
   free. It reuses .meter from the score screens rather than growing a second progress
   component; the only thing added is which pool's colour fills it. */
.live-card .card-head p a { color: var(--accent-ink); }
.live-card.alarm { border-color: var(--critical-ink); }
.live-none { padding: 0 24px 4px; }
table.live { font-size: 0.84rem; }
table.live td { vertical-align: top; }
.live-site { font-weight: 600; }

/* Where a run came from, when that is not the batch below. Drawn as a link because the
   answer to "what else was in that enqueue" is one click away and nothing else on the
   screen offers it. */
.live-origin { display: block; font-size: 0.75rem; color: var(--accent-ink); }
.live-elapsed { display: block; font-size: 0.75rem; color: var(--ink-muted); }
.live-quiet { display: block; font-size: 0.75rem; color: var(--critical-ink); }
.live-progress .meter { display: inline-block; width: 84px; vertical-align: middle; }
.live-progress .meter i.page { background: var(--c-page); }
.live-progress .meter i.pdf { background: var(--c-pdf); }
.live-progress .pct {
  display: inline-block; margin-left: 6px; font-weight: 600;
  font-variant-numeric: tabular-nums;
}
.live-progress .of { display: block; font-size: 0.78rem; color: var(--ink-muted); }

/* The estimate is the one figure here that has not happened yet, so it is set apart from
   the measurements beside it rather than typeset identically to them. */
.live-eta { font-size: 0.79rem; color: var(--ink-2); font-style: italic; }
.live-worker { display: block; font-size: 0.78rem; color: var(--ink-muted); }
.lease-ok { font-size: 0.75rem; color: var(--ink-muted); }
.lease-bad { font-size: 0.75rem; font-weight: 650; color: var(--critical-ink); }
.live-queued, .live-foot { padding-inline: 24px; }

/* What just ended. A list rather than a second table: these rows are read one at a time,
   looking for one name, and a table's alignment buys nothing when every row is a sentence. */
.finished { padding: 4px 24px 2px; border-top: 1px solid var(--line); margin-top: 4px; }
.finished h3 {
  font-size: 0.72rem; font-weight: 650; letter-spacing: 0.09em; text-transform: uppercase;
  color: var(--ink-muted); margin: 12px 0 8px;
}
.finished ul { list-style: none; margin: 0; padding: 0; }
.finished li {
  display: flex; flex-wrap: wrap; align-items: baseline; gap: 4px 10px;
  padding: 6px 0; border-bottom: 1px solid var(--line); font-size: 0.83rem;
}
.finished li:last-child { border-bottom: 0; }
.outcome-ok, .outcome-bad {
  font-size: 0.7rem; font-weight: 650; letter-spacing: 0.06em; text-transform: uppercase;
  padding: 2px 7px; border-radius: 999px;
}
.outcome-ok { color: var(--good-ink); background: var(--good-wash); }
.outcome-bad { color: var(--critical-ink); background: var(--critical-wash); }
.finished-when { color: var(--ink-muted); font-variant-numeric: tabular-nums; }
.finished-work { color: var(--ink-2); }
.finished-tag {
  font-size: 0.72rem; color: var(--ink-muted);
  border: 1px solid var(--line); border-radius: 999px; padding: 1px 8px;
}
.finished-error { flex-basis: 100%; font-size: 0.78rem; color: var(--critical-ink); }

.legend { display: flex; flex-wrap: wrap; gap: 6px 20px; padding: 2px 24px 6px; font-size: 0.79rem; color: var(--ink-2); }
.legend span { display: inline-flex; align-items: center; gap: 7px; }
.legend .key { display: inline-block; border-radius: 2px; flex: none; }
.legend .key.slot { width: 18px; height: 2px; background: var(--c-slot); }
.legend .key.wait { width: 18px; height: 7px; background: var(--c-wait); }
.legend .key.page { width: 18px; height: 10px; background: var(--c-page); }
.legend .key.pdf  { width: 18px; height: 10px; background: var(--c-pdf); }
.legend .key.fail { width: 18px; height: 10px; background: var(--c-fail); }
.legend .key.dead {
  width: 18px; height: 10px; border: 1px solid var(--c-slot);
  background: repeating-linear-gradient(45deg, var(--surface) 0 2px, var(--c-slot) 2px 4px);
}

/* min-width rather than a breakpoint: sixty rows of site labels do not compress, so the
   chart scrolls sideways inside its own container and the page body never does. */
/* The swimlane is HTML on a grid, not one scaled SVG.

   The SVG version needed min-width: 1150px and scrolled sideways below it, because a
   viewBox scales its text along with everything else: the site labels were only legible
   while the chart was wide. That is the wrong way round -- the labels have a size they need
   to be and the bars do not. So the rows are a grid, the bars are positioned by percentage
   inside their track, and the type is ordinary page type. It fits any width and never
   scrolls sideways.

   The band is still SVG, because a step area is a path and nothing else draws one. It
   stretches with preserveAspectRatio="none" against a fixed pixel height, which is safe
   only because it carries no text, no radii and no pattern fill -- its two strokes are
   pinned with non-scaling-stroke below. Do not put text in it. */
.swimlane {
  --label-w: 190px;
  --dur-w: 108px;
  --row-h: 20px;
  --band-h: 96px;
  padding: 2px 24px 14px;
}

.lane-row {
  display: grid;
  grid-template-columns: var(--label-w) minmax(0, 1fr) var(--dur-w);
  align-items: center;
  column-gap: 10px;
  height: var(--row-h);
}

/* Flex rather than plain text, so a run can carry a tag without the site name losing its
   ellipsis. justify-content keeps the bare-text labels in the lane heads right-aligned,
   which text-align no longer does once this is a flex container. */
.lane-label {
  display: flex; align-items: center; justify-content: flex-end; gap: 6px;
  text-align: right; font-size: 0.78rem; color: var(--ink-2);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.lane-label .name { min-width: 0; overflow: hidden; text-overflow: ellipsis; }

/* "This run was not a first attempt." Left of the site name deliberately: the name stays
   against its own bar, which is what the eye follows across the row. */
.lane-flag {
  flex: none; font-size: 0.62rem; font-weight: 650; line-height: 1.55;
  color: var(--ink-muted); background: var(--surface);
  border: 1px solid var(--line); border-radius: 999px; padding: 0 6px;
  white-space: nowrap;
}
.chart-foot .lane-flag { font-size: 0.68rem; }

/* Two facts in one cell: what the run took, and what one page of it cost. The second is
   the one a bar cannot carry -- length is total time, so contended and large look alike. */
.lane-dur {
  font-family: var(--mono); font-size: 0.72rem; color: var(--ink-muted);
  font-variant-numeric: tabular-nums; white-space: nowrap;
  display: flex; gap: 6px; align-items: baseline; justify-content: flex-end;
}
.lane-dur .took { color: var(--ink-2); }
.lane-dur .rate { color: var(--ink-muted); font-size: 0.94em; }
.lane-dur.over { color: var(--ink-2); font-weight: 650; }
.lane-dur .arrow { color: var(--ink-muted); margin-right: 1px; }

/* Every mark on a row is absolutely placed by percentage, so a row is a track and the
   track is the clock. Grid lines are emitted first, so the bars paint over them. */
.track { position: relative; height: 100%; min-width: 0; }
.track > span { position: absolute; top: 50%; transform: translateY(-50%); }
.track .grid { top: 0; bottom: 0; transform: none; width: 1px; height: 100%; background: var(--line); }
.track .slot { height: 3px; border-radius: 2px; background: var(--c-slot); }
.track .wait { height: 10px; border-radius: 2px; background: var(--c-wait); }
.track .bar { height: 14px; border-radius: 3px; }
.track .bar.page { background: var(--c-page); }
.track .bar.pdf { background: var(--c-pdf); }
.track .bar.failed { background: var(--c-fail); }
.track .bar.running { background: var(--teal); opacity: 0.75; }

/* The hatch is a gradient rather than an SVG pattern now, which also means it cannot be
   distorted by a stretched viewBox. Its ground is painted so the row stripe underneath
   does not show through and read as a third state. */
.track .dead {
  height: 14px; border-radius: 3px; border: 1px solid var(--c-slot);
  background: repeating-linear-gradient(45deg,
    var(--surface) 0 2.5px, var(--c-slot) 2.5px 5px);
}
.track .dead.failed { border-color: var(--c-fail); }

.lane-rows .run-row:nth-child(odd) { background: var(--surface-sunk); }
.run-row:hover { background: var(--minor-wash); }

.lane-head { height: 28px; align-items: end; }
.lane-head .lane-label {
  font-size: 0.72rem; font-weight: 680; letter-spacing: 0.06em; color: var(--ink);
}
.lane-head .track { border-bottom: 1px solid var(--line); height: 1px; }

.axis-row { height: 20px; align-items: end; }
.axis-row .track { overflow: visible; }
.axis-row .tick {
  transform: translateX(-50%); top: auto; bottom: 0;
  font-size: 0.7rem; color: var(--ink-muted); font-variant-numeric: tabular-nums;
}

.band-row { height: var(--band-h); }
.band-row .track { height: 100%; }
.band-scale { position: relative; height: 100%; overflow: visible; }
.band-scale .band-title {
  position: absolute; right: 0; top: 0;
  font-size: 0.68rem; letter-spacing: 0.05em; color: var(--ink-muted);
}
.band-scale .band-tick {
  position: absolute; right: 0; transform: translateY(-50%);
  font-size: 0.68rem; color: var(--ink-muted); font-variant-numeric: tabular-nums;
}

svg.band { position: absolute; inset: 0; width: 100%; height: 100%; display: block; }
svg.band .band-page { fill: var(--c-band); }
svg.band .band-pdf { fill: var(--c-pdf); opacity: 0.42; }
/* Pinned, because the viewBox stretches horizontally and an unpinned stroke would stretch
   with it -- a dashed line whose dashes are wider than they are tall. */
svg.band .band-held {
  fill: none; stroke: var(--ink-muted); stroke-width: 1; stroke-dasharray: 3 3;
  vector-effect: non-scaling-stroke;
}
svg.band .core-rule {
  stroke: var(--critical-ink); stroke-width: 1; stroke-dasharray: 5 3; opacity: 0.8;
  vector-effect: non-scaling-stroke;
}
.band-row .lane-dur { position: relative; height: 100%; }
.band-row .core-label {
  position: absolute; left: 0; transform: translateY(-50%);
  font-size: 0.68rem; font-weight: 650; color: var(--critical-ink); white-space: nowrap;
}

/* Below this the label column is more than a third of the width and the bars stop being
   readable as bars. Shrink the furniture rather than introducing a scrollbar. */
@media (max-width: 720px) {
  .swimlane { --label-w: 110px; --dur-w: 88px; padding-inline: 12px; }
  .lane-label { font-size: 0.72rem; }
  .lane-dur { font-size: 0.66rem; }
}

/* ---------------------------------------------------------------------------
   Documents — the PDF section's first screen.

   The composition bar and the four states are the only new vocabulary here; the
   clause tables, severity chips and cards are the product's own. Colours reuse
   the severity washes deliberately, so "never made accessible" reads at the same
   weight as a critical finding without inventing a second palette.
   --------------------------------------------------------------------------- */
.doc-bar {
  display: flex; height: 28px; margin: 14px 0 0;
  border: 1px solid var(--line); border-radius: 7px; overflow: hidden;
}
.doc-bar.mini { height: 14px; margin: 0; min-width: 90px; }
.doc-bar .seg { min-width: 2px; }
.doc-bar .seg.never    { background: var(--critical-ink); }
.doc-bar .seg.problems { background: var(--serious-ink); }
.doc-bar .seg.clean    { background: var(--good); }
/* Solid ink rather than a severity colour: a link that leads nowhere is not a severity,
   and it has to stay distinct from .unread beside it at the 2px minimum width. */
.doc-bar .seg.deadlink { background: var(--ink); }
.doc-bar .seg.unread   { background: var(--ink-muted); }

/* Ours, not the site's. Hatched rather than a fifth flat colour: it reads as "no data
   here" instead of as a fifth kind of document, and it stays distinguishable from
   .unread beside it at the 2px minimum width these segments can collapse to. */
.doc-bar .seg.notchecked {
  background: repeating-linear-gradient(
    135deg,
    var(--ink-muted) 0 2px,
    var(--surface) 2px 4px);
}

.doc-key {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
  gap: 14px; margin-top: 16px;
}
.doc-state { border-left: 3px solid var(--line); padding-left: 11px; }
.doc-state.never    { border-left-color: var(--critical-ink); }
.doc-state.problems { border-left-color: var(--serious-ink); }
.doc-state.clean    { border-left-color: var(--good); }
.doc-state.deadlink { border-left-color: var(--ink); }
.doc-state.unread   { border-left-color: var(--ink-muted); }
.doc-state.notchecked {
  border-left-color: var(--ink-muted);
  border-left-style: dashed;
}
.doc-state .v { font-size: 1.3rem; font-weight: 680; font-variant-numeric: tabular-nums; }
.doc-state .pct { font-size: 0.82rem; font-weight: 600; color: var(--ink-muted); margin-left: 5px; }
.doc-state .k { display: block; font-size: 0.85rem; font-weight: 650; margin-top: 1px; }
.doc-state .d { display: block; font-size: 0.79rem; color: var(--ink-muted); line-height: 1.45; margin-top: 3px; }

.doc-group { border-top: 1px solid var(--line-soft); }
.doc-group:first-of-type { border-top: 1px solid var(--line); }
.doc-group > summary {
  display: grid; grid-template-columns: 1fr auto; gap: 8px 16px;
  align-items: center; padding: 13px 4px; cursor: pointer; list-style: none;
}
.doc-group > summary::-webkit-details-marker { display: none; }
.doc-group > summary:hover { background: var(--surface-sunk); }
.doc-group .g-name { grid-column: 1; font-weight: 650; font-size: 0.94rem; }
.doc-group .g-why {
  grid-column: 1; font-size: 0.8rem; color: var(--ink-muted);
  line-height: 1.45; max-width: 68ch;
}
.doc-group .g-n { grid-column: 2; grid-row: 1 / span 2; text-align: right; }
.doc-group .g-n b { font-size: 1.05rem; font-variant-numeric: tabular-nums; }
.doc-group .g-n small { display: block; font-size: 0.73rem; color: var(--ink-muted); }

.doc-clauses, .doc-ages { width: 100%; border-collapse: collapse; font-size: 0.85rem; }
.doc-clauses { margin: 0 0 14px; }
.doc-ages { margin-top: 12px; }
.doc-clauses th, .doc-ages th {
  text-align: left; font-size: 0.7rem; letter-spacing: 0.07em; text-transform: uppercase;
  color: var(--ink-muted); font-weight: 700; padding: 7px 10px;
  border-bottom: 1px solid var(--line);
}
.doc-clauses td, .doc-ages td {
  padding: 9px 10px; border-bottom: 1px solid var(--line-soft); vertical-align: top;
}
.doc-clauses tr:last-child td, .doc-ages tr:last-child td { border-bottom: none; }
.doc-clauses .num, .doc-ages .num { text-align: right; font-variant-numeric: tabular-nums; white-space: nowrap; }
.doc-ages td small { display: block; font-size: 0.74rem; color: var(--ink-muted); font-weight: 400; }

@media (max-width: 720px) {
  .doc-ages th:nth-child(3), .doc-ages td:nth-child(3) { display: none; }
}

/* ---------------------------------------------------------------------------
   The document list. Filters are tabs over one query; the pills reuse the
   composition bar's colours so a state means the same thing on both screens.
   --------------------------------------------------------------------------- */
.doc-filters { display: flex; flex-wrap: wrap; gap: 6px; margin: 0 0 16px; }
.doc-filter {
  display: flex; align-items: baseline; gap: 7px;
  padding: 6px 11px; border: 1px solid var(--line); border-radius: 6px;
  background: var(--surface); color: var(--ink); text-decoration: none;
  font-size: 0.85rem;
}
.doc-filter:hover { border-color: var(--line-strong); }
.doc-filter.on { border-color: var(--ink); box-shadow: inset 0 -2px 0 var(--ink); font-weight: 650; }
.doc-filter .n { font-variant-numeric: tabular-nums; color: var(--ink-muted); font-size: 0.8rem; }
.doc-filter.on .n { color: inherit; }

.doc-list { width: 100%; border-collapse: collapse; }
.doc-list th, .doc-list td { text-align: left; vertical-align: top; padding: 9px 12px 9px 0; }
.doc-list tbody tr + tr { border-top: 1px solid var(--line); }
.doc-list small { display: block; font-size: 0.78rem; color: var(--ink-muted); margin-top: 2px; }
.doc-list small.why { max-width: 34rem; line-height: 1.45; }
.doc-list .section, .doc-list .unknown { color: var(--ink-muted); font-size: 0.88rem; }
/* The linking page is where a dead link is fixed, so it has to read as something to click. */
.doc-list a.section { text-decoration: underline; text-underline-offset: 2px; }
.doc-list .more-pages { display: block; font-size: 0.8rem; margin-top: 2px; }
.doc-list td.date { white-space: nowrap; font-variant-numeric: tabular-nums; }
.doc-list th.num, .doc-list td.num { text-align: right; font-variant-numeric: tabular-nums; white-space: nowrap; }
.linked-from li { margin: 6px 0; }
.linked-from small { display: block; color: var(--ink-muted); }

.doc-pill {
  display: inline-block; white-space: nowrap;
  font-size: 0.74rem; font-weight: 650; padding: 2px 8px; border-radius: 999px;
  border: 1px solid currentColor;
}
.doc-pill.never    { color: var(--critical-ink); }
.doc-pill.problems { color: var(--serious-ink); }
.doc-pill.clean    { color: var(--good-ink); }
.doc-pill.deadlink { color: var(--ink); }
.doc-pill.unread   { color: var(--ink-muted); }
.doc-pill.notchecked { color: var(--ink-muted); border-style: dashed; }

.doc-state-link { display: inline-block; margin-top: 6px; font-size: 0.8rem; }
.empty { color: var(--ink-muted); }

/* --- one document ------------------------------------------------------- */
.crumb { margin: 0 0 10px; font-size: 0.85rem; }
.doc-title { margin: 0 0 6px; }
.doc-meta { margin: 0 0 20px; color: var(--ink-muted); font-size: 0.9rem; }
.doc-meta .sep { margin: 0 7px; opacity: 0.6; }
.doc-meta .kind { text-transform: uppercase; letter-spacing: 0.06em; font-size: 0.78rem; }
.engine-said { display: block; margin-top: 5px; padding: 8px 11px; line-height: 1.5; }

.clause-group .consequence { margin: 0 0 14px; color: var(--ink-muted); max-width: 46rem; }
.clauses { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 14px; }
.clause { border-left: 3px solid var(--line); padding-left: 12px; }
.clause-head { display: flex; flex-wrap: wrap; align-items: baseline; gap: 10px; margin-bottom: 5px; }
.clause-head .ref { font-size: 0.82rem; font-weight: 600; }
.clause-head .count { font-size: 0.8rem; color: var(--ink-muted); font-variant-numeric: tabular-nums; }

/* The standard's own sentence. Quoted, never paraphrased -- what a client reads
   here is what an auditor reads. */
.requirement {
  margin: 0; padding: 0; border: 0;
  font-size: 0.92rem; line-height: 1.55; max-width: 48rem;
  color: var(--ink);
}
.requirement::before { content: "“"; }
.requirement::after  { content: "”"; }

/* --- the library ------------------------------------------------------- */
.doc-list.sections td { padding-right: 20px; }
.doc-list.sections .num { text-align: right; font-variant-numeric: tabular-nums; white-space: nowrap; }
.doc-list.sections .num small { color: var(--ink-muted); }
.doc-list .bodies { color: var(--ink-muted); }
.doc-list.sections .doc-bar.mini { min-width: 140px; }
.scoped-to { margin: -6px 0 16px; color: var(--ink-muted); }
/* The two ways out of the summary. A row of their own, because as two muted lines at the
   foot of the composition card nobody found them. */
.doc-jump {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
  gap: 12px; margin: 18px 0;
}
.doc-jump a {
  display: block; padding: 14px 16px; text-decoration: none;
  background: var(--surface); border: 1px solid var(--line); border-radius: 8px;
  color: var(--ink);
}
.doc-jump a:hover { border-color: var(--ink-muted); }
.doc-jump a strong { display: block; }
.doc-jump a strong::after { content: " →"; }
.doc-jump a span { display: block; margin-top: 3px; font-size: 0.83rem; color: var(--ink-muted); }

/* The PDF overview's What changed reuses the page overview's .changes list; its heading is a
   card h2 rather than the overview's h3, so it keeps the card's size. */
#changed .changed-head h2 { margin: 0; }
.change-names { margin-top: 3px; font-size: 0.83rem; }
.change-where { display: block; color: var(--ink-muted); font-size: 0.83rem; margin-top: 2px; }
.change-names summary { cursor: pointer; color: var(--ink-muted); }
.change-names ul { margin: 6px 0 2px; }
.change-names small { color: var(--ink-muted); margin-left: 6px; }

/* What to do next: numbered, because the order is the advice. */
.next-steps { margin: 0; padding-left: 1.3rem; display: flex; flex-direction: column; gap: 10px; max-width: 70ch; }
.next-steps li { line-height: 1.5; }
.next-steps li::marker { font-weight: 650; }

/* The per-kind counts above the office-file list. */
.kind-count { margin-right: 14px; white-space: nowrap; }
.doc-list.sections .usage small,
.doc-list.sections .quiet small { display: block; color: var(--ink-muted); font-size: 0.75rem; }
.doc-list.sections .usage .silent,
.doc-list.sections .quiet .silent { color: var(--ink-muted); font-weight: 600; }
/* The static second line under a heading that is not itself a second sort link. */
.doc-list.sections th small.sub { display: block; font-weight: 400; font-size: 0.76rem; color: var(--ink-muted); margin-top: 2px; }
.doc-list.sections th a.sort { color: inherit; text-decoration: none; border-bottom: 1px dotted var(--line-strong); }
.doc-list.sections th a.sort:hover { border-bottom-style: solid; }
.doc-list.sections th a.sort.on { font-weight: 700; border-bottom: 2px solid var(--ink); }
.doc-list.sections th a.sort.sub { display: block; font-weight: 400; font-size: 0.76rem; color: var(--ink-muted); margin-top: 2px; }
.doc-list.sections th a.sort.sub.on { color: var(--ink); font-weight: 650; }
.sort-note { color: var(--ink-muted); }

/* Content health's tables, one shape for every check since 2026-09-25:
   Page | Status or Size | Linked page, Image or Last seen | Visits.

   The address columns wrap anywhere and share the width. An address has no spaces, so without
   this the browser sizes the column to the longest unbroken stretch in it and the table grows
   past its card -- it did exactly that on 2026-09-23 with a 676-character Proofpoint rewrite
   on lowergwynedd.org. */
.health-list .health-url { width: 38%; overflow-wrap: anywhere; }

/* The short columns take only what they need. */
.health-list .health-status,
.health-list .health-date { white-space: nowrap; width: 1%; }

/* Centred, not right-aligned. These values carry their unit, and the units are different
   widths, so flush-right aligns "MB" against "KB" and leaves the digits ragged. Centred in a
   narrow column reads as a column of magnitudes. Tabular figures so 8.5 and 19.2 at least
   occupy the same width. The visit count is the same kind of number and gets the same rule. */
.health-list .health-size,
.health-list .health-visits {
  text-align: center;
  white-space: nowrap;
  width: 1%;
  padding-left: 18px; padding-right: 18px;
  font-variant-numeric: tabular-nums;
}
.health-list .health-quiet {
  display: block; font-size: 0.76rem; color: var(--ink-muted);
}

/* ---------- a published-page check (/Alerts/Check) ---------- */
.check-head h1 { margin: 4px 0 2px; font-size: 1.45rem; line-height: 1.3; }
.check-head .eyebrow { margin: 0; font-size: 0.74rem; letter-spacing: 0.05em; text-transform: uppercase; color: var(--ink-muted); }
.check-address { margin: 0 0 8px; font-size: 0.86rem; overflow-wrap: anywhere; }
.check-outcome { margin: 10px 0 4px; font-weight: 600; }
.check-history { margin: 6px 0 0; padding-left: 20px; display: grid; gap: 6px; font-size: 0.88rem; }
.check-history .current { font-weight: 650; }
.check-history-when { display: inline-block; min-width: 13ch; color: var(--ink-muted); font-variant-numeric: tabular-nums; }
