/* ── Reset & base ──────────────────────────────────────────── */
*, *::before, *::after { margin: 0; padding: 0; box-sizing: border-box; }

/*
 * ── Design tokens ─────────────────────────────────────────────
 *
 * Canonical tokens (colors, font, type scale, spacing, radii) come from the CDN:
 *   https://cdn.neorgon.org/v1.0.0/styles/base.css   (linked in index.html)
 * Do NOT redeclare them here — a local copy is how the fleet drifted. The block
 * below holds only what base.css does not define, plus the per-site accent.
 *
 * Motion: base.css still ships --ease-snap (bounce) for legacy sites, but it is
 * deprecated for new work (impeccable law: ease-out exponential, no bounce).
 * Use --ease-out below.
 */

:root {
  /* Per-site identity — set to this site's hub card accent (PROJECTS.md section 4).
     Antique gold: a ledger tone, and unused elsewhere in the Data section. */
  --accent: #ca8a04;
  --accent-bright: #eab308;

  /* Money reads better in a monospaced column: digits line up, so two totals
     are comparable at a glance without reading them. */
  --mono: ui-monospace, 'SF Mono', SFMono-Regular, Menlo, Consolas, monospace;

  /* Semantics this site needs for numbers. Money is the accent; a saving is
     positive, an unknown is neither good nor bad and must not look like zero. */
  --good: #34d399;
  --warn: #fbbf24;
  /* .5, not .45: at .45 the word "unpriced" measured 4.47:1 on --bg, just under
     the 4.5:1 it needs. An unknown has to stay readable to stay honest. */
  --unknown: rgba(255, 255, 255, .5);

  /* Site extras base.css does not define */
  --surface-toast: #1a1730;
  --danger: #e11d48;
  --danger-hover: #f43f5e;
  --ease-out: cubic-bezier(0.22, 1, 0.36, 1);
  --control-height: 44px;
  --control-height-sm: 36px;
  --shadow-card: 0 12px 28px rgba(15, 23, 42, 0.5);
  --shadow-modal: 0 24px 48px rgba(2, 6, 23, 0.58);
  --z-sticky-header: 200;
  --z-modal: 400;
  --z-toast: 500;
}

/* Author display values (e.g. .section/.stack flex) would otherwise defeat the
   hidden attribute — restore its authority once, globally. */
[hidden] { display: none !important; }

html { height: 100%; }
body {
  font-family: var(--font);
  background: var(--bg);
  min-height: 100%;
  color: var(--text-primary);
  display: flex; flex-direction: column;
}

body.modal-open {
  overflow: hidden;
}

main#main {
  flex: 1;
  display: flex;
  flex-direction: column;
  min-height: 0;
  width: 100%;
}

/* ── Layout ────────────────────────────────────────────────── */
/*
 * GOTCHA: body { display:flex; flex-direction:column } + margin: 0 auto
 * on a child collapses its width — always pair with width: 100%.
 *
 * GOTCHA: External images (YouTube ggpht, Gravatar, etc.) may block loading
 * when the Referer header points to localhost or an unknown origin. Fix:
 * 1. Add <meta name="referrer" content="no-referrer"> in <head> (global)
 * 2. Or add referrerpolicy="no-referrer" on each <img> tag
 */
.container {
  max-width: 900px;
  width: 100%;
  margin: 0 auto;
  padding: var(--space-6);
  flex: 1;
}

/* ── Side-rail shell (opt-in) ──────────────────────────────────
 * A desktop window is far wider than a comfortable line of text, so a single
 * centred column leaves two large empty gutters and still runs the prose to
 * 100+ characters. This puts the section nav in the left gutter and narrows
 * the reading column, without changing .container for sites that do not use it.
 *
 *   <div class="shell">
 *     <nav class="shell__rail" aria-label="Section"> … </nav>
 *     <div class="shell__main"> … </div>
 *   </div>
 *
 * Below 940px it collapses to one column and the rail becomes a scrollable
 * row, so the same markup works on a phone with no JavaScript involved. From
 * 1240px up, the spare width goes to the right margin only, so the reading
 * column walks onto the page's centre line instead of sitting right of it.
 */
.shell {
  width: 100%;
  max-width: 1240px;
  margin: 0 auto;
  padding: var(--space-6) var(--space-6) 64px;
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--space-6);
  align-content: start;
  flex: 1;
}
/* A 1fr track is minmax(auto, 1fr): a wide child (a board, a table, a long
   code line) sets min-content and stretches the column past the viewport. */
.shell > * { min-width: 0; }

.shell__rail {
  display: flex;
  gap: var(--space-1);
  padding: 4px;
  background: var(--surface-1);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  overflow-x: auto;
  scrollbar-width: none;
}
.shell__rail::-webkit-scrollbar { display: none; }

@media (min-width: 940px) {
  .shell {
    grid-template-columns: 208px minmax(0, 1fr);
    gap: var(--space-8);
    padding: var(--space-8) var(--space-8) 80px;
  }
  .shell__rail {
    position: sticky;
    top: 76px;          /* clears the app-mode header */
    flex-direction: column;
    align-items: stretch;
    gap: 2px;
    padding: var(--space-2);
    overflow: visible;
    align-self: start;
  }
}

/* The rail sits in the left gutter, but with only two tracks it does not stay
   there: the rail plus its gap add to main's left side and nothing balances
   them on the right, so main sits half that distance right of the page's centre
   line (120px at a 208px rail). A column that far off centre reads as a
   misalignment rather than as a nav beside it.
   The correction is to stop splitting the spare width between both margins and
   spend all of it on the right. Main then walks back onto the centre line as the
   window grows, reaching it at 1240 + 208 + 32 = 1480px, and it costs main no
   width at any point on the way: the shell widens, main does not move relative
   to the rail. Below 1240px the shell already fills the viewport, so there is no
   spare width to spend and any correction would come straight out of the
   reading column. That trade is not worth making. */
@media (min-width: 1240px) {
  .shell {
    --shell-nudge: calc(208px + var(--space-8));
    max-width: calc(1240px + var(--shell-nudge));
    padding-right: clamp(
      var(--space-8),
      calc(var(--space-8) + 100vw - 1240px),
      calc(var(--space-8) + var(--shell-nudge))
    );
  }
}

/* Vertical rhythm: use inside .container or nested regions */
.stack {
  display: flex;
  flex-direction: column;
  gap: var(--space-6);
}
.stack--tight { gap: var(--space-4); }
/* Major sections need a real break. At 32px a heading sits almost on top of
   the block above it and the page reads as one undifferentiated column. */
.stack--loose { gap: 56px; }

/* Page section: title row + body (replaces “card everything” for grouping) */
.section {
  display: flex;
  flex-direction: column;
  gap: var(--space-6);
}
.section__header {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  justify-content: space-between;
  gap: var(--space-4);
}
.section__titles {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  min-width: 0;
}
.section__title {
  font-size: var(--text-xl);
  font-weight: 600;
  letter-spacing: 0.02em;
  color: var(--text-primary);
}
.section__lead {
  font-size: var(--text-sm);
  color: var(--text-muted);
  line-height: 1.6;
  max-width: 64ch;   /* 65-75 characters is the readable measure */
}
.section__lead code {
  font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", monospace;
  font-size: 0.92em;
  color: var(--text-secondary);
}
.stack > .card { margin-bottom: 0; }
.toolbar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
}

/* ── Header ────────────────────────────────────────────────────
 * Owned by the Neorgon Header Kit (css/neorgon-header.css — canonical
 * source: packages/neorgon-ui/header/). Do NOT style .header-bar,
 * .header-logo*, .header-title-link, .header-subtitle, .header-right,
 * .header-actions, or .header-home here. Auth styles (.auth-*) stay
 * site-owned below.
 */
nav {
  display: flex;
  align-items: center;
  gap: var(--space-2);
}

/* ── Buttons (canonical) ─────────────────────────────────────
 * Use .btn + a variant. Aliases: .nav-link = toolbar ghost; .auth-* map below.
 */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  min-height: var(--control-height);
  padding: 0 var(--space-4);
  border-radius: var(--radius-sm);
  font-family: inherit;
  font-size: var(--text-sm);
  font-weight: 600;
  line-height: 1.2;
  text-decoration: none;
  cursor: pointer;
  border: 1px solid transparent;
  background: transparent;
  color: var(--text-primary);
  transition:
    background-color var(--dur),
    color var(--dur),
    border-color var(--dur),
    box-shadow var(--dur),
    transform var(--dur-fast);
  -webkit-tap-highlight-color: transparent;
}
.btn:disabled,
.btn[aria-disabled="true"] {
  opacity: 0.45;
  cursor: not-allowed;
  pointer-events: none;
}
.btn--sm {
  min-height: var(--control-height-sm);
  padding: 0 var(--space-3);
  font-size: var(--text-xs);
}
.btn--block { width: 100%; }
.btn--icon {
  min-width: var(--control-height);
  padding: 0;
}
/* Gold is a light accent, so the label on it has to be dark: white on #ca8a04
   measures 2.94:1 and fails AA outright. This near-black is tinted toward the
   accent's own hue rather than being #000, and measures 6.31:1 (9.67:1 on the
   brighter hover fill). */
.btn--primary {
  background: var(--accent);
  color: #1a1204;
  border-color: transparent;
}
.btn--primary:hover { background: var(--accent-bright); }
.btn--secondary {
  background: var(--surface-2);
  color: var(--text-primary);
  border-color: var(--border-strong);
}
.btn--secondary:hover {
  background: rgba(255, 255, 255, 0.1);
}
.btn--ghost {
  background: transparent;
  color: rgba(255,255,255,0.92);
  border-color: rgba(255,255,255,0.18);
}
.btn--ghost:hover {
  background: rgba(255,255,255,0.1);
  color: #fff;
}
.btn--danger {
  background: var(--danger);
  color: #fff;
  border-color: transparent;
}
.btn--danger:hover { background: var(--danger-hover); }

/* Toolbar ghost (header nav) — same as .btn.btn--ghost.btn--sm */
.nav-link {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: var(--control-height);
  padding: 0 var(--space-3);
  border: 1px solid rgba(255,255,255,0.18);
  border-radius: var(--radius-sm);
  background: transparent;
  color: rgba(255,255,255,0.92);
  cursor: pointer;
  font-family: inherit;
  font-size: var(--text-sm);
  font-weight: 600;
  transition: background-color var(--dur), color var(--dur), border-color var(--dur);
}
.nav-link:hover {
  background: rgba(255,255,255,0.1);
  color: #fff;
}

.auth-toggle {
  display: flex;
  align-items: center;
  justify-content: center;
  min-width: var(--control-height);
  min-height: var(--control-height);
  padding: 0;
  border: 1px solid rgba(255,255,255,0.18);
  border-radius: var(--radius-sm);
  background: transparent;
  color: rgba(255,255,255,0.92);
  cursor: pointer;
  font-family: inherit;
  transition: background-color var(--dur), color var(--dur), border-color var(--dur);
}
.auth-toggle:hover {
  background: rgba(255,255,255,0.1);
  color: #fff;
}
.auth-toggle svg {
  width: 22px;
  height: 22px;
  flex-shrink: 0;
  display: block;
}
.auth-toggle.logged-in { color: var(--accent); }
.auth-toggle.logged-in svg circle:nth-of-type(2) {
  fill: currentColor;
  stroke: none;
}

/* ── Sheet (inline panel under header) — not a modal ─────────
 * Use for auth, filters, compact forms. No backdrop; page scrolls.
 * For blocking overlays use .modal below.
 */
.auth-panel {
  background: rgba(255,255,255,0.02);
  border-bottom: 1px solid var(--border-subtle);
  max-height: 0;
  overflow: hidden;
  transition: max-height 0.3s ease-out;
}
.auth-panel.open { max-height: 300px; }
.auth-panel-inner {
  padding: var(--space-6);
  max-width: 400px;
  margin: 0 auto;
}
.auth-tabs {
  display: flex;
  gap: var(--space-2);
  margin-bottom: var(--space-4);
}
.auth-tab {
  flex: 1;
  min-height: var(--control-height-sm);
  padding: 0 var(--space-4);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: transparent;
  color: var(--text-muted);
  cursor: pointer;
  font-family: inherit;
  font-size: var(--text-sm);
  font-weight: 600;
  transition: background-color var(--dur), color var(--dur), border-color var(--dur);
}
.auth-tab.active {
  background: var(--surface-2);
  color: var(--text-primary);
  border-color: var(--border-strong);
}
.auth-form input {
  width: 100%;
  padding: var(--space-3) var(--space-4);
  margin-bottom: var(--space-3);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--surface-1);
  color: var(--text-primary);
  font-size: var(--text-sm);
  font-family: inherit;
}
.auth-submit {
  width: 100%;
  min-height: var(--control-height);
  padding: 0 var(--space-4);
  border: none;
  border-radius: var(--radius-sm);
  background: var(--accent);
  color: #fff;
  font-family: inherit;
  font-weight: 600;
  font-size: var(--text-sm);
  cursor: pointer;
  transition: background-color var(--dur);
}
.auth-submit:hover { background: var(--accent-bright); }
#authUser {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
}
.auth-username {
  color: var(--text-primary);
  font-weight: 600;
}
.auth-logout {
  min-height: var(--control-height-sm);
  padding: 0 var(--space-3);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: transparent;
  color: var(--text-muted);
  cursor: pointer;
  font-family: inherit;
  font-size: var(--text-sm);
  font-weight: 600;
  transition: background-color var(--dur), color var(--dur), border-color var(--dur);
}
.auth-logout:hover {
  background: var(--surface-2);
  color: var(--text-primary);
}
.auth-divider {
  height: 1px;
  background: var(--border-subtle);
}

/* ── Modal (blocking overlay) ──────────────────────────────── */
.modal[hidden] {
  display: none !important;
}
.modal {
  position: fixed;
  inset: 0;
  z-index: var(--z-modal);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--space-4);
}
.modal__backdrop {
  position: absolute;
  inset: 0;
  background: rgba(2, 4, 12, 0.85);
  -webkit-backdrop-filter: blur(10px); /* Safari still needs the prefix */
  backdrop-filter: blur(10px);
}
.modal__dialog {
  position: relative;
  z-index: 1;
  display: flex;
  flex-direction: column;
  width: min(100%, 520px);
  max-height: min(90vh, 680px);
  overflow: hidden;
  background: var(--surface-1);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-modal);
}
/* For modals holding option grids or editors — narrow dialogs cramp them */
.modal__dialog--wide {
  width: min(100%, 760px);
}
.modal__header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
  padding: var(--space-4) var(--space-6);
  border-bottom: 1px solid var(--border-subtle);
}
.modal__header h2 {
  font-size: var(--text-lg);
  font-weight: 600;
  color: var(--text-primary);
}
.modal__body {
  padding: var(--space-6);
  overflow: auto;
  font-size: var(--text-sm);
  color: var(--text-secondary);
  line-height: 1.55;
}
.modal__body code {
  font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", monospace;
  font-size: 0.9em;
  color: var(--text-primary);
}
.modal__footer {
  display: flex;
  flex-wrap: wrap;
  justify-content: flex-end;
  gap: var(--space-2);
  padding: var(--space-4) var(--space-6);
  border-top: 1px solid var(--border-subtle);
}

/* ── Card ──────────────────────────────────────────────────── */
/*
 * GOTCHA: backdrop-filter creates a stacking context — dropdowns inside
 * a card can be clipped by a later card's stacking context. Fix by adding
 * position: relative; z-index: 10 to the card that owns the open dropdown.
 *
 * Default: static surface (no hover lift — avoids generic “dashboard demo”).
 * Use .card--interactive for raised hover + entrance motion.
 *
 * GOTCHA: a <button> or <a> used AS a card does not inherit body text color —
 * buttons default to canvas text (near-black), rendering invisible on the dark
 * bg. When a card is an interactive element, set color: var(--text-primary)
 * and font-family: var(--font) explicitly on it.
 */
.card {
  background: var(--surface-1);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  padding: var(--space-6);
  margin-bottom: var(--space-6);
  /* No backdrop-filter here on purpose. --surface-1 is 3% white over a flat
     background, so a blur behind it has nothing to blur: it cost a composited
     layer per card and bought no visible difference. The blurs that remain
     (modal scrim, sticky filter bar, sticky table head) each sit over moving
     content, which is the only case that earns one. */
  transition: border-color var(--dur), box-shadow var(--dur);
}
.card:hover {
  border-color: var(--border-strong);
  box-shadow: 0 8px 20px rgba(15, 23, 42, 0.45);
}
.card--interactive {
  transition:
    transform var(--dur) ease-out,
    box-shadow var(--dur) ease-out,
    border-color var(--dur);
  animation: fadeIn 0.35s ease-out both;
}
.card--interactive:hover {
  transform: translateY(-3px);
  box-shadow: var(--shadow-card);
  border-color: var(--border-strong);
}
.card--enter {
  animation: fadeIn 0.35s ease-out both;
}
.card--flat:hover {
  box-shadow: none;
}
/* Card with an action row, in an equal-height grid: pin the toolbar to the
   bottom so buttons align across cards whose text wraps differently. */
.card--actions {
  display: flex;
  flex-direction: column;
}
.card--actions > .toolbar:last-child {
  margin-top: auto;
}

/* ── Button press feedback ─────────────────────────────────── */
button:active,
.btn:active,
.nav-link:active,
.auth-toggle:active,
.auth-tab:active,
.auth-submit:active,
.auth-logout:active {
  transform: scale(0.98);
  transition-duration: var(--dur-fast);
}

@keyframes fadeIn {
  from { opacity: 0; transform: translateY(6px); }
  to   { opacity: 1; transform: translateY(0); }
}

/* ── Skeleton loading ──────────────────────────────────────── */
@keyframes shimmer { to { background-position: -200% 0; } }
.skeleton {
  background: linear-gradient(90deg, var(--surface-1) 25%, var(--surface-2) 50%, var(--surface-1) 75%);
  background-size: 200% 100%;
  animation: shimmer 1.5s ease-in-out infinite;
  border-radius: var(--radius);
}

/* ── Toast ─────────────────────────────────────────────────── */
.toast {
  position: fixed;
  bottom: var(--space-6);
  right: var(--space-6);
  z-index: var(--z-toast);
  max-width: min(360px, calc(100vw - var(--space-8)));
  background: var(--surface-toast);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: var(--space-3) var(--space-4);
  font-size: var(--text-sm);
  color: var(--text-primary);
  box-shadow: var(--shadow-card);
  pointer-events: none;
  transform: translateY(8px);
  opacity: 0;
  transition: opacity 0.2s ease-out, transform 0.2s ease-out;
}
.toast.visible {
  opacity: 1;
  transform: translateY(0);
}

/* ── Footer ────────────────────────────────────────────────────
   Owned by the Neorgon Footer Kit (css/neorgon-footer.css).
   Don't add .neo-footer rules here — edit packages/neorgon-ui/footer/
   and re-run sync-footer.sh. */

/* ── Focus styles ──────────────────────────────────────────── */
:focus-visible {
  outline: 2px solid var(--accent-bright);
  outline-offset: 2px;
}

/* ── Skip link ─────────────────────────────────────────────── */
.skip-link {
  position: absolute;
  top: -100%;
  left: var(--space-4);
  z-index: 1000;
  padding: var(--space-2) var(--space-4);
  background: var(--accent-bright);
  color: #000;
  font-weight: 600;
  font-size: var(--text-sm);
  border-radius: 0 0 var(--radius-sm) var(--radius-sm);
  text-decoration: none;
  transition: top var(--dur);
}
.skip-link:focus { top: 0; }

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

/* ── Responsive ────────────────────────────────────────────── */
@media (max-width: 600px) {
  .container { padding: var(--space-4); }
  .toast {
    left: var(--space-4);
    right: var(--space-4);
    max-width: none;
  }
}

/* ══════════════════════════════════════════════════════════════
 * Releve
 *
 * Everything above this line is the fleet template. Everything below is this
 * site: a statement made of numbers. Two rules run through it.
 *
 *   1. A figure and its provenance travel together. Every dollar on this page
 *      is a counterfactual computed from tokens times a published rate, so no
 *      number is shown without the label that says so.
 *   2. Unknown is not zero. An unpriced model gets --unknown and the word
 *      "unpriced", never $0.00 in the same column as a real total.
 * ════════════════════════════════════════════════════════════ */

/* ── Numbers ───────────────────────────────────────────────── */
.num {
  font-family: var(--mono);
  font-variant-numeric: tabular-nums;
}
.num--money { color: var(--accent-bright); }
.num--muted { color: var(--text-muted); }
.num--unknown { color: var(--unknown); font-style: italic; }
.num--good { color: var(--good); }
/* A figure the visitor changed. Not an error, but it must not look measured. */
.num--warn { color: var(--warn); }

/* ── Source banner ─────────────────────────────────────────────
 * The first thing a visitor needs to know is whose numbers these are. The
 * public site loads synthetic data; a local run loads real data. Saying which
 * is not a footnote, so it sits above the headline. */
.source {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-3) var(--space-4);
  background: color-mix(in srgb, var(--accent) 7%, transparent);
  border: 1px solid color-mix(in srgb, var(--accent) 32%, transparent);
  border-radius: var(--radius);
  font-size: var(--text-sm);
  color: var(--text-secondary);
}
.source__tag {
  flex: 0 0 auto;
  padding: 2px 8px;
  border-radius: 999px;
  background: color-mix(in srgb, var(--accent) 22%, transparent);
  border: 1px solid color-mix(in srgb, var(--accent) 45%, transparent);
  color: var(--text-primary);
  font-size: var(--text-xs);
  font-weight: 600;
  letter-spacing: .04em;
  text-transform: uppercase;
}
.source__note { flex: 1 1 240px; min-width: 0; }
.source__actions { display: flex; gap: var(--space-2); flex-wrap: wrap; }

/* ── Headline ──────────────────────────────────────────────────
 * Two figures that answer different questions, side by side because the whole
 * point is the gap between them. Never stack one above the other: vertical
 * order reads as precedence, and neither of these outranks the other. */
.headline {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(230px, 1fr));
  gap: var(--space-4);
}
.headline__cell {
  padding: var(--space-4) var(--space-6);
  background: var(--surface-1);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
}
.headline__cell--lead {
  border-color: color-mix(in srgb, var(--accent) 40%, transparent);
  background: color-mix(in srgb, var(--accent) 7%, var(--surface-1));
}
.headline__label {
  display: block;
  font-size: var(--text-xs);
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--text-muted);
}
.headline__value {
  display: block;
  margin-top: var(--space-2);
  font-family: var(--mono);
  font-variant-numeric: tabular-nums;
  font-size: clamp(1.6rem, 4.5vw, 2.4rem);
  font-weight: 600;
  line-height: 1.05;
  color: var(--text-primary);
}
.headline__cell--lead .headline__value { color: var(--accent-bright); }
.headline__foot {
  display: block;
  margin-top: var(--space-2);
  font-size: var(--text-xs);
  color: var(--text-muted);
}

/* ── Filter bar ────────────────────────────────────────────────
 * Sticky, because a filter you cannot see is a filter you forget you set, and
 * every number below it is conditional on this row. */
.filters {
  position: sticky;
  top: 64px;
  z-index: 20;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-3);
  background: color-mix(in srgb, var(--bg) 88%, transparent);
  backdrop-filter: blur(12px);
  border: 1px solid var(--border);
  border-radius: var(--radius);
}
.filters__label {
  font-size: var(--text-xs);
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--text-muted);
}
.filters__spacer { flex: 1 1 auto; }
.chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  height: var(--control-height-sm);
  padding: 0 10px;
  background: var(--surface-1);
  border: 1px solid var(--border);
  border-radius: 999px;
  color: var(--text-secondary);
  font-family: var(--font);
  font-size: var(--text-sm);
  cursor: pointer;
  transition: border-color var(--dur), color var(--dur), background var(--dur);
}
.chip:hover { border-color: var(--border-strong); color: var(--text-primary); }
.chip[aria-pressed="true"],
.chip--on {
  background: color-mix(in srgb, var(--accent) 20%, transparent);
  border-color: color-mix(in srgb, var(--accent) 55%, transparent);
  color: var(--text-primary);
}
.chip__x { color: var(--text-muted); font-size: var(--text-xs); }
.chip--empty { color: var(--text-muted); cursor: default; border-style: dashed; }

/* ── Inputs ────────────────────────────────────────────────── */
.field {
  display: inline-flex;
  flex-direction: column;
  gap: 4px;
  min-width: 0;
}
.field > span {
  font-size: var(--text-xs);
  color: var(--text-muted);
}
.input,
.select {
  height: var(--control-height-sm);
  padding: 0 10px;
  background: var(--surface-1);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  color: var(--text-primary);
  font-family: var(--font);
  font-size: var(--text-sm);
}
.input--num {
  font-family: var(--mono);
  font-variant-numeric: tabular-nums;
  text-align: right;
  width: 8ch;
}
/* The border change stays on :focus, but without the `outline: none` it used to
   carry. A class selector beats :focus-visible on specificity, so that one
   declaration left the ~20 number inputs in the projector and the rate editor
   as the only controls on the page with no focus ring: a 1px hue shift. */
.input:focus, .select:focus { border-color: var(--accent); }

input[type="range"] {
  width: 100%;
  /* The native control is 16px tall, under the 24px WCAG 2.5.8 floor. The track
     stays as drawn; this only grows the box the thumb can be grabbed in. */
  min-height: 24px;
  accent-color: var(--accent-bright);
}

/* ── Data table ────────────────────────────────────────────────
 * Wrapped in .table-wrap: a breakdown of 40 projects with six numeric columns
 * is wider than a phone, and a page that scrolls sideways as a whole is worse
 * than a table that does. */
.table-wrap {
  overflow-x: auto;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface-1);
}
.table {
  width: 100%;
  border-collapse: collapse;
  font-size: var(--text-sm);
}
.table th,
.table td {
  padding: var(--space-2) var(--space-3);
  text-align: left;
  white-space: nowrap;
  border-bottom: 1px solid var(--border-subtle);
}
.table thead th {
  position: sticky;
  top: 0;
  z-index: 1;
  background: color-mix(in srgb, var(--bg) 92%, transparent);
  backdrop-filter: blur(8px);
  font-size: var(--text-xs);
  letter-spacing: .05em;
  text-transform: uppercase;
  color: var(--text-muted);
  font-weight: 600;
}
.table tbody tr:last-child td { border-bottom: 0; }
.table tbody tr:hover { background: var(--surface-1); }
.table td.n, .table th.n {
  text-align: right;
  font-family: var(--mono);
  font-variant-numeric: tabular-nums;
}
/* `all: unset` resets display to inline, which left these 14px tall: below the
   24px floor WCAG 2.5.8 sets for a target that is not inline text. Filling the
   header cell's own height gets there without changing the layout. */
.table__sort {
  all: unset;
  display: flex;
  align-items: center;
  min-height: 24px;
  cursor: pointer;
  color: inherit;
  font: inherit;
  letter-spacing: inherit;
  text-transform: inherit;
}
.table th.n .table__sort { justify-content: flex-end; }
.table__sort:hover { color: var(--text-primary); }
.table th[aria-sort] .table__sort::after { content: ' ▾'; color: var(--accent-bright); }
.table__key {
  max-width: 26ch;
  overflow: hidden;
  text-overflow: ellipsis;
}
/* The share column is a bar drawn in the cell, so a row's weight is legible
   without comparing two numbers. */
.rowbar {
  position: relative;
  display: block;
  height: 6px;
  width: 90px;
  border-radius: 3px;
  background: var(--surface-2);
  overflow: hidden;
}
.rowbar > i {
  position: absolute;
  inset: 0 auto 0 0;
  background: var(--accent);
  border-radius: 3px;
}
.table tbody tr.is-picked { background: color-mix(in srgb, var(--accent) 12%, transparent); }
.table tbody tr[data-pick] { cursor: pointer; }

/* ── Two-up and three-up grids ─────────────────────────────── */
.grid {
  display: grid;
  gap: var(--space-4);
  grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
}
.grid--wide { grid-template-columns: repeat(auto-fit, minmax(340px, 1fr)); }

/* ── Prose blocks ──────────────────────────────────────────── */
.prose { font-size: var(--text-sm); color: var(--text-secondary); line-height: 1.65; }
.prose p + p { margin-top: var(--space-3); }
.prose ol, .prose ul { margin: var(--space-3) 0 0 1.2em; }
.prose li + li { margin-top: var(--space-2); }
.prose code,
code.inline {
  font-family: var(--mono);
  font-size: .92em;
  padding: 1px 5px;
  background: var(--surface-2);
  border-radius: 4px;
}
/* Every link in the content needs a colour of its own. With only `.prose a`
   declared, a bare <a> anywhere else (in a .note, in a <dd>) fell back to the
   browser's default blue: 2.04:1 on this background, against 4.5:1 needed.
   The :not([class]) half cannot touch a classed component's own colour. */
.prose a,
main a:not([class]) { color: var(--accent-bright); }

/* A definition row: the label, then the answer. Used all over the Method
   section, where every claim needs its source next to it. */
.deflist { display: grid; gap: var(--space-3); }
.deflist > div {
  display: grid;
  gap: 2px;
  grid-template-columns: minmax(0, 1fr);
}
@media (min-width: 720px) {
  .deflist > div { grid-template-columns: 190px minmax(0, 1fr); gap: var(--space-4); }
}
.deflist dt { font-size: var(--text-sm); font-weight: 600; color: var(--text-primary); }
.deflist dd { font-size: var(--text-sm); color: var(--text-secondary); line-height: 1.6; }

/* ── Command block ─────────────────────────────────────────────
 * The scripts are the product as much as the page is, so a command has to be
 * copyable in one action and readable without one. */
.cmd {
  position: relative;
  display: flex;
  align-items: flex-start;
  gap: var(--space-3);
  padding: var(--space-3) var(--space-4);
  background: #05070f;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  overflow-x: auto;
}
.cmd > code {
  flex: 1 1 auto;
  font-family: var(--mono);
  font-size: var(--text-sm);
  color: var(--text-primary);
  white-space: pre;
  background: none;
  padding: 0;
}
.cmd > code .c { color: var(--text-muted); }
.cmd__copy { flex: 0 0 auto; }

/* ── Drop zone ─────────────────────────────────────────────── */
.drop {
  display: grid;
  place-items: center;
  gap: var(--space-2);
  padding: var(--space-8) var(--space-6);
  border: 1px dashed var(--border-strong);
  border-radius: var(--radius);
  background: var(--surface-1);
  text-align: center;
  color: var(--text-secondary);
  font-size: var(--text-sm);
  transition: border-color var(--dur), background var(--dur);
}
.drop.is-over {
  border-color: var(--accent-bright);
  background: color-mix(in srgb, var(--accent) 10%, transparent);
}

/* ── Note / caveat ─────────────────────────────────────────────
 * For the sentences that keep a number honest. Deliberately quiet: a caveat
 * shouted every 200px stops being read. */
.note {
  padding: var(--space-3) var(--space-4);
  border: 1px solid var(--border);
  background: var(--surface-1);
  border-radius: var(--radius-sm);
  font-size: var(--text-sm);
  color: var(--text-secondary);
  line-height: 1.6;
}
/* A variant reads through its border and a wash of the same hue, not through a
   stripe down one edge. The wash stays under 8% so the body text keeps the
   contrast it measured against the plain surface. */
.note--warn { border-color: color-mix(in srgb, var(--warn) 42%, transparent);   background: color-mix(in srgb, var(--warn) 7%, transparent); }
.note--good { border-color: color-mix(in srgb, var(--good) 38%, transparent);   background: color-mix(in srgb, var(--good) 6%, transparent); }
.note--bad  { border-color: color-mix(in srgb, var(--danger) 42%, transparent); background: color-mix(in srgb, var(--danger) 7%, transparent); }
.note code { font-family: var(--mono); font-size: .92em; }

/* ── Rail links ────────────────────────────────────────────────
 * .nav-link ships with the template; this adds the "you are here" state the
 * scroll observer sets, plus the section number. */
.rail__link {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  font-size: var(--text-sm);
}
.rail__num {
  flex: 0 0 auto;
  width: 18px;
  font-family: var(--mono);
  font-size: var(--text-xs);
  color: var(--text-muted);
}
.nav-link[aria-current="true"] {
  background: color-mix(in srgb, var(--accent) 18%, transparent);
  color: var(--text-primary);
}
.nav-link[aria-current="true"] .rail__num { color: var(--accent-bright); }

/* ── Legend ────────────────────────────────────────────────── */
.legend {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2) var(--space-4);
  font-size: var(--text-xs);
  color: var(--text-secondary);
}
.legend > span { display: inline-flex; align-items: center; gap: 6px; }
.legend i {
  width: 9px; height: 9px;
  border-radius: 2px;
  background: var(--accent);
}

/* ── Slider row (projector) ────────────────────────────────── */
.slider {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: baseline;
  gap: var(--space-2) var(--space-3);
}
.slider__head { display: contents; }
.slider__name { font-size: var(--text-sm); color: var(--text-secondary); }
.slider__val {
  font-family: var(--mono);
  font-variant-numeric: tabular-nums;
  font-size: var(--text-sm);
  color: var(--text-primary);
}
.slider > input[type="range"] { grid-column: 1 / -1; }

/* ── Responsive ────────────────────────────────────────────── */
@media (max-width: 939px) {
  .filters { top: 0; }
}
@media (max-width: 600px) {
  .headline__cell { padding: var(--space-3) var(--space-4); }
  .table th, .table td { padding: 6px var(--space-2); }
}
