/*! NewsBridge design system — FINAL (base: proposal v2 "Calm Reader", with fixes and grafts from v1 and v3)
 *
 * Readability-first for readers aged 65+. Borrowed from thelobbynews.com/overwatch: square geometry,
 * near-black canvas, white (ink in light mode) as the only accent, green = good / red = problem,
 * mono for numbers, dates and short labels, corner-bracket crop marks on key panels only, a restrained glow.
 *
 * 0  Contents and conventions
 *    - Type is rem-only (no px font sizes). px only for hairlines, focus rings, bracket strokes and dots.
 *    - Breakpoints (em, so the text-size switcher never flips them; browser zoom does):
 *        40em = 640px   48em = 768px   64em = 1024px (sidebar)   90em = 1440px
 *    - Container queries use rem, so layouts also react to the text-size switcher:
 *        "main" (the page column), "topbar", "display" (text & colours form), "login".
 *      Every container rule has a sensible fallback (the one-column layout) for older browsers.
 *    - Radius is 0 everywhere except status dots.
 *    - No @layer (drops everything on iOS < 15.4). Precedence = source order + single-class components.
 *    - JS hooks are data-* attributes only (see app.js). CSS never selects IDs.
 *    - Selected / current state = filled surface + a second non-colour cue (bar, bracket or check mark).
 *    - Mono UPPERCASE only for labels of 3 words or fewer; letter-spacing never above 0.08em.
 * 1  Tokens: light (default)                       @tokens-light
 * 2  Tokens: dark, follows the OS                   @tokens-dark-os    KEEP IDENTICAL TO 3
 * 3  Tokens: dark, chosen by the user               @tokens-dark-user  KEEP IDENTICAL TO 2
 * 4  Text size (2 steps)
 * 5  Reset and base elements
 * 6  Accessibility helpers
 * 7  Layout primitives
 * 8  App shell (sidebar, top bar, bottom tab bar, More sheet, Text & colours panel)
 * 9  Components (icon … code box, tables, pager, empty state)
 * 10 Page blocks (news-, story-, chat-, dash-, admin-, login-, error-) live in pages/<page>.css
 * 11 Utilities
 * 12 Motion, contrast and forced colours
 * 13 Print
 */

/* ============================================================================
   1  TOKENS — LIGHT (default)
   ========================================================================== */
/* @tokens-light */
:root {
  color-scheme: light;

  /* surfaces: canvas is one step darker than panels; insets step toward pure white */
  --bg: #e9e9ec;
  --surface-1: #f8f8f8;
  --surface-2: #efeff2;
  --surface-3: #e2e2e7;
  --inset: #ffffff;

  /* lines */
  --border-subtle: #d9d9de;
  --border: #c6c6cd;
  --border-strong: #9a9aa3;
  --border-control: #71717b;
  --bracket: #3f3f46;

  /* text */
  --text: #0a0a0b;
  --text-body: #1d1d20;
  --text-2: #37373e;
  --text-muted: #4b4b54;
  --text-disabled: #6a6a74;

  /* accent = ink in light mode (white in dark mode) */
  --accent: #0a0a0b;
  --accent-hover: #2c2c31;
  --on-accent: #fafafa;
  --accent-glow: rgba(10, 10, 11, 0.16);

  /* status */
  --success: #005c40;
  --success-mark: #007a55;
  --success-tint: #e3f5ec;
  --success-tint-border: #86cdae;
  --success-tint-text: #00563c;
  --danger: #a8000a;
  --danger-mark: #d9000b;
  --danger-tint: #fdecec;
  --danger-tint-border: #eb9ea1;
  --danger-tint-text: #920610;
  --info: #1240d0;
  --info-mark: #2a78d6;
  --info-tint: #e8f0ff;
  --info-tint-border: #94b5f2;
  --info-tint-text: #11379a;
  --warning: #7c4300;
  --warning-mark: #a86b00;
  --warning-tint: #fff4d6;
  --warning-tint-border: #dcb055;
  --warning-tint-text: #6e3700;

  /* the Overwatch "PASSED" glow badge, inverted for light mode */
  --badge-pos-bg: #0a0a0b;
  --badge-pos-text: #5ee9b5;
  --badge-pos-border: #0a0a0b;

  --focus: #0a0a0b;
  --selection-bg: #0a0a0b;
  --selection-text: #fafafa;
  --hover: rgba(10, 10, 11, 0.05);
  --pressed: rgba(10, 10, 11, 0.09);

  /* inverted panel (API key reveal, skip link). Paper in dark mode, ink in light mode */
  --inverse-bg: #111113;
  --inverse-text: #f4f4f5;
  --inverse-muted: #a8a8b2;
  --inverse-control: #8a8a94;
  --inverse-border: #34343a;

  /* terminal "black box" — stays dark in both themes */
  --terminal-bg: #111113;
  --terminal-text: #e4e4e7;
  --terminal-muted: #a8a8b2;
  --terminal-success: #00d492;
  --terminal-border: #111113;

  /* segmented progress bar */
  --seg-on: var(--accent);
  --seg-on-hex: #0a0a0b;
  --seg-off: #cacad1;

  --tag-bg: #e7e7eb;
  --bubble-user: #e2e2e7;

  /* publication marks (squares, chart series). Names always stay in text colours */
  --pub-nyt: #2a78d6;
  --pub-ft: #d4531f;
  --pub-bg: #4a3aa7;
  --pub-wsj: #a87400;
  --pub-wp: #c94f80;

  /* charts (read by chart-utils.js; resolved values are hex or comma rgba only) */
  --chart-line: #0a0a0b;
  --chart-area: rgba(10, 10, 11, 0.07);
  --chart-axis: #71717b;
  --chart-grid: #dcdce1;
  --chart-text: #1d1d20;
  --chart-text-2: #4b4b54;
  --chart-zoom-fill: rgba(10, 10, 11, 0.1);

  --grid-dot: rgba(10, 10, 11, 0.07);
  --overlay-backdrop: rgba(10, 10, 11, 0.45);
  --shadow-overlay: 0 1.5rem 3rem rgba(10, 10, 11, 0.18);

  --display-weight: 400;          /* thin strokes look thinner on paper: 400 minimum in light mode */
  --icon-grade: 0;

  /* ---- chart aliases (resolve per theme; no need to repeat them in the dark blocks) ---- */
  --chart-surface: var(--surface-1);
  --chart-tooltip-bg: var(--inverse-bg);
  --chart-tooltip-text: var(--inverse-text);
  --chart-tooltip-border: var(--inverse-border);
  --chart-zoom-handle: var(--accent);
  --chart-1: var(--pub-nyt);
  --chart-2: var(--pub-ft);
  --chart-3: var(--pub-bg);
  --chart-4: var(--pub-wsj);
  --chart-5: var(--pub-wp);
  --chart-6: var(--text-muted);

  /* ---- non-colour tokens (shared by both themes) ---- */
  --font-sans: "IBM Plex Sans", system-ui, -apple-system, "Segoe UI", Roboto, Arial, sans-serif;
  --font-mono: "IBM Plex Mono", ui-monospace, "Cascadia Mono", Consolas, "SFMono-Regular", Menlo, monospace;
  --font-icon: "Material Symbols Sharp";

  /* Type scale, written for Large (1rem = 16px there; see section 4). Standard / Large in px:
     body 16 / 19, secondary 14.3 / 17, labels 12.6 / 15 */
  --fs-display: clamp(2.5rem, 1.9rem + 2.6vw, 4rem);
  --fs-h1: clamp(2.125rem, 1.8rem + 1.4vw, 3rem);
  --fs-h2: 1.625rem;
  --fs-headline: clamp(1.3125rem, 1.2rem + 0.45vw, 1.5rem);
  --fs-h3: 1.3125rem;
  --fs-lead: 1.3125rem;
  --fs-body: 1.1875rem;
  --fs-small: 1.0625rem;
  --fs-button: 1.125rem;
  --fs-label: 0.9375rem;
  --fs-meta: 1rem;
  --fs-num: clamp(2rem, 1.7rem + 0.9vw, 2.5rem);
  --fs-num-xl: clamp(3rem, 2.2rem + 3.5vw, 5rem);
  --fs-brand: 1.0625rem;

  --lh-body: 1.65;
  --lh-small: 1.55;
  --lh-head: 1.25;
  --lh-tight: 1.1;
  --lh-label: 1.35;
  --track-label: 0.05em;          /* never more than 0.08em */
  --track-brand: 0.08em;
  --measure: 68ch;

  --sp-1: 0.25rem;
  --sp-2: 0.5rem;
  --sp-3: 0.75rem;
  --sp-4: 1rem;
  --sp-5: 1.25rem;
  --sp-6: 1.5rem;
  --sp-8: 2rem;
  --sp-10: 2.5rem;
  --sp-12: 3rem;
  --sp-16: 4rem;

  --tap: 3rem;                    /* 40px at Standard, 48px at Large */
  --tap-lg: 3.5rem;               /* 47px / 56px: search, sign in, send */
  --gutter: 1rem;
  --panel-pad: 1.25rem;
  --sidebar-w: 17rem;
  --topbar-h: 4rem;
  --tabbar-h: 4.5rem;
  --page-max: 62rem;              /* page frame at Large text; Standard keeps the same width on screen (4 TEXT SIZE) */
  --page-max-wide: 82rem;
  --bracket-len: 1.25rem;
  --bracket-w: 1px;
  --busy-stripe: rgba(128, 128, 128, 0.3);
  --safe-bottom: env(safe-area-inset-bottom, 0px);
  --dur: 120ms;
  --z-sticky: 30;
  --z-tabbar: 40;
  --z-pop: 45;
  --z-skip: 100;
}

/* ============================================================================
   2  TOKENS — DARK, follows the OS unless the user chose Light
   ========================================================================== */
/* @tokens-dark-os */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    color-scheme: dark;
    --bg: #09090b;
    --surface-1: #111113;
    --surface-2: #18181b;
    --surface-3: #232327;
    --inset: #050506;
    --border-subtle: #26262b;
    --border: #35353b;
    --border-strong: #4d4d56;
    --border-control: #82828c;
    --bracket: #8b8b95;
    --text: #f4f4f5;
    --text-body: #dcdce1;
    --text-2: #c0c0c8;
    --text-muted: #a8a8b2;
    --text-disabled: #77777f;
    --accent: #ffffff;
    --accent-hover: #d9d9de;
    --on-accent: #09090b;
    --accent-glow: rgba(255, 255, 255, 0.22);
    --success: #00d492;
    --success-mark: #00bc7d;
    --success-tint: #062a1f;
    --success-tint-border: #0e7a59;
    --success-tint-text: #6ef0bf;
    --danger: #ff8a8c;
    --danger-mark: #fb2c36;
    --danger-tint: #3a0d10;
    --danger-tint-border: #8a1c1f;
    --danger-tint-text: #ffb0b0;
    --info: #7ab8ff;
    --info-mark: #3987e5;
    --info-tint: #0c1d3a;
    --info-tint-border: #2257a8;
    --info-tint-text: #add4ff;
    --warning: #ffc53d;
    --warning-mark: #f0b100;
    --warning-tint: #2e2206;
    --warning-tint-border: #8a6600;
    --warning-tint-text: #ffd580;
    --badge-pos-bg: #ffffff;
    --badge-pos-text: #006b4a;
    --badge-pos-border: #ffffff;
    --focus: #ffffff;
    --selection-bg: #ffffff;
    --selection-text: #09090b;
    --hover: rgba(255, 255, 255, 0.06);
    --pressed: rgba(255, 255, 255, 0.1);
    --inverse-bg: #f4f4f5;
    --inverse-text: #09090b;
    --inverse-muted: #4b4b54;
    --inverse-control: #71717b;
    --inverse-border: #c6c6cd;
    --terminal-bg: #050506;
    --terminal-text: #dcdce1;
    --terminal-muted: #a8a8b2;
    --terminal-success: #00d492;
    --terminal-border: #35353b;
    --seg-on: var(--accent);
    --seg-on-hex: #ffffff;
    --seg-off: #303036;
    --tag-bg: #1f1f23;
    --bubble-user: #232327;
    --pub-nyt: #3987e5;
    --pub-ft: #d95926;
    --pub-bg: #9085e9;
    --pub-wsj: #c98500;
    --pub-wp: #d55181;
    --chart-line: #f4f4f5;
    --chart-area: rgba(244, 244, 245, 0.09);
    --chart-axis: #82828c;
    --chart-grid: #26262b;
    --chart-text: #dcdce1;
    --chart-text-2: #a8a8b2;
    --chart-zoom-fill: rgba(244, 244, 245, 0.12);
    --grid-dot: rgba(255, 255, 255, 0.04);
    --overlay-backdrop: rgba(0, 0, 0, 0.65);
    --shadow-overlay: 0 1.5rem 4rem rgba(0, 0, 0, 0.7);
    --display-weight: 300;
    --icon-grade: -25;
  }
}

/* ============================================================================
   3  TOKENS — DARK, chosen by the user (KEEP IDENTICAL TO SECTION 2)
   ========================================================================== */
/* @tokens-dark-user */
:root[data-theme="dark"] {
    color-scheme: dark;
    --bg: #09090b;
    --surface-1: #111113;
    --surface-2: #18181b;
    --surface-3: #232327;
    --inset: #050506;
    --border-subtle: #26262b;
    --border: #35353b;
    --border-strong: #4d4d56;
    --border-control: #82828c;
    --bracket: #8b8b95;
    --text: #f4f4f5;
    --text-body: #dcdce1;
    --text-2: #c0c0c8;
    --text-muted: #a8a8b2;
    --text-disabled: #77777f;
    --accent: #ffffff;
    --accent-hover: #d9d9de;
    --on-accent: #09090b;
    --accent-glow: rgba(255, 255, 255, 0.22);
    --success: #00d492;
    --success-mark: #00bc7d;
    --success-tint: #062a1f;
    --success-tint-border: #0e7a59;
    --success-tint-text: #6ef0bf;
    --danger: #ff8a8c;
    --danger-mark: #fb2c36;
    --danger-tint: #3a0d10;
    --danger-tint-border: #8a1c1f;
    --danger-tint-text: #ffb0b0;
    --info: #7ab8ff;
    --info-mark: #3987e5;
    --info-tint: #0c1d3a;
    --info-tint-border: #2257a8;
    --info-tint-text: #add4ff;
    --warning: #ffc53d;
    --warning-mark: #f0b100;
    --warning-tint: #2e2206;
    --warning-tint-border: #8a6600;
    --warning-tint-text: #ffd580;
    --badge-pos-bg: #ffffff;
    --badge-pos-text: #006b4a;
    --badge-pos-border: #ffffff;
    --focus: #ffffff;
    --selection-bg: #ffffff;
    --selection-text: #09090b;
    --hover: rgba(255, 255, 255, 0.06);
    --pressed: rgba(255, 255, 255, 0.1);
    --inverse-bg: #f4f4f5;
    --inverse-text: #09090b;
    --inverse-muted: #4b4b54;
    --inverse-control: #71717b;
    --inverse-border: #c6c6cd;
    --terminal-bg: #050506;
    --terminal-text: #dcdce1;
    --terminal-muted: #a8a8b2;
    --terminal-success: #00d492;
    --terminal-border: #35353b;
    --seg-on: var(--accent);
    --seg-on-hex: #ffffff;
    --seg-off: #303036;
    --tag-bg: #1f1f23;
    --bubble-user: #232327;
    --pub-nyt: #3987e5;
    --pub-ft: #d95926;
    --pub-bg: #9085e9;
    --pub-wsj: #c98500;
    --pub-wp: #d55181;
    --chart-line: #f4f4f5;
    --chart-area: rgba(244, 244, 245, 0.09);
    --chart-axis: #82828c;
    --chart-grid: #26262b;
    --chart-text: #dcdce1;
    --chart-text-2: #a8a8b2;
    --chart-zoom-fill: rgba(244, 244, 245, 0.12);
    --grid-dot: rgba(255, 255, 255, 0.04);
    --overlay-backdrop: rgba(0, 0, 0, 0.65);
    --shadow-overlay: 0 1.5rem 4rem rgba(0, 0, 0, 0.7);
    --display-weight: 300;
    --icon-grade: -25;
}

/* ============================================================================
   4  TEXT SIZE — percent of the browser default, so a raised browser setting is respected.
   Text can still end up bigger than Large (browser zoom, a raised browser font size): the comments
   in these stylesheets call that "enlarged text".
   ========================================================================== */
html { font-size: 84.2105%; }                            /* Standard: 16 / 19, so body text (1.1875rem) is 16px */
html[data-text-size="large"] { font-size: 100%; }
/* The page frame (.page) is as wide on screen at Standard as at Large: smaller text gets more room in the
   same frame instead of a narrower column between wider empty margins. Line length stays capped by --measure.
   --frame-scale undoes the step above (19 / 16) in rem, so a raised browser font size still widens the frame. */
html { --frame-scale: calc(19 / 16); }
html[data-text-size="large"] { --frame-scale: 1; }

/* ============================================================================
   5  RESET AND BASE
   ========================================================================== */
*, *::before, *::after { box-sizing: border-box; }

html {
  -webkit-text-size-adjust: 100%;
  text-size-adjust: 100%;
  scroll-padding-top: var(--sp-4);
  scroll-padding-bottom: calc(var(--tabbar-h) + var(--safe-bottom) + var(--sp-4));
  scrollbar-gutter: stable;
}

body {
  margin: 0;
  min-height: 100vh;
  background: var(--bg);           /* propagates to the canvas. html has NO background on purpose,
                                      otherwise it would paint over the body::before dot grid */
  color: var(--text-body);
  font-family: var(--font-sans);
  font-size: var(--fs-body);
  font-weight: 400;
  line-height: var(--lh-body);
  font-synthesis-weight: none;
}

/* Overwatch dot grid, near the top of the canvas only, never behind reading text */
body::before {
  content: "";
  position: fixed;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  background: radial-gradient(circle, var(--grid-dot) 1px, transparent 1.5px) 0 0 / 28px 28px;
  -webkit-mask: linear-gradient(#000, transparent 30rem);
  mask: linear-gradient(#000, transparent 30rem);
}

::selection { background: var(--selection-bg); color: var(--selection-text); }

:where(h1, h2, h3, h4, h5, h6) {
  margin: 0;
  color: var(--text);
  font-family: var(--font-sans);
  line-height: var(--lh-head);
  text-wrap: pretty;
}
:where(h2) { font-size: var(--fs-h2); font-weight: 500; letter-spacing: -0.01em; }
:where(h3) { font-size: var(--fs-h3); font-weight: 600; }
:where(h4) { font-size: var(--fs-body); font-weight: 600; }
:where(p, ul, ol, dl, dd, figure, blockquote, fieldset, pre) { margin: 0; }
:where(p, li, dd, dt, td, th, h1, h2, h3, h4, blockquote, figcaption, label, legend) { overflow-wrap: break-word; }
:where(ul, ol)[role="list"] { list-style: none; padding: 0; }
:where(img, svg, video) { max-width: 100%; height: auto; }   /* never canvas: ECharts sizes its own canvases */
:where(svg) { display: block; }
:where(strong, b) { font-weight: 600; }
:where(hr) { border: 0; border-top: 1px solid var(--border-subtle); margin: var(--sp-6) 0; }

a {
  color: var(--text);
  text-decoration-line: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 0.2em;
  text-decoration-color: var(--text-muted);
}
a:hover { text-decoration-color: currentColor; text-decoration-thickness: 2px; }

:where(button, input, select, textarea) { font: inherit; color: inherit; letter-spacing: inherit; }
:where(button) { cursor: pointer; }
:where(input[type="checkbox"], input[type="radio"]) { accent-color: var(--accent); width: 1.25rem; height: 1.25rem; }
:where(table) { border-collapse: collapse; width: 100%; }
:where(code, kbd, samp) { font-family: var(--font-mono); font-size: 0.95em; }
:where(time) { font-variant-numeric: tabular-nums; }

/* ============================================================================
   6  ACCESSIBILITY HELPERS
   ========================================================================== */
:focus-visible { outline: 3px solid var(--focus); outline-offset: 2px; }
:focus:not(:focus-visible) { outline: none; }
[hidden] { display: none !important; }

.visually-hidden {
  position: absolute !important;
  width: 1px !important;
  height: 1px !important;
  margin: -1px !important;
  padding: 0 !important;
  overflow: hidden !important;
  clip-path: inset(50%) !important;
  white-space: nowrap !important;
  border: 0 !important;
}

.skip-link {
  position: absolute;
  top: var(--sp-3);
  left: var(--sp-3);
  z-index: var(--z-skip);
  padding: var(--sp-3) var(--sp-4);
  background: var(--inverse-bg);
  color: var(--inverse-text);
  font-weight: 600;
  font-size: var(--fs-button);
  text-decoration: underline;
  transform: translateY(-300%);
}
.skip-link:focus { transform: none; outline-color: var(--inverse-bg); }

/* things that only work with JavaScript are hidden until the head snippet has added html.js */
html:not(.js) [data-needs-js] { display: none !important; }

/* ============================================================================
   7  LAYOUT PRIMITIVES
   ========================================================================== */
.stack { display: flex; flex-direction: column; gap: var(--stack-gap, var(--sp-4)); }
.stack--sm { --stack-gap: var(--sp-2); }
.stack--lg { --stack-gap: var(--sp-8); }
.stack > * { min-width: 0; }
.cluster { display: flex; flex-wrap: wrap; align-items: center; gap: var(--cluster-gap, var(--sp-3)); }
.cluster--between { justify-content: space-between; }
.grid { display: grid; gap: var(--grid-gap, var(--sp-6)); grid-template-columns: repeat(auto-fit, minmax(min(100%, var(--grid-min, 18rem)), 1fr)); }
.grid > * { min-width: 0; }
.measure { max-width: var(--measure); }

/* ============================================================================
   8  APP SHELL
   ========================================================================== */
.shell { min-height: 100vh; min-height: 100dvh; }
.shell__main { min-width: 0; display: flex; flex-direction: column; min-height: 100vh; min-height: 100dvh; }

.main {
  flex: 1;
  container: main / inline-size;
  padding-bottom: calc(var(--tabbar-h) + var(--safe-bottom));
}
.main:focus { outline: none; }        /* programmatic target of the skip link only */

.page {
  width: 100%;
  max-width: calc(var(--page-max) * var(--frame-scale) + 2 * var(--gutter));
  margin-inline: auto;
  padding: var(--sp-6) var(--gutter) var(--sp-12);
}
.page--wide { max-width: calc(var(--page-max-wide) * var(--frame-scale) + 2 * var(--gutter)); }

@media (min-width: 48em) {
  :root { --gutter: 1.5rem; --panel-pad: 1.75rem; }
  html { scroll-padding-top: calc(var(--topbar-h) + var(--sp-4)); }
  .page { padding-top: var(--sp-8); }
}
@media (min-width: 64em) {
  :root { --gutter: 2.5rem; }
  html { scroll-padding-bottom: var(--sp-4); }
}

/* ---- brand ---- */
.brand {
  display: inline-flex;
  align-items: center;
  gap: 0.625rem;
  min-height: var(--tap);
  min-width: var(--tap);
  padding-inline: var(--sp-1);
  margin-inline-start: calc(-1 * var(--sp-1));
  color: var(--text);
  font-family: var(--font-mono);
  font-size: var(--fs-brand);
  font-weight: 600;
  line-height: 1;
  letter-spacing: var(--track-brand);
  text-transform: uppercase;
  text-decoration: none;
  white-space: nowrap;
}
.brand__mark {
  flex: none;
  width: 0.75rem;
  height: 0.75rem;
  background: var(--accent);
  box-shadow: 0 0 0.5rem var(--accent-glow);
}

/* ---- top bar ---- */
.topbar {
  container: topbar / inline-size;
  position: relative;
  z-index: var(--z-sticky);
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  min-height: var(--topbar-h);
  padding: env(safe-area-inset-top, 0px) var(--gutter) 0;
  background: var(--bg);
  border-bottom: 1px solid var(--border-subtle);
}
/* The top-bar wordmark folds away by itself only when the bar is narrower than the wordmark (extreme zoom): the
   brand is a one-line-tall wrapping flex box, so "NewsBridge" drops onto a clipped second line instead of making
   the page scroll sideways. It stays in the accessibility tree, so the link keeps its name. (The date never
   competes for the room: it is flex: none and only shows from 36rem.) */
.topbar__brand {
  flex: 0 1 auto;
  flex-wrap: wrap;
  align-content: flex-start;
  justify-content: flex-start;
  height: var(--tap);
  overflow: hidden;
  column-gap: 0.625rem;
  row-gap: 0;
}
.topbar__brand .brand__mark { margin-block: calc((var(--tap) - 0.75rem) / 2); }
.topbar__brand .brand__name { line-height: var(--tap); }

.topbar__sep { display: none; flex: none; width: 1px; height: 1.5rem; background: var(--border); }
.topbar__status {
  display: none;
  align-items: center;
  gap: var(--sp-2);
  font-size: var(--fs-small);
  color: var(--text-2);
  white-space: nowrap;
}

/* breadcrumb: one row. Only the last crumb gives way: a long conversation title wraps onto a second line
   inside the bar (two lines still fit its height), so the sticky bar stays one row tall and the brand and
   the date keep their size. */
.crumbs { display: none; min-width: 0; flex: 0 1 auto; }
.crumbs ol { display: flex; align-items: center; flex: 0 1 auto; min-width: 0; margin: 0; padding: 0; list-style: none; font-size: var(--fs-small); line-height: 1.25; }
.crumbs li { display: flex; align-items: center; flex: none; min-height: var(--tap); }
.crumbs li:last-child { flex: 0 1 auto; min-width: 0; }
.crumbs li + li::before { content: "/"; content: "/" / ""; flex: none; margin-inline: var(--sp-2); color: var(--text-muted); font-family: var(--font-mono); }
.crumbs a { display: inline-flex; align-items: center; min-height: var(--tap); min-width: 2.75rem; padding-inline: var(--sp-1); margin-inline: calc(-1 * var(--sp-1)); color: var(--text-2); text-decoration-color: var(--border-control); }
.crumbs [aria-current="page"] { min-width: 0; color: var(--text); font-weight: 500; overflow-wrap: anywhere; }

@media (min-width: 48em) {
  .topbar { position: sticky; top: 0; }
  .topbar__sep, .crumbs { display: flex; }
  .topbar__brand { flex-shrink: 0; }      /* from here on the breadcrumb is what gives way */
}
@media (min-width: 64em) {
  .topbar__brand, .topbar__brand + .topbar__sep { display: none; }
  .topbar--standalone .topbar__brand { display: flex; }        /* sign-in and error pages have no sidebar */
}
.main--standalone { padding-bottom: 0; }                          /* no tab bar on sign-in and error pages */
/* today's date appears only when the bar has room for it at the current text size, so the sticky bar never
   wraps to two lines: tablets, phones held sideways, laptops and desktops, not phones held upright.
   KEEP IN STEP with the News dateline in pages/news.css, which steps aside wherever this shows. */
@container topbar (min-width: 36rem) {
  .topbar__status { display: inline-flex; flex: none; margin-inline-start: auto; }
}

/* ---- Text & colours panel (non-modal). Laptops and desktops only: it belongs to the sidebar's
   "Text & colours" (under "Your account") and opens beside the sidebar; app.js sets `top` so it is level with
   that button, moved up just enough to stay on screen. Phones and tablets have the same controls in the More
   sheet; the sign-in and error pages have none (choices made earlier on the device still apply there). ---- */
html:not(.js) [data-popover-target] { display: none; }
.display-pop {
  position: fixed;
  top: var(--sp-3);
  left: calc(var(--sidebar-w) + var(--sp-3));
  z-index: var(--z-pop);
  width: min(34rem, calc(100vw - var(--sidebar-w) - 2 * var(--sp-3)));
  max-height: calc(100vh - 2 * var(--sp-3));
  max-height: calc(100dvh - 2 * var(--sp-3));
  overflow: auto;
  overscroll-behavior: contain;
  padding: var(--sp-6);
  background: var(--surface-2);
  border: 1px solid var(--border-strong);
  box-shadow: var(--shadow-overlay);
}
.display-pop__title { font-size: var(--fs-h3); font-weight: 600; margin-bottom: var(--sp-5); }
.display-pop__title:focus { outline: none; }
.display-form { container: display / inline-size; display: flex; flex-direction: column; gap: var(--sp-6); }
.display-note { font-size: var(--fs-small); color: var(--text-2); display: flex; gap: var(--sp-2); align-items: flex-start; }
.display-note .icon { margin-top: 0.15em; }

/* ---- sidebar (>= 64em) ---- */
.sidebar { display: none; }
.sidebar__brand {
  display: flex;
  align-items: center;
  min-height: var(--topbar-h);
  padding: 0 var(--sp-6);
  background: var(--surface-2);
  border-bottom: 1px solid var(--border-subtle);
}
.nav { display: flex; flex-direction: column; flex: 1; padding: var(--sp-4) var(--sp-3); }
.nav__list { display: flex; flex-direction: column; gap: var(--sp-1); margin: 0; padding: 0; list-style: none; }
.nav__heading {
  margin: var(--sp-6) var(--sp-3) var(--sp-2);
  padding-top: var(--sp-5);
  border-top: 1px solid var(--border-subtle);
}
.nav__link {
  position: relative;
  display: flex;
  align-items: center;
  gap: 0.875rem;
  min-height: 3.25rem;
  padding: var(--sp-2) var(--sp-4);
  color: var(--text-2);
  font-size: var(--fs-button);
  font-weight: 500;
  line-height: 1.25;
  text-decoration: none;
}
.nav__link:hover { color: var(--text); background-image: linear-gradient(var(--hover), var(--hover)); }
.nav__link:active { background-image: linear-gradient(var(--pressed), var(--pressed)); }
.nav__link[aria-current="page"] {
  color: var(--text);
  font-weight: 600;
  background-color: var(--surface-3);
  background-image: none;           /* no state layer on the current item: keeps its verified contrast */
  box-shadow: inset 4px 0 0 var(--accent);
}
.nav__link[aria-current="page"] .icon { font-variation-settings: "FILL" 1, "wght" 400, "GRAD" var(--icon-grade), "opsz" 24; }
/* "Text & colours" is a button that opens its panel; it looks like the links, and stays lit while the panel is open */
button.nav__link { width: 100%; border: 0; background-color: transparent; text-align: start; }
.nav__link[aria-expanded="true"] { color: var(--text); background-color: var(--surface-3); background-image: none; }
/* Overwatch "[ ALL_ROLL_CALLS ]" brackets — only on the current page, so they mean "you are here" */
.nav__link[aria-current="page"] .nav__label::before,
.nav__link[aria-current="page"] .nav__label::after { font-family: var(--font-mono); font-weight: 400; color: var(--text-muted); }
.nav__link[aria-current="page"] .nav__label::before { content: "["; content: "[" / ""; margin-inline-end: 0.4em; }
.nav__link[aria-current="page"] .nav__label::after { content: "]"; content: "]" / ""; margin-inline-start: 0.4em; }

.sidebar__foot { padding: var(--sp-5) var(--sp-6) var(--sp-6); border-top: 1px solid var(--border-subtle); }
.sidebar__user { margin-bottom: var(--sp-3); font-size: var(--fs-small); color: var(--text-2); line-height: 1.4; overflow-wrap: anywhere; }
.sidebar__user strong { display: block; color: var(--text); font-weight: 500; font-size: var(--fs-body); }

@media (min-width: 64em) {
  .shell { display: grid; grid-template-columns: var(--sidebar-w) minmax(0, 1fr); }
  .sidebar {
    position: sticky;
    top: 0;
    display: flex;
    flex-direction: column;
    height: 100vh;
    height: 100dvh;
    overflow-y: auto;
    overscroll-behavior: contain;
    scroll-padding-bottom: 12rem;       /* a link reached with the Tab key stops clear of the pinned foot */
    background: var(--surface-1);
    border-right: 1px solid var(--border);
    z-index: calc(var(--z-sticky) + 1); /* its Text & colours panel overlaps the page: keep it above the sticky top bar */
  }
  /* "Signed in as" and Sign out stay on screen on short laptops and with enlarged text:
     the links scroll under the foot, never the other way round */
  .sidebar__foot { position: sticky; bottom: 0; background: var(--surface-1); }
  .main { padding-bottom: 0; }
}
@media (min-width: 64em) and (max-width: 89.99em) { :root { --sidebar-w: 15.5rem; } }
/* short laptop screens (800px high and less): a little tighter, so everything fits without scrolling with enlarged text */
@media (min-width: 64em) and (max-height: 50em) {
  .nav { padding-block: var(--sp-2); }
  .nav__heading { margin-top: var(--sp-3); padding-top: var(--sp-3); }
  .sidebar__foot { padding-block: var(--sp-3) var(--sp-4); }
  .sidebar__user strong { display: inline; }
}
/* shorter still (720px high and less: a 1366 × 768 laptop with the browser's bars showing, or a zoomed page):
   tighter again, so the last Admin link is not left under the pinned foot. Links stay at least 44px tall. */
@media (min-width: 64em) and (max-height: 45em) {
  .nav__link { min-height: 2.75rem; }
  .nav__heading { margin-top: var(--sp-2); padding-top: var(--sp-2); }
  .sidebar__foot { padding-top: var(--sp-2); }      /* the bottom stays at the page's scroll-padding: Tab to Sign out never nudges the page */
  .sidebar__user { margin-bottom: var(--sp-2); }
}
/* 640px high and less (the same laptop with a bookmarks bar as well): the links close up, so all six still show with enlarged text */
@media (min-width: 64em) and (max-height: 40em) {
  .nav { padding-top: var(--sp-1); }
  .nav__list { gap: 0; }
  .nav__link:focus-visible { z-index: 1; }            /* links touch here: keep the whole focus ring above the next link */
  .nav__heading { margin-block: var(--sp-1); }
}
/* Admins at Large text: nine sidebar items (the Admin group, Change password, Text & colours) need about 880px at
   full spacing, about 900px on the Change password page, where the current "[ Change password ]" wraps. So their
   sidebar tightens sooner and further, on top of the steps above; readers and Standard text keep the steps above.
   900px high and less: links 44px tall (the Standard height), headings and foot a little tighter. */
@media (min-width: 64em) and (max-height: 56.25em) {
  html[data-text-size="large"] .sidebar--admin .nav { padding-block: var(--sp-2); }
  html[data-text-size="large"] .sidebar--admin .nav__link { min-height: 2.75rem; }
  html[data-text-size="large"] .sidebar--admin .nav__heading { margin-top: var(--sp-3); padding-top: var(--sp-3); }
  html[data-text-size="large"] .sidebar--admin .sidebar__foot { padding-block: var(--sp-3) var(--sp-4); }
  html[data-text-size="large"] .sidebar--admin .sidebar__user strong { display: inline; }
}
/* 752px high and less (the step above needs about 740px): the links close up and the headings tighten, so the
   last item, Text & colours, is not left under the pinned foot down to about 630px high. Lower than that, the links
   scroll under the foot. Links and headings touch here, so the focus ring is drawn inside the link. */
@media (min-width: 64em) and (max-height: 47em) {
  html[data-text-size="large"] .sidebar--admin .nav { padding-block: var(--sp-1); }
  html[data-text-size="large"] .sidebar--admin .nav__list { gap: 0; }
  html[data-text-size="large"] .sidebar--admin .nav__link { padding-block: var(--sp-1); }   /* one-line links keep 44px (min-height); a wrapped one grows by less */
  html[data-text-size="large"] .sidebar--admin .nav__link:focus-visible { outline-offset: -3px; }
  html[data-text-size="large"] .sidebar--admin .nav__heading { margin: var(--sp-1) var(--sp-3) 0; padding-top: var(--sp-1); }
  html[data-text-size="large"] .sidebar--admin .sidebar__foot { padding-top: var(--sp-2); }
  html[data-text-size="large"] .sidebar--admin .sidebar__user { margin-bottom: var(--sp-1); }
}
/* 640px high and less: the last few pixels, so all nine still show on the Change password page */
@media (min-width: 64em) and (max-height: 40em) {
  html[data-text-size="large"] .sidebar--admin .nav__heading { margin-top: 0; }
  html[data-text-size="large"] .sidebar--admin .sidebar__foot { padding-top: var(--sp-1); }
}

/* ---- bottom tab bar (< 64em) ---- */
.tabbar {
  position: fixed;
  z-index: var(--z-tabbar);
  left: 0;
  right: 0;
  bottom: 0;
  padding: 0 env(safe-area-inset-right, 0px) var(--safe-bottom) env(safe-area-inset-left, 0px);
  background: var(--surface-1);
  border-top: 1px solid var(--border);
}
.tabbar__list {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  max-width: 40rem;
  margin: 0 auto;
  padding: 0;
  list-style: none;
}
.tabbar__item {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 0.2rem;
  width: 100%;
  min-height: var(--tabbar-h);
  padding: 0.375rem 0.25rem;
  background: none;
  border: 0;
  border-top: 4px solid transparent;
  color: var(--text-2);
  font-family: var(--font-sans);
  font-size: 0.9375rem;
  font-weight: 500;
  line-height: 1.2;
  text-align: center;
  text-decoration: none;
}
.tabbar__item:hover { color: var(--text); background-image: linear-gradient(var(--hover), var(--hover)); }
.tabbar__item[aria-current="page"],
.tabbar__item[data-current="true"] {
  color: var(--text);
  font-weight: 600;
  background-color: var(--surface-3);
  background-image: none;
  border-top-color: var(--accent);
}
.tabbar__item[aria-current="page"] .icon,
.tabbar__item[data-current="true"] .icon { font-variation-settings: "FILL" 1, "wght" 400, "GRAD" var(--icon-grade), "opsz" 24; }
.tabbar__item:focus-visible { outline-offset: -4px; }

@media (min-width: 64em) { .tabbar { display: none; } }
@media (max-height: 30em) and (max-width: 63.99em) {       /* phone held sideways */
  :root { --tabbar-h: var(--tap); }
  .tabbar__item { flex-direction: row; gap: var(--sp-2); }
}
/* …and narrow as well (a desktop window at 400% zoom): the words alone, so the four items never collide */
@media (max-height: 30em) and (max-width: 30em) { .tabbar__item .icon { display: none; } }
/* While a text field has focus on a touch screen the tab bar steps aside for the on-screen keyboard.
   Nothing else may change: <main> keeps its bottom padding, so the page keeps its height and nothing
   moves at the moment a field gains or loses focus (a button that moves on blur never receives the tap
   that caused the blur). */
html.kb-open .tabbar { display: none; }
html.dialog-open { overflow: hidden; }

/* ---- dialogs and the More sheet ---- */
.dialog {
  width: min(34rem, calc(100% - 2rem));
  max-width: none;
  max-height: calc(100% - 2rem);
  margin: auto;
  padding: 0;
  overflow: auto;
  overscroll-behavior: contain;
  color: var(--text-body);
  background: var(--surface-2);
  border: 1px solid var(--border-strong);
  box-shadow: var(--shadow-overlay);
}
.dialog::backdrop { background: rgba(9, 9, 11, 0.6); }  /* literal: ::backdrop may not inherit custom properties */
.dialog__inner { position: relative; display: flex; flex-direction: column; gap: var(--sp-4); padding: var(--sp-6); }
.dialog__head { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: var(--sp-3); }
.dialog__title { font-size: var(--fs-h2); font-weight: 500; line-height: var(--lh-head); }
.dialog__title:focus { outline: none; }
.dialog__actions { display: flex; flex-wrap: wrap; gap: var(--sp-3); margin-top: var(--sp-2); }
.dialog__section { display: flex; flex-direction: column; gap: var(--sp-3); padding-top: var(--sp-5); border-top: 1px solid var(--border-subtle); }

@media (max-width: 47.99em) {
  .sheet {
    width: 100%;
    max-width: 100%;
    max-height: 90vh;
    max-height: 90dvh;
    margin: auto 0 0;
    border-width: 1px 0 0;
  }
  .sheet .dialog__inner { padding-bottom: calc(var(--sp-6) + var(--safe-bottom)); }
}
/* Without JavaScript the More tab is a plain link to #more-sheet; show the sheet as a fixed panel */
html:not(.js) .sheet:target {
  display: block;
  position: fixed;
  inset: auto 0 0 0;
  z-index: 60;
  width: 100%;
  max-height: 90vh;
  margin: 0;
}
.sheet__list { display: flex; flex-direction: column; margin: 0; padding: 0; list-style: none; }
.sheet__link {
  display: flex;
  align-items: center;
  gap: 0.875rem;
  min-height: 3.5rem;
  padding: var(--sp-2) var(--sp-3);
  border-bottom: 1px solid var(--border-subtle);
  color: var(--text);
  font-size: var(--fs-button);
  font-weight: 500;
  text-decoration: none;
}
.sheet__link::after { content: "›"; content: "›" / ""; margin-inline-start: auto; color: var(--text-muted); font-size: 1.5rem; line-height: 1; }
.sheet__link:hover { background-image: linear-gradient(var(--hover), var(--hover)); }
.sheet__link[aria-current="page"] { background-color: var(--surface-3); background-image: none; box-shadow: inset 4px 0 0 var(--accent); font-weight: 600; }

/* ============================================================================
   9  COMPONENTS
   ========================================================================== */

/* ---- icon (Material Symbols Sharp, loaded with display=block and an icon_names subset) ---- */
.icon {
  display: inline-block;
  flex: none;
  width: 1em;
  height: 1em;
  overflow: hidden;
  font-family: var(--font-icon);
  font-size: 1.5rem;
  font-style: normal;
  font-weight: 400;
  line-height: 1;
  letter-spacing: normal;
  text-transform: none;
  white-space: nowrap;
  word-wrap: normal;
  direction: ltr;
  font-feature-settings: "liga";
  -webkit-font-smoothing: antialiased;
  font-variation-settings: "FILL" 0, "wght" 400, "GRAD" var(--icon-grade), "opsz" 24;
  user-select: none;
}
/* Until app.js has seen the icon font arrive (html.icons-ready) the icons keep their space but stay blank, so a
   blocked or failed font never shows as clipped letters ("ne News"). Every icon sits beside its words.
   Without JavaScript nothing changes. */
html.js:not(.icons-ready) .icon { visibility: hidden; }
.icon--sm { font-size: 1.25rem; }
.icon--xl { font-size: 2.5rem; }
.icon--inline { font-size: 0.85em; vertical-align: -0.12em; margin-inline-start: 0.3em; }
.icon--fill { font-variation-settings: "FILL" 1, "wght" 400, "GRAD" var(--icon-grade), "opsz" 24; }
/* Keeps the external-link icon on the same line as the link's last word (ui.ext_link). The pair moves to
   the next line together; only a last "word" wider than the whole line (a web address, or a very long word
   with enlarged text on a small phone) may break inside, instead of pushing the page sideways.
   An inline-block does not take its parent's underline, hence text-decoration: inherit. */
.nowrap-tail { display: inline-block; max-width: 100%; overflow-wrap: anywhere; text-decoration: inherit; }

/* ---- kicker: mono, uppercase, <= 3 words ---- */
.kicker {
  font-family: var(--font-mono);
  font-size: var(--fs-label);
  font-weight: 500;
  line-height: var(--lh-label);
  letter-spacing: var(--track-label);
  text-transform: uppercase;
  color: var(--text-2);
}
.kicker--muted { color: var(--text-muted); }
.kicker--mark { display: inline-flex; align-items: center; gap: 0.625rem; }
.kicker--mark::before { content: ""; flex: none; width: 0.5625rem; height: 0.5625rem; background: var(--accent); }

.meta { font-family: var(--font-mono); font-size: var(--fs-meta); color: var(--text-muted); font-variant-numeric: tabular-nums; letter-spacing: 0.01em; }

/* dateline: mono, mixed case (a date is data, not a label, so it is never uppercased) */
.dateline { display: inline-flex; align-items: center; gap: 0.625rem; color: var(--text-2); font-family: var(--font-mono); font-size: var(--fs-meta); font-weight: 500; font-variant-numeric: tabular-nums; }
.dateline::before { content: ""; flex: none; width: 0.5625rem; height: 0.5625rem; background: var(--accent); box-shadow: 0 0 0.5rem var(--accent-glow); }

/* ---- lead paragraph with the thin left rule ---- */
.lead {
  max-width: 60ch;
  padding-left: var(--sp-5);
  border-left: 2px solid var(--border-strong);
  font-size: var(--fs-lead);
  line-height: 1.6;
  color: var(--text-body);
}

/* ---- page header ---- */
.page-header { display: flex; flex-wrap: wrap; align-items: flex-end; justify-content: space-between; gap: var(--sp-5) var(--sp-8); margin-bottom: var(--sp-8); }
.page-header__text { flex: 1 1 30rem; min-width: 0; display: flex; flex-direction: column; gap: var(--sp-3); }
.page-header__title { font-size: var(--fs-h1); font-weight: 400; line-height: var(--lh-tight); letter-spacing: -0.02em; }
.page-header__lede { margin-top: var(--sp-2); }
.page-header__actions { display: flex; flex-wrap: wrap; gap: var(--sp-3); }
@media (max-width: 39.99em) {
  .page-header { margin-bottom: var(--sp-6); }
  .page-header__lede { font-size: var(--fs-body); }
  .page-header__actions { flex: 1 1 100%; }
  .page-header__actions > .btn, .page-header__actions > form { flex: 1 1 100%; }
  .page-header__actions > form > .btn { width: 100%; }      /* a POST button (a one-button form) as the action */
}

/* ---- corner-bracket crop marks (key panels only) ---- */
:where(.frame) { position: relative; }   /* zero specificity: never overrides a component's own position */
.frame::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 1;
  pointer-events: none;
  --l: var(--bracket-len);
  --w: var(--bracket-w);
  --c: var(--bracket);
  background:
    linear-gradient(var(--c) 0 0) top left / var(--l) var(--w),
    linear-gradient(var(--c) 0 0) top left / var(--w) var(--l),
    linear-gradient(var(--c) 0 0) top right / var(--l) var(--w),
    linear-gradient(var(--c) 0 0) top right / var(--w) var(--l),
    linear-gradient(var(--c) 0 0) bottom left / var(--l) var(--w),
    linear-gradient(var(--c) 0 0) bottom left / var(--w) var(--l),
    linear-gradient(var(--c) 0 0) bottom right / var(--l) var(--w),
    linear-gradient(var(--c) 0 0) bottom right / var(--w) var(--l);
  background-repeat: no-repeat;
}

/* ---- panel ---- */
.panel { position: relative; background: var(--surface-1); border: 1px solid var(--border); min-width: 0; }
.panel__head {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-2) var(--sp-4);
  min-height: 3.5rem;
  padding: var(--sp-3) var(--panel-pad);
  background: var(--surface-2);
  border-bottom: 1px solid var(--border-subtle);
}
/* Every panel head uses the Overwatch "■ SYS_NAV" label: mono, uppercase, <= 3 words, with the accent square */
.panel__title {
  display: inline-flex;
  align-items: center;
  gap: 0.625rem;
  margin: 0;
  color: var(--text);
  font-family: var(--font-mono);
  font-size: var(--fs-label);
  font-weight: 600;
  line-height: var(--lh-label);
  letter-spacing: var(--track-label);
  text-transform: uppercase;
}
.panel__title::before { content: ""; flex: none; width: 0.5625rem; height: 0.5625rem; background: var(--accent); }
.panel__meta { font-family: var(--font-mono); font-size: var(--fs-meta); color: var(--text-2); font-variant-numeric: tabular-nums; }
.panel__body { padding: var(--panel-pad); }
/* vertical rhythm for plain bodies only; grid / stack / cluster bodies manage their own gaps */
.panel__body:not(.grid, .stack, .cluster, .form-grid) > * + * { margin-top: var(--sp-4); }
.panel__foot { padding: var(--sp-4) var(--panel-pad); border-top: 1px solid var(--border-subtle); font-size: var(--fs-small); color: var(--text-2); }
.panel--flush > .panel__body { padding: 0; }
.panel--inset { background: var(--inset); }

/* inverted panel — "COMMAND INPUT" look. Remaps tokens so nested components just work */
.panel--inverse {
  --text: var(--inverse-text);
  --text-body: var(--inverse-text);
  --text-2: var(--inverse-text);
  --text-muted: var(--inverse-muted);
  --border: var(--inverse-border);
  --border-subtle: var(--inverse-border);
  --border-control: var(--inverse-control);
  --focus: var(--inverse-text);
  --accent: var(--inverse-text);
  --accent-hover: var(--inverse-muted);
  --on-accent: var(--inverse-bg);
  --accent-glow: transparent;
  --bracket: var(--inverse-muted);
  --hover: rgba(128, 128, 128, 0.12);
  background: var(--inverse-bg);
  color: var(--inverse-text);
  border-color: var(--inverse-border);
}

/* ---- buttons ---- */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--sp-2);
  min-height: var(--tap);
  min-width: var(--tap);
  padding: var(--sp-2) var(--sp-5);
  border: 2px solid transparent;
  border-radius: 0;
  background: transparent;
  color: var(--text);
  font-family: var(--font-sans);
  font-size: var(--fs-button);
  font-weight: 600;
  line-height: 1.2;
  text-align: center;
  text-decoration: none;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
.btn:hover { background-image: linear-gradient(var(--hover), var(--hover)); }
.btn:active { background-image: linear-gradient(var(--pressed), var(--pressed)); }
.btn__label { min-width: 0; }
.btn--lg { min-height: var(--tap-lg); padding-inline: var(--sp-6); }
.btn--block { display: flex; width: 100%; }

.btn--primary { background-color: var(--accent); border-color: var(--accent); color: var(--on-accent); }
.btn--primary:hover { background-color: var(--accent-hover); border-color: var(--accent-hover); background-image: none; }
.btn--primary:focus-visible { outline-offset: 3px; }

.btn--secondary { border-color: var(--border-control); color: var(--text); }
.btn--secondary:hover { border-color: var(--text); }

.btn--danger { border-color: var(--danger-mark); color: var(--danger); }
.btn--danger:hover { background-color: var(--danger-tint); background-image: none; }

.btn--quiet {
  padding-inline: var(--sp-2);
  color: var(--text);
  font-weight: 500;
  text-decoration-line: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 0.2em;
  text-decoration-color: var(--text-muted);
}
.btn--quiet:hover { background-image: none; text-decoration-color: currentColor; text-decoration-thickness: 2px; }

/* busy: the label + a still hourglass carry the state; the fill keeps its colour and gains a hatch */
.btn[aria-busy="true"] { cursor: progress; }
.btn[aria-busy="true"],
.btn[aria-busy="true"]:hover {
  background-image: repeating-linear-gradient(135deg, transparent 0 0.375rem, var(--busy-stripe) 0.375rem 0.75rem);
}
.btn--primary[aria-busy="true"],
.btn--primary[aria-busy="true"]:hover { background-color: var(--accent); border-color: var(--accent); border-style: dashed; }

/* disabled: never opacity — dashed border + readable text */
.btn:disabled:not([aria-busy="true"]),
.btn[aria-disabled="true"]:not([aria-busy="true"]) {
  background: var(--surface-2);
  border: 2px dashed var(--border-strong);
  color: var(--text-disabled);
  cursor: not-allowed;
  box-shadow: none;
}

/* ---- disclosure (native <details>) ---- */
.disclosure > summary { list-style: none; width: fit-content; max-width: 100%; }
.disclosure > summary::-webkit-details-marker { display: none; }
.disclosure > summary .disclosure__chev { margin-inline-start: var(--sp-1); }
.disclosure[open] > summary .disclosure__chev { transform: rotate(180deg); }
.disclosure__body { margin-top: var(--sp-4); }
.disclosure .when-open { display: none; }
.disclosure[open] .when-open { display: inline; }
.disclosure[open] .when-closed { display: none; }
/* While its form is open, a filled summary ("Follow a new story", "Create a new key") steps back to the
   outlined look, so the form's own submit button is the only filled button on screen. */
.disclosure[open] > summary.btn--primary { background-color: transparent; border-color: var(--border-control); color: var(--text); }
.disclosure[open] > summary.btn--primary:hover { border-color: var(--text); background-image: linear-gradient(var(--hover), var(--hover)); }

/* ---- form fields ---- */
.field { display: flex; flex-direction: column; gap: 0.375rem; min-width: 0; }
.field__label { color: var(--text); font-size: var(--fs-button); font-weight: 600; line-height: 1.35; }
.field__label .optional { font-weight: 400; color: var(--text-2); }
.field__hint { color: var(--text-2); font-size: var(--fs-small); line-height: var(--lh-small); max-width: var(--measure); }
.field__error { display: flex; align-items: flex-start; gap: var(--sp-2); color: var(--danger); font-size: var(--fs-small); font-weight: 600; line-height: var(--lh-small); }
.field__error .icon { font-size: 1.25rem; margin-top: 0.1em; }

.input, .select, .textarea {
  display: block;
  width: 100%;
  min-width: 0;
  min-height: 3.25rem;
  padding: 0 var(--sp-4);
  border: 2px solid var(--border-control);
  border-radius: 0;
  background: var(--inset);
  color: var(--text);
  font-family: var(--font-sans);
  font-size: var(--fs-body);
  line-height: 1.4;
}
.input:hover, .select:hover, .textarea:hover { border-color: var(--text-2); }
.input:focus-visible, .select:focus-visible, .textarea:focus-visible { border-color: var(--text); }
.input::placeholder, .textarea::placeholder { color: var(--text-muted); opacity: 1; }
.input--lg { min-height: var(--tap-lg); }
.input[type="date"] { min-height: 3.25rem; padding-block: 0.5rem; font-family: var(--font-mono); font-size: var(--fs-meta); }
.input[type="number"] { font-family: var(--font-mono); font-size: var(--fs-meta); font-variant-numeric: tabular-nums; }
.input[type="search"]::-webkit-search-cancel-button { -webkit-appearance: none; }
.textarea { min-height: 7rem; padding: 0.875rem var(--sp-4); line-height: var(--lh-body); resize: vertical; }
/* after the hover and focus rules, and as specific as they are: an invalid field stays red while it is focused */
.field--error .input, .field--error .select, .field--error .textarea,
.input[aria-invalid="true"], .select[aria-invalid="true"], .textarea[aria-invalid="true"] { border-color: var(--danger-mark); }

.select-wrap { position: relative; }
.select { appearance: none; -webkit-appearance: none; padding-right: 3rem; cursor: pointer; }
.select-wrap::after {
  content: "";
  position: absolute;
  right: 1.125rem;
  top: 50%;
  width: 0.625rem;
  height: 0.625rem;
  border-right: 2px solid var(--text);
  border-bottom: 2px solid var(--text);
  transform: translateY(-70%) rotate(45deg);
  pointer-events: none;
}

.input-suffix { display: flex; align-items: stretch; }
.input-suffix .input { flex: 1 1 auto; border-right: 0; }
.input-suffix__unit { display: flex; align-items: center; padding: 0 var(--sp-4); border: 2px solid var(--border-control); background: var(--surface-2); color: var(--text-2); font-size: var(--fs-small); }

/* search field — a real label, a visible hint and a real button; a "search" icon sits inside the field:
     <div class="prompt-input">{{ ui.icon('search', 'prompt-input__icon') }}<input class="input input--lg" …></div>
   (There is no "›" prompt glyph any more: older readers took it for typed text.) */
.prompt-input { position: relative; flex: 999 1 14rem; min-width: 0; }
.prompt-input__icon {
  position: absolute;
  left: var(--sp-3);
  top: 50%;
  transform: translateY(-50%);
  color: var(--text-muted);
  pointer-events: none;
}
.prompt-input .input { padding-left: 2.875rem; }

/* password with visible Show/Hide */
.password { display: flex; align-items: stretch; }
.password .input { flex: 1 1 auto; border-right: 0; }
.password .btn { min-height: 3.25rem; border-color: var(--border-control); background-color: var(--surface-2); flex: none; }

/* form grid: 1 col phone, 2 cols from 40rem of main, actions always below */
.form-grid { display: grid; gap: var(--sp-5); grid-template-columns: minmax(0, 1fr); }
.form-grid__full { grid-column: 1 / -1; }
.form-grid__actions { grid-column: 1 / -1; display: flex; flex-wrap: wrap; gap: var(--sp-3); }
@container main (min-width: 40rem) {
  .form-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
/* hints differ in length: each box sits at the bottom of its row, so boxes side by side line up */
.form-grid .field > .input, .form-grid .field > .select-wrap, .form-grid .field > .textarea { margin-top: auto; }
@media (max-width: 39.99em) { .form-grid__actions > .btn { flex: 1 1 100%; } }

/* ---- segmented radio control (text size, colours) ----
   A clean one-column list on narrow screens (never 2 + 1), all options in one row (two sizes, three
   colours) when the form is wide enough at the current text size (30rem). Checked = inverted fill +
   check mark (two cues, not colour alone). */
.seg { min-width: 0; margin: 0; padding: 0; border: 0; }
.seg__legend { margin-bottom: var(--sp-2); padding: 0; color: var(--text); font-size: var(--fs-button); font-weight: 600; }
.seg__options { display: grid; grid-template-columns: minmax(0, 1fr); gap: 2px; background: var(--border-control); border: 2px solid var(--border-control); }
.seg__opt { position: relative; display: flex; min-width: 0; }
.seg__input { position: absolute; inset: 0; width: 100%; height: 100%; margin: 0; opacity: 0; cursor: pointer; z-index: 1; }
.seg__face {
  flex: 1;
  display: flex;
  align-items: center;
  justify-content: flex-start;
  gap: var(--sp-2);
  min-width: 0;
  min-height: var(--tap);
  padding: var(--sp-2) var(--sp-4);
  background: var(--surface-1);
  color: var(--text-2);
  font-size: var(--fs-button);
  font-weight: 500;
  line-height: 1.2;
  text-align: start;
}
/* check slot: reserved in the list layout so labels line up */
.seg__face::before {
  content: "";
  flex: none;
  width: 1.5rem;
  font-family: var(--font-icon);
  font-size: 1.5rem;
  font-weight: 400;
  line-height: 1;
  font-feature-settings: "liga";
  font-variation-settings: "FILL" 0, "wght" 400, "GRAD" var(--icon-grade), "opsz" 24;
}
.seg__opt:hover .seg__face { color: var(--text); background-image: linear-gradient(var(--hover), var(--hover)); }
.seg__input:checked + .seg__face,
.seg__opt:hover .seg__input:checked + .seg__face { background: var(--accent); color: var(--on-accent); font-weight: 600; }
.seg__input:checked + .seg__face::before { content: "\2713"; content: "check" / ""; }
html.js:not(.icons-ready) .seg__input:checked + .seg__face::before { content: "\2713"; content: "\2713" / ""; font-family: inherit; }   /* no icon font: a plain tick */
.seg__input:focus-visible + .seg__face { position: relative; z-index: 2; outline: 3px solid var(--focus); outline-offset: 2px; }
.seg__hint { margin-top: var(--sp-2); }
.seg__face--md { font-size: 1rem; }
.seg__face--lg { font-size: 1.1875rem; }
@container display (min-width: 30rem) {
  .seg__options { grid-template-columns: none; grid-auto-flow: column; grid-auto-columns: minmax(0, 1fr); }
  .seg__face { justify-content: center; padding-inline: var(--sp-3); text-align: center; }
  .seg__input:not(:checked) + .seg__face::before { display: none; }
  .seg__input:checked + .seg__face .icon { display: none; }      /* the check mark takes the icon's place */
}

/* toggle-button group (aria-pressed), e.g. chart ranges */
.toggle-group { display: flex; flex-wrap: wrap; gap: var(--sp-2); }
.btn[aria-pressed="true"] { background-color: var(--surface-3); background-image: none; border-color: var(--text); box-shadow: inset 0 -4px 0 var(--accent); }

/* ---- filter chips (links or aria-pressed buttons). Current = inverted fill + check, like .seg ---- */
.chip-list { display: flex; flex-wrap: wrap; gap: var(--sp-2); margin: 0; padding: 0; list-style: none; }
.chip {
  display: inline-flex;
  align-items: center;
  gap: 0.375rem;
  min-height: var(--tap);
  padding: var(--sp-2) var(--sp-4);
  border: 2px solid var(--border-control);
  background: var(--surface-1);
  color: var(--text);
  font-family: var(--font-sans);
  font-size: var(--fs-button);
  font-weight: 500;
  line-height: 1.2;
  text-decoration: none;
  cursor: pointer;
}
.chip:hover { border-color: var(--text); background-image: linear-gradient(var(--hover), var(--hover)); }
.chip[aria-current="true"],
.chip[aria-pressed="true"] { background: var(--accent); border-color: var(--accent); color: var(--on-accent); font-weight: 600; }
.chip[aria-current="true"]::before,
.chip[aria-pressed="true"]::before {
  content: "\2713";
  content: "check" / "";
  font-family: var(--font-icon);
  font-size: 1.375rem;
  font-weight: 400;
  line-height: 1;
  font-feature-settings: "liga";
  margin-inline-start: -0.25rem;
}
html.js:not(.icons-ready) .chip[aria-current="true"]::before,
html.js:not(.icons-ready) .chip[aria-pressed="true"]::before { content: "\2713"; content: "\2713" / ""; font-family: inherit; }   /* no icon font: a plain tick */
.chip[aria-current="true"]:focus-visible, .chip[aria-pressed="true"]:focus-visible { outline-offset: 3px; }

/* ---- publication label + byline (publication · date), used on every page that lists articles ---- */
.pub { display: inline-flex; align-items: center; gap: 0.625rem; color: var(--text-2); font-family: var(--font-mono); font-size: var(--fs-label); font-weight: 500; letter-spacing: var(--track-label); text-transform: uppercase; }
.pub::before { content: ""; flex: none; width: 0.625rem; height: 0.625rem; background: var(--pub, var(--text-muted)); }
.pub--wsj { --pub: var(--pub-wsj); }
.pub--ft { --pub: var(--pub-ft); }
.pub--nyt { --pub: var(--pub-nyt); }
.pub--bg { --pub: var(--pub-bg); }
.pub--wp { --pub: var(--pub-wp); }
.byline { display: flex; flex-wrap: wrap; align-items: center; gap: var(--sp-1) 1.75rem; overflow: hidden; }
/* the date, or the plain <span>Date unknown</span> that ui.date() prints when there is none */
.byline time, .byline > span:not(.pub) { position: relative; color: var(--text-muted); font-family: var(--font-mono); font-size: var(--fs-meta); }
/* The separator "·" is drawn in the gap before the date. When the date wraps onto a line of its own, the dot
   lands outside the byline's left edge and is clipped (overflow: hidden), so no line starts with a stray "·". */
.byline time::before, .byline > span:not(.pub)::before { content: "·"; content: "·" / ""; position: absolute; top: 0; right: calc(100% + 0.575rem); width: 0.6rem; text-align: center; }

/* ---- tags (non-interactive) ---- */
.tag-list { display: flex; flex-wrap: wrap; gap: var(--sp-2); margin: 0; padding: 0; list-style: none; }
.tag { padding: 0.1875rem 0.625rem; background: var(--tag-bg); color: var(--text-2); font-size: 1rem; line-height: 1.45; }

/* ---- badges ---- */
.badge {
  display: inline-flex;
  align-items: center;
  gap: 0.375rem;
  min-height: 2rem;
  padding: 0.125rem 0.625rem;
  border: 1px solid var(--border-control);
  color: var(--text);
  font-family: var(--font-mono);
  font-size: var(--fs-label);
  font-weight: 600;
  line-height: 1.2;
  letter-spacing: var(--track-label);
  text-transform: uppercase;
  white-space: nowrap;
  vertical-align: middle;
}
.badge .icon { font-size: 1.125rem; }
.badge--positive { background: var(--badge-pos-bg); border-color: var(--badge-pos-border); color: var(--badge-pos-text); box-shadow: 0 0 0.75rem var(--accent-glow); }
.badge--negative, .badge--danger { background: var(--danger-tint); border-color: var(--danger-tint-border); color: var(--danger-tint-text); }
.badge--success { background: var(--success-tint); border-color: var(--success-tint-border); color: var(--success-tint-text); }
.badge--info { background: var(--info-tint); border-color: var(--info-tint-border); color: var(--info-tint-text); }
.badge--warning { background: var(--warning-tint); border-color: var(--warning-tint-border); color: var(--warning-tint-text); }
.badge--neutral { background: transparent; }

/* ---- status: dot + word (never a dot alone) ---- */
.status { display: inline-flex; align-items: center; gap: var(--sp-2); color: var(--text); font-size: var(--fs-small); line-height: 1.3; }
.status__dot { flex: none; width: 0.625rem; height: 0.625rem; border-radius: 50%; background: var(--success-mark); }
.status__dot--waiting { background: var(--warning-mark); }
.status__dot--working { background: var(--info-mark); }
.status__dot--problem { background: var(--danger-mark); }
.status__dot--off { background: transparent; box-shadow: inset 0 0 0 2px var(--text-muted); }

/* ---- alerts / flash messages ---- */
.alert {
  display: flex;
  align-items: flex-start;
  gap: var(--sp-3);
  padding: var(--sp-4) var(--sp-5);
  border: 1px solid var(--border-control);
  border-left-width: 4px;
  background: var(--surface-1);
  color: var(--text);
}
.alert > .icon { margin-top: 0.1em; }
.alert__body { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: var(--sp-1); }
.alert__title { font-weight: 600; color: var(--text); }
.alert__body p { color: var(--text-body); }
.alert__dismiss { flex: none; margin: calc(-1 * var(--sp-2)) calc(-1 * var(--sp-3)) calc(-1 * var(--sp-2)) 0; }
.alert--success { background: var(--success-tint); border-color: var(--success-tint-border); border-left-color: var(--success-mark); }
.alert--success > .icon { color: var(--success-tint-text); }
.alert--info { background: var(--info-tint); border-color: var(--info-tint-border); border-left-color: var(--info-mark); }
.alert--info > .icon { color: var(--info-tint-text); }
.alert--warning { background: var(--warning-tint); border-color: var(--warning-tint-border); border-left-color: var(--warning-mark); }
.alert--warning > .icon { color: var(--warning-tint-text); }
.alert--danger { background: var(--danger-tint); border-color: var(--danger-tint-border); border-left-color: var(--danger-mark); }
.alert--danger > .icon { color: var(--danger-tint-text); }
/* narrow at the current text size: the message stays beside its icon (it never drops under it) and
   "Dismiss" goes on a line of its own, lined up under the text */
@container main (max-width: 30rem) {
  .alert { flex-wrap: wrap; }
  .alert__body { flex-basis: calc(100% - 1.5rem - var(--sp-3)); }
  .alert__dismiss { margin: 0 0 0 calc(1.5rem + var(--sp-3) - var(--sp-2)); }
}

/* plain note line with icon (AI disclosure, how-to) */
.note { display: flex; align-items: flex-start; gap: var(--sp-2); color: var(--text-2); font-size: var(--fs-small); line-height: var(--lh-small); max-width: var(--measure); }
.note > .icon { font-size: 1.25rem; margin-top: 0.12em; color: var(--text-muted); }

/* ---- stat tiles ---- */
.stat-grid { display: grid; gap: var(--sp-4); grid-template-columns: repeat(auto-fit, minmax(min(100%, 14rem), 1fr)); margin: 0; }
/* two across only from 34rem: at 24rem a six-digit figure ("48,213") no longer fitted its tile on 390-414px phones */
@container main (min-width: 1px) { .stat-grid { grid-template-columns: minmax(0, 1fr); } }
@container main (min-width: 34rem) { .stat-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@container main (min-width: 52rem) { .stat-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
.stat { display: flex; flex-direction: column; gap: var(--sp-2); min-height: 7.5rem; padding: var(--sp-5); background: var(--inset); border: 1px solid var(--border); min-width: 0; }
.stat__label { order: 0; }
.stat__value { margin: 0; color: var(--text); font-family: var(--font-mono); font-size: var(--fs-num); font-weight: 400; line-height: 1.1; font-variant-numeric: tabular-nums; overflow-wrap: anywhere; }
.stat__value--good { color: var(--success); }
.stat__note { color: var(--text-2); font-size: var(--fs-small); line-height: 1.4; display: flex; align-items: center; gap: var(--sp-2); flex-wrap: wrap; }

/* ---- segmented block progress bar with glow ---- */
.meter { display: flex; flex-direction: column; gap: var(--sp-2); }
.meter__head { display: flex; flex-wrap: wrap; align-items: baseline; justify-content: space-between; gap: var(--sp-1) var(--sp-4); }
.meter__label { display: inline-flex; align-items: center; gap: var(--sp-2); color: var(--text); font-size: var(--fs-small); font-weight: 500; }
.meter__value { color: var(--text); font-family: var(--font-mono); font-size: var(--fs-meta); font-weight: 600; font-variant-numeric: tabular-nums; }
.segbar { position: relative; height: 0.75rem; --segs: 20; --gap: 2px; }
.segbar--lg { height: 1rem; }
.segbar__track, .segbar__fill > i {
  position: absolute;
  inset: 0;
  -webkit-mask: repeating-linear-gradient(90deg, #000 0 calc(100% / var(--segs) - var(--gap)), transparent 0 calc(100% / var(--segs)));
  mask: repeating-linear-gradient(90deg, #000 0 calc(100% / var(--segs) - var(--gap)), transparent 0 calc(100% / var(--segs)));
}
.segbar__track { background: var(--seg-off); }
.segbar__fill { position: absolute; inset: 0; filter: drop-shadow(0 0 3px var(--accent-glow)); }
.segbar__fill > i { background: var(--seg-fill, var(--seg-on)); clip-path: inset(0 calc(100% - var(--pct, 0%)) 0 0); }
.segbar--danger { --seg-fill: var(--danger-mark); }

/* ---- terminal log (the honest "black box") ---- */
.terminal {
  padding: var(--sp-4) var(--sp-5);
  background: var(--terminal-bg);
  border: 1px solid var(--terminal-border);
  color: var(--terminal-text);
  font-size: var(--fs-small);
  line-height: 1.6;
  --focus: var(--terminal-text);
}
.terminal__line { display: flex; gap: var(--sp-3); }
.terminal__line::before { content: "›"; content: "›" / ""; flex: none; color: var(--terminal-muted); font-family: var(--font-mono); font-weight: 600; }
.terminal__line--done { color: var(--terminal-success); font-weight: 500; }
.cursor-block { display: inline-block; width: 0.6em; height: 1.1em; margin-inline-start: 0.35em; vertical-align: text-bottom; background: var(--terminal-success); }

/* ---- code box (API key) ---- */
.code-box {
  display: block;
  padding: var(--sp-3) var(--sp-4);
  border: 1px solid var(--border-control);
  background: transparent;
  color: var(--text);
  font-family: var(--font-mono);
  font-size: 1.125rem;
  font-weight: 500;
  line-height: 1.5;
  overflow-wrap: anywhere;
  word-break: break-all;
  user-select: all;
}

/* ---- tables (.table--stack becomes labelled cards on phones and whenever the page column is narrow) ---- */
.table-wrap { min-width: 0; }
.table { width: 100%; border-collapse: collapse; font-size: var(--fs-small); }
.table th {
  padding: var(--sp-3) var(--sp-4);
  background: var(--inset);
  border-bottom: 1px solid var(--border);
  color: var(--text-muted);
  font-family: var(--font-mono);
  font-size: var(--fs-label);
  font-weight: 500;
  letter-spacing: var(--track-label);
  text-align: left;
  text-transform: uppercase;
  vertical-align: bottom;
}
.table td { padding: 0.875rem var(--sp-4); border-bottom: 1px solid var(--border-subtle); color: var(--text-2); vertical-align: top; }
.table td:first-child { color: var(--text); font-size: var(--fs-body); font-weight: 500; }
.table .num, .table time { font-family: var(--font-mono); font-size: var(--fs-meta); font-variant-numeric: tabular-nums; }
.table tr.is-current td { background: var(--surface-3); }
.table tr.is-current td:first-child { box-shadow: inset 4px 0 0 var(--accent); }
@media (min-width: 48em) { .table-wrap { overflow-x: auto; } }
/* Cards on phones (screen under 48em) … */
@media (max-width: 47.99em) {
  .table--stack thead { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
  .table--stack, .table--stack tbody, .table--stack tr, .table--stack td { display: block; width: 100%; }
  .table--stack tr { padding: var(--sp-4) var(--panel-pad); border-bottom: 1px solid var(--border); }
  .table--stack tr.is-current { background: var(--surface-3); box-shadow: inset 4px 0 0 var(--accent); }
  .table--stack tr.is-current td:first-child { box-shadow: none; }
  .table--stack td, .table--stack tr.is-current td { display: grid; grid-template-columns: minmax(5.5rem, 34%) minmax(0, 1fr); gap: var(--sp-3); padding: var(--sp-1) 0; border: 0; background: none; box-shadow: none; }
  .table--stack td::before { content: attr(data-label); content: attr(data-label) / ""; padding-top: 0.15em; color: var(--text-muted); font-family: var(--font-mono); font-size: var(--fs-label); font-weight: 500; letter-spacing: var(--track-label); text-transform: uppercase; }
}
/* … and, with the same declarations, whenever <main> is under 46rem at the current text size: with Large
   text on a tablet or a small laptop the column is too narrow in rem for a row of columns.
   (The block above stays for browsers without container queries.) KEEP THE TWO BLOCKS IDENTICAL. */
@container main (max-width: 45.99rem) {
  .table--stack thead { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
  .table--stack, .table--stack tbody, .table--stack tr, .table--stack td { display: block; width: 100%; }
  .table--stack tr { padding: var(--sp-4) var(--panel-pad); border-bottom: 1px solid var(--border); }
  .table--stack tr.is-current { background: var(--surface-3); box-shadow: inset 4px 0 0 var(--accent); }
  .table--stack tr.is-current td:first-child { box-shadow: none; }
  .table--stack td, .table--stack tr.is-current td { display: grid; grid-template-columns: minmax(5.5rem, 34%) minmax(0, 1fr); gap: var(--sp-3); padding: var(--sp-1) 0; border: 0; background: none; box-shadow: none; }
  .table--stack td::before { content: attr(data-label); content: attr(data-label) / ""; padding-top: 0.15em; color: var(--text-muted); font-family: var(--font-mono); font-size: var(--fs-label); font-weight: 500; letter-spacing: var(--track-label); text-transform: uppercase; }
}

/* ---- pagination ---- */
/* phones: one full-width button per row, so "Previous page" / "Next page" stay on one line at every text size */
.pager {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  grid-template-areas: "status" "prev" "next";
  gap: var(--sp-3);
  align-items: center;
  margin-top: var(--sp-6);
}
.pager__status { grid-area: status; text-align: center; font-family: var(--font-mono); font-size: var(--fs-meta); color: var(--text-2); font-variant-numeric: tabular-nums; }
.pager__prev { grid-area: prev; }
.pager__next { grid-area: next; }
@media (min-width: 40em) {
  .pager { grid-template-columns: 1fr auto 1fr; grid-template-areas: "prev status next"; }
  .pager__prev { justify-self: start; }
  .pager__next { justify-self: end; }
}

/* ---- empty state ---- */
.empty {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--sp-4);
  padding: var(--sp-8) var(--panel-pad);
  border: 1px dashed var(--border-strong);
  background: var(--surface-1);
}
.empty > .icon { color: var(--text-muted); }
.empty__title { font-size: var(--fs-h2); font-weight: 500; }
.empty__text { max-width: var(--measure); color: var(--text-body); }
.empty ul { padding-left: 1.25em; color: var(--text-body); }

/* ---- to-top ---- */
.to-top { margin-top: var(--sp-8); }

/* ============================================================================
   10 PAGE BLOCKS live in static/css/pages/<page>.css (one <link> per page, after this file)
   ========================================================================== */

/* ============================================================================
   11 UTILITIES
   ========================================================================== */
.u-mono { font-family: var(--font-mono); }
.u-muted { color: var(--text-2); }
.u-small { font-size: var(--fs-small); line-height: var(--lh-small); }
.u-break { overflow-wrap: anywhere; }
.u-nowrap { white-space: nowrap; }
.u-tabular { font-variant-numeric: tabular-nums; }
.u-success { color: var(--success); }
.u-danger { color: var(--danger); }
.dot-sep::before { content: "·"; content: "·" / ""; }

/* ============================================================================
   12 MOTION, CONTRAST, FORCED COLOURS
   ========================================================================== */
/* Transitions are switched on by app.js (html.is-ready) two frames after the page is ready. Without this,
   Chrome can start them while the stylesheet is still arriving, and links and buttons fade in from the
   browser's default colours (blue link text) on every page load. No JavaScript = no transitions. */
@media (prefers-reduced-motion: no-preference) {
  html.is-ready :where(.btn, .chip, .input, .select, .textarea, .nav__link, .tabbar__item, .seg__face, .sheet__link, a) {
    transition: background-color var(--dur) ease, border-color var(--dur) ease, color var(--dur) ease, text-decoration-color var(--dur) ease;
  }
}
html.theme-switching *, html.theme-switching *::before, html.theme-switching *::after { transition: none !important; }
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { animation: none !important; transition: none !important; scroll-behavior: auto !important; }
}
@media (prefers-contrast: more) {
  :root { --accent-glow: transparent; --border: var(--border-control); --border-subtle: var(--border-control); }
  body::before { display: none; }
}
@media (forced-colors: active) {
  .frame::before, body::before { display: none; }
  .btn, .chip, .badge, .seg__options, .tag, .stat, .panel, .alert { border: 1px solid CanvasText; }
  .segbar__track { background: GrayText; }
  .segbar__fill > i, .pub-bars__bar > i { forced-color-adjust: none; background: CanvasText; }
  .segbar__fill { filter: none; }
  .nav__link[aria-current="page"], .tabbar__item[aria-current="page"], .tabbar__item[data-current="true"] { outline: 2px solid Highlight; outline-offset: -2px; }
  .seg__input:checked + .seg__face, .chip[aria-current="true"], .chip[aria-pressed="true"], .btn[aria-pressed="true"] { forced-color-adjust: none; background: Highlight; color: HighlightText; }
  .status__dot { forced-color-adjust: none; background: CanvasText; }
  .brand__mark, .kicker--mark::before, .panel__title::before, .dateline::before, .pub::before, .pub-bars__name::before { forced-color-adjust: none; background: CanvasText; }
}

/* ============================================================================
   13 PRINT — always light, no chrome, URLs after external links
   ========================================================================== */
@media print {
  :root, :root[data-theme] {
    color-scheme: light;
    --bg: #ffffff; --surface-1: #ffffff; --surface-2: #ffffff; --surface-3: #f2f2f2; --inset: #ffffff;
    --text: #000000; --text-body: #000000; --text-2: #222222; --text-muted: #444444;
    --border: #999999; --border-subtle: #cccccc;
    /* fills that sit behind text: light values, so "Background graphics" never prints dark text on a dark fill */
    --tag-bg: #e7e7eb; --bubble-user: #e2e2e7; --accent: #0a0a0b; --on-accent: #fafafa;
    --success-tint: #e3f5ec; --success-tint-text: #00563c; --danger-tint: #fdecec; --danger-tint-text: #920610;
    --info-tint: #e8f0ff; --info-tint-text: #11379a; --warning-tint: #fff4d6; --warning-tint-text: #6e3700;
  }
  body::before, .sidebar, .topbar, .tabbar, dialog, .pager, .skip-link, .to-top, .results-bar__jump,
  .story-card__actions, .chat-composer, .display-pop, .news-search { display: none !important; }
  .main { padding: 0; }
  .panel, .stat { break-inside: avoid; }
  a[target="_blank"]::after { content: " (" attr(href) ")"; font-size: 0.8em; overflow-wrap: anywhere; }
}
