/* darkapp stylesheet.
 *
 * Every value in :root is specified in docs/DESIGN.md, and tests/test_design_tokens.py fails if
 * the two disagree in either direction. Below :root there are no hex colours, no font sizes that
 * are not scale tokens, and no spacing literals -- also enforced. If a value is needed and is not
 * a token, the token set is what should change.
 *
 * The governing idea is "a measurement instrument, not a marketing page". Density is a feature.
 * Colour is reserved for severity and appears nowhere else, so the chrome is monochrome petrol
 * throughout. Hairlines instead of shadows. No gradients.
 *
 * Dark is the default ground (ADR 0011). The light theme is derived: every literal for both themes
 * lives in the single :root block below, and the theme blocks that follow it contain nothing but
 * var() aliases. That is what keeps "no hex outside :root" true of a two-theme stylesheet, and it
 * is why there is exactly one :root -- the token parser collects from every :root in the file and
 * lets the last one win, so a second block would silently redocument the light values as the
 * defaults.
 */

/* --- Faces --------------------------------------------------------------
 *
 * Self-hosted, per docs/DESIGN.md 3.1. They were specified from the first design commit and
 * never actually loaded: there was no app/static/fonts/, so every page rendered in system
 * fallbacks. Light-on-dark text blooms and reads optically heavier than the same text
 * dark-on-light, so every ink level and hairline weight in :root is a judgement about optical
 * weight -- and judging that against a face we do not ship would mean redoing it the day the
 * real ones landed.
 *
 * Static instances, one file per weight, not variable fonts. Section 3.3 allows two weights and
 * no more; a variable file would need a weight range here and would quietly make a third
 * available. A static file cannot. Provenance and byte sizes are in
 * app/static/fonts/MANIFEST.json; all three families are OFL 1.1.
 *
 * No build step is involved -- these are fetched files, checked in, exactly as the vendored
 * htmx is (ADR 0003 still holds).
 */

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

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

@font-face {
  font-family: "Public Sans";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("../fonts/public-sans-400-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
  font-family: "Public Sans";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("../fonts/public-sans-400-latin-ext.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

@font-face {
  font-family: "Public Sans";
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url("../fonts/public-sans-600-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
  font-family: "Public Sans";
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url("../fonts/public-sans-600-latin-ext.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

@font-face {
  font-family: "Source Serif 4";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("../fonts/source-serif-4-400-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
  font-family: "Source Serif 4";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("../fonts/source-serif-4-400-latin-ext.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

@font-face {
  font-family: "Source Serif 4";
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url("../fonts/source-serif-4-600-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
  font-family: "Source Serif 4";
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url("../fonts/source-serif-4-600-latin-ext.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
:root {
  /* --- Surfaces and ink. Dark is the default, so the unsuffixed name is the dark value. --- */
  --surface: #0c1a21;
  --surface-sunken: #060e12;
  --card: #142a33;
  --ink: #e9f0f2;
  /* Not pure white: at --text-md on a near-black ground it haloes. Pure white exists once in this
   * system, as --overlay-light, and it is only ever drawn on a screenshot. */
  --ink-strong: #f7fbfb;
  --ink-muted: #9db2ba;
  --hairline: #274753;
  --hairline-strong: #3b6472;
  --scrim: rgba(3, 8, 11, 0.86);

  /* --- The one functional colour ramp. Severity only, never decoration. ---
   *
   * Re-derived for a dark ground. The light ramp inverts here -- its high measures 2.10:1 on this
   * surface and its low 8.54:1, so the most severe finding would be the least visible. These five
   * clear AA on the worst ground (--card) with a floor of 4.91:1.
   *
   * What ascends with severity is chroma, not luminance: the red end of the hue circle cannot
   * reach a high luminance without becoming pink. Weight is carried where it always was -- the
   * chip bar's height and width, and the written label. */
  --sev-info: #85979d;
  --sev-low: #d2b263;
  --sev-moderate: #ef9231;
  --sev-elevated: #ff7a3d;
  --sev-high: #ff6a5e;

  /* On a light ground three ramp values are too pale to serve as text or a thin border, which is
   * why these counterparts exist. On a dark ground the ramp is already text-safe, so they alias --
   * the token stays so no component has to know which theme it is in. */
  --sev-info-ink: var(--sev-info);
  --sev-low-ink: var(--sev-low);
  --sev-moderate-ink: var(--sev-moderate);
  --sev-elevated-ink: var(--sev-elevated);
  --sev-high-ink: var(--sev-high);

  /* --- Gauges. The arcs are drawn in --sev-*-ink, never the fills: a 9px stroke on a 44px radius
   * is a thin border, and section 2.2's rule about pale fills applies to it. --- */
  --gauge-track: #1e3d49;
  --gauge-void: #3d6472;
  --gauge-tick: #7d99a3;
  --gauge-arc: 9px;
  --gauge-ring: 5px;

  /* --- The evidence mount. A screenshot is lit however it is lit, so it is mounted rather than
   * adapted: a band of neutral board between the chrome and the capture. A white page steps up
   * 1.88:1 from its mount instead of 15:1 from the chrome. --- */
  --mat: #b6bfbd;
  --mat-inner: #eceeed;
  --mat-edge: #6d7a77;

  /* The only two colours that do not change with the theme, because the thing they are drawn on
   * does not either. The bbox is a three-ring sandwich so one ring always survives, whatever the
   * captured page looks like underneath it. */
  --overlay-light: #ffffff;
  --overlay-dark: #0a1013;

  /* --- The light counterparts. Read only by the theme blocks below, and every one is the value
   * the light theme shipped with, except --sev-low-light. --- */
  --surface-light: #f1f3f2;
  --surface-sunken-light: #e7ebe9;
  --card-light: #ffffff;
  --ink-light: #17313b;
  --ink-strong-light: #12262e;
  --ink-muted-light: #546d78;
  --hairline-light: #d6dcda;
  --hairline-strong-light: #b9c3c0;
  --scrim-light: rgba(23, 49, 59, 0.72);
  --sev-info-light: #8c9a9e;
  /* Corrected from #c9b27a, which measured 1.86:1 on --surface-light against informational's
   * 2.61:1 -- the light ramp was not monotonic at the bottom either. This measures 2.74:1, which
   * sits between informational and moderate and restores the ascent. */
  --sev-low-light: #b28e4c;
  --sev-moderate-light: #c9822f;
  --sev-elevated-light: #a8532b;
  --sev-high-light: #8a2e22;
  --sev-info-ink-light: #637176;
  --sev-low-ink-light: #836c35;
  --sev-moderate-ink-light: #9a6324;
  --sev-elevated-ink-light: #a8532b;
  --sev-high-ink-light: #8a2e22;
  --gauge-track-light: #d2dad7;
  --gauge-void-light: #869390;
  --gauge-tick-light: #546d78;
  --mat-light: #dfe4e2;
  --mat-inner-light: #f7f8f8;
  --mat-edge-light: #98a5a2;

  /* --- Type. Three families with distinct jobs. --- */
  --font-ui: "Public Sans", "Segoe UI Variable Text", "Segoe UI", system-ui, -apple-system, sans-serif;
  --font-prose: "Source Serif 4", Georgia, "Times New Roman", serif;
  --font-mono: "IBM Plex Mono", ui-monospace, Consolas, "Cascadia Mono", monospace;

  /* Seven steps, named by role. DESIGN.md section 3.2. */
  --text-2xs: 11px;
  --text-xs: 12px;
  --text-sm: 13px;
  --text-md: 15px;
  --text-lg: 17px;
  --text-xl: 20px;
  --text-2xl: 26px;

  --leading-tight: 1.25;
  --leading-normal: 1.5;
  --leading-prose: 1.6;

  /* --- Spacing rhythm: 4 / 8 / 12 / 16 / 24 / 32 / 48. No other step. --- */
  --s1: 4px;
  --s2: 8px;
  --s3: 12px;
  --s4: 16px;
  --s5: 24px;
  --s6: 32px;
  --s7: 48px;

  /* --- Structure --- */
  --radius: 4px;
  --measure: 68ch;
  --shell-max: 1120px;
  --focus-width: 2px;
  --hairline-gap: 2px;

  /* --- Motion. One curve; two durations; one orchestrated moment. --- */
  --motion-fast: 120ms;
  --motion-base: 200ms;
  --ease-out: cubic-bezier(0.2, 0, 0, 1);
}

/* The light theme. Twenty-five aliases and nothing else.
 *
 * html[data-theme=...] rather than :root for two reasons: (0,1,1) beats :root's (0,1,0) so the
 * aliases win, and the token parser in tests/test_design_tokens.py collects from every :root block
 * in the file with the last one winning, so a second :root would redocument the light values as
 * the defaults and the design document would quietly become wrong.
 *
 * The severity inks are re-stated rather than left to inherit. --sev-info-ink: var(--sev-info) is
 * a declaration, so an inherited one would re-resolve against the light fill -- 2.90:1 on white,
 * which is the bug the counterparts exist to prevent. */
html[data-theme="light"] {
  color-scheme: light;
  --surface: var(--surface-light);
  --surface-sunken: var(--surface-sunken-light);
  --card: var(--card-light);
  --ink: var(--ink-light);
  --ink-strong: var(--ink-strong-light);
  --ink-muted: var(--ink-muted-light);
  --hairline: var(--hairline-light);
  --hairline-strong: var(--hairline-strong-light);
  --scrim: var(--scrim-light);
  --sev-info: var(--sev-info-light);
  --sev-low: var(--sev-low-light);
  --sev-moderate: var(--sev-moderate-light);
  --sev-elevated: var(--sev-elevated-light);
  --sev-high: var(--sev-high-light);
  --sev-info-ink: var(--sev-info-ink-light);
  --sev-low-ink: var(--sev-low-ink-light);
  --sev-moderate-ink: var(--sev-moderate-ink-light);
  --sev-elevated-ink: var(--sev-elevated-ink-light);
  --sev-high-ink: var(--sev-high-ink-light);
  --gauge-track: var(--gauge-track-light);
  --gauge-void: var(--gauge-void-light);
  --gauge-tick: var(--gauge-tick-light);
  --mat: var(--mat-light);
  --mat-inner: var(--mat-inner-light);
  --mat-edge: var(--mat-edge-light);
}

@media (prefers-color-scheme: light) {
  html[data-theme="auto"] {
    color-scheme: light;
    --surface: var(--surface-light);
    --surface-sunken: var(--surface-sunken-light);
    --card: var(--card-light);
    --ink: var(--ink-light);
    --ink-strong: var(--ink-strong-light);
    --ink-muted: var(--ink-muted-light);
    --hairline: var(--hairline-light);
    --hairline-strong: var(--hairline-strong-light);
    --scrim: var(--scrim-light);
    --sev-info: var(--sev-info-light);
    --sev-low: var(--sev-low-light);
    --sev-moderate: var(--sev-moderate-light);
    --sev-elevated: var(--sev-elevated-light);
    --sev-high: var(--sev-high-light);
    --sev-info-ink: var(--sev-info-ink-light);
    --sev-low-ink: var(--sev-low-ink-light);
    --sev-moderate-ink: var(--sev-moderate-ink-light);
    --sev-elevated-ink: var(--sev-elevated-ink-light);
    --sev-high-ink: var(--sev-high-ink-light);
    --gauge-track: var(--gauge-track-light);
    --gauge-void: var(--gauge-void-light);
    --gauge-tick: var(--gauge-tick-light);
    --mat: var(--mat-light);
    --mat-inner: var(--mat-inner-light);
    --mat-edge: var(--mat-edge-light);
  }
}

/* Paper is a light ground, always. This is the theme part only: DESIGN.md section 8's "no print
 * stylesheet yet" still stands, and still waits on the WeasyPrint pipeline. */
@media print {
  html[lang] {
    color-scheme: light;
    --surface: var(--surface-light);
    --surface-sunken: var(--surface-sunken-light);
    --card: var(--card-light);
    --ink: var(--ink-light);
    --ink-strong: var(--ink-strong-light);
    --ink-muted: var(--ink-muted-light);
    --hairline: var(--hairline-light);
    --hairline-strong: var(--hairline-strong-light);
    --scrim: var(--scrim-light);
    --sev-info: var(--sev-info-light);
    --sev-low: var(--sev-low-light);
    --sev-moderate: var(--sev-moderate-light);
    --sev-elevated: var(--sev-elevated-light);
    --sev-high: var(--sev-high-light);
    --sev-info-ink: var(--sev-info-ink-light);
    --sev-low-ink: var(--sev-low-ink-light);
    --sev-moderate-ink: var(--sev-moderate-ink-light);
    --sev-elevated-ink: var(--sev-elevated-ink-light);
    --sev-high-ink: var(--sev-high-ink-light);
    --gauge-track: var(--gauge-track-light);
    --gauge-void: var(--gauge-void-light);
    --gauge-tick: var(--gauge-tick-light);
    --mat: var(--mat-light);
    --mat-inner: var(--mat-inner-light);
    --mat-edge: var(--mat-edge-light);
  }
}

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

html {
  color-scheme: dark;
}

body {
  margin: 0;
  background: var(--surface);
  color: var(--ink);
  font-family: var(--font-ui);
  font-size: var(--text-md);
  line-height: var(--leading-normal);
  /* Disables subpixel rendering, which is what thins light-on-dark text back toward its intended
   * weight. That and --ink not being pure white are the two mitigations for bloom. */
  -webkit-font-smoothing: antialiased;
  /* A missing face degrades to the fallback stack rather than to a synthesised smear. On a dark
   * ground a synthesised bold is far more visible than it is on a light one -- and with static
   * instances of exactly two weights there is nothing legitimate left to synthesise. */
  font-synthesis: none;
}

/* Visible focus everywhere. Section 7 makes keyboard operability a quality floor, not an extra. */
:focus-visible {
  outline: var(--focus-width) solid var(--ink);
  outline-offset: 2px;
}

a {
  color: var(--ink);
  text-decoration-thickness: 1px;
  text-underline-offset: 2px;
}

a:hover {
  color: var(--ink-muted);
}

/* --- Skip link ---------------------------------------------------------- */

.skip-link {
  position: absolute;
  left: -9999px;
  top: var(--s2);
  padding: var(--s2) var(--s3);
  background: var(--card);
  border: 1px solid var(--ink);
  border-radius: var(--radius);
  z-index: 10;
}

.skip-link:focus {
  left: var(--s2);
}

/* --- Frame -------------------------------------------------------------- */

.masthead {
  border-bottom: 1px solid var(--hairline);
  background: var(--card);
}

.masthead__inner,
.shell {
  max-width: var(--shell-max);
  margin: 0 auto;
  padding: 0 var(--s5);
}

.masthead__inner {
  display: flex;
  align-items: baseline;
  gap: var(--s3);
  padding-top: var(--s3);
  padding-bottom: var(--s3);
}

.wordmark {
  font-size: var(--text-md);
  font-weight: 600;
  letter-spacing: -0.01em;
  text-decoration: none;
}

.masthead__tagline {
  color: var(--ink-muted);
  font-size: var(--text-sm);
}

.masthead__spacer {
  flex: 1;
}

.masthead__meta {
  color: var(--ink-muted);
  font-size: var(--text-xs);
  font-variant-numeric: tabular-nums;
}

/* --- Theme switch ------------------------------------------------------- */

/* Three buttons in a hairline group. Monochrome, like the rest of the chrome: colour is reserved
 * for severity, and which ground the interface is drawn on is not a finding. The pressed state is
 * carried by fill and by aria-pressed, never by colour alone. */
.theme-switch {
  display: flex;
  border: 1px solid var(--hairline);
  border-radius: var(--radius);
  overflow: hidden;
}

.theme-switch__option {
  font-family: inherit;
  font-size: var(--text-2xs);
  padding: var(--s1) var(--s2);
  border: 0;
  background: transparent;
  color: var(--ink-muted);
  cursor: pointer;
}

.theme-switch__option + .theme-switch__option {
  border-left: 1px solid var(--hairline);
}

.theme-switch__option:hover {
  background: var(--surface);
  color: var(--ink);
}

.theme-switch__option.is-on {
  background: var(--surface-sunken);
  color: var(--ink);
  font-weight: 600;
}

.shell {
  padding-top: var(--s6);
  padding-bottom: var(--s7);
}

/* --- Section headings --------------------------------------------------- */

/* Sentence case, hairline rule, no all-caps eyebrow labels. */
.section-title {
  margin: 0 0 var(--s4);
  padding-bottom: var(--s2);
  border-bottom: 1px solid var(--hairline);
  font-size: var(--text-sm);
  font-weight: 600;
  color: var(--ink-muted);
}

/* --- Scan form ---------------------------------------------------------- */

.scan {
  background: var(--card);
  border: 1px solid var(--hairline);
  padding: var(--s5);
}

.scan__label {
  display: block;
  font-size: var(--text-sm);
  font-weight: 600;
  margin-bottom: var(--s2);
}

.scan__hint {
  color: var(--ink-muted);
  font-size: var(--text-sm);
  margin: 0 0 var(--s4);
  max-width: var(--measure);
}

.scan__row {
  display: flex;
  gap: var(--s2);
  align-items: stretch;
}

.scan__input {
  flex: 1;
  min-width: 0;
  font-family: var(--font-mono);
  font-size: var(--text-md);
  padding: var(--s3);
  color: var(--ink);
  background: var(--card);
  border: 1px solid var(--ink-muted);
  border-radius: var(--radius);
}

.scan__input::placeholder {
  color: var(--ink-muted);
  opacity: 0.7;
}

.scan__input[aria-invalid="true"] {
  border-color: var(--sev-high);
  border-width: 2px;
}

/* --- Buttons ------------------------------------------------------------ */

/* Monochrome petrol. No arrows appended to labels. */
.btn {
  font-family: inherit;
  font-size: var(--text-md);
  font-weight: 600;
  padding: var(--s3) var(--s5);
  border-radius: var(--radius);
  border: 1px solid var(--ink);
  background: var(--ink);
  color: var(--card);
  cursor: pointer;
  white-space: nowrap;
  transition: background var(--motion-fast) var(--ease-out);
}

.btn:hover {
  background: var(--ink-strong);
}

.btn--quiet {
  background: transparent;
  color: var(--ink);
}

.btn--quiet:hover {
  background: var(--surface);
}

/* --- Options disclosure ------------------------------------------------- */

.options {
  margin-top: var(--s4);
  border-top: 1px solid var(--hairline);
  padding-top: var(--s3);
}

.options > summary {
  font-size: var(--text-sm);
  color: var(--ink-muted);
  cursor: pointer;
  width: fit-content;
}

.options > summary:hover {
  color: var(--ink);
}

.options__grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  gap: var(--s4);
  margin-top: var(--s4);
}

.field__label {
  display: block;
  font-size: var(--text-xs);
  font-weight: 600;
  color: var(--ink-muted);
  margin-bottom: var(--s1);
}

.field__control {
  width: 100%;
  font-family: inherit;
  font-size: var(--text-md);
  padding: var(--s2) var(--s3);
  color: var(--ink);
  background: var(--card);
  border: 1px solid var(--hairline);
  border-radius: var(--radius);
}

/* --- Permission gate ---------------------------------------------------- */

/* Section 8 requires an explicit authorisation confirmation before a domain is scanned, recorded
 * with a timestamp. It is not fine print, so it is not styled as fine print. */
.permission {
  display: flex;
  gap: var(--s3);
  margin-top: var(--s4);
  padding: var(--s3) var(--s4);
  border: 1px solid var(--hairline);
  border-left: 3px solid var(--ink-muted);
  background: var(--surface);
}

.permission input[type="checkbox"] {
  margin: var(--s1) 0 0;
  width: 15px;
  height: 15px;
  accent-color: var(--ink);
  flex: none;
}

.permission label {
  font-size: var(--text-sm);
  max-width: var(--measure);
}

/* --- Notices ------------------------------------------------------------ */

.notice {
  margin: 0 0 var(--s4);
  padding: var(--s3) var(--s4);
  border: 1px solid var(--hairline);
  border-left: 3px solid var(--ink);
  background: var(--card);
  font-size: var(--text-sm);
}

.notice--error {
  border-left-color: var(--sev-high);
}

.notice__title {
  font-weight: 600;
  margin: 0 0 var(--s1);
}

.notice p {
  margin: 0;
  max-width: var(--measure);
}

.notice code {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
}

/* --- Data tables -------------------------------------------------------- */

/* Square corners on data tables, per section 7. */
.panel {
  margin-top: var(--s7);
}

.table-wrap {
  overflow-x: auto;
  border: 1px solid var(--hairline);
  background: var(--card);
}

table.data {
  width: 100%;
  border-collapse: collapse;
  font-size: var(--text-sm);
}

table.data th {
  text-align: left;
  font-weight: 600;
  font-size: var(--text-2xs);
  letter-spacing: 0.02em;
  color: var(--ink-muted);
  padding: var(--s2) var(--s4);
  border-bottom: 1px solid var(--hairline-strong);
  white-space: nowrap;
}

table.data td {
  padding: var(--s3) var(--s4);
  border-bottom: 1px solid var(--hairline);
  vertical-align: baseline;
}

table.data tr:last-child td {
  border-bottom: 0;
}

table.data .num {
  font-variant-numeric: tabular-nums;
  text-align: right;
}

table.data .url {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
}

.empty {
  padding: var(--s6) var(--s4);
  text-align: center;
  color: var(--ink-muted);
  font-size: var(--text-sm);
}

/* --- Severity ----------------------------------------------------------- */

/* Never colour alone. Three encodings: bar height, bar width, and a written label. The bar also
 * carries a hairline border so its silhouette is visible where the fill is pale -- --sev-low is
 * 2.07:1 against white, which is a shape you can only see by its edge. */
.sev {
  display: inline-flex;
  align-items: center;
  gap: var(--s2);
  white-space: nowrap;
}

.sev__bar {
  width: 3px;
  height: 1em;
  background: var(--sev-info);
  border: 1px solid var(--sev-info-ink);
  flex: none;
}

.sev__label {
  font-size: var(--text-2xs);
  color: var(--sev-info-ink);
}

.sev--low .sev__bar { background: var(--sev-low); border-color: var(--sev-low-ink); height: 1.15em; }
.sev--low .sev__label { color: var(--sev-low-ink); }

.sev--moderate .sev__bar { background: var(--sev-moderate); border-color: var(--sev-moderate-ink); height: 1.3em; }
.sev--moderate .sev__label { color: var(--sev-moderate-ink); }

.sev--elevated .sev__bar { background: var(--sev-elevated); border-color: var(--sev-elevated-ink); height: 1.45em; width: 4px; }
.sev--elevated .sev__label { color: var(--sev-elevated-ink); }

.sev--high .sev__bar { background: var(--sev-high); border-color: var(--sev-high-ink); height: 1.6em; width: 5px; }
.sev--high .sev__label { color: var(--sev-high-ink); }

/* --- Disclaimer --------------------------------------------------------- */

/* Section 9 requires this on every report page and every export, always visible, never buried in
 * a footer. It sits in the normal flow at full contrast. */
.disclaimer {
  margin-top: var(--s7);
  padding-top: var(--s4);
  border-top: 1px solid var(--hairline);
  font-family: var(--font-prose);
  font-size: var(--text-md);
  line-height: var(--leading-prose);
  color: var(--ink-muted);
  max-width: var(--measure);
}

.disclaimer strong {
  color: var(--ink);
  font-weight: 600;
}

/* --- Lifecycle status --------------------------------------------------- */

/* Deliberately NOT on the severity ramp. Colour is reserved for severity, and "queued" is not a
 * mild finding: it is the absence of one. `failed` is the single exception -- a scan that did not
 * run is a result the operator must not read past. */
.status {
  display: inline-block;
  padding: var(--s1) var(--s2);
  border: 1px solid var(--hairline);
  border-radius: var(--radius);
  font-size: var(--text-2xs);
  letter-spacing: 0.02em;
  color: var(--ink-muted);
  background: var(--surface);
  white-space: nowrap;
}

.status--done { color: var(--ink); border-color: var(--ink-muted); }
.status--failed { color: var(--sev-high-ink); border-color: var(--sev-high-ink); }

/* --- Report head -------------------------------------------------------- */

.report-head { margin-bottom: var(--s5); }

.report-head__eyebrow {
  margin: 0 0 var(--s1);
  font-size: var(--text-sm);
  color: var(--ink-muted);
  font-variant-numeric: tabular-nums;
}

.report-head__url {
  margin: 0 0 var(--s4);
  font-size: var(--text-2xl);
  font-weight: 600;
  line-height: var(--leading-tight);
  overflow-wrap: anywhere;
}

/* A definition list rather than a table: these are attributes of one thing, not rows. */
.meta {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s2) var(--s6);
  margin: 0;
  padding: var(--s3) 0;
  border-top: 1px solid var(--hairline);
  border-bottom: 1px solid var(--hairline);
}

.meta__pair { margin: 0; }

.meta dt {
  font-size: var(--text-xs);
  letter-spacing: 0.02em;
  color: var(--ink-muted);
  margin-bottom: var(--s1);
}

.meta dd {
  margin: 0;
  font-size: var(--text-md);
  font-variant-numeric: tabular-nums;
}

/* --- Motion ------------------------------------------------------------- */

@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}

/* --- Narrow viewports --------------------------------------------------- */

@media (max-width: 640px) {
  .masthead__inner,
  .shell {
    padding-left: var(--s4);
    padding-right: var(--s4);
  }

  .masthead__tagline,
  .masthead__meta {
    display: none;
  }

  .scan {
    padding: var(--s4);
  }

  .scan__row {
    flex-direction: column;
  }

  .btn {
    width: 100%;
  }
}

/* --- Progress tracker --------------------------------------------------- */

/* A stage list, not a bare bar. DESIGN.md 6.5: a percentage invites the question "of what?", and
 * only the list can say why a stage produced nothing -- which, while most of the pipeline is
 * still being built, is the most important thing on the page.
 *
 * State is encoded by marker shape and by a written word, never by colour: the ramp belongs to
 * severity, and a skipped stage is not a mild finding. */

.progress {
  margin-top: var(--s6);
  border: 1px solid var(--hairline);
  background: var(--card);
  padding: var(--s5);
}

.progress__head {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--s3);
}

.progress__head .section-title {
  border-bottom: 0;
  padding-bottom: 0;
  margin: 0;
}

.progress__headline {
  margin: 0;
  font-size: var(--text-sm);
  color: var(--ink);
  font-variant-numeric: tabular-nums;
}

.progress__track {
  margin-top: var(--s3);
  height: 4px;
  background: var(--surface-sunken);
  border: 1px solid var(--hairline);
  overflow: hidden;
}

.progress__fill {
  display: block;
  height: 100%;
  background: var(--ink);
  transition: width var(--motion-base) var(--ease-out);
}

.stages {
  list-style: none;
  margin: var(--s4) 0 0;
  padding: 0;
}

.stage {
  display: grid;
  grid-template-columns: auto 1fr auto;
  align-items: start;
  gap: var(--s3);
  padding: var(--s2) 0;
  border-top: 1px solid var(--hairline);
}

.stage:first-child {
  border-top: 0;
}

/* Shape carries the state as well as the word does: hollow is not yet, solid is settled, a bar is
 * skipped. Legible in greyscale and to anyone who does not read the label. */
.stage__marker {
  width: 9px;
  height: 9px;
  margin: var(--s1) 0 0;
  border: 1px solid var(--ink-muted);
  background: transparent;
  flex: none;
}

.stage--done .stage__marker { background: var(--ink); border-color: var(--ink); }
.stage--failed .stage__marker { background: var(--sev-high-ink); border-color: var(--sev-high-ink); }
.stage--skipped .stage__marker { height: 3px; margin-top: var(--s2); background: var(--hairline-strong); }

.stage--running .stage__marker {
  background: var(--ink);
  border-color: var(--ink);
  animation: stage-pulse 1.4s var(--ease-out) infinite;
}

/* The one piece of motion outside the ledger, and it is functional rather than decorative: it is
 * the only signal that distinguishes a scan in flight from a page that has stopped responding.
 * Collapsed to nothing by the global prefers-reduced-motion rule, where the written state still
 * carries it. */
@keyframes stage-pulse {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.35; }
}

.stage__body { min-width: 0; }

.stage__label {
  margin: 0;
  font-size: var(--text-sm);
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--s2);
}

.stage__state {
  font-size: var(--text-2xs);
  letter-spacing: 0.02em;
  color: var(--ink-muted);
}

.stage--running .stage__state { color: var(--ink); font-weight: 600; }
.stage--failed .stage__state { color: var(--sev-high-ink); font-weight: 600; }

.stage__reason {
  margin: var(--s1) 0 0;
  font-size: var(--text-xs);
  color: var(--ink-muted);
  max-width: var(--measure);
}

.stage__ms {
  font-size: var(--text-xs);
  color: var(--ink-muted);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

/* --- Stated empty state ------------------------------------------------- */

/* An empty findings list reads as "this page is clean", which is the one thing this product must
 * never imply about a third party's site. So the empty state is prose, left-aligned and at full
 * reading size, rather than a centred grey line that looks like a placeholder. */
.empty--stated {
  padding: var(--s5);
  text-align: left;
  color: var(--ink-muted);
  font-family: var(--font-prose);
  font-size: var(--text-md);
  line-height: var(--leading-prose);
  max-width: var(--measure);
}

.empty--stated p {
  margin: 0 0 var(--s2);
}

.empty--stated p:last-child {
  margin-bottom: 0;
}

.empty__lead {
  font-family: var(--font-ui);
  font-size: var(--text-sm);
  font-weight: 600;
  color: var(--ink);
}

.empty--stated strong {
  color: var(--ink);
}

/* --- Capture summary ---------------------------------------------------- */

/* The screenshot is evidence, so it is framed as evidence: square corners, a hairline, no shadow,
 * no rounded "card". It sits beside the counts rather than above them, because the question an
 * operator asks first is "did it reach the page I meant", and the answer is visual. */

.capture__grid {
  display: grid;
  grid-template-columns: minmax(0, 3fr) minmax(220px, 1fr);
  gap: var(--s5);
  align-items: start;
}

.shot {
  margin: 0;
  min-width: 0;
}

.shot__image {
  display: block;
  width: 100%;
  height: auto;
  border: 1px solid var(--mat-edge);
  background: var(--mat-inner);
}

.shot__caption {
  margin: var(--s2) 0 0;
  font-size: var(--text-xs);
  color: var(--ink-muted);
}

.counts {
  margin: 0;
  border-top: 1px solid var(--hairline);
}

.counts__pair {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--s3);
  padding: var(--s2) 0;
  border-bottom: 1px solid var(--hairline);
}

.counts dt {
  font-size: var(--text-sm);
  color: var(--ink-muted);
}

.counts dd {
  margin: 0;
  font-size: var(--text-md);
  font-variant-numeric: tabular-nums;
  font-weight: 600;
}

.capture__note {
  margin: var(--s3) 0 0;
  font-size: var(--text-xs);
  color: var(--ink-muted);
  overflow-wrap: anywhere;
}

.capture__note span {
  display: block;
  font-weight: 600;
  color: var(--ink);
}

.capture__note code {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
}

/* Not on the severity ramp: an error page is a fact about the capture, not a finding about the
 * site. It is marked by weight and a rule, the way every other non-severity emphasis is. */
.capture__note--flag {
  padding-left: var(--s3);
  border-left: 3px solid var(--ink);
  color: var(--ink);
}

@media (max-width: 900px) {
  .capture__grid {
    grid-template-columns: minmax(0, 1fr);
  }
}

/* Elapsed time. One stage dominates a scan -- capture is tens of seconds where the rest are
 * milliseconds -- so the bar sits at one value throughout and a number that moves is what
 * distinguishes working from stuck. */
.progress__elapsed {
  display: inline-block;
  min-width: 4ch;
  margin-right: var(--s2);
  padding: 0 var(--s1);
  border: 1px solid var(--hairline);
  background: var(--surface);
  color: var(--ink-muted);
  font-size: var(--text-2xs);
  font-variant-numeric: tabular-nums;
}

/* --- Visually hidden ---------------------------------------------------- */

/* The clip-path form, not the classic negative-margin recipe: that literal would fail the spacing
 * rule in tests/test_design_tokens.py, and this form is the better one anyway. */
.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* --- The instrument header ---------------------------------------------- */

/* Three gauges over one ground, replacing the segmented risk strip: the strip drew penalty share,
 * and the radials draw share, subscore and coverage state, so keeping both would be two drawings of
 * the same data with the poorer one on top.
 *
 * Every per-scan number reaches the SVG as a presentation attribute rather than an inline style, so
 * none of this needs the style-src-attr unlock that the progress bar and the bbox overlay do. */

.instrument {
  margin-top: var(--s6);
  padding: var(--s5);
  border: 1px solid var(--hairline);
  background: var(--card);
}

.instrument__grid {
  display: grid;
  grid-template-columns: minmax(0, 200px) minmax(0, 1fr);
  gap: var(--s5);
  align-items: start;
}

.instrument__panes {
  display: grid;
  gap: var(--s4);
  min-width: 0;
}

/* The recess the dial and the waterfall sit in. A gauge reads as an instrument when it is set into
 * the panel rather than printed on it, and on a dark ground a well is the cheapest way to say so
 * without a shadow. */
.gauge-well {
  padding: var(--s4);
  border: 1px solid var(--hairline);
  background: var(--surface-sunken);
}

.instrument__note {
  margin: var(--s4) 0 0;
  padding-top: var(--s3);
  border-top: 1px solid var(--hairline);
  font-size: var(--text-xs);
  color: var(--ink-muted);
  max-width: var(--measure);
}

/* --- The risk dial ------------------------------------------------------ */

.dial {
  margin: 0;
}

/* The reading is real HTML stacked over the drawing, so every size stays on the type scale and no
 * SVG <text> has to be positioned by hand. */
.dial__stack {
  display: grid;
}

.dial__svg,
.dial__read {
  grid-area: 1 / 1;
}

.dial__svg {
  width: 100%;
  height: auto;
}

.dial__track {
  fill: none;
  stroke: var(--gauge-track);
  stroke-width: var(--gauge-arc);
}

/* butt rather than round: a rounded cap overhangs the value it is drawn to, which on a dial that
 * has ticks is a mark that disagrees with its own graduations. */
.dial__value {
  fill: none;
  stroke: var(--sev-info-ink);
  stroke-width: var(--gauge-arc);
  stroke-linecap: butt;
  transition: stroke-dasharray var(--motion-base) var(--ease-out);
}

.dial__value--low { stroke: var(--sev-low-ink); }
.dial__value--moderate { stroke: var(--sev-moderate-ink); }
.dial__value--elevated { stroke: var(--sev-elevated-ink); }
.dial__value--high { stroke: var(--sev-high-ink); }

.dial__tick {
  stroke: var(--gauge-tick);
  stroke-width: 1;
}

.dial__cap {
  stroke: var(--ink);
  stroke-width: 2;
  transition:
    x1 var(--motion-base) var(--ease-out),
    y1 var(--motion-base) var(--ease-out),
    x2 var(--motion-base) var(--ease-out),
    y2 var(--motion-base) var(--ease-out);
}

.dial__read {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: end;
  margin: 0;
  padding-bottom: var(--s2);
}

.dial__band {
  font-size: var(--text-sm);
  font-weight: 600;
  letter-spacing: 0.02em;
}

.dial__number {
  font-size: var(--text-xl);
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  line-height: var(--leading-tight);
}

.dial__scale,
.dial__caption {
  font-size: var(--text-xs);
  color: var(--ink-muted);
}

.dial__caption {
  margin-top: var(--s2);
  text-align: center;
}

/* --- The category radials ----------------------------------------------- */

.radials {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s3) var(--s4);
  list-style: none;
  margin: 0;
  padding: 0;
}

.radial {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  grid-template-rows: auto auto;
  align-items: center;
  gap: 0 var(--s2);
}

.radial__svg {
  grid-row: 1 / 3;
  width: 34px;
  height: 34px;
}

.radial__track {
  fill: none;
  stroke: var(--gauge-track);
  stroke-width: var(--gauge-ring);
}

.radial__value {
  fill: none;
  stroke: var(--sev-info-ink);
  stroke-width: var(--gauge-ring);
  stroke-linecap: butt;
}

.radial__value--low { stroke: var(--sev-low-ink); }
.radial__value--moderate { stroke: var(--sev-moderate-ink); }
.radial__value--elevated { stroke: var(--sev-elevated-ink); }
.radial__value--high { stroke: var(--sev-high-ink); }

/* Checked and clear: a complete ring with a dot in it. The dot is the mark that distinguishes it
 * from the dashed ring below at a glance, before the words are read. */
.radial__dot {
  fill: var(--ink-muted);
}

/* Nothing has been built to check this. A dashed ring, because a gap in the ring is the honest
 * shape for a gap in the catalogue -- and it must never be mistaken for the solid ring above,
 * which is a category that was checked and came back clean. */
.radial--unchecked .radial__track {
  stroke: var(--gauge-void);
  stroke-dasharray: 2 3;
}

.radial__label {
  align-self: end;
  font-size: var(--text-sm);
  font-weight: 600;
}

.radial__state {
  align-self: start;
  font-size: var(--text-2xs);
  color: var(--ink-muted);
}

.radial--unchecked .radial__state {
  font-style: italic;
}

/* --- The contribution waterfall ----------------------------------------- */

.wf__title {
  margin: 0 0 var(--s2);
  font-size: var(--text-2xs);
  font-weight: 600;
  color: var(--ink-muted);
}

.wf__svg {
  display: block;
  width: 100%;
  height: 72px;
}

.wf__grid {
  fill: var(--gauge-tick);
  opacity: 0.45;
}

.wf__step {
  fill: var(--sev-info-ink);
}

.wf__step--low { fill: var(--sev-low-ink); }
.wf__step--moderate { fill: var(--sev-moderate-ink); }
.wf__step--elevated { fill: var(--sev-elevated-ink); }
.wf__step--high { fill: var(--sev-high-ink); }

/* Marked by a rule and by weight, not by a colour: the cap is a fact about the scale, not a
 * finding about the page. */
.wf__clamp {
  margin: var(--s3) 0 0;
  padding-left: var(--s3);
  border-left: 3px solid var(--ink);
  font-size: var(--text-xs);
  color: var(--ink);
  max-width: var(--measure);
}

.wf__detail {
  margin-top: var(--s3);
}

.wf__detail summary {
  font-size: var(--text-xs);
  color: var(--ink-muted);
  cursor: pointer;
}

.wf__detail summary:hover {
  color: var(--ink);
}

.wf__detail .table-wrap {
  margin-top: var(--s3);
}

.wf__note {
  margin: var(--s3) 0 0;
  font-size: var(--text-xs);
  color: var(--ink-muted);
  max-width: var(--measure);
}

@media (max-width: 900px) {
  .instrument__grid {
    grid-template-columns: minmax(0, 1fr);
  }

  .dial__svg {
    max-width: 200px;
    margin: 0 auto;
  }
}

/* --- The two panes ------------------------------------------------------ */

/* minmax(0, ...) on both tracks, never a bare 1fr: an auto minimum lets a 1440px screenshot or a
 * long selector blow the track out and give the whole page a horizontal scrollbar. */
.report-panes {
  display: grid;
  grid-template-columns: minmax(0, 360px) minmax(0, 1fr);
  gap: var(--s5);
  margin-top: var(--s7);
  align-items: start;
}

/* --- Findings ledger ---------------------------------------------------- */

.ledger__count {
  font-variant-numeric: tabular-nums;
  color: var(--ink);
}

.ledger {
  list-style: none;
  margin: 0;
  padding: 0;
  border: 1px solid var(--hairline);
  background: var(--card);
}

.ledger li + li .ledger__row {
  border-top: 1px solid var(--hairline);
}

.ledger__row {
  display: grid;
  gap: var(--s1);
  padding: var(--s3) var(--s4);
  border-left: 3px solid transparent;
  text-decoration: none;
  color: var(--ink);
}

.ledger__row:hover {
  background: var(--surface);
  color: var(--ink);
}

/* Selection is a border and a ground, never colour alone. */
.ledger__row.is-selected {
  border-left-color: var(--ink);
  background: var(--surface-sunken);
}

.ledger__title {
  font-size: var(--text-sm);
  font-weight: 600;
  overflow-wrap: anywhere;
}

.ledger__tags {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s1);
}

.tag {
  font-size: var(--text-2xs);
  letter-spacing: 0.02em;
  color: var(--ink-muted);
  border: 1px solid var(--hairline);
  padding: 0 var(--s1);
}

.tag--id {
  font-family: var(--font-mono);
}

/* --- Evidence pane ------------------------------------------------------ */

.evidence-pane {
  min-width: 0;
}

.evidence__title {
  margin: 0 0 var(--s3);
  font-size: var(--text-md);
  font-weight: 600;
}

.evidence__figure {
  margin: 0 0 var(--s4);
}

/* The mount. DESIGN.md 2.4: a screenshot is lit however the captured page is lit, so it is mounted
 * rather than adapted -- a band of neutral board between the chrome and the capture, with a bevel
 * where they meet. A white page then steps up 1.88:1 from its mount instead of 15:1 from the
 * chrome, and a dark page still has an edge.
 *
 * Inside the mat the ground is a screenshot, so every mark drawn on it uses the ramp measured
 * against a light ground -- in both themes. The theme stops here. Ten aliases and no literals,
 * which is what keeps this invisible to the no-hex rule. */
.shot-mat {
  display: block;
  padding: var(--s3);
  background: var(--mat);
  border: 1px solid var(--hairline-strong);
  --sev-info: var(--sev-info-light);
  --sev-low: var(--sev-low-light);
  --sev-moderate: var(--sev-moderate-light);
  --sev-elevated: var(--sev-elevated-light);
  --sev-high: var(--sev-high-light);
  --sev-info-ink: var(--sev-info-ink-light);
  --sev-low-ink: var(--sev-low-ink-light);
  --sev-moderate-ink: var(--sev-moderate-ink-light);
  --sev-elevated-ink: var(--sev-elevated-ink-light);
  --sev-high-ink: var(--sev-high-ink-light);
}

/* The percentage overlay is exact only if this box is the image box, so this element carries no
 * padding. The mat's padding is one level out, on .shot-mat, for exactly that reason: percentages
 * resolve against the padding box of the positioning ancestor, so padding here would shift every
 * rectangle in the product by the width of the mount, silently. The bevel is a border, which sits
 * outside the padding box and so disturbs nothing. tests/test_overlay.py pins this. */
.shot-frame {
  position: relative;
  display: block;
  padding: 0;
  border: 1px solid var(--mat-edge);
  background: var(--mat-inner);
}

.shot-frame__image {
  display: block;
  width: 100%;
  height: auto;
}

/* Three rings, because the ground is somebody else's page. The severity colour is the middle one;
 * a light ring inside it and a dark ring outside mean the rectangle has an edge over a white
 * banner, over a black hero, and over anything between. Those two are the only colours in the
 * system that do not change with the theme, because the thing they are drawn on does not either. */
.bbox {
  position: absolute;
  border: 2px solid var(--sev-info);
  outline: 1px solid var(--overlay-light);
  outline-offset: 0;
  pointer-events: none;
}

/* `inset` is a dimension, not rhythm: the spacing rule covers padding, margin and the gaps, and
 * this is the outer ring of a border stack. */
.bbox::after {
  content: "";
  position: absolute;
  inset: -4px;
  border: 1px solid var(--overlay-dark);
}

.evidence--low .bbox { border-color: var(--sev-low); }
.evidence--moderate .bbox { border-color: var(--sev-moderate); }
.evidence--elevated .bbox { border-color: var(--sev-elevated); }
.evidence--high .bbox { border-color: var(--sev-high); }

.evidence__caption,
.evidence__nofigure {
  margin: var(--s2) 0 0;
  font-size: var(--text-xs);
  color: var(--ink-muted);
}

.evidence__explanation {
  margin: 0 0 var(--s4);
  font-family: var(--font-prose);
  font-size: var(--text-lg);
  line-height: var(--leading-prose);
  max-width: var(--measure);
}

.measured {
  margin: 0 0 var(--s4);
  border-top: 1px solid var(--hairline);
}

.measured__pair {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--s3);
  padding: var(--s1) 0;
  border-bottom: 1px solid var(--hairline);
}

.measured dt {
  font-size: var(--text-xs);
  color: var(--ink-muted);
}

.measured dd {
  margin: 0;
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  overflow-wrap: anywhere;
  text-align: right;
}

.evidence__where,
.evidence__searched,
.evidence__legal,
.evidence__remediation {
  margin: 0 0 var(--s3);
  font-size: var(--text-sm);
  max-width: var(--measure);
}

.evidence__where span,
.evidence__searched span,
.evidence__legal span,
.evidence__remediation span {
  display: block;
  font-size: var(--text-2xs);
  letter-spacing: 0.02em;
  color: var(--ink-muted);
}

.evidence__where code,
.evidence__related code {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  overflow-wrap: anywhere;
}

.evidence__quote {
  margin: 0 0 var(--s3);
  padding-left: var(--s3);
  border-left: 3px solid var(--hairline-strong);
  font-family: var(--font-prose);
  font-size: var(--text-md);
  max-width: var(--measure);
}

.evidence__related {
  list-style: none;
  margin: 0 0 var(--s3);
  padding: 0;
  font-size: var(--text-sm);
}

.evidence__related span {
  font-size: var(--text-2xs);
  color: var(--ink-muted);
}

.evidence__remediation {
  padding: var(--s3);
  border-left: 3px solid var(--ink);
  background: var(--surface);
}

/* --- Checks that did not run -------------------------------------------- */

.unrun__lead {
  margin: 0 0 var(--s3);
  font-size: var(--text-sm);
  color: var(--ink-muted);
  max-width: var(--measure);
}

.unrun {
  list-style: none;
  margin: 0;
  padding: 0;
  border: 1px solid var(--hairline);
  background: var(--card);
}

.unrun__item {
  display: grid;
  grid-template-columns: minmax(0, 90px) minmax(0, 1fr);
  gap: var(--s3);
  padding: var(--s2) var(--s4);
  border-bottom: 1px solid var(--hairline);
  font-size: var(--text-sm);
}

.unrun__item:last-child {
  border-bottom: 0;
}

.unrun__item--errored {
  border-left: 3px solid var(--sev-high);
}

.unrun__id {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  color: var(--ink);
}

.unrun__reason {
  color: var(--ink-muted);
}

/* --- Narrow viewports --------------------------------------------------- */

@media (max-width: 900px) {
  .report-panes {
    grid-template-columns: minmax(0, 1fr);
  }
}

.unrun__more {
  margin-top: var(--s3);
}

.unrun__more > summary {
  font-size: var(--text-sm);
  color: var(--ink-muted);
  cursor: pointer;
  width: fit-content;
}

.unrun__more > summary:hover {
  color: var(--ink);
}

.unrun__more .unrun {
  margin-top: var(--s2);
}

.unrun__item--unbuilt .unrun__reason {
  font-style: italic;
}

/* --- Ledger filters ------------------------------------------------------ */

/* Chips, not a select. Three facets of three or four values each is a set of things to see at
 * once, and a dropdown hides the counts -- which are half the reason to look: "source: rule 9,
 * llm 3" tells an auditor what kind of report this is before they read a word of it. */
.filters {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s2) var(--s4);
  margin: 0 0 var(--s3);
  padding: 0 0 var(--s3);
  border-bottom: 1px solid var(--hairline);
}

.filters__group {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: var(--s1);
}

.filters__label {
  font-size: var(--text-2xs);
  letter-spacing: 0.04em;
  color: var(--ink-muted);
  margin-right: var(--s1);
}

.chip {
  display: inline-flex;
  align-items: baseline;
  gap: var(--s1);
  padding: var(--s1) var(--s2);
  border: 1px solid var(--hairline);
  border-radius: var(--radius);
  font-size: var(--text-xs);
  line-height: var(--leading-tight);
  color: var(--ink-muted);
  text-decoration: none;
  background: var(--card);
}

.chip:hover { border-color: var(--hairline-strong); color: var(--ink); }

/* The selected chip is the one carrying ink, not colour: section 7 reserves colour for severity,
 * and a filter is not a severity. */
.chip.is-on {
  background: var(--ink);
  border-color: var(--ink);
  color: var(--card);
}

.chip__count {
  font-variant-numeric: tabular-nums;
  font-size: var(--text-2xs);
  opacity: 0.75;
}

/* Applied by the browser when a facet is on. The server does the same thing by rendering fewer
 * rows, so the two never disagree -- whichever filtered, the ledger shows the same set. */
.ledger [data-row][hidden] { display: none; }

/* --- Categories with nothing to report ----------------------------------- */

/* A bar with no severity. Same geometry as the severity bars so the legend stays a column, drawn
 * in the hairline rather than the ramp: nothing was found, so nothing should read as a level. */
.sev--none .sev__bar {
  background: var(--surface-sunken);
  border-color: var(--hairline-strong);
}

/* --- The Sources pages -------------------------------------------------- */

/* The knowledge base behind the catalogue. Dense by default -- the index is a table an auditor
 * scans, and the detail page is a document they read -- so the two use different measures. */

.masthead__nav {
  display: flex;
  gap: var(--s4);
  margin-left: var(--s5);
}

.masthead__link {
  font-size: var(--text-sm);
  color: var(--ink-muted);
  text-decoration: none;
}

.masthead__link:hover {
  color: var(--ink);
  text-decoration: underline;
}

/* Marked by weight and a rule, never by colour: which page you are on is not a finding. */
.masthead__link[aria-current="page"] {
  color: var(--ink);
  font-weight: 600;
  border-bottom: 2px solid var(--ink);
}

.sources__lead {
  margin: var(--s3) 0 0;
  max-width: var(--measure);
  font-size: var(--text-sm);
  color: var(--ink-muted);
}

.sources__search {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--s2);
  margin-bottom: var(--s4);
}

.sources__search-label {
  font-size: var(--text-sm);
  color: var(--ink-muted);
}

.sources__search .scan__input {
  flex: 1;
  min-width: 0;
}

.sources__table {
  font-size: var(--text-sm);
}

.sources__id a {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  white-space: nowrap;
}

.sources__review {
  font-size: var(--text-2xs);
}

.sources__back {
  margin: var(--s4) 0 0;
  font-size: var(--text-sm);
}

/* Implementation state. Monochrome: whether a check is built is a fact about this tool, not a
 * severity, and the ramp is reserved for the latter. */
.state {
  display: inline-block;
  padding: 0 var(--s1);
  border: 1px solid var(--hairline-strong);
  border-radius: var(--radius);
  font-size: var(--text-2xs);
  color: var(--ink-muted);
  white-space: nowrap;
}

.state--implemented {
  color: var(--ink);
  border-color: var(--ink-muted);
}

.state--not-built,
.state--model-layer {
  font-style: italic;
}

/* Review status. Also monochrome, and also for a reason: whether a citation has been checked is a
 * fact about our own process. An unreviewed one is set in italic and says so in words. */
.review {
  font-size: var(--text-2xs);
  color: var(--ink-muted);
}

.review--unreviewed,
.review--unknown,
.review--none {
  font-style: italic;
}

.review--reviewed {
  color: var(--ink);
}

.source__prose {
  margin: var(--s3) 0 0;
  max-width: var(--measure);
  font-family: var(--font-prose);
  font-size: var(--text-lg);
  line-height: var(--leading-prose);
}

.source__subhead {
  margin: var(--s5) 0 0;
  font-size: var(--text-sm);
  font-weight: 600;
  color: var(--ink-muted);
}

.source__list,
.source__refs,
.source__legal {
  margin: var(--s2) 0 0;
  padding-left: var(--s5);
  max-width: var(--measure);
  font-size: var(--text-md);
  line-height: var(--leading-normal);
}

.source__list li,
.source__refs li {
  margin-top: var(--s2);
}

.source__legal {
  list-style: none;
  padding-left: 0;
}

/* One citation. The rule on the left is the same device the error notice uses: a mark that says
 * "this is a block to read as a unit" without spending a colour on it. */
.legal {
  margin-top: var(--s4);
  padding-left: var(--s3);
  border-left: 2px solid var(--hairline-strong);
}

.legal--unreviewed {
  border-left-style: dashed;
}

.legal__citation {
  margin: 0;
  font-weight: 600;
}

.legal__status,
.legal__note,
.legal__works {
  margin: var(--s1) 0 0;
  font-size: var(--text-xs);
  color: var(--ink-muted);
}

.legal__note {
  font-family: var(--font-prose);
  font-size: var(--text-md);
  line-height: var(--leading-normal);
}

.works {
  list-style: none;
  margin: var(--s4) 0 0;
  padding: 0;
}

.work {
  margin-top: var(--s5);
  padding-top: var(--s4);
  border-top: 1px solid var(--hairline);
}

.work:first-child {
  border-top: 0;
  padding-top: 0;
}

.work__title {
  margin: 0;
  font-size: var(--text-md);
  font-weight: 600;
}

.work__meta {
  margin: var(--s1) 0 0;
  font-size: var(--text-xs);
  color: var(--ink-muted);
}

.work__prose {
  margin: var(--s3) 0 0;
  max-width: var(--measure);
  font-family: var(--font-prose);
  font-size: var(--text-md);
  line-height: var(--leading-prose);
}

.work__take {
  margin: var(--s3) 0 0;
  max-width: var(--measure);
}

.work__pair {
  margin-top: var(--s2);
}

.work__pair dt {
  font-size: var(--text-2xs);
  font-weight: 600;
  color: var(--ink-muted);
}

.work__pair dd {
  margin: 0;
  font-size: var(--text-sm);
  line-height: var(--leading-normal);
}

.work__citing {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--s1);
  margin: var(--s3) 0 0;
  font-size: var(--text-2xs);
  color: var(--ink-muted);
}

@media (max-width: 640px) {
  .masthead__nav {
    margin-left: var(--s3);
    gap: var(--s3);
  }
}

/* Out to the knowledge base, from a finding and from a check that did not run. Not a severity, so
 * it is set in the muted ink like every other piece of apparatus. */
.evidence__source {
  margin: var(--s3) 0 0;
  font-size: var(--text-xs);
  color: var(--ink-muted);
}

.evidence__source span {
  display: block;
  font-weight: 600;
  color: var(--ink);
}

.evidence__refs {
  list-style: none;
  margin: var(--s1) 0 0;
  padding: 0;
}

.evidence__refs li {
  margin-top: var(--s1);
}
