/* Weighted Caseload Hub — near-monochrome instrument palette.
 *
 * Direction (Ben, 2026-09-01, after the impeccable critique scored the
 * EUSD-token build 22/40): ink on off-white, ONE hairline weight, ONE
 * accent used only where you can click, and color appearing ONLY where a
 * number has crossed a threshold. A month with nothing wrong renders
 * entirely monochrome — so any color at all means "look here."
 *
 * Consequences, deliberate:
 *  - No chromatic page ground. White content, faintly sunken chrome.
 *  - No card-on-tinted-ground. Sections separate with hairlines + space.
 *    No drop shadows: one separation device, at a weight that reads.
 *  - GREEN status renders as plain ink. "Fine" needs no color.
 *  - The top bar is quiet. Navigation is not the loudest thing on screen.
 *  - EUSD navy survives as the single interactive accent, so the district
 *    tie is kept without spending the saturation budget on chrome.
 *
 * Every text/background pair here was contrast-computed before commit;
 * muted text clears AA on BOTH the ground and panels, which is the class
 * of bug that produced the previous build's 4.16:1 failure. */

:root {
  /* Surfaces */
  --bg: #FFFFFF;          /* content ground */
  --bg-sunken: #FAFAFA;   /* table headers, quiet zones */
  --bg-hover: #F4F5F6;    /* row hover — distinct from both of the above */

  /* Ink ramp (all AA+ on bg and bg-sunken) */
  --ink: #1A1C1E;         /* primary text            17.1:1 on white */
  --ink-2: #4E5459;       /* secondary text           7.7:1 */
  --ink-muted: #646A70;   /* labels, captions         5.5:1 / 5.2:1 sunken */

  /* Lines — one hairline weight, one stronger boundary */
  --line: #E4E6E8;
  --line-strong: #C7CBCF;

  /* The single accent: interactive only. Never decoration, never chrome. */
  --accent: #0032A0;
  --accent-hover: #00215C;
  --accent-wash: #EDF1FA;
  /* Section hues: identity for navigation, never data. They colour only a
     section's icon and the nav's current-page rule (DESIGN.md Landmarks;
     enforced by tests/test_design_rules.py). All >= 4.5:1 on white. */
  --hue-dashboard: var(--accent);
  --hue-slps: #0F6E73;      /* 6.0:1 */
  --hue-schools: #84345F;   /* 7.96:1 */
  --hue-reports: #6B4C9A;   /* 6.72:1 */

  /* Threshold colors — the ONLY other color in the system. */
  --over: #B02A1F;        /* over cap        6.6:1 */
  --approaching: #8A5300; /* approaching cap 6.3:1 */

  /* Charts read these; nothing hard-codes a hex in Python. Series 1 is
     slate so a single-series chart stays monochrome; series 2 is ink,
     separating from slate by lightness and from the accent entirely —
     Accent Navy never marks a data series (the One Accent Rule). */
  --chart-1: #4E5459;
  --chart-2: var(--ink);  /* ink, not the accent — the One Accent Rule */
  --chart-3: #B02A1F;
  --chart-4: #8A5300;
  --chart-5: #4E5459;
  --chart-grid: rgba(26, 28, 30, 0.06);

  /* Spacing (4px base) */
  --space-1: 4px;
  --space-2: 8px;
  --space-3: 12px;
  --space-4: 16px;
  --space-5: 20px;
  --space-6: 24px;
  --space-8: 32px;
  --space-12: 48px;
  --space-16: 64px;

  --radius-sm: 4px;
  --radius-md: 6px;
  --radius-lg: 8px;

  --focus-ring: 2px solid #0032A0;
  --focus-ring-dark: 2px solid #1A1C1E;
  --focus-offset: 2px;

  /* Type ramp: six sizes, tokenised. 13 deltas/captions/axis · 15 body,
     table, labels · 18 section h2, hero lede · 20 h1 · 26 figures · 52 hero. */
  --text-xs: 0.8125rem;
  --text-base: 0.9375rem;
  --text-md: 1.125rem;
  --text-lg: 1.25rem;
  --text-xl: 1.625rem;
  --text-hero: 3.25rem;
  --control-h: 36px;

  --font-ui: "Open Sans", system-ui, "Segoe UI", Arial, sans-serif;
  --font-mono: ui-monospace, "SF Mono", "Cascadia Mono", Menlo, Consolas, monospace;
  --bar-h: 56px;
  --z-dropdown: 10;
  --z-sticky: 20;
  --z-skip: 100;

  /* Motion: one curve, one duration band (product register: 150-250ms). */
  --ease: cubic-bezier(0.22, 1, 0.36, 1);
  --dur: 180ms;

  /* ── Legacy aliases: surfaces not yet reworked keep rendering ─────── */
  --color-bg: var(--bg);
  --color-surface: var(--bg);
  --color-border: var(--line);
  --color-border-strong: var(--line-strong);
  --color-text: var(--ink);
  --color-text-secondary: var(--ink-2);
  --color-text-tertiary: var(--ink-muted);
  --color-text-muted: var(--ink-muted);
  --color-primary: var(--accent);
  --color-primary-hover: var(--accent-hover);
  --color-primary-subtle: var(--accent-wash);
  --color-navy: var(--accent);
  --color-midnight: var(--accent-hover);
  --color-wash: var(--accent-wash);
  --color-bg-subtle: var(--bg-sunken);
  --color-danger: var(--over);
  --color-error: var(--over);
  --color-warning: var(--approaching);
  --color-success: var(--ink-2);
  --color-info: var(--accent);
  --color-danger-bg: #FBEEEC;
  --color-warning-bg: #FBF3E7;
  --color-success-bg: var(--bg-sunken);
  --color-info-bg: var(--accent-wash);
  --color-link: var(--accent);
  --font-sans: var(--font-ui);
  --shadow-card: none;
  --shadow-hover: none;
}

/* ── Fonts (self-hosted, latin subset — no CDN, CSP stays 'self') ── */
/* ── Base ─────────────────────────────────────────────── */
html {
  font-size: 16px;
  /* Anchors and keyboard focus land below the sticky bar (3px rule + bar). */
  scroll-padding-top: calc(var(--bar-h) + 3px + var(--space-4));
}

body {
  margin: 0;
  background: var(--bg);
  color: var(--ink);
  font-family: var(--font-ui);
  font-size: 0.9375rem;
  line-height: 1.5;
  min-height: 100vh;
  -webkit-font-smoothing: antialiased;
}

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

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

/* Bootstrap's reboot puts 1rem under every <p>; our own components own
   their spacing, so neutralize it inside them rather than fighting it. */
.panel p,
.triage p,
.stat-grid p,
.rail-list p {
  margin: 0;
}

/* Tabular numerals wherever figures column up. */
.num,
.numeric,
.hero-figure,
.stat-value,
.standing-figure,
.triage-figure,
.totals-figure {
  font-variant-numeric: tabular-nums;
}

/* Visible to assistive tech only (form labels that the layout carries). */
.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* ── Skip link ────────────────────────────────────────── */
.skip-link {
  position: absolute;
  left: -9999px;
  top: auto;
}

.skip-link:focus {
  left: var(--space-4);
  top: var(--space-4);
  background: var(--accent);
  color: #fff;
  padding: var(--space-3) var(--space-4);
  border-radius: var(--radius-sm);
  z-index: var(--z-skip);
}

/* ── Focus ────────────────────────────────────────────── */
a:focus-visible,
button:focus-visible,
input:focus-visible,
select:focus-visible,
summary:focus-visible,
[tabindex]:focus-visible {
  outline: var(--focus-ring);
  outline-offset: var(--focus-offset);
}

/* ── Shell: top bar, white content ────────────────────── */
.app-bar {
  position: sticky;
  top: 0;
  z-index: var(--z-sticky);
  background: var(--bg);
  border-top: 3px solid var(--accent);
  border-bottom: 1px solid var(--line);
}

.app-bar-inner {
  max-width: 1120px;
  margin: 0 auto;
  padding: 0 var(--space-8);
  height: var(--bar-h);
  box-sizing: border-box;
  display: flex;
  align-items: center;
  gap: var(--space-6);
}

.wordmark {
  flex: none;
  display: inline-flex;
  align-items: center;
  gap: var(--space-3);
  text-decoration: none;
  color: var(--ink);
}

.wordmark-mark {
  display: inline-flex;
  color: var(--accent);
}

.wordmark-name {
  font-size: var(--text-base);
  font-weight: 700;
  letter-spacing: -0.01em;
}

.wordmark-rule {
  width: 1px;
  height: 14px;
  background: var(--line-strong);
}

.wordmark-org {
  font-size: var(--text-xs);
  color: var(--ink-2);
  white-space: nowrap;
}

.app-nav {
  display: flex;
  align-self: stretch;
  gap: var(--space-3);
  min-width: 0;
  overflow-x: auto;
  scrollbar-width: none;
  /* The fade covers only this trailing padding, so a nav that fits is
     never faded and a clipped one is never silently cut. */
  padding-right: var(--space-8);
  mask-image: linear-gradient(to right, #000 calc(100% - var(--space-8)), transparent);
  -webkit-mask-image: linear-gradient(to right, #000 calc(100% - var(--space-8)), transparent);
}

.app-nav::-webkit-scrollbar {
  display: none;
}

.app-nav a {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  padding: 0;
  color: var(--ink-2);
  font-size: var(--text-base);
  font-weight: 400;
  text-decoration: none;
  white-space: nowrap;
  transition: color var(--dur) var(--ease);
}

.app-nav a:hover {
  color: var(--ink);
}

/* Icons: line glyphs that inherit colour. Slate by default, ink on hover;
   the current link's icon takes the section hue below. */
.icon {
  flex: none;
}

.app-nav .icon {
  color: var(--ink-muted);
  transition: color var(--dur) var(--ease);
}

/* A neutral section (Upload, Methodology, My Caseload) marks current with
   the identity navy; the hued sections override this below. */
.app-nav a[aria-current] .icon {
  color: var(--accent);
}

.app-nav a:hover .icon {
  color: var(--ink);
}

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

/* Page-header icon: inside the h1, before the text, slate. */
h1 > .icon {
  vertical-align: -4px;
  margin-right: var(--space-2);
  color: var(--ink-muted);
}

.empty-icon {
  display: flex;
  margin: 0 0 var(--space-3);
  color: var(--ink-muted);
}

.icon.hue-dashboard,
.app-nav a.hue-dashboard[aria-current] .icon {
  color: var(--hue-dashboard);
}

.app-nav a.hue-dashboard[aria-current] {
  box-shadow: inset 0 -2px 0 var(--hue-dashboard);
}

.icon.hue-slps,
.app-nav a.hue-slps[aria-current] .icon {
  color: var(--hue-slps);
}

.app-nav a.hue-slps[aria-current] {
  box-shadow: inset 0 -2px 0 var(--hue-slps);
}

.icon.hue-schools,
.app-nav a.hue-schools[aria-current] .icon {
  color: var(--hue-schools);
}

.app-nav a.hue-schools[aria-current] {
  box-shadow: inset 0 -2px 0 var(--hue-schools);
}

.icon.hue-reports,
.app-nav a.hue-reports[aria-current] .icon {
  color: var(--hue-reports);
}

.app-nav a.hue-reports[aria-current] {
  box-shadow: inset 0 -2px 0 var(--hue-reports);
}

/* Current page: ink + weight + a label-width 2px rule on the hairline. */
.app-nav a[aria-current] {
  color: var(--ink);
  font-weight: 600;
  box-shadow: inset 0 -2px 0 var(--accent);
}

.app-nav-gap {
  flex: none;
  width: 1px;
  height: 16px;
  background: var(--line);
  align-self: center;
}

.app-bar-end {
  margin-left: auto;
  display: flex;
  align-items: center;
  gap: var(--space-5);
  flex: none;
}

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

.data-stamp-stale {
  color: var(--approaching);
}

.data-stamp-panel {
  display: none;
  margin: 0 0 var(--space-3);
}

.account-menu {
  position: relative;
}

.account-menu > summary {
  list-style: none;
  cursor: pointer;
  font-size: var(--text-xs);
  font-weight: 600;
  color: var(--ink);
  padding: 6px 0;
  max-width: 18ch;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.account-menu > summary::-webkit-details-marker {
  display: none;
}

.account-menu[open] > summary {
  color: var(--accent);
}

.account-menu-panel {
  position: absolute;
  right: 0;
  top: calc(100% + 6px);
  z-index: var(--z-dropdown);
  min-width: 220px;
  box-sizing: border-box;
  background: var(--bg);
  border: 1px solid var(--line-strong);
  border-radius: var(--radius-md);
  padding: var(--space-4);
}

.account-menu-who {
  margin: 0 0 var(--space-3);
  font-size: var(--text-base);
  color: var(--ink);
}

.account-menu-who span {
  font-size: var(--text-xs);
  color: var(--ink-muted);
}

.btn-ghost-dark {
  align-self: flex-start;
  background: transparent;
  border: 1px solid var(--line-strong);
  border-radius: var(--radius-sm);
  color: var(--ink-2);
  font-family: var(--font-ui);
  font-size: var(--text-xs);
  font-weight: 600;
  padding: 6px var(--space-3);
  min-height: 32px;
  cursor: pointer;
}

.btn-ghost-dark:hover {
  color: var(--ink);
  border-color: var(--ink-muted);
}

/* ── Content column ───────────────────────────────────── */
.app-main {
  max-width: 1120px;
  margin: 0 auto;
  padding: var(--space-8) var(--space-8) var(--space-16);
  box-sizing: border-box;
}

.app-footer {
  max-width: 1120px;
  margin: 0 auto;
  padding: var(--space-6) var(--space-8) var(--space-8);
  box-sizing: border-box;
  border-top: 1px solid var(--line);
  font-size: var(--text-xs);
  color: var(--ink-muted);
}

.app-footer p {
  margin: 0;
}

.page-header {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  justify-content: space-between;
  gap: var(--space-4);
  padding-bottom: var(--space-5);
  border-bottom: 1px solid var(--line);
  margin-bottom: var(--space-8);
}

.page-header h1,
.page-title {
  font-size: var(--text-lg);
  line-height: 1.3;
  font-weight: 600;
  letter-spacing: -0.01em;
  margin: 0;
  text-wrap: balance;
}

.page-context {
  color: var(--ink-2);
  font-size: var(--text-base);
  margin: var(--space-1) 0 0;
}

.page-actions {
  display: flex;
  align-items: center;
  gap: var(--space-2);
}

/* ── Month control: constant footprint, 3 tab stops ───── */
.month-nav {
  display: flex;
  align-items: center;
  gap: var(--space-2);
}

.month-step {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: var(--control-h);
  height: var(--control-h);
  box-sizing: border-box;
  border: 1px solid var(--ink-muted);
  border-radius: var(--radius-sm);
  color: var(--ink-2);
  text-decoration: none;
  font-size: var(--text-base);
}

.month-step:hover {
  border-color: var(--ink-muted);
  color: var(--ink);
}

.month-step[aria-disabled="true"] {
  color: var(--line-strong);
  pointer-events: none;
}

.month-form {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  margin: 0;
}

/* ── Sections: hairlines and space, not cards ─────────── */
.section {
  margin-bottom: var(--space-8);
}

.table-footnote {
  margin-top: var(--space-3);
}

.section-head {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-3);
  margin-bottom: var(--space-4);
}

.section-head h2,
.panel-title,
.section-title {
  font-size: var(--text-md);
  font-weight: 600;
  letter-spacing: 0;
  margin: 0;
  color: var(--ink);
}

.section-note {
  font-size: var(--text-base);
  color: var(--ink-2);
  margin: 0;
}

/* ── Entry tiles (dashboard) ──────────────────────────────
   Four doors ("Go deeper") under the triage band. Hairline, no fill:
   they are navigation, so they stay quieter than the figures above. */
.entry-tiles {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: var(--space-3);
  margin: 0 0 var(--space-8);
}

.entry-tile {
  display: grid;
  grid-template-columns: auto 1fr;
  grid-template-areas:
    "icon label"
    "icon fact";
  column-gap: var(--space-3);
  align-items: center;
  min-height: 72px;
  padding: var(--space-3) var(--space-4);
  border: 1px solid var(--line);
  border-radius: 4px;
  color: var(--ink);
  text-decoration: none;
  transition: border-color var(--dur) var(--ease);
}

.entry-tile .icon {
  grid-area: icon;
  color: var(--ink-muted);
}

.entry-tile-label {
  grid-area: label;
  font-weight: 600;
  font-size: var(--text-base);
}

.entry-tile-fact {
  grid-area: fact;
  font-size: var(--text-xs);
  color: var(--ink-2);
  font-variant-numeric: tabular-nums;
}

.entry-tile:hover {
  border-color: var(--line-strong);
}

.entry-tile:hover .entry-tile-label {
  text-decoration: underline;
  text-underline-offset: 3px;
}

.entry-tile.hue-slps .icon {
  color: var(--hue-slps);
}

.entry-tile.hue-schools .icon {
  color: var(--hue-schools);
}

.entry-tile.hue-reports .icon {
  color: var(--hue-reports);
}

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

@media print {
  .entry-tiles {
    display: none;
  }
}

/* Panels persist for not-yet-reworked surfaces: hairline, no shadow. */
.panel,
.hero-card {
  background: var(--bg);
  border: 1px solid var(--line);
  border-radius: var(--radius-md);
  padding: var(--space-6);
}

/* ── Triage band: two column tracks, three regions. The stats row is a
   subgrid of the same two tracks, so every left edge lands on one of two
   lines. DOM order is hero, stats, strip (keyboard reaches the figures
   before the strip's tick links); the strip is placed by grid area. ── */
.triage {
  display: grid;
  grid-template-columns: minmax(280px, 2fr) minmax(0, 3fr);
  grid-template-rows: auto auto;
  gap: var(--space-6) var(--space-12);
  align-items: start;
  padding-bottom: var(--space-6);
  border-bottom: 1px solid var(--line);
  margin-bottom: var(--space-6);
}

.triage-hero {
  grid-column: 1;
  grid-row: 1;
}

.triage-dist {
  grid-column: 2;
  grid-row: 1;
  min-width: 0;
}

.triage-stats {
  grid-column: 1 / -1;
  grid-row: 2;
  display: grid;
  grid-template-columns: subgrid;
  padding-top: var(--space-5);
  border-top: 1px solid var(--line);
}

@supports not (grid-template-columns: subgrid) {
  .triage-stats {
    grid-template-columns: minmax(280px, 2fr) minmax(0, 3fr);
    gap: 0 var(--space-12);
  }
}

.triage-stats-pair {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 0 var(--space-8);
}

/* Fixed row heights keep label / figure / delta on shared baselines across
   nesting levels without a subgrid on every cell. */
.triage-stat .triage-label {
  height: 22px;
  margin: 0;
  white-space: nowrap;
}

.triage-stat .triage-stat-figure {
  height: 32px;
}

.triage-figure,
.standing-figure {
  font-size: var(--text-hero);
  line-height: 1;
  font-weight: 700;
  letter-spacing: -0.02em;
  color: var(--ink);
  margin: 0;
  display: block;
  width: fit-content;
  text-decoration: none;
}

a.triage-figure:hover,
a.triage-stat-figure:hover,
a.stat-value:hover {
  color: inherit;
  text-decoration: underline;
  text-underline-offset: 4px;
}

/* Color appears ONLY when a threshold is crossed. */
.triage-figure-alert,
.standing-figure-alert {
  color: var(--over);
}

.triage-lede,
.standing-lede {
  font-size: var(--text-md);
  font-weight: 600;
  color: var(--ink);
  margin: var(--space-2) 0 0;
  text-wrap: balance;
}

.triage-changes,
.standing-changes {
  font-size: var(--text-base);
  color: var(--ink-2);
  margin: var(--space-4) 0 0;
  max-width: 46ch;
  text-wrap: pretty;
  line-height: 1.6;
}

.triage-changes-label,
.standing-changes-label {
  font-weight: 600;
  color: var(--ink);
  margin-right: var(--space-2);
}

.triage-changes .sep,
.standing-changes .sep {
  color: var(--ink-muted);
}

.triage-changes a,
.standing-changes a {
  text-decoration: none;
}

.triage-changes a:hover,
.standing-changes a:hover {
  text-decoration: underline;
  text-underline-offset: 3px;
}

.triage-finding {
  font-size: var(--text-md);
  font-weight: 600;
  color: var(--ink);
  margin: 0 0 var(--space-2);
  line-height: 1.3;
}

.triage-stat-figure {
  font-size: var(--text-xl);
  line-height: 1.1;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  color: var(--ink);
  margin: 0;
  text-decoration: none;
  display: block;
  width: fit-content;
}

/* Declared AFTER the base rule so the threshold colors win the cascade. */
.triage-stat-figure-warn {
  color: var(--approaching);
}

.triage-stat-figure-alert {
  color: var(--over);
}

/* ── Standing band (My Caseload) ───────────────────────
   The same two tracks as the dashboard's triage band, so the reference
   surface and the dashboard read as one system: the answer on the left,
   the supporting figures on the right, the change line beneath, and a
   hairline closing the band. DOM order is hero, stats, change line —
   the change line is placed by grid area, so a keyboard reaches the
   figures before the link into the excluded section.

   `.standing-figure` shares the hero rules above rather than restating
   them; only the threshold colors are declared here (after the base
   rule, so the cascade cannot drop them). ── */
.standing {
  display: grid;
  grid-template-columns: minmax(280px, 2fr) minmax(0, 3fr);
  grid-template-rows: auto auto;
  gap: var(--space-6) var(--space-12);
  align-items: start;
  padding-bottom: var(--space-6);
  border-bottom: 1px solid var(--line);
  margin-bottom: var(--space-8);
}

.standing-hero {
  grid-column: 1;
  grid-row: 1;
}

.standing-hero .metric-label {
  margin-bottom: var(--space-1);
}

/* `.stat-grid.standing-stats` on purpose: the auto-fit columns and zero
   row gutter `.stat-grid` declares later in this file would otherwise
   win on source order and put three stats in a row. Two columns fixed,
   because the pair becomes four when SLPA or Rover is set. */
.stat-grid.standing-stats {
  grid-column: 2;
  grid-row: 1;
  min-width: 0;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--space-6) var(--space-8);
}

/* A subgrid takes the parent's row gutter unless it declares its own —
   without this, the 24px between stat ROWS would also open up between
   each stat's label, figure, and delta. */
.standing-stats > * {
  row-gap: 0;
}

/* Airier than the dashboard's totals (Part A §3): this reader is
   checking two figures, not scanning fifty. */
.standing-stats .metric-label {
  margin-bottom: var(--space-1);
}

.standing-stats .metric-delta {
  margin-top: var(--space-2);
}

.standing-changes {
  grid-column: 1 / -1;
  grid-row: 2;
  margin: 0;
  /* It spans both tracks, so it takes the prose measure rather than the
     dashboard's narrow one — the sentence reads on one line. */
  max-width: 70ch;
}

.standing-figure-warn {
  color: var(--approaching);
}

/* Declared after the shared hero rule so the threshold color wins. */
.standing-figure-alert {
  color: var(--over);
}

/* A quiet line under the header when the month asked for is not the
   month shown. Present only when there is something to say. */
.month-notice {
  font-size: var(--text-base);
  color: var(--ink-2);
  max-width: 70ch;
  margin: calc(-1 * var(--space-5)) 0 var(--space-6);
}

/* Page header: her name, and the manager's way back beside it. */
.page-heading {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--space-4);
  min-width: 0;
}

.page-heading-link {
  white-space: nowrap;
}

/* ── Roster (My Caseload) ─────────────────────────────── */
/* `.roster-group` now styles only the "Changes since" fold's summary: a
   native <details>, so it needs no script under CSP and still prints.
   The roster itself is the flat `.roster-table` below. */
.roster-group {
  margin-bottom: var(--space-4);
}

.roster-group > summary {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--space-1) var(--space-3);
  padding: var(--space-2) 0;
  font-size: var(--text-base);
  cursor: pointer;
  border-bottom: 1px solid transparent;
  list-style: none;
}

/* Two vendors, one job: drop the native disclosure triangle so the
   word "Show"/"Hide" is the only affordance. */
.roster-group > summary::-webkit-details-marker {
  display: none;
}

/* Open: a hairline under the header, the way every other section here
   separates. Closed: the line would be a box edge with nothing in it. */
.roster-group[open] > summary {
  border-bottom-color: var(--line);
  margin-bottom: var(--space-2);
}

.roster-group-name {
  font-weight: 600;
  color: var(--ink);
}

.roster-group-meta {
  color: var(--ink-2);
}

.roster-group-toggle {
  width: 44px;
  text-align: right;
  font-size: var(--text-xs);
  color: var(--ink-2);
}

.roster-group-toggle::after {
  content: "Show";
}

.roster-group[open] > summary .roster-group-toggle::after {
  content: "Hide";
}

/* The changes fold sits between the roster total and the excluded section. */
.roster-changes {
  margin-top: var(--space-5);
}

/* "Placement in May" and "Where now" are sentences; let everything but
   the SEIS ID wrap so the table fits a phone instead of scrolling. */
.roster-changes-table th:not(:first-child),
.roster-changes-table td:not(:first-child) {
  white-space: normal;
}

.roster-changes .table-scroll + .table-scroll,
.roster-changes .table-scroll + .empty-note,
.roster-changes .empty-note + .table-scroll {
  margin-top: var(--space-4);
}

/* The line the table adds up to, right-aligned under the Weight column. */
.roster-total {
  text-align: right;
  margin: var(--space-4) 0 0;
  color: var(--ink-2);
}

/* ── Roster table: one flat, sortable, findable list (T8.8) ──
   Sort and find are progressive enhancement from roster-table.js; with
   no script the server's name order stands and the headers are text. */
.roster-tools {
  display: flex;
  align-items: center;
  gap: var(--space-3);
}

.roster-find {
  font-family: var(--font-ui);
  font-size: var(--text-base);
  color: var(--ink);
  background: var(--bg);
  border: 1px solid var(--ink-muted);
  border-radius: var(--radius-sm);
  padding: 0 var(--space-3);
  height: var(--control-h);
  min-height: var(--control-h);
  box-sizing: border-box;
  width: 16rem;
  max-width: 100%;
}

.roster-find::placeholder {
  color: var(--ink-muted);
  opacity: 1;
}

.roster-find:focus {
  border-color: var(--accent);
}

/* Reserve the wider of the two readings so the box does not shift as the
   count changes between "75 students" and "12 of 75 shown". */
.roster-count {
  margin: 0;
  min-width: 8.5em;
  font-size: var(--text-xs);
  color: var(--ink-muted);
  white-space: nowrap;
}

/* The scroll wrapper's overflow would trap position: sticky inside a
   box that never scrolls vertically; the roster fits its column. */
.table-scroll:has(> .roster-table) {
  overflow: visible;
}

.roster-table {
  table-layout: auto;
}

table.roster-table thead th {
  position: sticky;
  top: calc(var(--bar-h) + 3px);
  z-index: var(--z-sticky);
  background: var(--bg);
  height: 40px;
  box-sizing: border-box;
  /* border-collapse borders scroll away with the row; a shadow travels. */
  border-bottom: 0;
  box-shadow: inset 0 -1px 0 var(--line-strong);
}

html.has-sticky-thead {
  scroll-padding-top: calc(var(--bar-h) + 3px + 40px + var(--space-4));
}

.sort-btn {
  font: inherit;
  letter-spacing: inherit;
  color: inherit;
  background: none;
  border: 0;
  padding: 0;
  margin: 0;
  cursor: pointer;
  text-align: inherit;
  transition: color 150ms ease;
}

.sort-btn:hover {
  color: var(--ink);
}

table.roster-table th[aria-sort] .sort-btn {
  color: var(--ink);
}

th[aria-sort="ascending"] .sort-btn::after {
  content: " \2191";
}

th[aria-sort="descending"] .sort-btn::after {
  content: " \2193";
}

.cell-age,
.cell-missing {
  color: var(--ink-muted);
}

.cell-seis-phone {
  display: none;
}

@media screen and (max-width: 720px) {
  .roster-tools {
    width: 100%;
  }

  .roster-find {
    flex: 1 1 auto;
    width: auto;
    min-width: 0;
  }

  .roster-count {
    min-width: 0;
  }

  /* The roster, the changes tables and the excluded table all fold the
     SEIS ID under the name on a phone, so none of them scrolls sideways. */
  table.roster-table .cell-seis,
  table.roster-table th[data-sort="seis"],
  table.roster-changes-table .cell-seis,
  #excluded .data-table .cell-seis {
    display: none;
  }

  table.roster-table td,
  table.roster-table th,
  table.roster-changes-table td,
  table.roster-changes-table th,
  #excluded .data-table td,
  #excluded .data-table th {
    white-space: normal;
    padding-left: var(--space-2);
    padding-right: var(--space-2);
  }

  table.roster-table td:first-child,
  table.roster-table th:first-child,
  table.roster-changes-table td:first-child,
  table.roster-changes-table th:first-child,
  #excluded .data-table td:first-child,
  #excluded .data-table th:first-child {
    padding-left: 0;
  }

  table.roster-table td:last-child,
  table.roster-table th:last-child,
  table.roster-changes-table td:last-child,
  table.roster-changes-table th:last-child,
  #excluded .data-table td:last-child,
  #excluded .data-table th:last-child {
    padding-right: 0;
  }

  .cell-seis-phone {
    display: block;
    font-size: var(--text-xs);
    color: var(--ink-muted);
  }

  /* The two-row phone bar is 92px: the thead sticks under it, never over
     the nav strip, and anchors clear bar + thead. */
  table.roster-table thead th {
    top: 92px;
  }

  html.has-sticky-thead {
    scroll-padding-top: calc(92px + 40px + var(--space-4));
  }
}

/* Print-only month line: on screen the month select already says it. */
.record-line {
  display: none;
  margin: 0;
}

.roster-total .num {
  font-weight: 700;
  color: var(--ink);
}

/* ── Chart pair (My Caseload) ─────────────────────────── */
/* The same two tracks as the standing band above it, so the page has two
   vertical lines rather than four: History (four points) reads fine in
   the narrow track, and Programs — whose category names sit to the left
   of every bar — gets the wide one. */
.charts-pair {
  display: grid;
  grid-template-columns: minmax(280px, 2fr) minmax(0, 3fr);
  gap: 0 var(--space-12);
}

/* Grid items already stretch to the taller of the pair (Programs); the
   section becomes a column so History's chart takes the height left over
   below its heading instead of leaving dead white under a 200px canvas. */
.charts-pair > .section {
  display: flex;
  flex-direction: column;
}

.charts-pair .chart-container {
  flex: 1;
  min-height: 240px;
}

/* Same specificity as the rule above, so Programs keeps its 360px floor. */
.charts-pair .chart-container-tall {
  min-height: 360px;
}

/* ── Labels: sentence case, quiet. No uppercase tracking. ── */
.metric-label,
.triage-label {
  font-size: var(--text-base);
  font-weight: 400;
  color: var(--ink-2);
  margin: 0 0 var(--space-1);
  letter-spacing: 0;
  text-transform: none;
}

.metric-delta,
.triage-delta {
  font-size: var(--text-xs);
  color: var(--ink-muted);
  margin: var(--space-1) 0 0;
  min-height: 20px; /* reserved: layout never shifts when absent */
}

/* ── District totals: one quiet aligned grid ──────────── */
.stat-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
  gap: 0 var(--space-8);
}

/* Subgrid keeps label / figure / delta on shared baselines even when a
   label wraps — the defect that put one figure 18px above its siblings. */
.stat-grid > * {
  display: grid;
  grid-template-rows: subgrid;
  grid-row: span 3;
  align-content: start;
}

@supports not (grid-template-rows: subgrid) {
  .stat-grid {
    align-items: end;
  }

  .stat-grid > * {
    display: block;
  }
}

.stat-value,
.totals-figure,
.metric-value {
  font-size: var(--text-xl);
  line-height: 1.2;
  font-weight: 700;
  color: var(--ink);
  margin: 0;
  text-decoration: none;
}

a.stat-value:hover,
a.totals-figure:hover {
  text-decoration: underline;
  text-underline-offset: 3px;
}

/* Subordinated fraction: exact value kept, scanning made easy. */
.figure-frac {
  font-size: 0.7em;
  font-weight: 600;
  color: var(--ink-muted);
}

.figure-unit {
  font-size: 0.75rem;
  font-weight: 400;
  color: var(--ink-muted);
  margin-left: var(--space-1);
}

/* ── Tables: header is a label, not a band ────────────── */
.table-scroll {
  overflow-x: auto;
}

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

.data-table caption {
  caption-side: top;
  text-align: left;
  font-size: var(--text-xs);
  color: var(--ink-muted);
  padding: 0 0 var(--space-2);
}

.data-table th,
.data-table td {
  text-align: left;
  padding: 10px var(--space-4);
  border-bottom: 1px solid var(--line);
  white-space: nowrap;
}

.data-table th:first-child,
.data-table td:first-child {
  padding-left: 0;
}

.data-table th:last-child,
.data-table td:last-child {
  padding-right: 0;
}

.data-table thead th {
  background: var(--bg);
  color: var(--ink-2);
  font-weight: 600;
  font-size: var(--text-xs);
  letter-spacing: 0.02em;
  border-bottom: 1px solid var(--line-strong);
}

.data-table thead th a {
  color: var(--ink-muted);
}

.data-table tbody tr:hover {
  background: var(--bg-hover);
}

/* A column of names is not a text block, so position and hover carry the
   affordance; a permanent underline on every row reads as noise. */
.data-table tbody a {
  text-decoration: none;
}

.data-table tbody a:hover {
  text-decoration: underline;
  text-underline-offset: 3px;
}

.data-table td.numeric,
.data-table th.numeric {
  text-align: right;
}

/* Threshold emphasis inside tables: the number itself carries it. */
.cell-over {
  color: var(--over);
  font-weight: 700;
}

.cell-approaching {
  color: var(--approaching);
  font-weight: 600;
}

/* ── Status chips: text-first. GREEN is ink — "fine" needs no color. ── */
.status-badge {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 1px 0;
  font-size: 0.8125rem;
  font-weight: 600;
  color: var(--ink-2);
  background: none;
  white-space: nowrap;
}

.status-badge::before {
  content: "";
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--line-strong);
  flex: none;
}

.status-badge-red {
  color: var(--over);
}

.status-badge-red::before {
  background: var(--over);
}

.status-badge-orange {
  color: var(--approaching);
}

.status-badge-orange::before {
  background: var(--approaching);
}

.status-badge-green {
  color: var(--ink-2);
}

.status-badge-green::before {
  background: var(--line-strong);
}

/* Outreach is an agreement flag, not a cap breach, and the accent is
   reserved for things you can click. So the chip stays neutral and the
   WEIGHTED FIGURE carries the signal instead (.cell-outreach) — the
   number itself is the evidence. */
.status-badge-outreach {
  color: var(--ink-2);
  margin-left: var(--space-3);
  font-weight: 500;
}

.status-badge-outreach::before {
  background: var(--ink-muted);
}

.cell-outreach {
  font-weight: 700;
}

.status-badge-previewed {
  color: var(--approaching);
}

.status-badge-previewed::before {
  background: var(--approaching);
}

.status-badge-committed {
  color: var(--ink-2);
}

.status-badge-committed::before {
  background: var(--ink-2);
}

.status-badge-superseded {
  color: var(--ink-muted);
}

.status-badge-superseded::before {
  background: var(--line-strong);
}

/* ── Buttons ──────────────────────────────────────────── */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  height: var(--control-h);
  min-height: var(--control-h);
  box-sizing: border-box;
  padding: 0 var(--space-4);
  font-family: var(--font-ui);
  font-size: 0.875rem;
  font-weight: 600;
  border-radius: var(--radius-sm);
  border: 1px solid transparent;
  cursor: pointer;
  text-decoration: none;
}

.btn:focus-visible {
  outline: var(--focus-ring);
  outline-offset: var(--focus-offset);
  box-shadow: none;
}

.btn-primary {
  background: var(--accent);
  color: #fff;
}

.btn-primary:hover {
  background: var(--accent-hover);
  color: #fff;
}

.btn-secondary,
.btn-secondary:hover,
.btn-secondary:focus-visible,
.btn-secondary:active {
  background: var(--bg);
  border-color: var(--ink-muted);
  color: var(--ink);
}

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

.btn-ghost {
  background: transparent;
  color: var(--accent);
}

.btn-ghost:hover {
  background: var(--accent-wash);
}

/* ── Forms ────────────────────────────────────────────── */
input[type="text"],
input[type="email"],
input[type="file"],
select {
  font-family: var(--font-ui);
  font-size: var(--text-base);
  color: var(--ink);
  background: var(--bg);
  border: 1px solid var(--ink-muted);
  border-radius: var(--radius-sm);
  padding: 0 var(--space-3);
  height: var(--control-h);
  min-height: var(--control-h);
  box-sizing: border-box;
}

input:focus,
select:focus {
  border-color: var(--accent);
}

.upload-form {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
  max-width: 480px;
  margin-top: var(--space-6);
}

.upload-form-field label {
  display: block;
  font-size: 0.8125rem;
  font-weight: 600;
  color: var(--ink);
  margin-bottom: var(--space-1);
}

.form-error {
  color: var(--over);
  font-size: 0.75rem;
  margin: var(--space-1) 0 0;
}

.upload-actions {
  display: flex;
  gap: var(--space-3);
  margin-top: var(--space-4);
}

/* Full border, not a side stripe. */
.info-banner {
  background: var(--bg-sunken);
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  padding: var(--space-3) var(--space-4);
  font-size: 0.875rem;
  color: var(--ink-2);
  margin-top: var(--space-4);
}

/* ── Chips / filters / sort ───────────────────────────── */
.chip-row {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  margin: var(--space-4) 0;
}

.chip {
  padding: 5px var(--space-3);
  border: 1px solid var(--line-strong);
  border-radius: 99px;
  font-size: 0.8125rem;
  font-weight: 600;
  text-decoration: none;
  color: var(--ink-2);
  background: var(--bg);
}

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

.chip-active {
  background: var(--ink);
  color: #fff;
  border-color: var(--ink);
}

.chip-active:hover {
  color: #fff;
}

.filter-search {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  align-items: center;
  margin: var(--space-4) 0;
}

.filter-search input[type="text"] {
  max-width: 240px;
}

.sort-link {
  color: inherit;
  text-decoration: none;
}

.sort-link:hover {
  text-decoration: underline;
}

.cap-badge {
  border-bottom: 1px dotted var(--line-strong);
  cursor: help;
}

.inline-form {
  display: inline-block;
  margin: 0;
}

.toggle-btn {
  padding: 5px var(--space-3);
  border: 1px solid var(--line-strong);
  border-radius: var(--radius-sm);
  background: var(--bg);
  color: var(--ink-2);
  font-family: var(--font-ui);
  font-size: 0.75rem;
  font-weight: 600;
  cursor: pointer;
  min-height: 30px;
}

.toggle-btn:hover {
  border-color: var(--ink-muted);
}

.toggle-btn-on {
  background: var(--ink);
  color: #fff;
  border-color: var(--ink);
}

/* ── Misc content ─────────────────────────────────────── */
.eyebrow {
  font-size: 0.8125rem;
  font-weight: 600;
  color: var(--ink-muted);
  margin: 0 0 var(--space-1);
}

.lede {
  color: var(--ink-2);
  font-size: 0.9375rem;
  max-width: 70ch;
  margin: 0 0 var(--space-6);
  text-wrap: pretty;
}

.subsection-title {
  font-size: 0.875rem;
  font-weight: 600;
  color: var(--ink-2);
  margin: 0 0 var(--space-2);
}

.section-block {
  margin-top: var(--space-8);
}

.plain-list {
  margin: 0;
  padding-left: 20px;
  color: var(--ink-2);
  font-size: 0.875rem;
}

.sample-reason {
  font-size: 0.875rem;
  color: var(--ink-2);
  margin: 0 0 var(--space-2);
}

.empty-note {
  color: var(--ink-muted);
  font-size: var(--text-base);
}

.summary-row td {
  color: var(--ink-muted);
  font-style: italic;
}

.rail-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-5);
  font-size: 0.875rem;
}

.metric-row {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
  gap: var(--space-5);
}

.metric-card {
  background: var(--bg);
  border: 1px solid var(--line);
  border-radius: var(--radius-md);
  padding: var(--space-4);
}

/* ── Pagination (list surfaces) ───────────────────────── */
.pagination {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  margin-top: var(--space-5);
  font-size: 0.875rem;
}

.pagination a,
.pagination span {
  display: inline-flex;
  align-items: center;
  padding: 5px var(--space-3);
  border: 1px solid var(--line-strong);
  border-radius: var(--radius-sm);
  text-decoration: none;
  color: var(--ink-2);
  background: var(--bg);
}

.pagination .current {
  background: var(--ink);
  color: #fff;
  border-color: var(--ink);
}

/* Section-head icon (My Caseload): 18px, ink-muted, before the h2 text. */
.section-head h2 > .icon-muted {
  margin-right: var(--space-2);
}

.section-head h2 > .icon-muted .icon {
  vertical-align: -3px;
}

/* ── Methodology ("How counts work") ─────────────────────
   A reading page on the system. The header holds the title, a one-line
   lede and the jump list; every section opens on a hairline; the tables
   share one 640px measure with the same two figure columns, so the
   counts line up from the grid down through the program list. Ink and
   Slate only: nothing here has crossed a threshold (the Threshold Rule). */
.method-head {
  flex: 1 1 100%;
  min-width: 0;
}

.method-lede {
  max-width: 70ch;
  margin: var(--space-2) 0 0;
  color: var(--ink-2);
  text-wrap: pretty;
}

.method-jump ul {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2) var(--space-6);
  margin: var(--space-4) 0 0;
  padding: 0;
  list-style: none;
}

.method-section + .method-section,
.method-sources {
  padding-top: var(--space-6);
  border-top: 1px solid var(--line);
}

.method-table {
  max-width: 640px;
}

.method-table .data-table th,
.method-table .data-table td {
  white-space: normal;
  vertical-align: baseline;
}

/* The two figure columns keep one width in every table here. */
.method-table thead th.numeric {
  width: 22%;
}

.method-table tbody th {
  font-weight: 400;
  color: var(--ink);
}

/* The grid's counts are the page's figures: 18px, bold, ink. */
.count-grid td {
  font-size: var(--text-md);
  font-weight: 700;
  color: var(--ink);
}

.count-grid-programs,
.status-grid-rule {
  display: block;
  color: var(--ink-2);
}

.method-table .status-grid td {
  white-space: nowrap;
}

.method-text,
.method-note,
.method-list,
.method-terms,
.method-sources p {
  max-width: 70ch;
  text-wrap: pretty;
}

.method-text {
  margin: 0 0 var(--space-4);
}

.method-text:last-child {
  margin-bottom: 0;
}

.method-note {
  margin: var(--space-3) 0 var(--space-4);
  color: var(--ink-2);
}

.method-note:last-child {
  margin-bottom: 0;
}

.method-list {
  margin: 0;
  padding-left: var(--space-5);
}

.method-list li + li {
  margin-top: var(--space-2);
}

.method-list strong {
  font-weight: 600;
}

/* Term beside its rule; stacked on a phone. */
.method-terms {
  display: grid;
  grid-template-columns: max-content minmax(0, 1fr);
  gap: var(--space-3) var(--space-6);
  margin: 0;
}

.method-terms dt {
  font-weight: 600;
}

.method-terms dd {
  margin: 0;
}

.method-sources p {
  margin: 0;
  font-size: var(--text-xs);
  color: var(--ink-muted);
}

@media (max-width: 720px) {
  .method-terms {
    grid-template-columns: minmax(0, 1fr);
    gap: 0;
  }

  .method-terms dd {
    margin-bottom: var(--space-3);
  }
}

/* Two Letter pages: no jump list, black rules, tables whole. The page
   prints on a 14px root (scoped by :has, so no other page changes) and
   tighter rows; 15px type and screen spacing ran to a third page. */
@media print {
  html:has(.method-header) {
    font-size: 14px;
  }

  .method-jump {
    display: none;
  }

  .method-header,
  .method-section + .method-section,
  .method-sources {
    border-color: #000;
  }

  .method-header {
    padding-bottom: var(--space-2);
    margin-bottom: var(--space-3);
  }

  .method-table.table-scroll {
    overflow: visible;
  }

  .method-section {
    margin-bottom: var(--space-3);
  }

  .method-section + .method-section,
  .method-sources {
    padding-top: var(--space-2);
  }

  .method-section .section-head {
    margin-bottom: var(--space-1);
  }

  .method-table caption {
    padding-bottom: var(--space-1);
  }

  .method-table .data-table th,
  .method-table .data-table td {
    padding-top: 2px;
    padding-bottom: 2px;
    line-height: 1.35;
  }

  .method-text,
  .method-note {
    margin-bottom: var(--space-2);
  }

  .method-note {
    margin-top: var(--space-2);
  }

  .method-terms {
    grid-template-columns: max-content minmax(0, 1fr);
    gap: var(--space-1) var(--space-5);
  }

  .method-terms dd {
    margin-bottom: 0;
  }

  .method-table tr,
  .method-list li,
  .method-terms dd {
    break-inside: avoid;
  }

  #counts {
    break-inside: avoid;
  }
}

/* ── Data graphics (server-rendered SVG; see templatetags/viz.py) ──
   Colour lives here, not in the markup, so the palette has one home.
   Ink by default; the threshold red appears only past a cap. */
.spark {
  display: block;
  overflow: visible;
}

.spark-line {
  fill: none;
  stroke: var(--ink-muted);
  stroke-width: 1.25;
  stroke-linejoin: round;
  stroke-linecap: round;
  vector-effect: non-scaling-stroke;
}

.spark-dot {
  fill: var(--ink);
}

.deviation {
  display: block;
}

.deviation-axis {
  stroke: var(--line);
  stroke-width: 1;
}

.deviation-zero {
  stroke: var(--ink);
  stroke-width: 1.5;
}

.deviation-over {
  fill: var(--over);
}

.deviation-under {
  fill: var(--ink-muted);
}

.strip {
  display: block;
  width: 100%;
  overflow: visible;
}

.strip-axis {
  stroke: var(--line);
  stroke-width: 1;
  vector-effect: non-scaling-stroke;
}

.strip-cap {
  stroke: var(--ink);
  stroke-width: 1;
  stroke-dasharray: 2 2;
  vector-effect: non-scaling-stroke;
}

.strip-tick {
  stroke: var(--ink-muted);
  stroke-width: 2;
  vector-effect: non-scaling-stroke;
}

.strip-tick-over {
  stroke: var(--over);
}

.strip-below {
  fill: var(--ink-2);
  font-weight: 600;
}

.strip-cap-label,
.strip-scale {
  font-family: var(--font-ui);
  font-size: 13px;
  fill: var(--ink-2);
}

/* Hover/click layer: each tick is a link; the tooltip IS the explanation. */
.strip-hit {
  fill: transparent;
  cursor: pointer;
}

.strip-link .strip-tip {
  visibility: hidden;
  pointer-events: none;
}

.strip-link:hover .strip-tip,
.strip-link:focus .strip-tip,
.strip-link:focus-visible .strip-tip {
  visibility: visible;
}

.strip-tip-bg {
  fill: var(--ink);
}

.strip-tip-text {
  font-family: var(--font-ui);
  font-size: 13px;
  font-weight: 600;
  fill: #fff;
}

.strip-link:focus-visible {
  outline: none;
}

.strip-link:focus-visible .strip-hit {
  stroke: var(--accent);
  stroke-width: 2;
}

.strip-cap-label {
  fill: var(--ink);
  font-weight: 600;
}

/* A figure and its sparkline sit on one line: the number reads first,
   the shape confirms it. */
.figure-with-spark {
  display: flex;
  align-items: center;
  gap: var(--space-3);
}

/* Table cell holding a graphic rather than a value. */
.cell-viz {
  width: 148px;
  padding-top: 6px;
  padding-bottom: 6px;
}

/* ── Charts ───────────────────────────────────────────── */
.chart-container {
  position: relative;
  height: 200px;
}

.chart-container canvas {
  max-width: 100%;
}

/* A horizontal-bar chart with more than six categories needs the rows
   to stay readable; the template adds this when the count warrants it
   (CSP forbids a computed inline height). */
.chart-container-tall {
  height: 360px;
  min-height: 360px;
}

/* ── htmx ─────────────────────────────────────────────── */
.htmx-indicator {
  opacity: 0;
  transition: opacity var(--dur) ease-in;
}

.htmx-request .htmx-indicator,
.htmx-request.htmx-indicator {
  opacity: 1;
}

/* ── Auth pages ───────────────────────────────────────── */
.auth-shell {
  display: flex;
  justify-content: center;
  padding-top: var(--space-12);
}

.auth-card {
  width: 100%;
  max-width: 380px;
}

.auth-card-header {
  text-align: center;
  margin-bottom: var(--space-6);
}

.auth-mark {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  border-radius: var(--radius-md);
  background: var(--ink);
  color: #fff;
  font-weight: 700;
  font-size: 1rem;
  margin-bottom: var(--space-3);
}

.auth-footer-note {
  text-align: center;
  color: var(--ink-muted);
  font-size: 0.75rem;
  margin-top: var(--space-4);
}

.google-icon {
  width: 18px;
  height: 18px;
}

/* ── Errors ───────────────────────────────────────────── */
.error-card {
  background: var(--bg);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  padding: var(--space-8);
  max-width: 480px;
  margin: var(--space-12) auto;
  text-align: center;
}

.error-code {
  font-family: var(--font-mono);
  font-size: 2rem;
  font-weight: 600;
  color: var(--ink-muted);
  margin: 0 0 var(--space-2);
}

/* ── Motion: state changes only ───────────────────────── */
.data-table tbody tr,
.chip,
.btn,
.toggle-btn,
.month-step {
  transition: background-color var(--dur) var(--ease),
    border-color var(--dur) var(--ease), color var(--dur) var(--ease);
}

/* ── Responsive ───────────────────────────────────────── */
@media (max-width: 1024px) {
  .app-bar-inner {
    padding: 0 var(--space-5);
    gap: var(--space-5);
  }

  .app-footer {
    padding: var(--space-6) var(--space-5) var(--space-8);
  }

  .app-main {
    padding: var(--space-6) var(--space-5) var(--space-12);
  }

  .triage {
    grid-template-columns: 1fr;
    gap: var(--space-6);
  }

  .triage-hero,
  .triage-dist,
  .triage-stats {
    grid-column: 1;
    grid-row: auto;
  }

  .triage-stats {
    grid-template-columns: 1fr;
    gap: var(--space-5) 0;
  }

  .standing {
    grid-template-columns: minmax(0, 1fr);
    gap: var(--space-6);
  }

  .standing-hero,
  .stat-grid.standing-stats,
  .standing-changes {
    grid-column: 1;
    grid-row: auto;
  }

  .charts-pair {
    grid-template-columns: minmax(0, 1fr);
  }
}

/* Two figures side by side stop being readable before the phone does. */
@media (max-width: 640px) {
  .stat-grid.standing-stats {
    grid-template-columns: minmax(0, 1fr);
  }
}

/* The bar sheds its identity text, then its stamp, before it ever
   stacks: links never go vertical (critique P1, 2026-10-16). */
@media (max-width: 1180px) {
  .wordmark-rule,
  .wordmark-org {
    display: none;
  }
}

/* The manager bar carries two more links and a stamp. The bar's inner
   column is capped at 1120px, so no viewport is wide enough for the
   six-link nav (506px) AND the identity text: a dense bar sheds the
   identity text at every width and the last link is never cut (T8.6c). */
.app-bar-dense .wordmark-rule,
.app-bar-dense .wordmark-org {
  display: none;
}

/* Icons made the manager's six-link nav ~170px wider (T8.9 L1). The bar
   sheds the nav icons FIRST (below 1180px) and keeps the stale-data stamp,
   the bar's only status signal, until 900px like everyone else; the hued
   underline still marks the current section without its icon. The viewer
   bar is narrower and keeps both down to the phone strip. */
/* "How counts work" (2026-10-21) is 60px longer than "Methodology". Inside
   the 1120px column the viewer bar sheds the department from the org text
   and the manager bar sheds the words before the stamp's date, both below
   1540px, so the last label is whole and nothing else moves. */
@media (max-width: 1540px) {
  .wordmark-org-dept {
    display: none;
  }

  .app-bar-dense .data-stamp-prefix {
    display: none;
  }
}

@media (min-width: 721px) and (max-width: 1180px) {
  .app-bar-dense .app-nav .icon {
    display: none;
  }
}

@media (max-width: 900px) {
  .data-stamp-bar {
    display: none;
  }

  .data-stamp-panel {
    display: block;
    margin: var(--space-3) 0 0;
  }
}

/* Phone: two rows — wordmark + account, then the nav as a full-width
   strip that scrolls sideways under a right-edge fade. The fade covers
   the strip's trailing padding, so the last label is whole once scrolled
   to the end and only an unreached label is ever faded. */
@media (max-width: 720px) {
  html {
    scroll-padding-top: calc(92px + var(--space-4));
  }

  .app-bar-inner {
    height: auto;
    flex-wrap: wrap;
    gap: 0 var(--space-5);
  }

  .wordmark,
  .app-bar-end {
    height: 48px;
  }

  .app-nav {
    order: 3;
    flex: 1 0 100%;
    height: 40px;
    gap: var(--space-5);
    margin: 0 calc(-1 * var(--space-5));
    padding: 0 var(--space-8) 0 var(--space-5);
    box-sizing: border-box;
  }

  .account-menu > summary {
    min-height: 32px;
    box-sizing: border-box;
    display: flex;
    align-items: center;
  }
}

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

/* ── Print ────────────────────────────────────────────── */
@media print {
  /* A sticky thead defeats the browser's per-page header repetition. */
  table.roster-table thead th {
    position: static;
    box-shadow: none;
    border-bottom: 2px solid #000;
  }

  .roster-tools {
    display: none;
  }

  .app-bar,
  .page-actions,
  .skip-link {
    display: none;
  }

  body {
    background: #fff;
    color: #000;
  }

  .app-main {
    max-width: none;
    margin: 0;
    padding: 0;
  }

  a {
    color: #000;
    text-decoration: none;
  }

  .panel,
  .hero-card {
    border-color: #000;
  }

  .standing {
    border-bottom-color: #000;
    break-inside: avoid;
  }

  /* Two charts side by side do not survive a portrait page. */
  .charts-pair {
    grid-template-columns: minmax(0, 1fr);
  }

  /* "Show" is a screen affordance; on paper it is a word with no job,
     so it goes and the total reclaims the column it reserved. */
  .roster-group-toggle {
    display: none;
  }

  .record-line {
    display: block;
    font-size: 13px;
    color: #000;
  }

  .section-head {
    break-after: avoid;
  }

  .charts-pair > .section,
  .roster-group > summary {
    break-inside: avoid;
  }

  .data-table td,
  .data-table th {
    padding: 4px 8px;
  }

  /* The methodology link prints where it points. */
  .section-head a[href]::after {
    content: " (" attr(href) ")";
  }

  .data-table thead th {
    background: #fff;
    color: #000;
    border-bottom: 2px solid #000;
  }

  @page {
    margin: 1.5cm;
  }
}
