/* =============================================================================
   WHERE IT GOES — Component layer
   Depends on tokens.css + base.css. Built to the Stripe/Linear/Ramp bar.
   Marcus: adopt these class names, or rename in your markup and keep the values.
   The two load-bearing components — .cat-row (size/overshoot bar) and the leak
   flag treatment — are documented inline; build them exactly as specced.
   ============================================================================= */

/* ---------------------------------------------------------------------------
   APP SHELL — desktop is a true two-column product: input RAIL + diagnostic
   CANVAS. It spends the width. Phone collapses to one column (see media query).
   --------------------------------------------------------------------------- */
.app {
  min-height: 100vh;
  background: var(--paper);
}

.app__shell {
  max-width: var(--shell-max);
  margin: 0 auto;
  display: grid;
  grid-template-columns: var(--rail-width) minmax(0, 1fr);
  align-items: start;
  gap: 0;
}

/* Top bar — quiet. Wordmark + the privacy line, always visible. */
.topbar {
  grid-column: 1 / -1;
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: var(--space-4) var(--page-pad);
  border-bottom: 1px solid var(--line);
  background: var(--paper);
}
.wordmark {
  font-size: var(--text-md);
  font-weight: var(--weight-semibold);
  letter-spacing: var(--tracking-tight);
  color: var(--ink-strong);
}
.wordmark span { color: var(--ink-faint); font-weight: var(--weight-regular); }

/* The privacy promise — load-bearing copy, always on screen, calm not shouty. */
.privacy-line {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  font-size: var(--text-xs);
  color: var(--ink-muted);
}
.privacy-line::before {
  content: "";
  width: 6px; height: 6px; border-radius: 50%;
  background: var(--ok-ink);
  flex: 0 0 auto;
}

/* ---- INPUT RAIL --------------------------------------------------------- */
.rail {
  position: sticky;
  top: 0;
  align-self: start;
  max-height: 100vh;
  overflow-y: auto;
  background: var(--surface-rail);
  border-right: 1px solid var(--line);
  padding: var(--space-6) var(--space-5) var(--space-8);
}
.rail__group { margin-bottom: var(--space-6); }
.rail__group-title {
  font-size: var(--text-2xs);
  font-weight: var(--weight-semibold);
  letter-spacing: var(--tracking-caps);
  text-transform: uppercase;
  color: var(--ink-faint);
  padding-bottom: var(--space-3);
  margin-bottom: var(--space-3);
  border-bottom: 1px solid var(--line);
}

/* ---- CANVAS ------------------------------------------------------------- */
.canvas {
  padding: var(--space-6) var(--page-pad) var(--space-9);
  max-width: calc(var(--canvas-max) + var(--page-pad) * 2);
}
.canvas__stack { display: flex; flex-direction: column; gap: var(--panel-gap); }

/* Two-up panel row for the desktop width (e.g. surplus + plan side by side). */
.panel-row {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--panel-gap);
}

/* ---------------------------------------------------------------------------
   INPUTS — quiet, aligned, a ledger you fill in. Label left, money field right.
   --------------------------------------------------------------------------- */
.field {
  display: grid;
  grid-template-columns: 1fr auto;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-2) 0;
}
.field__label {
  font-size: var(--text-sm);
  color: var(--ink);
  line-height: var(--leading-snug);
}
.field__hint {
  display: block;
  font-size: var(--text-2xs);
  color: var(--ink-faint);
}

.money-input {
  display: inline-flex;
  align-items: center;
  background: var(--surface);
  border: 1px solid var(--line-strong);
  border-radius: var(--radius-sm);
  padding: 0 var(--space-3);
  height: 34px;
  transition: border-color var(--dur) var(--ease), box-shadow var(--dur) var(--ease);
}
.money-input:focus-within {
  border-color: var(--accent);
  box-shadow: var(--ring);
}
.money-input__prefix {
  font-family: var(--font-mono);
  font-size: var(--text-sm);
  color: var(--ink-faint);
  margin-right: 2px;
}
.money-input input {
  border: 0; background: transparent; outline: none;
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  font-size: var(--text-sm);
  color: var(--ink-strong);
  text-align: right;
  width: 84px;
  padding: 0;
}
.money-input input::placeholder { color: var(--ink-ghost); }

/* Add / manage category — a text button, no drama. */
.btn-text {
  background: none; border: 0; padding: var(--space-1) 0;
  color: var(--accent); font-size: var(--text-sm); font-weight: var(--weight-medium);
}
.btn-text:hover { color: var(--accent-hover); text-decoration: underline; }

/* Primary action (rare — the tool is live, so actions are minimal). */
.btn {
  display: inline-flex; align-items: center; gap: var(--space-2);
  background: var(--accent); color: var(--accent-ink);
  border: 1px solid var(--accent); border-radius: var(--radius-md);
  padding: var(--space-2) var(--space-4);
  font-size: var(--text-sm); font-weight: var(--weight-medium);
  transition: background var(--dur) var(--ease);
}
.btn:hover { background: var(--accent-hover); }
.btn--ghost {
  background: transparent; color: var(--ink);
  border-color: var(--line-strong);
}
.btn--ghost:hover { background: var(--surface-sunk); }

/* ---------------------------------------------------------------------------
   PANEL — the calm card. Every diagnostic lives in one. One clear read each.
   --------------------------------------------------------------------------- */
.panel {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-panel);
  padding: var(--space-5) var(--space-6);
}
.panel--flush { padding: 0; }              /* for full-bleed table panels */
.panel__head {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: var(--space-4);
  margin-bottom: var(--space-4);
}
.panel__head--flush { padding: var(--space-5) var(--space-6) var(--space-3); margin: 0; }
.panel__note { font-size: var(--text-xs); color: var(--ink-faint); }

/* ---------------------------------------------------------------------------
   THE HEADLINE NUMBER — surplus / deficit. One big honest figure per state.
   No gauge. Just the number, its sign, and a plain sentence.
   --------------------------------------------------------------------------- */
.headline-number { display: flex; flex-direction: column; gap: var(--space-2); }
.headline-number__label { }
.headline-number__value { display: flex; align-items: baseline; gap: var(--space-3); }
.headline-number__caption { font-size: var(--text-sm); color: var(--ink-muted); max-width: 44ch; }

/* ===========================================================================
   ★ LOAD-BEARING COMPONENT 1: THE CATEGORY ROW
   "Actually big vs feels big" — ONE size bar carrying TWO facts.
   Layout per row (desktop):
     [ label + kind tag ] [ ————— size bar with benchmark tick ————— ] [ $ ] [ % ]
   =========================================================================== */
.cat-table { width: 100%; }

.cat-row {
  display: grid;
  grid-template-columns:
    18px                /* flag edge marker gutter */
    minmax(150px, 1.4fr)/* label + tag */
    minmax(180px, 3fr)  /* the bar */
    92px                /* dollars */
    58px;               /* percent */
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-3) var(--space-4) var(--space-3) 0;
  border-bottom: 1px solid var(--line-faint);
  position: relative;
}
.cat-row:last-child { border-bottom: 0; }
.cat-row:hover { background: var(--surface-sunk); }

/* (1) Flag edge marker — colored left edge ONLY when flagged. Legible at a glance.
   Clay for the two HEADLINE kinds (over-benchmark, subscription-creep); amber for
   essential-note (groceries/utilities — quiet, never headline, §3.3.1). */
.cat-row__edge { width: 3px; height: 100%; border-radius: 2px; justify-self: start; }
.cat-row--leak  .cat-row__edge { background: var(--leak-ink); }   /* over-benchmark / subscription-creep */
.cat-row--note  .cat-row__edge { background: var(--watch-ink); }  /* essential-note — amber, de-emphasized */
.cat-row--watch .cat-row__edge { background: var(--watch-ink); }
/* within-range and under rows carry NO edge — being fine draws no ink */

/* (2) Label — always full-strength ink; you must be able to read what it is. */
.cat-row__label {
  font-size: var(--text-sm);
  color: var(--ink);
  font-weight: var(--weight-medium);
  line-height: var(--leading-snug);
}
/* KIND TAG — maps 1:1 to the engine's three leak kinds (§3.3). Copy:
     leakType "over-benchmark"     → .cat-row__tag--leak   text "OVER RANGE"
     leakType "subscription-creep" → .cat-row__tag--leak   text "RECURRING"
     leakType "essential-note"     → .cat-row__tag--note   text "WORTH A GLANCE"
   A row may also carry the isLargeAndOver badge alongside the kind tag. */
.cat-row__tag {
  display: inline-block;
  margin-top: 3px;
  margin-right: 4px;
  font-size: var(--text-2xs);
  font-weight: var(--weight-semibold);
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  padding: 1px 6px;
  border-radius: 3px;
}
.cat-row__tag--leak  { color: var(--leak-ink);  background: var(--leak-fill);  }  /* over-benchmark & subscription-creep */
.cat-row__tag--note  { color: var(--watch-ink); background: var(--watch-fill); }  /* essential-note — quiet, never headline */
.cat-row__tag--watch { color: var(--watch-ink); background: var(--watch-fill); }  /* alias */
/* in-range rows show no tag, or an optional whisper: */
.cat-row__tag--ok    { color: var(--ok-ink);    background: transparent; padding-left: 0; }

/* isLargeAndOver badge — the "sticky, most damaging" leaks (§3.3.3). A display
   emphasis worn ALONGSIDE the kind tag, not a fourth kind. Outlined, not filled,
   so it reads as a badge on the tag rather than a second alarm. */
.cat-row__badge {
  display: inline-block;
  margin-top: 3px;
  font-size: var(--text-2xs);
  font-weight: var(--weight-semibold);
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  padding: 0 5px;
  border-radius: 3px;
  color: var(--leak-ink);
  border: 1px solid var(--leak-line);
  background: transparent;
}

/* (3) THE BAR — the heart of it. Set FOUR inline custom props per row:
     --size  (0–1): dollars ÷ largest outflow's dollars  → grey fill length
     --lo    (0–1): band LOW edge  $ ÷ largest outflow $  → sane-band start
     --bench (0–1): band HIGH edge $ ÷ largest outflow $  → sane-band end / ceiling
   Layer order on the track (back→front): sane RANGE band, size fill, clay
   overshoot past the high edge, then the two range ticks on top. */
.bar {
  position: relative;
  height: 12px;
  background: var(--surface-sunk);
  border-radius: 6px;
  overflow: hidden;
}

/* Sane RANGE band — faint sage zone from LOW edge to HIGH edge (engine §5:
   the benchmark is a range, not one number). Always shown so the reader sees
   the whole acceptable window on every row. */
.bar__range {
  position: absolute; top: 0; bottom: 0;
  left: calc(var(--lo, 0) * 100%);
  width: calc((var(--bench, 0) - var(--lo, 0)) * 100%);
  background: var(--bar-range);
}

.bar__size {
  position: absolute; inset: 0 auto 0 0;
  width: calc(var(--size, 0) * 100%);
  background: var(--bar-size);
  border-radius: 6px;
}
.cat-row--lead .bar__size { background: var(--bar-size-lead); } /* biggest 1–2 */

/* Overshoot segment — from the HIGH edge tick to the end of the size fill. Only
   rendered on over-range rows. Clay = the part above the sane ceiling = exactly
   the recoverable dollars (trim to the high edge, never to zero). */
.bar__overshoot {
  position: absolute; top: 0; bottom: 0;
  left: calc(var(--bench, 0) * 100%);
  width: calc((var(--size, 0) - var(--bench, 0)) * 100%);
  background: var(--bar-overshoot);
}

/* Range ticks — LOW edge (start of sane) and HIGH edge (the ceiling of "fine").
   Always shown so the window is legible even on in-range and under rows. */
.bar__lo {
  position: absolute; top: -2px; bottom: -2px;
  left: calc(var(--lo, 0) * 100%);
  width: 1px;
  background: var(--bar-range-lo);
}
.bar__bench {
  position: absolute; top: -2px; bottom: -2px;
  left: calc(var(--bench, 0) * 100%);
  width: 2px;
  background: var(--bar-benchmark);
  border-radius: 1px;
}

/* (4) Dollars + percent — the ledger figures, right-aligned, tabular. */
.cat-row__amt {
  font-family: var(--font-mono); font-variant-numeric: tabular-nums;
  font-size: var(--text-sm); color: var(--ink-strong); text-align: right;
}
.cat-row__pct {
  font-family: var(--font-mono); font-variant-numeric: tabular-nums;
  font-size: var(--text-xs); color: var(--ink-faint); text-align: right;
}

/* Recoverable figure — when a leak, the money-back number sits under dollars,
   in gain-ink. Positive framing on the one number that is genuinely good news. */
.cat-row__recover {
  display: block; margin-top: 2px;
  font-family: var(--font-mono); font-variant-numeric: tabular-nums;
  font-size: var(--text-2xs); color: var(--gain-ink); text-align: right;
}

/* ===========================================================================
   ★ LOAD-BEARING COMPONENT 2: THE LEAK CALLOUT (summary list)
   A distinct panel that gathers only the flags, each stating KIND + money back.
   Calm: clay accents on the left edge and tag, gain-ink on the recoverable $.
   =========================================================================== */
.leak-list { display: flex; flex-direction: column; }
.leak-item {
  display: grid;
  grid-template-columns: 3px 1fr auto;
  gap: var(--space-4);
  align-items: center;
  padding: var(--space-4) 0;
  border-bottom: 1px solid var(--line-faint);
}
.leak-item:last-child { border-bottom: 0; }
.leak-item__edge { background: var(--leak-ink); border-radius: 2px; align-self: stretch; }
.leak-item__body { display: flex; flex-direction: column; gap: 3px; }
.leak-item__name { font-size: var(--text-base); font-weight: var(--weight-medium); color: var(--ink); }
.leak-item__why  { font-size: var(--text-sm); color: var(--ink-muted); max-width: 52ch; }
.leak-item__kind {
  align-self: flex-start;
  font-size: var(--text-2xs); font-weight: var(--weight-semibold);
  letter-spacing: var(--tracking-wide); text-transform: uppercase;
  color: var(--leak-ink); background: var(--leak-fill);
  padding: 1px 6px; border-radius: 3px;
}
.leak-item__recover { text-align: right; }
.leak-item__recover .amt {
  font-family: var(--font-mono); font-variant-numeric: tabular-nums;
  font-size: var(--text-lg); font-weight: var(--weight-medium); color: var(--gain-ink);
}
.leak-item__recover .cap { display: block; font-size: var(--text-2xs); color: var(--ink-faint); }

/* Total-recoverable band — the sum, honest, at the foot of the leaks panel. */
.recover-total {
  display: flex; align-items: baseline; justify-content: space-between;
  padding: var(--space-4) var(--space-6);
  margin-top: var(--space-2);
  background: var(--gain-fill); border-top: 1px solid var(--gain-line);
  border-radius: 0 0 var(--radius-lg) var(--radius-lg);
}
.recover-total__label { font-size: var(--text-sm); color: var(--gain-ink); font-weight: var(--weight-medium); }
.recover-total__value {
  font-family: var(--font-mono); font-variant-numeric: tabular-nums;
  font-size: var(--text-xl); font-weight: var(--weight-medium); color: var(--gain-ink);
  white-space: nowrap;
}
.recover-total__label { flex: 0 1 auto; }

/* ===========================================================================
   ★ THE PERCEPTION-GAP REVEAL (§3.3.2b) — the single best "aha".
   Guess captured BEFORE the actual is revealed. This panel sits guessed ↔ actual
   side by side and headlines the multiple. It is a DISPLAY reveal, distinct from
   the recoverable-dollars leak — calm, factual, never a jump-scare.
   =========================================================================== */
.gap-reveal {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  gap: var(--space-5);
  padding: var(--space-5) var(--space-6);
}
.gap-reveal__side { display: flex; flex-direction: column; gap: var(--space-1); }
.gap-reveal__side--actual { text-align: right; }
.gap-reveal__label {
  font-size: var(--text-2xs); letter-spacing: var(--tracking-caps);
  text-transform: uppercase; color: var(--ink-faint);
}
.gap-reveal__value {
  font-family: var(--font-mono); font-variant-numeric: tabular-nums;
  font-size: var(--text-xl); font-weight: var(--weight-medium); color: var(--ink-strong);
}
.gap-reveal__value--guessed { color: var(--ink-muted); }
.gap-reveal__value--actual  { color: var(--leak-ink); }
/* The multiple, centered between the two figures. The whole story in one number. */
.gap-reveal__mult {
  display: flex; flex-direction: column; align-items: center; gap: 2px;
  padding: 0 var(--space-4);
}
.gap-reveal__mult-value {
  font-family: var(--font-mono); font-variant-numeric: tabular-nums;
  font-size: var(--text-2xl); font-weight: var(--weight-medium); color: var(--leak-ink);
  letter-spacing: var(--tracking-tight); line-height: 1;
}
.gap-reveal__mult-cap { font-size: var(--text-2xs); color: var(--ink-faint); text-align: center; }
/* Population-context line — "most people are off by 2.5×" (perceptionGapRatioReference). */
.gap-reveal__context {
  grid-column: 1 / -1;
  padding-top: var(--space-3); margin-top: var(--space-1);
  border-top: 1px solid var(--line);
  font-size: var(--text-xs); color: var(--ink-muted); text-align: center;
}
@media (max-width: 880px) {
  .gap-reveal { grid-template-columns: 1fr 1fr; row-gap: var(--space-4); }
  .gap-reveal__mult { grid-column: 1 / -1; order: 3; padding: 0; }
  .gap-reveal__side--actual { text-align: right; }
}

/* The two-step subscription input prompt — the guess field must come first,
   before the actual is tallied, or the reveal is contaminated (§3.3.2 UI req). */
.two-step { display: flex; flex-direction: column; gap: var(--space-3); }
.two-step__step { display: flex; align-items: baseline; gap: var(--space-2); }
.two-step__ord {
  font-family: var(--font-mono); font-size: var(--text-2xs); color: var(--accent);
  font-weight: var(--weight-semibold);
}

/* Ranking-panel intro note — the one line that keeps A and B distinct in the
   reader's head. A = dollars ("what's actually big"); B = overshoot %
   ("what's leaking"). Never merged. */
.rank-note {
  font-size: var(--text-xs); color: var(--ink-muted);
  max-width: 56ch; line-height: var(--leading-normal);
}

/* Ranking B row shows overshoot as its lead figure (% over) with $ overshoot
   beneath — both, per §2.2: the aha is %, the plan is dollars. */
.cat-row__over {
  font-family: var(--font-mono); font-variant-numeric: tabular-nums;
  font-size: var(--text-sm); color: var(--leak-ink); text-align: right; font-weight: var(--weight-medium);
}
.cat-row__over small { display: block; font-size: var(--text-2xs); color: var(--gain-ink); font-weight: var(--weight-regular); }

/* Unaccounted-for / unallocated residual note (§4) — honest, could be either. */
.residual-note {
  display: flex; align-items: flex-start; gap: var(--space-3);
  padding: var(--space-4) var(--space-5);
  background: var(--surface-sunk); border: 1px solid var(--line);
  border-radius: var(--radius-md);
  font-size: var(--text-sm); color: var(--ink-muted); line-height: var(--leading-normal);
}
.residual-note strong { color: var(--ink); font-weight: var(--weight-medium); }

/* ---------------------------------------------------------------------------
   THE PLAN — the four-stage waterfall (§6.3):
     1 employer match → 2 high-interest debt (≥8% avalanche, cards first)
     → 3 emergency fund → 4 invest.
   Rendered as ordered steps, each a calm row with allocation + rationale.
   --------------------------------------------------------------------------- */
.plan-steps { counter-reset: step; display: flex; flex-direction: column; gap: var(--space-3); }
.plan-step {
  display: grid;
  grid-template-columns: 28px 1fr auto;
  gap: var(--space-4);
  align-items: start;
  padding: var(--space-4);
  background: var(--surface-sunk);
  border: 1px solid var(--line);
  border-radius: var(--radius-md);
}
.plan-step__num {
  counter-increment: step;
  width: 24px; height: 24px; border-radius: 50%;
  display: grid; place-items: center;
  background: var(--accent); color: var(--accent-ink);
  font-family: var(--font-mono); font-size: var(--text-xs); font-weight: var(--weight-semibold);
}
.plan-step__num::before { content: counter(step); }
.plan-step__body { display: flex; flex-direction: column; gap: 2px; }
.plan-step__name { font-size: var(--text-base); font-weight: var(--weight-medium); color: var(--ink); }
.plan-step__desc { font-size: var(--text-sm); color: var(--ink-muted); max-width: 50ch; }
.plan-step__amt {
  font-family: var(--font-mono); font-variant-numeric: tabular-nums;
  font-size: var(--text-md); font-weight: var(--weight-medium); color: var(--ink-strong);
  text-align: right; white-space: nowrap;
}
.plan-step__amt small { display: block; font-size: var(--text-2xs); color: var(--ink-faint); font-family: var(--font-ui); }

/* ---------------------------------------------------------------------------
   THE PROJECTION — "what this becomes" (§6.4). ALWAYS 4% REAL base, with
   3% (cautious) / 5% (optimistic) sensitivity × [5, 10, 20] yr. Every figure is
   in TODAY'S dollars (the return is real, so the FV is inflation-adjusted) —
   the label says so, always. Never a single rosy nominal figure.
   --------------------------------------------------------------------------- */
.projection {
  padding: var(--space-4) 0 0;
  border-top: 1px solid var(--line);
  margin-top: var(--space-4);
}
.projection__lead {
  display: flex; align-items: baseline; gap: var(--space-3);
  margin-bottom: var(--space-4);
}
.projection__value {
  font-family: var(--font-mono); font-variant-numeric: tabular-nums;
  font-size: var(--text-xl); font-weight: var(--weight-medium); color: var(--gain-ink);
}
.projection__assume { font-size: var(--text-xs); color: var(--ink-faint); }

/* The sensitivity matrix — rows = horizon (5/10/20 yr), cols = rate (3/4/5%).
   The 4% column is the emphasized base; 3% and 5% flank it, quieter. */
.proj-matrix { width: 100%; border-collapse: collapse; }
.proj-matrix th, .proj-matrix td {
  padding: var(--space-2) var(--space-3);
  text-align: right;
  font-family: var(--font-mono); font-variant-numeric: tabular-nums;
  font-size: var(--text-sm);
}
.proj-matrix thead th {
  font-size: var(--text-2xs); letter-spacing: var(--tracking-wide); text-transform: uppercase;
  color: var(--ink-faint); font-family: var(--font-ui); font-weight: var(--weight-semibold);
  border-bottom: 1px solid var(--line-strong);
}
.proj-matrix tbody th {
  text-align: left; color: var(--ink-muted); font-family: var(--font-ui);
  font-weight: var(--weight-regular); font-size: var(--text-sm);
}
.proj-matrix td { color: var(--ink); }
.proj-matrix .rate-base { color: var(--ink-faint); }         /* the "4% REAL" header, emphasized below */
.proj-matrix thead .rate-base { color: var(--gain-ink); }
.proj-matrix td.col-base {
  color: var(--gain-ink); font-weight: var(--weight-medium);
  background: var(--gain-fill);
}
.proj-matrix tbody tr:not(:last-child) td,
.proj-matrix tbody tr:not(:last-child) th { border-bottom: 1px solid var(--line-faint); }
.proj-foot {
  margin-top: var(--space-3); font-size: var(--text-xs); color: var(--ink-faint);
  line-height: var(--leading-normal);
}
/* Aged-out childcare — the second, deferred projection stream (§6.4a). */
.proj-aged-out {
  margin-top: var(--space-4); padding-top: var(--space-4);
  border-top: 1px dashed var(--line-strong);
  font-size: var(--text-sm); color: var(--ink-muted); line-height: var(--leading-normal);
}
.proj-aged-out strong { color: var(--gain-ink); font-weight: var(--weight-medium); }

/* ---------------------------------------------------------------------------
   EMPTY / FIRST-RUN — before numbers are in, the canvas is a calm invitation,
   never a wall of zeroes. One sentence + arrow to the rail.
   --------------------------------------------------------------------------- */
.empty {
  display: grid; place-items: center; text-align: center;
  min-height: 320px; padding: var(--space-8);
  color: var(--ink-muted);
}
.empty__title { font-size: var(--text-lg); color: var(--ink); margin-bottom: var(--space-2); }
.empty p { max-width: 42ch; font-size: var(--text-sm); }

/* ---------------------------------------------------------------------------
   LEGEND — a small, always-available key so the two-layer bar reads correctly.
   --------------------------------------------------------------------------- */
.legend { display: flex; flex-wrap: wrap; gap: var(--space-4); font-size: var(--text-2xs); color: var(--ink-muted); }
.legend__item { display: inline-flex; align-items: center; gap: var(--space-2); }
.legend__swatch { width: 22px; height: 8px; border-radius: 4px; }
.legend__swatch--size  { background: var(--bar-size); }
.legend__swatch--range { background: var(--bar-range); border: 1px solid var(--bar-range-lo); }
.legend__swatch--over  { background: var(--bar-overshoot); }
.legend__swatch--bench { width: 2px; height: 12px; background: var(--bar-benchmark); border-radius: 1px; }

/* =============================================================================
   PHONE — one well-composed column. NOT a squeezed desktop. The rail becomes a
   collapsible top section; the bar keeps both layers; figures stay tabular.
   ============================================================================= */
@media (max-width: 880px) {
  .app__shell { grid-template-columns: 1fr; }
  .topbar { flex-direction: column; align-items: flex-start; gap: var(--space-2); padding: var(--space-4); }
  .rail {
    position: static; max-height: none; overflow: visible;
    border-right: 0; border-bottom: 1px solid var(--line);
    padding: var(--space-5) var(--space-4) var(--space-6);
  }
  .canvas { padding: var(--space-5) var(--space-4) var(--space-8); }
  .panel-row { grid-template-columns: 1fr; }
  .panel { padding: var(--space-4) var(--space-4); border-radius: var(--radius-md); }

  /* Category row reflows: label on top, bar full width beneath, figures right. */
  .cat-row {
    grid-template-columns: 14px 1fr auto;
    grid-template-areas:
      "edge label  amt"
      "edge bar    bar";
    row-gap: var(--space-2);
    padding: var(--space-3) var(--space-2) var(--space-3) 0;
  }
  .cat-row__edge { grid-area: edge; }
  .cat-row__labelcell { grid-area: label; }
  .bar { grid-area: bar; }
  .cat-row__amt { grid-area: amt; }
  .cat-row__pct { display: none; } /* percent folds into the amt cell on phone */

  .figure { font-size: var(--text-xl); }
  .figure--hero { font-size: var(--text-2xl); }
  .plan-step { grid-template-columns: 24px 1fr; }
  .plan-step__amt { grid-column: 1 / -1; text-align: left; padding-left: 40px; }
}

@media (max-width: 420px) {
  :root { --page-pad: var(--space-4); }
  .cat-row__amt { font-size: var(--text-xs); }
}
