/*
 * Q-Day Index scientific design system (Phase 17, IMPLEMENTATION_PLAN.md §47).
 * See docs/design-system.md for the full token/component reference. Every addition here must
 * honour §47's "avoid" list: no matrix effects, no aggressive neon, no fast animation, no
 * unexplained colours, no decorative claims.
 *
 * Colour tokens are the `dataviz` skill's validated reference palette (computed colourblind-
 * safety + contrast checks, not eyeballed) -- this is what makes the palette "explained" rather
 * than arbitrary. Dark values are declared under both the OS media query and a `data-theme`
 * attribute scope, so a future theme toggle can stamp `data-theme` on the root element and win
 * in both directions -- no toggle UI exists yet, this costs nothing to have ready.
 *
 * Mobile-first breakpoints (item 13, docs/responsive-design.md, ADR-0007) -- CSS custom
 * properties can't be read inside @media conditions, so these stay literal pixel values in each
 * min-width rule below, all sourced from this one list, never re-invented per rule:
 *   Mobile 320-767px (base, no media query) · Tablet 768-1023px · Desktop 1024-1439px ·
 *   Large desktop 1440px+
 */

/* WP4 (#69, docs/design-direction.md §2). Dark is the default now, not an alternate: the
   direction document's palette table is the source of these values and nothing may be introduced
   here that is not in it. The previous light values are kept below as the alternate theme --
   the on-page theme control, which stamps `data-theme="light"` -- so the machinery Phase 17 built
   keeps working rather than being replaced. Dark renders for every visitor regardless of
   `prefers-color-scheme`; see the light block below for why.

   §2's own names (`--paper`, `--ink`, `--accent`, ...) are declared as the primary tokens, and the
   Phase 17 names (`--page-plane`, `--text-primary`, ...) alias onto them. Every existing component
   therefore inherits the new palette without being rewritten, and a component written from the
   direction document can use the document's names. */

:root {
  color-scheme: dark;

  /* §2 palette table, verbatim. */
  --paper: #0e0f0e;
  --card: #161816;
  --card2: #1b1e1b;
  --ink: #f4f3ee;
  --ink2: #b9b7ae;
  --ink3: #84837b;
  --rule: #2a2c29;
  --hair: #1e211e;
  --accent: #4ade80;
  --warn: #fb923c;
  --bad: #f87171;

  /* Phase 17 names, kept as aliases so no component had to change. */
  --page-plane: var(--paper);
  --surface: var(--card);
  --surface-raised: var(--card2);
  --text-primary: var(--ink);
  --text-secondary: var(--ink2);
  --text-muted: var(--ink3);
  --gridline: var(--rule);
  --border: var(--rule);
  --baseline: var(--hair);

  /* Status colours -- item 4. Fixed, never themed, never reused for chart series. Usage rule:
     always paired with an icon + label -- some steps are sub-3:1 contrast by design, so colour
     alone must never carry the meaning. The warning/critical steps take §2's values so the two
     systems do not disagree on a dark plane. */
  --status-good: var(--accent);
  --status-good-text: var(--accent);
  --status-warning: var(--warn);
  --status-serious: #ec835a;
  --status-critical: var(--bad);

  /* Sequential blue ramp -- item 5. Magnitude/continuous encodings. Unchanged: §2 is explicit
     that the accent is never a chart series colour, so "green" keeps one meaning on the site. */
  --sequential-100: #cde2fb;
  --sequential-250: #86b6ef;
  --sequential-400: #3987e5;
  --sequential-450: #3987e5;
  --sequential-500: #256abf;
  --sequential-600: #184f95;
  --sequential-700: #0d366b;

  --diverging-cool: #3987e5;
  --diverging-warm: #e34948;
  --diverging-mid: #383835;

  /* Categorical palette -- item 5. Fixed order, NEVER cycled or re-ordered per-chart. */
  --cat-1-blue: #3987e5;
  --cat-2-orange: #d95926;
  --cat-3-aqua: #199e70;
  --cat-4-yellow: #c98500;
  --cat-5-magenta: #d55181;
  --cat-6-green: #008300;
  --cat-7-violet: #9085e9;
  --cat-8-red: #e66767;

  /* Typography -- §3. Serif display, sans body, mono for code/hashes/equations. The serif stack
     is an INTERIM: §3 requires a self-hosted face before launch, because a system stack renders
     differently on every operating system. Recorded as a known gap in docs/design-direction.md,
     not ticked off. */
  --font-display: Georgia, "Iowan Old Style", "Palatino Linotype", Palatino, "Times New Roman",
    serif;
  --font-sans: system-ui, -apple-system, "Segoe UI", sans-serif;
  --font-mono: ui-monospace, "SFMono-Regular", Consolas, monospace;
  --text-xs: 0.75rem;
  --text-sm: 0.875rem;
  --text-base: 1rem;
  --text-lg: 1.125rem;
  --text-xl: 1.25rem;
  --text-2xl: 1.5rem;
  --text-3xl: 1.875rem;
  --text-4xl: 2.25rem;
  /* §3's fluid display sizes. */
  --text-hero: clamp(44px, 6.2vw, 76px);
  --text-page-title: clamp(38px, 4.6vw, 56px);
  --text-section: 26px;
  --line-height-base: 1.6;
  --line-height-tight: 0.96;
  --letter-spacing-display: -0.025em;

  /* Spacing scale -- item 2. 4px grid. */
  --space-1: 0.25rem;
  --space-2: 0.5rem;
  --space-3: 0.75rem;
  --space-4: 1rem;
  --space-5: 1.5rem;
  --space-6: 2rem;
  --space-7: 3rem;
  --space-8: 4rem;

  /* §4 layout constants. */
  --content-max: 1200px;
  --page-padding: clamp(20px, 5vw, 60px);
  --section-rhythm: 52px;
  --hero-gap: 36px;
  --hero-collapse: 940px;

  --radius: 10px;
  --focus-ring: 0 0 0 3px rgba(74, 222, 128, 0.45);
  --transition-fast: 150ms ease;
}

/* Founder decision from the §9 pass (#98): **dark is the site's appearance, not a preference.**
   It renders for every visitor regardless of what their operating system reports -- Windows
   defaults to light, so keying the theme off `prefers-color-scheme` meant most visitors saw the
   variant the founder did not choose, and an identity that depends on an OS setting is not an
   identity.

   Light is still fully supported: same tokens, same contrast checks, reached by the on-page
   control stamping `data-theme="light"` on the root. It stopped being automatic; it did not stop
   being supported. */

:root[data-theme="light"] {
  color-scheme: light;
  --paper: #f9f9f7;
  --card: #fcfcfb;
  --card2: #ffffff;
  --ink: #0b0b0b;
  --ink2: #52514e;
  --ink3: #6d6c66;
  --rule: #d8d7cf;
  --hair: #e1e0d9;
  --accent: #14804a;
  --warn: #b45309;
  --bad: #b91c1c;

  --status-serious: #c2410c;

  --sequential-450: #2a78d6;
  --diverging-cool: #2a78d6;
  --diverging-mid: #f0efec;

  --cat-1-blue: #2a78d6;
  --cat-2-orange: #eb6834;
  --cat-3-aqua: #1baf7a;
  --cat-4-yellow: #eda100;
  --cat-5-magenta: #e87ba4;
  --cat-7-violet: #4a3aa7;
  --cat-8-red: #e34948;

  --focus-ring: 0 0 0 3px rgba(20, 128, 74, 0.45);
}

/* Base reset */

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

body {
  margin: 0;
  background: var(--page-plane);
  color: var(--text-primary);
  font-family: var(--font-sans);
  font-size: var(--text-base);
  line-height: var(--line-height-base);
}

/* Launch Task 2. Inline identifiers -- commit SHAs, container digests, publication digests -- are
   single unbreakable tokens up to 71 characters long. One of them on /model-history/4/ had a
   min-content width of 638px, which is wider than a phone: Chrome answered by shrinking the whole
   page to fit, so every line of that page rendered at roughly 60% size on a 390px screen while
   reporting no horizontal overflow at all. Breaking the token is the only thing that lets the page
   lay out at the device width. `anywhere` rather than `break-all` so a short identifier still moves
   to the next line intact before it is split. */
code {
  overflow-wrap: anywhere;
}

/* §3. Serif display headings -- the single largest change from the previous implementation, and
   the fastest signal that this is an instrument rather than a campaign. The earlier reading of
   §47's "avoid decorative claims" as forbidding a serif face was a misreading: the prohibition is
   on claims that are decorative, not on typefaces. Sizes are fluid per §3. */

h1, h2, h3, h4, h5, h6 {
  font-family: var(--font-display);
  font-weight: 600;
  line-height: var(--line-height-tight);
  letter-spacing: var(--letter-spacing-display);
  color: var(--ink);
  margin: 0 0 var(--space-3);
}

h1 { font-size: var(--text-page-title); }
h2 { font-size: var(--text-section); }
h3 { font-size: var(--text-xl); }
h4 { font-size: var(--text-lg); }

p {
  margin: 0 0 var(--space-4);
  color: var(--text-secondary);
}

a {
  color: var(--accent);
}

a:hover {
  text-decoration-thickness: 2px;
}

.tabular-nums {
  font-variant-numeric: tabular-nums;
}

/* §4. One fluid column rather than three stepped max-widths: `clamp()` covers 320 -> 1440 in one
   rule, and the 1200px ceiling is the direction document's. */

.container {
  max-width: var(--content-max);
  margin: 0 auto;
  padding: var(--page-padding);
}

/* Focus -- item 7. Global, not per-component, so every interactive element gets it. */

:focus-visible {
  outline: none;
  box-shadow: var(--focus-ring);
  border-radius: var(--radius);
}

/* Card -- item 6 */

/* §4: 10px radius, 1px --rule border, 18-22px padding. */

.card {
  background: var(--card);
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  padding: 20px;
  margin-bottom: var(--space-4);
}

/* Badge -- item 12's visual shell (content lives in components/model_version_badge.html) */

.badge {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  font-size: var(--text-sm);
  color: var(--text-secondary);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 999px;
  padding: var(--space-1) var(--space-3);
}

/* Status badge variants -- item 4's first real usage (Phase 18 home page "model status"
   section). Icon + label always accompanies these in markup, never colour alone. */

.badge--good {
  border-color: var(--status-good);
  color: var(--status-good-text);
}

.badge--warning {
  border-color: var(--status-warning);
  color: var(--text-primary);
}

/* Visually-hidden but screen-reader-accessible content. */

.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;
}

/* P10-P90 forecast band -- item 5's first real usage (Phase 18 home page hero). The fill uses
   the sequential blue ramp; the marker is positioned by a real computed percentage (linear
   interpolation between the P10/P90 dates), not fixed at 50% -- the distribution is not
   necessarily symmetric. */

.p10-p90-band {
  position: relative;
  height: var(--space-6);
  border-radius: var(--radius);
  background: linear-gradient(90deg, var(--sequential-250), var(--sequential-500));
  margin: var(--space-4) 0;
}

.p10-p90-band__marker {
  position: absolute;
  top: -4px;
  bottom: -4px;
  width: 3px;
  background: var(--text-primary);
  border-radius: 2px;
}

/* WP3 (§5.1a state 2) -- a band whose upper end is censored. The bar runs P10 to the modelled
   horizon and the unresolved region is drawn as a hatch rather than as more forecast: the reader
   must be able to see, not infer, that the right-hand end is where the model stopped resolving
   and not a P90. No marker is ever placed in this state -- a marker at the horizon edge is the
   clamped percentile E4 exists to prevent, and §5.1a forbids it explicitly.

   The hatch is a repeating-linear-gradient over the same neutral gridline colour used elsewhere,
   so it reads as "no data here" in both themes without introducing an unexplained colour. */

.p10-p90-band--censored {
  background: linear-gradient(90deg, var(--sequential-250), var(--sequential-400));
}

.p10-p90-band__censored-region {
  position: absolute;
  top: 0;
  bottom: 0;
  right: 0;
  border-radius: 0 var(--radius) var(--radius) 0;
  border-left: 2px solid var(--text-primary);
  background: repeating-linear-gradient(
    135deg,
    var(--gridline),
    var(--gridline) 4px,
    var(--surface) 4px,
    var(--surface) 8px
  );
}

/* §5.1 item 1 -- the hero answers in words first, with the answer word in accent. This is the
   minimum §5.1 needs; the full token/typography pass of #69 step 1 is separate work and this
   deliberately does not pre-empt it. */

.hero-answer {
  margin: var(--space-5) 0;
}

.hero-answer__question {
  font-size: var(--text-lg);
  color: var(--text-secondary);
  margin-bottom: var(--space-2);
}

.hero-answer__verdict {
  font-size: var(--text-2xl);
  line-height: 1.1;
  margin-bottom: var(--space-3);
}

/* --sequential-450 rather than -600: it is one of the two ramp steps the dark blocks remap, so
   the accent word stays legible in both themes. -600 is only defined light and would sit at a
   dark blue on the dark page plane. */

.hero-answer__verdict .accent {
  color: var(--sequential-450);
}

.hero-answer__lede {
  font-size: 16px;
  line-height: 1.6;
  color: var(--ink2);
  max-width: 56ch;
  margin: 16px 0 var(--space-5);
}

.hero-answer__detail {
  color: var(--text-primary);
  margin-bottom: var(--space-2);
}

.disclaimer-note {
  color: var(--text-secondary);
  font-size: var(--text-sm);
  margin-top: var(--space-2);
}

/* Phase 20 (§50) -- probability visualisation. Both figures reset the browser's default
   <figure> margin so they sit on the same 4px spacing grid as everything else. */

.interval-band,
.cdf-chart {
  margin: var(--space-5) 0;
}

.interval-band__caption,
.cdf-chart__caption {
  color: var(--text-secondary);
  font-size: var(--text-sm);
  margin-bottom: var(--space-2);
}

.interval-band__axis {
  display: flex;
  justify-content: space-between;
  color: var(--text-secondary);
  font-size: var(--text-sm);
  margin-top: calc(-1 * var(--space-2));
  margin-bottom: var(--space-4);
}

/* §50 item 9. SVG text scales with the viewBox transform, so a single wide viewBox at width:100%
   cannot both stay legible on a phone and avoid oversized text on a desktop: a 720-unit viewBox
   in a 288px column renders a 12-unit label at 4.8 CSS px. The chart is therefore emitted at two
   sizes (public/charts.py's MOBILE_LAYOUT / WIDE_LAYOUT, whose legibility floor is enforced by a
   test) and exactly one is shown. Mobile-first per ADR-0007: the narrow variant is the base rule,
   the wide one is the min-width override. */

.cdf-chart__svg {
  display: block;
  width: 100%;
  height: auto;
  overflow: visible;
}

.cdf-chart__svg--wide {
  display: none;
  /* Never scaled above 1:1 -- past this width the chart stays 720px rather than growing its text
     along with the container. */
  max-width: 720px;
}

@media (min-width: 768px) {
  .cdf-chart__svg--mobile {
    display: none;
  }

  .cdf-chart__svg--wide {
    display: block;
  }
}

.cdf-chart__line {
  fill: none;
  /* 450, not 500: Phase 17 gave 400/450 dark-mode overrides but left 500 at its light value,
     which lands near WCAG 1.4.11's 3:1 floor for graphical objects on the dark surface. 450 is
     the ramp step that is already documented as readable in both themes. */
  stroke: var(--sequential-450);
  stroke-width: 2.5;
  stroke-linejoin: miter;
}

.cdf-chart__axis {
  stroke: var(--baseline);
  stroke-width: 1.5;
}

.cdf-chart__gridline {
  stroke: var(--gridline);
  stroke-width: 1;
}

/* Explanatory note under either figure -- carries the censoring/undefined-percentile wording,
   which is content, not decoration, so it stays at secondary (not muted) ink. */

.figure-note {
  font-size: var(--text-sm);
  color: var(--text-secondary);
}

.cdf-chart__marker {
  stroke: var(--text-secondary);
  stroke-width: 1.5;
  stroke-dasharray: 4 3;
}

/* Where the year-binned curve stands on a percentile's date -- deliberately the same ink as the
   curve itself, so it reads as a point *on the line* rather than as a third series. */
.cdf-chart__marker-dot {
  fill: var(--sequential-450);
  stroke: var(--surface);
  stroke-width: 1;
}

.cdf-chart__tick-label,
.cdf-chart__marker-label {
  fill: var(--text-secondary);
  /* font-size is a presentation attribute on each <text>, driven by the variant's ChartLayout --
     a CSS rule here would override it and silently detach the rendered size from the layout the
     legibility test checks. */
  font-variant-numeric: tabular-nums;
}

.cdf-chart__marker-label {
  fill: var(--text-primary);
  font-weight: 600;
}

.cdf-chart__legend {
  color: var(--text-secondary);
  font-size: var(--text-sm);
}

/* Tables. Until the first browser pass (#89) these had no rules at all, so every table on the
   site inherited the browser defaults: a centred caption wrapping over several ragged lines and a
   shrink-to-fit body sitting indented under it. Left-aligned caption, full-width body, and the
   same gridline token the charts use. */

table {
  width: 100%;
  border-collapse: collapse;
  font-size: var(--text-sm);
  font-variant-numeric: tabular-nums;
}

caption {
  text-align: left;
  color: var(--text-secondary);
  font-size: var(--text-sm);
  margin-bottom: var(--space-2);
}

/* §4 table treatment: uppercase 10.5px letterspaced headers in --ink3, 11px cells, hairline
   row rules. */

th,
td {
  text-align: left;
  padding: 11px;
  border-bottom: 1px solid var(--hair);
  vertical-align: top;
}

th {
  font-family: var(--font-sans);
  font-size: 10.5px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ink3);
  font-weight: 600;
}

/* The value table can outgrow a narrow viewport; it scrolls inside its own box so the page body
   never scrolls horizontally. */

.cdf-chart__table-scroll {
  overflow-x: auto;
}

/* Compact probability grid.

   Replaces 31 stacked rows with a grid that is five across on desktop. The years read left to
   right and then down, which is simply DOM order under `grid-auto-flow: row` -- the default. No
   rule reorders anything, so chronological order cannot drift at a breakpoint: only the column
   count changes.

   `minmax(0, 1fr)` rather than `1fr`: a bare `1fr` floors at the content's min-content width, so a
   long value pushes the grid wider than its container and the page scrolls sideways. The zero
   minimum is what keeps that from happening at any width, which is the requirement, not a
   preference. */

.cdf-grid {
  margin-top: 20px;
}

.cdf-grid__caption {
  margin: 0 0 10px;
  color: var(--ink2);
  font-size: 14px;
}

.cdf-grid__cells {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 1px;
  margin: 0;
  border: 1px solid var(--hair);
  background: var(--hair);
}

@media (min-width: 768px) {
  .cdf-grid__cells {
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }
}

@media (min-width: 1024px) {
  .cdf-grid__cells {
    grid-template-columns: repeat(5, minmax(0, 1fr));
  }
}

/* The 1px gap over a hairline background draws the rules, so each cell only needs its own
   surface -- no per-cell borders to double up at the seams. */

.cdf-grid__cell {
  min-width: 0;
  padding: 8px 10px;
  background: var(--card);
}

.cdf-grid__cell dt {
  color: var(--ink3);
  font-size: 12px;
  letter-spacing: 0.03em;
}

.cdf-grid__cell dd {
  margin: 1px 0 0;
  color: var(--ink);
  font-size: 15px;
  line-height: 1.3;
}

/* Restrained on purpose: a rule down the leading edge and a slightly raised surface, not a filled
   block. Thirty-one cells with three of them shouting would read as a chart of the emphasis
   rather than of the data. The accompanying `.cdf-grid__mark` carries the same fact in text, so
   the distinction survives greyscale, colour blindness and a screen reader. */

.cdf-grid__cell--percentile {
  background: var(--card2);
  box-shadow: inset 2px 0 0 var(--accent);
}

/* Evidence coverage panel.

   One full-width panel rather than two columns of rows: eight rows is short enough that a second
   column would only move the scanning problem sideways, and a single column keeps the backend's
   order unambiguous at every width -- which two columns cannot, since a reader has to decide
   whether to read down or across.

   No breakpoint changes the structure. The three columns wrap their text instead, so nothing
   overflows on a narrow screen and the order is the same everywhere. That is why there is no media
   query here: not an omission. */

.coverage__intro {
  margin: 0 0 16px;
  color: var(--ink2);
  max-width: 62ch;
}

.coverage__table {
  width: 100%;
  border-collapse: collapse;
  table-layout: auto;
}

.coverage__table th,
.coverage__table td {
  padding: 10px 12px;
  border-bottom: 1px solid var(--hair);
  text-align: left;
  vertical-align: baseline;
}

.coverage__table thead th {
  color: var(--ink3);
  font-size: 12px;
  font-weight: 500;
  letter-spacing: 0.04em;
  text-transform: uppercase;
}

.coverage__label {
  color: var(--ink);
  font-weight: 500;
}

.coverage__count-header,
.coverage__count {
  /* The count column takes only what it needs, so the two text columns keep the width. */
  width: 1%;
  white-space: nowrap;
}

.coverage__count {
  color: var(--ink);
}

.coverage__status {
  color: var(--ink2);
}

/* Decoration on top of a word that is already there. A reader in greyscale, or one who cannot
   distinguish the accent at all, loses nothing -- the status is text in its own column. */

.coverage__row--covered .coverage__label {
  box-shadow: inset 2px 0 0 var(--accent);
  padding-left: 12px;
}

.coverage__row--covered .coverage__status {
  color: var(--accent);
}

.cdf-grid__mark {
  display: flex;
  gap: 4px;
  align-items: baseline;
  margin: 3px 0 0;
  color: var(--accent);
  font-size: 11px;
  letter-spacing: 0.04em;
}

/* Home-page section rhythm */

/* §4 section rhythm: 52px of vertical padding, separated by a 1px hairline rule. */

.section {
  padding: var(--section-rhythm) 0;
  border-bottom: 1px solid var(--hair);
}

.section:last-of-type {
  border-bottom: 0;
}

/* Section eyebrow -- §2 lists it as an accent usage. */

.section__eyebrow {
  font-family: var(--font-sans);
  font-size: 10.5px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--accent);
  margin-bottom: var(--space-2);
}

.stub-note {
  font-size: var(--text-sm);
  color: var(--text-muted);
  font-style: italic;
}

/* Evidence constellation -- Phase 19, §49. The SVG fallback (server-rendered, always in the
   markup) is shown until constellation.js confirms a real WebGL scene is running and adds
   `.constellation--active`; covers no-JS, no-WebGL and prefers-reduced-motion alike. */

.constellation {
  position: relative;
  max-width: 480px;
  aspect-ratio: 1 / 1;
  margin: var(--space-5) auto;
}

.constellation-canvas {
  position: absolute;
  inset: 0;
}

.constellation-canvas canvas {
  display: block;
  width: 100%;
  height: 100%;
}

.constellation-fallback {
  display: block;
  width: 100%;
  height: 100%;
}

.constellation--active .constellation-fallback {
  display: none;
}

/* §4 -- the navigation is a bar with a rule under it, not a row of underlined links. */

nav.site-nav {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--space-5);
  padding: var(--space-4) 0;
  margin-bottom: var(--space-6);
  border-bottom: 1px solid var(--hair);
  font-size: var(--text-sm);
}

/* `:not(.button-accent)` rather than raising the button's specificity (#109). The nav's link rule
   is 0,0,1,2 and `.button-accent` is 0,0,1,0, so the CTA inside the nav was taking --ink2 as its
   foreground on the accent fill -- 1.15:1, which reads as a blank green pill rather than as text.
   The identical button in the rail was fine, because nothing overrides it there.

   Raising the button's specificity would fix this instance and fail the next time the component
   is placed inside anything with its own link rule. Excluding it here scopes the rule to what it
   is actually for: the link list. The same exclusion also stops the button taking the hover
   underline treatment it was never designed for. */

nav.site-nav a:not(.button-accent) {
  color: var(--ink2);
  text-decoration: none;
  padding-bottom: var(--space-1);
  border-bottom: 1px solid transparent;
}

nav.site-nav a:not(.button-accent):hover,
nav.site-nav a:not(.button-accent):focus-visible {
  color: var(--ink);
  border-bottom-color: var(--accent);
}

/* The wordmark: first link in the bar, in the display face so the register is set before the
   reader gets to the hero. */

nav.site-nav a:first-child:not(.button-accent) {
  font-family: var(--font-display);
  font-size: var(--text-lg);
  color: var(--ink);
  letter-spacing: var(--letter-spacing-display);
  margin-right: auto;
}

/* #102 B2: the mock's footer is a row of identity spans, not one grey line. It wraps rather than
   scrolls at 320, and the note keeps its own line so the legal disclaimer never shares one with
   the filing reference. */

footer.site-footer {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2) var(--space-5);
  align-items: baseline;
  margin-top: var(--space-8);
  padding-top: var(--space-5);
  border-top: 1px solid var(--rule);
  color: var(--ink3);
  font-size: var(--text-xs);
}

.site-footer__links {
  margin-left: auto;
}

.site-footer__note {
  flex-basis: 100%;
  color: var(--ink3);
}

/* Scientific disclaimer -- item 10's visual shell */

.disclaimer {
  font-size: var(--text-sm);
  color: var(--text-secondary);
  /* §2 lists the disclaimer's left border as one of the six places the accent is used. */
  border-left: 3px solid var(--accent);
  padding: var(--space-3) var(--space-4);
  margin: var(--space-5) 0;
  background: var(--surface);
}

/* Source citation -- item 11's visual shell */

.citation {
  font-size: var(--text-sm);
  color: var(--text-secondary);
  padding: var(--space-2) 0;
  border-bottom: 1px solid var(--gridline);
}

.citation__name {
  color: var(--text-primary);
  font-weight: 600;
}

/* Loading / error states -- item 9. Defined for future async-loaded content (no page in this
   phase loads anything asynchronously yet). */

.skeleton {
  background: linear-gradient(90deg, var(--gridline) 25%, var(--border) 37%, var(--gridline) 63%);
  background-size: 400% 100%;
  animation: skeleton-shimmer 1.4s ease infinite;
  border-radius: var(--radius);
  color: transparent;
}

@keyframes skeleton-shimmer {
  0% { background-position: 100% 50%; }
  100% { background-position: 0 50%; }
}

.error-state {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  color: var(--status-critical);
  font-size: var(--text-sm);
  padding: var(--space-3) var(--space-4);
  border: 1px solid var(--status-critical);
  border-radius: var(--radius);
}

.error-state::before {
  content: "⚠";
}

/* Reduced motion -- item 8. Disables every transition/animation this stylesheet defines. */

@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;
  }
}

/* ---------------------------------------------------------------------------
   Phase 21 (§51) -- evidence list, filters, pagination, breadcrumbs.

   All spacing on the existing 4px grid and all colour from existing tokens: this phase adds no new
   custom property, so there is still nothing in this stylesheet whose value is unexplained (§47).
   The filter form is mobile-first per ADR-0007 -- one column below 768px, auto-fitting columns
   above, using the same 768px breakpoint docs/responsive-design.md fixed.
   --------------------------------------------------------------------------- */

.breadcrumb {
  font-size: var(--text-sm);
  color: var(--text-secondary);
  margin-bottom: var(--space-3);
}

.filter-form {
  border: 1px solid var(--border);
  border-radius: 6px;
  padding: var(--space-4);
  margin: var(--space-5) 0;
}

.filter-form__row,
.filter-form__field {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
}

.filter-form__grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-4);
  margin-top: var(--space-4);
}

@media (min-width: 768px) {
  .filter-form__grid {
    /* Never narrower than 12rem: a multi-select whose option text is clipped is worse than fewer
       columns, and category labels here run to "Cryptanalytic resources (ECC)". */
    grid-template-columns: repeat(auto-fit, minmax(12rem, 1fr));
  }
}

.filter-form label {
  font-size: var(--text-sm);
  font-weight: 600;
}

.filter-form input,
.filter-form select,
.filter-form button {
  font: inherit;
  color: var(--text-primary);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 4px;
  padding: var(--space-2);
  /* Without this a long <option> stretches the grid column past the viewport on narrow screens. */
  max-width: 100%;
}

.filter-form__actions {
  display: flex;
  align-items: center;
  gap: var(--space-4);
  margin-top: var(--space-4);
}

.filter-form button {
  font-weight: 600;
  cursor: pointer;
}

.evidence-list {
  list-style: none;
  padding: 0;
  margin: var(--space-4) 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
}

.evidence-list__title {
  font-size: var(--text-lg);
  margin: 0 0 var(--space-2);
}

.evidence-list__meta {
  font-size: var(--text-sm);
  color: var(--text-secondary);
  margin: 0 0 var(--space-2);
}

.evidence-list__summary {
  margin: 0;
}

.pagination {
  display: flex;
  align-items: center;
  gap: var(--space-4);
  margin: var(--space-5) 0;
}

.pagination__status {
  color: var(--text-secondary);
  font-size: var(--text-sm);
}

.evidence-detail__meta {
  font-size: var(--text-sm);
  color: var(--text-secondary);
}

blockquote {
  margin: var(--space-4) 0;
  padding-left: var(--space-4);
  border-left: 3px solid var(--border);
  color: var(--text-secondary);
}

/* Retracted evidence (§51, founder decision 2026-07-26). Uses the existing status-danger token
   rather than a new colour, and pairs it with the literal word "Retracted" in the markup -- §47
   item 6 requires colour to be paired with an icon or label, never carrying meaning alone. */

.retraction-notice {
  border: 2px solid var(--status-critical);
  border-radius: 6px;
  padding: var(--space-4);
  margin-bottom: var(--space-5);
  color: var(--text-primary);
}

/* Phase 22 (§52) -- methodology page: table of contents and numbered equations. Existing tokens
   only; the equation body is set in the same tabular-numeral treatment the charts use so a
   formula and the figures it produces read consistently. */

.toc {
  border-left: 3px solid var(--border);
  padding-left: var(--space-4);
  margin: var(--space-5) 0;
  font-size: var(--text-sm);
}

.equation {
  border: 1px solid var(--border);
  border-radius: 6px;
  padding: var(--space-4);
  margin: var(--space-4) 0;
}

.equation__label {
  margin: 0 0 var(--space-2);
  color: var(--text-secondary);
  font-size: var(--text-sm);
}

.equation__body {
  margin: 0 0 var(--space-2);
  /* Wraps rather than scrolling the page sideways on a phone -- these are prose-length formulas,
     not code blocks. */
  overflow-wrap: anywhere;
}

.citation__caveat {
  color: var(--text-secondary);
}

/* Synthetic-data banner (issue #83 condition 1). Uses the fixed status-warning token with a text
   label beside it -- never colour alone, per item 4's usage rule. Full-width at the top of the
   content column so it is inside any screenshot that includes the page's first line. */

.synthetic-banner {
  border: 2px solid var(--status-warning);
  border-radius: var(--radius);
  background: var(--surface);
  color: var(--text-primary);
  padding: var(--space-3) var(--space-4);
  margin-bottom: var(--space-5);
  font-size: var(--text-sm);
}

/* §4 -- the home hero is a split: 1.35fr of content against a 0.95fr rail, 36px gap, collapsing
   to one column below 940px. The rail card sticks under the navigation rather than scrolling away,
   because it carries the model-status and QRP cards a reader wants while reading the forecast. */

.hero-split {
  display: grid;
  grid-template-columns: 1.35fr 0.95fr;
  gap: var(--hero-gap);
  align-items: start;
}

@media (max-width: 939px) {
  .hero-split {
    grid-template-columns: 1fr;
  }
}

.hero-rail {
  position: sticky;
  top: var(--space-5);
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
}

/* Below the collapse the rail is no longer beside anything, so sticking it to the viewport would
   trap it over the content it belongs beside. */

@media (max-width: 939px) {
  .hero-rail {
    position: static;
  }
}

.hero-answer__question {
  font-family: var(--font-sans);
  font-size: var(--text-lg);
  color: var(--ink3);
  margin-bottom: var(--space-2);
}

.hero-answer__verdict {
  font-family: var(--font-display);
  font-size: var(--text-hero);
  line-height: var(--line-height-tight);
  letter-spacing: var(--letter-spacing-display);
  color: var(--ink);
  margin-bottom: var(--space-4);
}

.hero-answer__verdict .accent {
  color: var(--accent);
}

.hero-answer__lede {
  font-size: 16px;
  line-height: 1.6;
  color: var(--ink2);
  max-width: 56ch;
  margin: 16px 0 var(--space-5);
}

.hero-answer__detail {
  color: var(--ink2);
  max-width: 62ch;
}

/* §4 chips: 999px pills. The active chip takes the accent border and accent text and is NEVER a
   filled background -- a filled chip reads as a button that has been pressed, and these select
   which forecast is shown rather than expressing a preference (§5.1 item 3). */

.chip-row {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  margin: var(--space-4) 0;
}

.chip {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-2) var(--space-4);
  border: 1px solid var(--rule);
  border-radius: 999px;
  background: transparent;
  color: var(--ink2);
  font-size: var(--text-sm);
  text-decoration: none;
}

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

.chip--active {
  border-color: var(--accent);
  color: var(--accent);
  background: transparent;
}

/* Primary button -- §2's remaining accent usage. Outlined for the same reason as the chip: this
   site does not shout. */

.button-primary {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-3) var(--space-5);
  border: 1px solid var(--accent);
  border-radius: var(--radius);
  color: var(--accent);
  background: transparent;
  font-size: var(--text-sm);
  text-decoration: none;
}

/* The interval band takes the accent fill (§2's list), replacing the sequential-blue gradient
   Phase 18 used. Series colours stay on the categorical palette, so green keeps one meaning. */

.p10-p90-band {
  background: linear-gradient(90deg, color-mix(in srgb, var(--accent) 45%, transparent),
    var(--accent));
}

.p10-p90-band--censored {
  background: linear-gradient(90deg, color-mix(in srgb, var(--accent) 45%, transparent),
    color-mix(in srgb, var(--accent) 80%, transparent));
}

.p10-p90-band__marker {
  background: var(--ink);
}

.p10-p90-band__censored-region {
  border-left-color: var(--ink);
  background: repeating-linear-gradient(
    135deg,
    var(--rule),
    var(--rule) 4px,
    var(--card) 4px,
    var(--card) 8px
  );
}

/* Theme control (#98, founder decision). Part of §4's navigation rather than a floating widget:
   the site states its appearance and offers the alternative in the same bar as everything else it
   states. Outlined like the chips -- this is a control, not a call to action. */

.theme-toggle {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-1) var(--space-3);
  border: 1px solid var(--rule);
  border-radius: 999px;
  background: transparent;
  color: var(--ink2);
  font-family: var(--font-sans);
  font-size: var(--text-xs);
  cursor: pointer;
}

.theme-toggle:hover {
  color: var(--ink);
  border-color: var(--ink3);
}

/* Only one of the two labels is ever shown: the button names the theme it switches *to*, which is
   what a reader expects from a control rather than a status readout. */

.theme-toggle__to-light,
.theme-toggle__to-dark {
  display: none;
}

:root:not([data-theme="light"]) .theme-toggle__to-light {
  display: inline;
}

:root[data-theme="light"] .theme-toggle__to-dark {
  display: inline;
}

/* #102 -- composition from the approved variant B mock. Tokens were already identical; these are
   the parts where the page was laid out differently from what was approved. */

/* A1: the question is the page's largest text, not the site's own name. */

.hero-answer__headline {
  font-family: var(--font-display);
  font-size: var(--text-hero);
  line-height: var(--line-height-tight);
  letter-spacing: var(--letter-spacing-display);
  color: var(--ink);
  margin: 0 0 var(--space-4);
}

.hero-answer__headline .accent {
  color: var(--accent);
}

.section--hero {
  padding-top: 0;
}

/* A3/A4: a filled accent button. The mock reserves the filled treatment for the one commercial
   action on the page -- the outlined `.button-primary` and `.chip` stay for everything else, so
   "filled" keeps meaning "this is the action". */

.button-accent {
  display: inline-block;
  font: 600 13px/1 var(--font-sans);
  padding: 11px 17px;
  border-radius: 8px;
  border: 1px solid var(--accent);
  background: var(--accent);
  color: var(--paper);
  text-decoration: none;
}

.button-accent:hover {
  filter: brightness(1.08);
}

.site-nav__cta {
  margin-left: var(--space-2);
}

/* The eyebrow inside a rail card is accent, per the mock; the section eyebrow above the hero is
   the muted one already defined further up. */

.eyebrow {
  font-size: 11px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--accent);
  font-weight: 700;
  margin: 0;
}

.railcard__title {
  font-size: 22px;
  margin: 10px 0 8px;
}

.railcard__lede {
  font-size: 13px;
  line-height: 1.6;
  color: var(--ink2);
  margin: 0;
}

.railcard__tag {
  font-size: 11px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--ink3);
  margin: 0;
}

.railcard__status {
  font-family: var(--font-display);
  font-size: 23px;
  color: var(--ink);
  margin: 8px 0 3px;
}

.railcard__note {
  font-size: 11.5px;
  color: var(--ink3);
  line-height: 1.55;
  margin: 11px 0 0;
}

.railcard__cta {
  display: block;
  text-align: center;
  margin-top: 18px;
}

.featlist {
  list-style: none;
  padding: 0;
  margin: 16px 0 0;
  display: grid;
  gap: 8px;
}

.featlist li {
  font-size: 13px;
  color: var(--ink2);
  padding-left: 19px;
  position: relative;
  line-height: 1.5;
}

/* Square accent markers rather than bullets -- the mock's, and they read as an index rather than
   as marketing ticks. */

.featlist li::before {
  content: "";
  position: absolute;
  left: 0;
  top: 6px;
  width: 7px;
  height: 7px;
  border-radius: 2px;
  background: var(--accent);
}

/* B3: the band reads as an instrument. A thin rule carrying an accent segment, with three
   labelled columns beneath -- not a thick progress bar with a table under it. The table stays as
   the accessible fallback. */

.p10-p90-band {
  height: 16px;
  border-radius: 9px;
}

.band-labels {
  display: flex;
  justify-content: space-between;
  gap: var(--space-3);
  margin-top: 8px;
  font-size: 12px;
  color: var(--ink3);
}

.band-labels__item {
  flex: 1;
}

.band-labels__item--mid {
  text-align: center;
}

.band-labels__item--end {
  text-align: right;
}

.band-labels b {
  display: block;
  font-family: var(--font-display);
  font-size: 15px;
  color: var(--ink);
  font-variant-numeric: tabular-nums;
  font-weight: 600;
}

/* §5.1 item 6 -- "what has to be true". Pills follow the mock: outlined, never filled, and
   `undefined` in --warn so it is visually distinct from `not met` rather than a quieter version of
   it. A condition whose inputs are missing is blocked, not zero, and the whole point of the panel
   is that a reader can see the difference at a glance. */

.pill {
  display: inline-block;
  font-size: 11px;
  padding: 3px 9px;
  border-radius: 999px;
  border: 1px solid var(--rule);
  color: var(--ink2);
  background: transparent;
}

.pill--met {
  border-color: color-mix(in srgb, var(--accent) 45%, transparent);
  color: var(--accent);
}

.pill--undefined {
  border-color: color-mix(in srgb, var(--warn) 45%, transparent);
  color: var(--warn);
}

.pill--not-met {
  border-color: color-mix(in srgb, var(--bad) 40%, transparent);
  color: var(--bad);
}

.gate-panel__headline {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--space-3);
  color: var(--ink2);
  max-width: 72ch;
}

.gate-grid {
  list-style: none;
  padding: 0;
  margin: var(--space-5) 0 0;
  display: grid;
  gap: var(--space-3);
  grid-template-columns: 1fr;
}

@media (min-width: 768px) {
  .gate-grid {
    grid-template-columns: repeat(2, 1fr);
  }
}

@media (min-width: 1024px) {
  .gate-grid {
    grid-template-columns: repeat(4, 1fr);
  }
}

.gate-card__state {
  margin: var(--space-2) 0;
}

.gate-card__description {
  font-size: 12.5px;
  line-height: 1.55;
  color: var(--ink2);
  margin: 0;
}

/* §5.1 items 3, 5 and 7. */

.chip-row__label {
  font-size: 10.5px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--ink3);
  align-self: center;
}

.chip-row__label--spaced {
  margin-left: var(--space-4);
}

/* A chip that is a statement rather than a control: same shape, no hover, no link affordance --
   the attack window is a fact about the run, not something a reader picks. */

.chip--static {
  color: var(--ink2);
  cursor: default;
}

.target-grid {
  list-style: none;
  padding: 0;
  margin: var(--space-5) 0 0;
  display: grid;
  gap: var(--space-3);
  grid-template-columns: 1fr;
}

@media (min-width: 768px) {
  .target-grid {
    grid-template-columns: repeat(3, 1fr);
  }
}

.target-card__interval {
  font-family: var(--font-display);
  font-size: 28px;
  color: var(--ink);
  font-variant-numeric: tabular-nums;
  margin: var(--space-2) 0 var(--space-3);
}

.citation-block {
  font-family: var(--font-mono);
  font-size: var(--text-sm);
  color: var(--ink2);
  background: var(--card);
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  padding: var(--space-4);
  overflow-x: auto;
  white-space: pre-wrap;
  word-break: break-word;
}

/* #121 -- the constellation section. */

.constellation-key {
  list-style: none;
  padding: 0;
  margin: var(--space-4) 0 0;
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
}

.constellation-key__count {
  color: var(--ink3);
}

/* The note explains a *deliberate* fallback and must not appear when the scene is simply running,
   nor when it failed -- a failure is not something to reassure the reader about. */

.constellation-note {
  display: none;
}

.constellation[data-fallback-reason="reduced-motion"] ~ .constellation-note {
  display: block;
}

/* Independent institutional context in the hero (external_outlooks.html). Existing tokens only --
   no new palette, and no chart: an expert survey's 10-year band and a migration deadline are not
   points on this model's curve, so nothing here may draw them as if they were. */

.outlooks__heading {
  font-size: var(--text-lg);
  margin-bottom: var(--space-2);
}

.outlooks__intro {
  color: var(--text-secondary);
  font-size: var(--text-sm);
  margin-bottom: var(--space-4);
}

/* Two columns where there is room, one below it. `auto-fit` with a floor rather than a media
   query: the cards live inside the hero's left column, whose width depends on the hero grid, so
   the breakpoint that matters is the container's, not the viewport's. */
.outlooks__grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(260px, 100%), 1fr));
  gap: var(--space-4);
}

.outlook {
  /* The grid's `gap` owns the spacing; `.card`'s own bottom margin would double it. */
  margin-bottom: 0;
}

.outlook__label {
  font-family: var(--font-sans);
  font-size: 10.5px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--text-secondary);
  margin: 0 0 var(--space-3);
}

.outlook__lines {
  margin: 0 0 var(--space-3);
  display: grid;
  gap: var(--space-3);
}

.outlook__line {
  display: grid;
  gap: 2px;
}

.outlook__figure {
  font-family: var(--font-display);
  font-size: var(--text-lg);
  line-height: 1.15;
  /* Long institutional ranges ("2026 -> 2030") must break rather than set the card's minimum
     width -- the failure that made /model-history/4/ shrink the whole page to fit. */
  overflow-wrap: anywhere;
}

.outlook__meaning {
  margin: 0;
  font-size: var(--text-sm);
  color: var(--text-secondary);
}

.outlook__supporting {
  font-size: var(--text-sm);
  color: var(--text-secondary);
  margin: 0 0 var(--space-2);
}

.outlook__caveat {
  font-size: var(--text-sm);
  margin: 0 0 var(--space-2);
  padding-left: var(--space-3);
  border-left: 2px solid var(--rule);
}

.outlook__source {
  font-size: var(--text-sm);
  color: var(--text-secondary);
  margin: 0;
}

.outlooks__note {
  margin-top: var(--space-4);
  font-size: var(--text-sm);
  color: var(--text-secondary);
}


/* Contact form and desktop downloads (Launch: pilot CTA correction). Existing tokens only. */

.contact__lede {
  max-width: 62ch;
  color: var(--text-secondary);
  margin-bottom: var(--space-5);
}

.contact-form {
  max-width: 34rem;
  display: grid;
  gap: var(--space-4);
}

.contact-form .field {
  display: grid;
  gap: var(--space-2);
}

.contact-form label {
  font-size: var(--text-sm);
  color: var(--text-secondary);
}

.contact-form input[type="text"],
.contact-form input[type="email"],
.contact-form textarea {
  font-family: var(--font-sans);
  font-size: var(--text-base);
  color: var(--text-primary);
  background: var(--card);
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  padding: 10px 12px;
  width: 100%;
}

.contact-form textarea {
  resize: vertical;
}

.contact-form button {
  justify-self: start;
}

.field__error {
  margin: 0;
  font-size: var(--text-sm);
  color: var(--status-warning, var(--accent));
}

.contact-form__errors {
  border-left: 2px solid var(--rule);
  padding-left: var(--space-3);
  font-size: var(--text-sm);
}

/* The honeypot. Removed from the layout entirely rather than merely hidden off-screen: nothing
   here should ever occupy space, and a person should never reach it by keyboard either (the
   field itself carries tabindex="-1"). */
.contact-form__trap {
  display: none;
}

.contact__note {
  max-width: 62ch;
  font-size: var(--text-sm);
  color: var(--text-secondary);
}

.downloads {
  list-style: none;
  padding: 0;
  margin: 0 0 var(--space-4);
  display: grid;
  gap: var(--space-3);
}

.downloads li {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--space-2) var(--space-3);
}

.downloads__note {
  font-size: var(--text-sm);
  color: var(--text-secondary);
}


/* Legal pages and the expanded footer (launch). Existing tokens only. */

.prose {
  max-width: 68ch;
}

.prose h2 {
  margin-top: var(--space-5);
}

.prose h3 {
  font-size: var(--text-base);
  margin-top: var(--space-4);
}

.prose li {
  margin-bottom: var(--space-2);
}

.legal__updated {
  font-size: var(--text-sm);
  color: var(--text-secondary);
}

.legal__address {
  font-style: normal;
  line-height: 1.7;
  padding-left: var(--space-3);
  border-left: 2px solid var(--rule);
}

/* Six links have to wrap on a phone without ever widening the page. `inline` links inside a
   flex-wrapping nav keep the separators attached to the link before them. */
.site-footer__links {
  display: flex;
  flex-wrap: wrap;
  gap: 0 var(--space-2);
  align-items: baseline;
}
