  /* ============================================================
     RICHIFI — ATELIER STRUCTURE: HAVEN LIGHT / NOCTURNE DARK
     Same bones as the old brutalist build (hairline grids, tabular
     figures, the same component set) restyled per design/HYBRID_SPEC.md:
     rounded surfaces, soft layered shadows, sentence-case labels, no
     monospace-as-voice, no marker-highlighter blocks, no report
     metaphor. Nocturne (deep navy + aurora violet) is the default
     `:root`; Haven (warm cream + sage) is `[data-theme="light"]`.
     Token NAMES are a JS contract (charts + inline styles read them
     via css()) — values may change, names may never be renamed or
     removed, or app.js's chart rendering breaks.
     ============================================================ */

  :root, [data-theme="dark"] {
    /* Dark = WAYPOINT dusk: indigo night sky, aurora-green primary, gold secondary
       (palette lifted from design/option-c-waypoint.html at the owner's request). */
    --ink: #10131c;
    --surface: #151926;
    --card: #181d2c;
    --card-hover: #1d2334;
    --border: #262c40;
    --border-subtle: #1e2333;
    --text: #e9ecf4;
    --text-secondary: #9aa2b8;
    --text-dim: #949bac;
    --gold: #5ee6a8;             /* primary accent is Waypoint's aurora green */
    --gold-dim: #3fbe85;
    --gold-glow: rgba(94,230,168,0.14);
    --marker: rgba(94,230,168,0.16);   /* legacy name — a quiet aurora wash, not a highlighter */
    --marker-ink: #d9f7e8;
    --row-hover: rgba(94,230,168,0.06);
    --emerald: #5ee6a8;
    --emerald-dim: rgba(94,230,168,0.12);
    --ruby: #f2758a;
    --ruby-dim: rgba(242,117,138,0.12);
    --blue: #6fa8ff;
    --blue-dim: rgba(111,168,255,0.12);
    --purple: #9d8cff;
    --purple-dim: rgba(157,140,255,0.12);
    --orange: #e8c95a;
    --orange-dim: rgba(232,201,90,0.12);
    /* Business-equity series colors (charts + detail-table cell text + sticky KPI values). */
    --biz-1: #5ee6a8;
    --biz-2: #6fa8ff;
    --biz-3: #9d8cff;
    --biz-4: #e8c95a;
    --biz-5: #f2758a;
    --grain-opacity: 0;
    --shadow-hard: 0 24px 60px -28px rgba(0,0,0,.7);
    --shadow-hard-sm: 0 12px 32px -18px rgba(0,0,0,.6);
    --chart-grid: #1e2333;
    --chart-grid-sub: #1e233388;
    --chart-label: #949bac;
    --chart-tooltip-bg: #181d2c;
    --chart-tooltip-border: #262c40;
    --chart-tooltip-title: #e9ecf4;
    --chart-tooltip-body: #9aa2b8;
    --serif: 'Schibsted Grotesk', sans-serif; /* legacy token name — display face */
    --mono: 'Instrument Sans', sans-serif;    /* deliberately NOT a monospace anymore */
    --sans: 'Instrument Sans', sans-serif;
    --radius-card: 16px;
    --radius-ctl: 9px;
    --radius-pill: 999px;
    --aurora: linear-gradient(100deg, #5ee6a8, #8ce0b0 45%, #e8c95a);
    --glass: rgba(255,255,255,.045);
    --nav-w: 236px; /* left rail width; not theme-dependent */
  }

  [data-theme="light"] {
    --ink: #faf6ef;
    --surface: #f3ede1;
    --card: #ffffff;
    --card-hover: #faf7f0;
    --border: #e9e1d2;
    --border-subtle: #f0e9dc;
    --text: #2d2a24;
    --text-secondary: #5b564a;
    --text-dim: #66625a;
    --gold: #4f6a4b;             /* primary accent is now sage */
    --gold-dim: #42603e;
    --gold-glow: #e7eee4;           /* Haven sage-soft */
    --marker: #e7eee4;           /* soft sage wash, not a highlighter */
    --marker-ink: #2d4a28;
    --row-hover: rgba(95,125,90,0.06);
    --emerald: #4f6a4b;          /* Haven success IS sage */
    --emerald-dim: #e7eee4;
    --ruby: #a94752;             /* 4.63 on --ruby-dim, the darkest ground it lands on */
    --ruby-dim: #f7e4e5;         /* Haven berry-soft */
    --blue: #406e8f;             /* 4.62 on --emerald-dim, the darkest ground it lands on */
    --blue-dim: #e3edf4;         /* Haven sky-soft */
    --purple: #765aaf;
    --purple-dim: rgba(124,95,184,0.10);
    --orange: #9b592b;
    --orange-dim: #fbeadd;       /* Haven apricot-soft */
    --biz-1: #4f6a4b;
    --biz-2: #406e8f;
    --biz-3: #765aaf;
    --biz-4: #9b592b;
    --biz-5: #a94752;
    --grain-opacity: 0;
    --shadow-hard: 0 1px 2px rgba(60,50,30,.05), 0 8px 24px -12px rgba(60,50,30,.18);
    --shadow-hard-sm: 0 1px 2px rgba(60,50,30,.05), 0 4px 12px -6px rgba(60,50,30,.14);
    --chart-grid: #eee7d9;
    --chart-grid-sub: #eee7d988;
    --chart-label: #66625a;
    --chart-tooltip-bg: #ffffff;
    --chart-tooltip-border: #e9e1d2;
    --chart-tooltip-title: #2d2a24;
    --chart-tooltip-body: #6f6a5e;
    --radius-card: 16px;
    --radius-ctl: 9px;
    --radius-pill: 999px;
    --aurora: linear-gradient(100deg, #4f6a4b, #7d9a70 45%, #e0813f); /* sage → apricot */
    --glass: rgba(255,255,255,.72);
  }

  * { margin:0; padding:0; box-sizing:border-box; }
  ::selection { background: var(--gold-glow); color: var(--text); }

  html { font-size: 16px; }

  body {
    font-family: var(--sans);
    font-feature-settings: 'tnum';
    background: var(--ink);
    color: var(--text);
    min-height: 100vh;
    overflow-x: hidden;
  }

  /* Nocturne gets two fixed radial aurora glows behind everything; Haven stays a flat cream —
     the glow is a dark-mode-only mood cue, not a texture both themes share. */
  [data-theme="dark"] body,
  :root:not([data-theme]) body {
    background-image:
      radial-gradient(900px 500px at 15% -10%, rgba(94,230,168,.06), transparent 60%),
      radial-gradient(900px 600px at 90% 0%, rgba(232,201,90,.05), transparent 55%);
    background-attachment: fixed;
    background-color: var(--ink);
  }
  [data-theme="light"] body { background-image: none; }

  /* Former paper-grain overlay: kept as a no-op (opacity 0 via --grain-opacity) rather than
     removed, since app.js/tests may still probe for the node; it paints nothing in either theme. */
  body::before {
    content: '';
    position: fixed;
    inset: 0;
    opacity: var(--grain-opacity);
    pointer-events: none;
    z-index: -1;
  }

  /* Custom scrollbar — square, mechanical */
  ::-webkit-scrollbar { width: 8px; height: 8px; }
  ::-webkit-scrollbar-track { background: transparent; }
  ::-webkit-scrollbar-thumb { background: var(--border); border-radius: var(--radius-ctl); }
  ::-webkit-scrollbar-thumb:hover { background: var(--text-dim); }

  body, .card, .company-col, .kpi, .chart-cell, .milestone,
  table, th, td, .data-section, .data-section-header, .exit-strip,
  .inputs-panel, .growth-toggle button, .field input[type=number],
  .section-panel, .asset-col, .debt-row, .holding-row {
    transition: background-color 0.35s ease, color 0.35s ease, border-color 0.35s ease;
  }

  /* === THEME TOGGLE === */
  .theme-toggle {
    position: relative;
    display: flex;
    align-items: center;
    gap: 10px;
    cursor: pointer;
    -webkit-user-select: none;
    user-select: none;
  }
  .theme-toggle-label {
    font-family: var(--mono);
    font-size: 0.66rem;
    font-weight: 500;
    color: var(--text-dim);
  }
  .theme-toggle-track {
    width: 44px;
    height: 22px;
    background: var(--surface);
    border-radius: var(--radius-ctl);
    position: relative;
    transition: background 0.35s ease, border-color 0.35s ease;
    border: 1px solid var(--border);
  }
  [data-theme="light"] .theme-toggle-track {
    background: var(--gold-glow);
    border-color: var(--gold);
  }
  .theme-toggle-thumb {
    position: absolute;
    top: 3px;
    left: 3px;
    width: 14px;
    height: 14px;
    border-radius: var(--radius-ctl);
    background: var(--text-dim);
    transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1), background 0.3s ease;
    display: flex;
    align-items: center;
    justify-content: center;
  }
  [data-theme="light"] .theme-toggle-thumb {
    transform: translateX(22px);
    background: var(--gold);
  }
  .theme-toggle-thumb svg { width: 10px; height: 10px; }
  .theme-toggle-thumb .icon-sun { display: none; }
  .theme-toggle-thumb .icon-moon { display: block; }
  [data-theme="light"] .theme-toggle-thumb .icon-sun { display: block; }
  [data-theme="light"] .theme-toggle-thumb .icon-moon { display: none; }

  /* === LAYOUT — fixed left rail + main column === */
  .main-col {
    margin-left: var(--nav-w);
    min-height: 100vh;
  }
  .wrapper {
    max-width: 1340px;
    margin: 0 auto;
    padding: 32px 36px 80px;
  }

  /* ============================================================
     STAGE 1 CHROME — icon rail + topline (design/restructure-home.html),
     tokens adapted: --accent->--gold, --accent-soft->--gold-glow,
     --card-2->--card-hover, --line->--border, --line-2->--border-subtle,
     --dim->--text-dim, --text-2->--text-secondary, --rail->--surface.
     Replaces the old .side-nav, .nav-brand, .nav-wordmark, .nav-nw-…,
     .nav-groups, .nav-group-…, .nav-data-… rail and the old .profile-strip.
     (NB: never write "asterisk-slash" inside a CSS comment — it ends it.)
     ============================================================ */
  .side-nav.rail {
    position: fixed;
    top: 0;
    left: 0;
    width: var(--nav-w);
    height: 100vh;
    z-index: 900;
    display: flex;
    flex-direction: column;
    gap: 2px;
    background: var(--surface);
    border-right: 1px solid var(--border-subtle);
    overflow-y: auto;
    overflow-x: hidden;
    padding: 16px 10px 12px;
    animation: fadeIn 0.5s ease-out;
  }
  .side-nav.rail::-webkit-scrollbar { width: 6px; }

  .brand { display: flex; align-items: center; gap: 9px; padding: 4px 10px 14px; }
  .brand .logo {
    width: 26px; height: 26px; border-radius: 8px; background: var(--aurora);
    color: #fff; display: grid; place-items: center; font-weight: 800;
    font-size: 0.85rem; font-family: var(--serif); flex: none;
  }
  .brand b { font-family: var(--serif); font-size: 1rem; font-weight: 700; letter-spacing: -0.01em; color: var(--text); }

  .nav-groups { display: flex; flex-direction: column; gap: 2px; }

  .nav-item {
    display: flex;
    align-items: center;
    gap: 10px;
    width: 100%;
    margin: 0;
    padding: 7px 10px;
    background: transparent;
    border: none;
    border-radius: 9px;
    cursor: pointer;
    text-align: left;
    font-family: var(--sans);
    font-size: 0.9rem;
    font-weight: 500;
    color: var(--text-secondary);
    transition: background 0.14s, color 0.14s;
  }
  .nav-item svg { width: 16px; height: 16px; flex: none; opacity: 0.9; }
  .nav-item.sub { padding-left: 36px; font-size: 0.85rem; }
  .nav-item:hover { color: var(--text); background: var(--card); }
  .nav-item.active {
    color: var(--gold);
    background: var(--gold-glow);
    font-weight: 600;
  }
  .nav-badge {
    margin-left: auto;
    flex: 0 0 auto;
    font-family: var(--mono);
    font-size: 0.76rem;
    font-weight: 500;
    font-variant-numeric: tabular-nums;
    color: var(--text-dim);
    letter-spacing: 0;
  }
  .nav-item.active .nav-badge { color: var(--gold); }

  .nav-sep {
    font-family: var(--mono);
    font-size: 0.7rem;
    font-weight: 600;
    color: var(--text-dim);
    padding: 14px 10px 5px;
  }

  /* Rail footer: Data & backups flyout + sync line */
  .rail-bottom {
    position: relative;
    margin-top: auto;
    padding: 8px 10px 0;
    border-top: 1px solid var(--border-subtle);
  }
  .data-flyout {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 7px 10px;
    margin: 0 -10px;
    border-radius: 9px;
    font-family: var(--sans);
    font-size: 0.85rem;
    color: var(--text-secondary);
    cursor: pointer;
  }
  .data-flyout:hover { background: var(--card); color: var(--text); }
  .data-flyout .menu-hint { font-size: 0.72rem; color: var(--text-dim); }
  .sync {
    display: flex;
    align-items: center;
    gap: 6px;
    font-family: var(--mono);
    font-size: 0.7rem;
    color: var(--text-dim);
    padding: 6px 0;
    cursor: pointer;
  }

  /* Flyout/popover shared shell (also used by .profile-pop .pop) */
  .pop {
    position: absolute;
    z-index: 20;
    background: var(--card);
    border: 1px solid var(--border);
    border-radius: 14px;
    box-shadow: var(--shadow-hard-sm);
    padding: 14px;
    display: none;
  }
  .data-pop {
    left: 0;
    right: 0;
    bottom: calc(100% + 6px);
    display: none;
    flex-direction: column;
    gap: 8px;
  }
  .rail-bottom.open .data-pop { display: flex; }
  .data-pop .price-status { font-size: 0.66rem; }
  .data-pop .nav-data-actions {
    display: flex;
    flex-wrap: wrap;
    gap: 5px;
    margin-top: 2px;
  }
  .data-pop .nav-data-actions .btn-export { flex: 0 0 auto; }

  /* === TOPLINE (replaces .report-masthead visually) === */
  .topline.report-masthead {
    display: flex;
    align-items: center;
    gap: 14px;
    padding: 14px 36px;
    margin: 0;
    border-bottom: 1px solid var(--border-subtle);
    position: sticky;
    top: 0;
    z-index: 850;
    background: color-mix(in srgb, var(--ink) 82%, transparent);
    backdrop-filter: blur(12px);
  }
  .topline .masthead-page, .topline h1.masthead-page {
    font-family: var(--serif);
    font-size: 1rem;
    font-weight: 700;
    letter-spacing: -0.01em;
    color: var(--text);
  }
  .topline .topline-date { color: var(--text-dim); font-family: var(--mono); font-size: 0.72rem; }
  .cmdk.cmdk-hint {
    margin-left: auto;
    display: flex;
    align-items: center;
    gap: 8px;
    border: 1px solid var(--border);
    border-radius: 9px;
    background: var(--card);
    color: var(--text-dim);
    font-family: var(--sans);
    font-size: 0.82rem;
    padding: 6px 12px;
    cursor: pointer;
    min-width: 230px;
  }
  .cmdk.cmdk-hint kbd {
    margin-left: auto;
    font-family: inherit;
    font-size: 0.7rem;
    border: 1px solid var(--border);
    border-radius: 5px;
    padding: 1px 5px;
    color: var(--text-dim);
  }
  .pill-btn {
    border: 1px solid var(--border);
    background: var(--card);
    color: var(--text-secondary);
    font-family: var(--sans);
    font-size: 0.82rem;
    font-weight: 500;
    border-radius: 9px;
    padding: 6px 12px;
    cursor: pointer;
  }
  .pill-btn:hover { color: var(--text); border-color: var(--gold); }

  /* HOUSEHOLD VIEWS: three-way segmented lens (You / partner / Everyone) in the topline, left
     of the profile pill. Same shell as .pill-btn (border-subtle frame, dark mono chrome) so it
     reads as chrome, not a new UI language; the active segment gets the gold accent everything
     else in the topline already uses for "this one is selected" (see .nav-item.active). */
  /* Stands where the view toggle would be on a shared-outcome page. Quiet by design: it is an
     explanation, not a control, so it must not read as something you can click. */
  .plan-view-shared {
    flex: 0 0 auto;
    font-family: var(--mono);
    font-size: 0.68rem;
    color: var(--text-secondary);
    letter-spacing: 0.02em;
    padding: 5px 10px;
    border: 1px dashed var(--border-subtle);
    border-radius: 9px;
    white-space: nowrap;
  }
  .plan-view-toggle {
    display: inline-flex;
    border: 1px solid var(--border-subtle);
    border-radius: 9px;
    overflow: hidden;
    flex: 0 0 auto;
  }
  .plan-view-toggle button {
    background: transparent;
    border: none;
    color: var(--text-dim);
    font-family: var(--sans);
    font-size: 0.78rem;
    font-weight: 500;
    padding: 6px 11px;
    cursor: pointer;
    transition: background 0.15s, color 0.15s;
  }
  .plan-view-toggle button + button { border-left: 1px solid var(--border-subtle); }
  .plan-view-toggle button:hover:not(.active) { color: var(--text-secondary); }
  .plan-view-toggle button.active { color: var(--gold); background: var(--gold-glow); font-weight: 600; }
  /* Click is a no-op while What-If is active (the sandbox has no lens to switch — see
     PLAN_VIEW_KEY's click handler in app.js), so the control reads as disabled rather than
     inviting a click that visibly does nothing. Same idiom as body.whatif-active elsewhere. */
  body.whatif-active .plan-view-toggle { opacity: 0.5; cursor: not-allowed; }
  body.whatif-active .plan-view-toggle button { cursor: not-allowed; }
  .profile-pop { position: relative; }
  .profile-pop .pop {
    right: 0;
    left: auto;
    top: calc(100% + 8px);
    width: 260px;
  }
  .profile-pop.open .pop { display: block; }

  /* Profile strip, restyled as stacked fields inside the popover */
  .profile-strip {
    display: flex;
    flex-direction: column;
    gap: 10px;
    font-family: var(--mono);
    font-size: 0.7rem;
  }
  .profile-strip .profile-field {
    display: flex;
    flex-direction: column;
    align-items: stretch;
    gap: 4px;
    color: var(--text-secondary);
  }
  .profile-strip .profile-field span {
    font-size: 0.72rem;
    color: var(--text-secondary);
  }
  .profile-strip input,
  .profile-strip select {
    width: 100%;
    background: var(--card-hover);
    border: 1px solid var(--border);
    color: var(--text);
    font-family: var(--sans);
    font-size: 0.88rem;
    padding: 7px 10px;
    border-radius: var(--radius-ctl);
  }
  .profile-strip input:focus, .profile-strip select:focus { outline: 1px solid var(--gold); }
  .profile-strip .profile-hint.note {
    margin-left: 0;
    color: var(--text-dim);
    font-size: 0.72rem;
    letter-spacing: 0;
  }

  .sticky-sentinel { height: 1px; }

  /* ============================================================
     STAGE 2 HOME — Home page card grid (design/restructure-home.html),
     tokens adapted per the same map as Stage 1: --accent->--gold,
     --accent-soft->--gold-glow, --card-2->--card-hover, --line->--border,
     --line-2->--border-subtle, --dim->--text-dim, --text-2->--text-secondary.
     Covers .headrow/.big/.delta-pills/.dp/.whatif/.band/.cell/.spark/.grid/
     .panel/.panel-head/.bottom/.ringwrap/.ring/.rfacts/table+.share-bar/
     .allchecks, plus the coach-row fold rules (.coach-row.good / .show-all
     / .expanded .coach-math) that make the compact "Needs attention" panel work.
     ============================================================ */
  .headrow { display: flex; align-items: flex-end; justify-content: space-between; gap: 20px; flex-wrap: wrap; }
  .headrow .cap { font-family: var(--sans); font-size: 0.85rem; color: var(--text-secondary); }
  .headrow .big {
    font-family: var(--serif); font-weight: 800; font-size: clamp(2.2rem, 4.5vw, 2.9rem);
    letter-spacing: -0.03em; line-height: 1.04; margin-top: 4px;
    font-variant-numeric: tabular-nums; font-feature-settings: 'tnum';
  }
  [data-theme="dark"] .headrow .big {
    background: var(--aurora); -webkit-background-clip: text; background-clip: text; color: transparent;
    filter: drop-shadow(0 0 22px rgba(94,230,168,.20));
  }
  .delta-pills { display: flex; gap: 6px; margin-top: 12px; flex-wrap: wrap; }
  .dp { font-family: var(--sans); font-size: 0.76rem; font-weight: 600; border-radius: var(--radius-pill); padding: 4px 10px; white-space: nowrap; }
  .dp.up { background: var(--emerald-dim); color: var(--emerald); }
  .dp.down { background: var(--ruby-dim); color: var(--ruby); }
  .dp.dim { background: var(--card-hover); color: var(--text-secondary); border: 1px solid var(--border); }
  .dp.warn { background: var(--gold-glow); color: var(--gold); }
  /* The one pill that does something when clicked, so it has to look like it. Kept as a <span>
     rather than a <button> so it inherits .dp's type and colour bands unchanged; the row is a
     delegated click root. NOTE: being a span means mobile-check does not measure it as a tap
     target, so the 44px floor is not enforced for us here — it is set by hand in the mobile
     block at the end of this file. Do not remove that rule assuming the harness covers it. */
  .dp-act { cursor: pointer; text-decoration: underline; text-decoration-style: dotted; text-underline-offset: 3px; }
  .dp-act:hover { filter: brightness(1.12); text-decoration-style: solid; }
  .dp-act:focus-visible { outline: 2px solid var(--gold); outline-offset: 2px; }
  .whatif {
    background: var(--gold); border: none; color: #fff; font-family: var(--sans);
    font-size: 0.88rem; font-weight: 650; border-radius: var(--radius-ctl); padding: 10px 18px;
    cursor: pointer; box-shadow: var(--shadow-hard-sm);
  }
  [data-theme="dark"] .whatif { background: var(--aurora); color: #fff; }
  .whatif:hover { opacity: 0.92; }
  .headrow .headline-story { margin-top: 12px; }

  .band {
    display: grid; grid-template-columns: repeat(4, 1fr); margin-top: 24px;
    background: var(--card); border: 1px solid var(--border); border-radius: var(--radius-card);
    overflow: hidden;
  }
  .band .cell { padding: 16px 20px; border-left: 1px solid var(--border-subtle); position: relative; }
  .band .cell:first-child { border-left: none; }
  .band .cell .l { font-family: var(--sans); font-size: 0.78rem; color: var(--text-secondary); }
  .band .cell .v {
    font-family: var(--serif); font-size: 1.4rem; font-weight: 700; letter-spacing: -0.02em;
    margin-top: 3px; font-variant-numeric: tabular-nums; font-feature-settings: 'tnum';
  }
  .band .cell .s { font-family: var(--mono); font-size: 0.7rem; color: var(--text-dim); margin-top: 2px; }
  .band .cell .v.red { color: var(--ruby); }
  .band .cell .v.green { color: var(--emerald); }
  .band .cell .v.blue { color: var(--blue); }
  .band .cell svg.spark { position: absolute; right: 16px; top: 20px; width: 64px; height: 24px; }
  @media (max-width: 900px) { .band { grid-template-columns: 1fr 1fr; } .band .cell:nth-child(3) { border-left: none; } }

  .grid { display: grid; grid-template-columns: 1.5fr 1fr; gap: 16px; margin-top: 16px; }
  /* Snapshot's grid holds only the projection panel since the Coach moved to its own page. */
  .grid.grid-solo { grid-template-columns: 1fr; }
  .panel {
    background: var(--card); border: 1px solid var(--border); border-radius: var(--radius-card);
    padding: 18px 20px;
  }
  .panel-head { display: flex; align-items: baseline; justify-content: space-between; margin-bottom: 12px; gap: 12px; flex-wrap: wrap; }
  .panel-head h3 { font-family: var(--serif); font-size: 0.95rem; font-weight: 700; letter-spacing: -0.01em; }
  .panel-head a.goto { font-family: var(--sans); font-size: 0.78rem; color: var(--gold); font-weight: 600; text-decoration: none; cursor: pointer; }
  .panel-head a.goto:hover { text-decoration: underline; }
  .panel-head .count { font-family: var(--mono); font-size: 0.68rem; color: var(--text-dim); margin-left: 8px; font-weight: 500; }
  /* Coach count reads at a glance: a pill, larger and brighter than the shared .count label, and
     emerald when nothing needs attention (all checks clear). #id beats .panel-head .count. */
  #coachCount { font-size: 0.78rem; font-weight: 700; color: var(--text-secondary); margin-left: 10px;
    padding: 3px 11px; border-radius: 999px; background: var(--surface); border: 1px solid var(--border); }
  #coachCount.all-clear { color: var(--emerald); background: var(--emerald-dim); border-color: transparent; }
  .panel.proj canvas { width: 100%; }
  /* Solo (full-width) the chart would keep Chart.js's 2:1 ratio and run ~540px tall, which
     pushes the retirement ring below the fold. Cap the box; Chart.js is responsive and sizes
     to the container it is given. */
  .grid.grid-solo .panel.proj { max-height: 420px; }
  .grid.grid-solo .panel.proj canvas { max-height: 340px; }

  .allchecks {
    width: 100%; margin-top: 10px; background: none; border: 1px dashed var(--border); border-radius: var(--radius-ctl);
    color: var(--text-secondary); font-family: var(--mono); font-size: 0.72rem; font-weight: 600; padding: 8px; cursor: pointer;
  }
  .allchecks:hover { color: var(--gold); border-color: var(--gold); }

  /* Plan detail — the "what can this projection see yet" strip under the Coach rows. Quiet by
     design: it reports coverage, it does not raise alarms, so nothing here uses a warn colour. */
  #planDetail { margin-top: 14px; padding-top: 12px; border-top: 1px solid var(--border); }
  #planDetail:empty { display: none; }
  .pd-head { display: flex; align-items: baseline; justify-content: space-between; gap: 10px; }
  .pd-title { font-family: var(--mono); font-size: 0.68rem; font-weight: 600; color: var(--text-secondary); letter-spacing: 0.04em; }
  .pd-count { font-family: var(--mono); font-size: 0.66rem; color: var(--text-dim); font-variant-numeric: tabular-nums; }
  .pd-track { height: 4px; border-radius: 2px; background: var(--border); margin-top: 8px; overflow: hidden; }
  .pd-fill { height: 100%; background: var(--gold); border-radius: 2px; transition: width 0.25s ease; }
  .pd-chips { display: flex; flex-direction: column; gap: 6px; margin-top: 10px; }
  .pd-chip {
    display: flex; align-items: center; width: 100%; text-align: left;
    background: none; border: 1px dashed var(--border); border-radius: var(--radius-ctl);
    color: var(--text-secondary); font-family: var(--mono); font-size: 0.66rem; font-weight: 600;
    /* min-height, not padding arithmetic: at 0.66rem the padded box came out 37px and mobile-check
       rejected it. A floor holds regardless of how the label wraps or what the font metrics do. */
    min-height: 44px; padding: 8px 12px; cursor: pointer; line-height: 1.4;
  }
  .pd-chip span { font-weight: 400; color: var(--text-dim); }
  .pd-chip:hover { color: var(--gold); border-color: var(--gold); }
  .pd-chip:hover span { color: var(--gold); }
  .pd-all { margin: 10px 0 0; font-family: var(--mono); font-size: 0.66rem; line-height: 1.45; color: var(--text-dim); }

  /* Fold the "good" coach rows under #coachAllChecks, and the math explanation under .expanded. */
  #coachPageBody .coach-row.good { display: none; }
  #coachPageBody.show-all .coach-row.good { display: flex; }
  #coachPageBody .coach-row { cursor: pointer; }
  #coachPageBody .coach-row .coach-math { display: none; }
  #coachPageBody .coach-row.expanded .coach-math { display: block; }

  .bottom { display: grid; grid-template-columns: 1fr 1.5fr; gap: 16px; margin-top: 16px; }
  /* Grid items default to min-width:auto, so a track refuses to shrink below its content's
     min-content width. On Home that kept both panels 504px wide inside a 375px viewport even
     after .bottom collapses to one column, scrolling the whole page sideways. */
  .grid > *, .bottom > * { min-width: 0; }
  .ringwrap { display: flex; align-items: center; gap: 18px; }
  .ring { width: 96px; height: 96px; flex: none; position: relative; }
  .ring svg { width: 100%; height: 100%; transform: rotate(-90deg); }
  .ring .mid {
    /* Flex column, centered on both axes — grid place-items left the anonymous text runs
       tracking to the row flow, which read as an off-center year (owner-reported). */
    position: absolute; inset: 0;
    display: flex; flex-direction: column; align-items: center; justify-content: center;
    text-align: center; line-height: 1.15;
    font-family: var(--serif); font-weight: 800; font-size: 1.15rem; color: var(--text);
  }
  .ring .mid small { display: block; font-family: var(--mono); font-size: 0.66rem; font-weight: 700; color: var(--text-dim); letter-spacing: 0.05em; }
  .rfacts { font-family: var(--sans); font-size: 0.85rem; color: var(--text-secondary); display: flex; flex-direction: column; gap: 6px; }
  .rfacts b { color: var(--text); font-weight: 650; }
  .rfacts .ok { color: var(--emerald); font-weight: 650; }

  #homePortfolio { width: 100%; border-collapse: collapse; font-family: var(--sans); font-size: 0.87rem; }
  #homePortfolio thead th { text-align: left; font-family: var(--mono); font-size: 0.68rem; font-weight: 600; color: var(--text-dim); padding: 6px 10px 8px; border-bottom: 1px solid var(--border); }
  #homePortfolio thead th.r, #homePortfolio td.r { text-align: right; }
  #homePortfolio tbody td { padding: 11px 10px; border-bottom: 1px solid var(--border-subtle); }
  #homePortfolio tbody tr:last-child td { border-bottom: none; }
  #homePortfolio tbody tr { cursor: pointer; }
  #homePortfolio tbody tr:hover td { background: var(--card-hover); }
  #homePortfolio td .cls { display: flex; align-items: center; gap: 9px; font-weight: 600; }
  #homePortfolio td .cls i { width: 9px; height: 9px; border-radius: 3px; flex: none; }
  #homePortfolio td.dim, #homePortfolio td.r.dim { color: var(--text-dim); }
  #homePortfolio .share-bar { height: 4px; border-radius: 2px; background: var(--card-hover); width: 90px; overflow: hidden; }
  #homePortfolio .share-bar div { height: 100%; border-radius: 2px; }
  #homePortfolio td .go { color: var(--text-dim); font-size: 0.8rem; }

  @media (max-width: 900px) { .grid, .bottom { grid-template-columns: 1fr; } }

  /* === TAB PANELS (switched by the left rail) === */
  .tab-panel { display: none; }
  .tab-panel.active { display: block; animation: fadeIn 0.25s ease-out; }

  /* === INPUTS PANEL === */
  .inputs-panel {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 0;
    overflow: hidden;
  }

  .company-col {
    padding: 26px 30px;
    background: var(--card);
    position: relative;
  }
  .company-col:first-child { border-right: 1px solid var(--border); }

  .company-col .col-header {
    display: flex;
    align-items: baseline;
    gap: 12px;
    margin-bottom: 22px;
    padding-bottom: 14px;
    border-bottom: 1px solid var(--border-subtle);
  }
  .company-col .col-name {
    font-family: var(--serif);
    font-size: 1.02rem;
    font-weight: 750;
    font-stretch: 116%;
    color: var(--text);
  }
  .company-col .col-stake {
    font-family: var(--mono);
    font-size: 0.66rem;
    font-weight: 500;
    color: var(--gold);
    letter-spacing: 0.06em;
    background: var(--gold-glow);
    padding: 3px 8px;
    border-radius: var(--radius-ctl);
    border: 1px solid var(--gold-dim);
  }
  /* Valuation reconciliation echo at the bottom of each company card */
  .equity-reconcile {
    margin-top: 20px;
    padding-top: 14px;
    border-top: 1px solid var(--border-subtle);
    font-family: var(--mono);
    font-size: 0.66rem;
    color: var(--text-dim);
    letter-spacing: 0.03em;
    line-height: 1.5;
  }

  /* === BUSINESS EQUITY LIST (R-07) — dynamic list of editable business cards === */
  .business-list {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 16px;
    margin-bottom: 20px;
  }
  /* A COLLAPSED business is a display line, not a card: it spans the full row so its meta reads
     on one line and its valuation lands in the same hard-right column every other page uses.
     Only the expanded editor keeps the two-up card layout. The 4px inter-line margin is dropped
     here — the grid's own 16px gap already separates them. */
  .business-list > .view-line { grid-column: 1 / -1; margin-bottom: 0; }
  /* Prominent TODAY / AT SALE value strip on each business card. The at-sale after-tax
     figure is what the plan banks on, so it carries the gold emphasis. */
  .biz-value-strip {
    display: flex; flex-wrap: wrap; gap: 8px 36px;
    padding: 10px 2px 12px; margin-bottom: 12px;
    border-bottom: 1px solid var(--border);
  }
  .biz-val-label {
    font-family: var(--mono); font-size: 0.66rem; color: var(--text-dim); margin-bottom: 3px;
  }
  .biz-val-num { font-family: var(--mono); font-size: 1.05rem; font-weight: 700; color: var(--text); }
  .biz-val-sale .biz-val-num { color: var(--gold); font-size: 1.2rem; }
  .biz-val-aftertax { font-size: 0.66rem; font-weight: 400; color: var(--text-secondary); margin-left: 4px; }
  .biz-val-sub { font-family: var(--mono); font-size: 0.66rem; color: var(--text-secondary); margin-top: 2px; }
  .biz-val-dim { color: var(--text-dim); font-size: 0.72rem; font-weight: 400; }
  .business-card {
    border: 1px solid var(--border);
    border-radius: var(--radius-card);
    background: var(--card);
    padding: 18px 20px 20px;
    position: relative;
  }
  .business-card.dragging { opacity: 0.4; }
  .business-card.drag-over { border-color: var(--gold); box-shadow: 0 0 0 3px var(--gold-glow); }
  .biz-card-head {
    display: flex;
    align-items: center;
    gap: 10px;
    margin-bottom: 18px;
    padding-bottom: 12px;
    border-bottom: 1px solid var(--border-subtle);
  }
  .biz-card-head .drag-handle { flex: 0 0 auto; color: var(--text-dim); cursor: grab; display: flex; align-items: center; }
  .biz-name-input {
    flex: 1 1 auto;
    min-width: 0;
    background: transparent;
    border: none;
    border-bottom: 1px solid transparent;
    color: var(--text);
    font-family: var(--serif);
    font-size: 0.98rem;
    font-weight: 750;
    font-stretch: 116%;
    letter-spacing: 0.02em;
    padding: 2px 2px 4px;
  }
  .biz-name-input::placeholder { color: var(--text-dim); font-weight: 600; }
  .biz-name-input:focus { outline: none; border-bottom-color: var(--gold); }
  .biz-card-actions { flex: 0 0 auto; display: flex; gap: 6px; }
  .biz-card-grid {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 16px 18px;
  }
  .biz-card-grid .field { margin-bottom: 0; }
  .biz-growth-field { grid-column: 1 / -1; }
  .biz-growth-toggle { margin-bottom: 10px; max-width: 220px; }
  .biz-growth-overall { display: flex; align-items: center; gap: 8px; }
  .biz-growth-overall input[type=range] { width: 100%; }
  .biz-growth-unit { font-family: var(--mono); font-size: 0.7rem; color: var(--text-dim); }
  .business-card .slider-field input[type=range] { width: 100%; margin-top: 2px; }
  .biz-aftertax { color: var(--emerald); }
  .biz-growth-yearly.per-year-fields { margin-top: 4px; }
  .per-year-row .per-year-input {
    flex: 0 0 72px;
    background: var(--card);
    border: 1px solid var(--border);
    color: var(--text);
    font-family: var(--mono);
    font-size: 0.72rem;
    padding: 4px 6px;
    border-radius: var(--radius-ctl);
  }
  .per-year-row .per-year-input:focus { outline: none; border-color: var(--gold); box-shadow: 0 0 0 3px var(--gold-glow); }
  .per-year-row .yr-unit { font-family: var(--mono); font-size: 0.7rem; color: var(--text-dim); }
  .business-card .sale-field { margin-top: 0; padding-top: 0; border-top: none; }
  .business-card .equity-reconcile { margin-top: 16px; }
  /* R2-10: in two columns each card's head is too narrow for the flex Business-name input,
     so long names ("Meridian Design Co") clipped — most visibly at 1024. Drop to one full-width
     card below 1280px, which gives the name room at every width the audit checks. */
  @media (max-width: 1280px) {
    .business-list { grid-template-columns: 1fr; }
  }
  @media (max-width: 520px) {
    .biz-card-grid { grid-template-columns: 1fr; }
  }

  /* Fields */
  .field { margin-bottom: 20px; }
  .field-label {
    font-family: var(--mono);
    font-size: 0.66rem;
    font-weight: 400;
    color: var(--text-dim);
    margin-bottom: 8px;
  }
  /* Provenance under a SINGULAR assumption — what the outside world says the number usually is,
     so a default can be judged instead of just accepted. 0.66rem/--text-dim is the floor set by
     80df32d; do not go dimmer or smaller.
     Deliberately NOT used inside .holding-row/.debt-row: those rows are `align-items: end` grids
     (style.css ~2152), so an always-visible line under one .field lifts that input off the row's
     baseline. That is the same hazard .price-fetch-note avoids by collapsing when :empty — a
     provenance line never collapses, so per-row rate fields carry their sources in the
     .field-label title= instead. */
  .field-src {
    font-family: var(--mono);
    font-size: 0.66rem;
    line-height: 1.45;
    color: var(--text-dim);
    max-width: 78ch;
  }
  .field-src:not(:empty) { margin-top: 6px; }

  /* Auto-Social-Security toggle. appearance:none lets an 18px checkbox carry pseudo-elements: a
     drawn check for state, and a transparent negative-inset ::after that grows the HIT box to
     44x44 without enlarging the visible control (the expander mobile-check explicitly credits).
     Check colour is --ink so it contrasts on --gold in both themes (light sage / dark aurora). */
  .ss-est-check {
    appearance: none; -webkit-appearance: none; position: relative;
    width: 18px; height: 18px; flex: none; margin: 0; cursor: pointer;
    border: 1px solid var(--border); border-radius: 3px; background: var(--ink);
  }
  .ss-est-check:checked { background: var(--gold); border-color: var(--gold); }
  .ss-est-check:checked::before {
    content: ''; position: absolute; left: 5px; top: 1px; width: 5px; height: 10px;
    border: solid var(--ink); border-width: 0 2px 2px 0; transform: rotate(45deg);
  }
  .ss-est-check:focus-visible { outline: 2px solid var(--gold); outline-offset: 2px; }
  .ss-est-check::after { content: ''; position: absolute; top: -13px; left: -13px; right: -13px; bottom: -13px; }
  /* Formatted-money fields are type=text (they carry grouped digits like "2,000,000"), so they
     must be styled alongside type=number or they fall back to the browser default — a white box
     with black text, which is exactly what the Businesses "Current Annual Revenue" field was
     rendering in dark theme. .holding-row/.debt-row text inputs were already covered further
     down; .biz-card-grid had no rule at all. */
  .field input[type=number],
  .field input[type=text] {
    width: 100%;
    background: var(--card);
    color: var(--text);
    border: 1px solid var(--border);
    border-radius: var(--radius-ctl);
    padding: 10px 13px;
    font-family: var(--mono);
    font-size: 0.88rem;
    font-weight: 400;
    font-variant-numeric: tabular-nums;
    outline: none;
    transition: border-color 0.2s, box-shadow 0.2s;
  }
  .field input[type=number]:focus,
  .field input[type=text]:focus {
    border-color: var(--gold);
    box-shadow: 0 0 0 3px var(--gold-glow);
  }
  .field input[type=number]::-webkit-inner-spin-button { opacity: 0; }

  /* Soft "implausible value" warning (audit R-24): a persistent --gold ring applied by markWarn(el)
     when a rate/percent field exceeds a plausibility threshold (e.g. APY > 15%, growth > 25%). Unlike
     .input-invalid it KEEPS the entered value and does NOT auto-clear — it stays until the value
     returns to range. Placed before .input-invalid so a hard-invalid ruby ring wins on the rare tie.
     Theme-scoped --gold / --gold-glow keep it correct in both dark and light. */
  .input-warn,
  .input-warn:focus,
  .input-warn:focus-visible {
    border-color: var(--gold) !important;
    box-shadow: 0 0 0 2px var(--gold-glow), 0 0 0 1px var(--gold) !important;
    outline-color: var(--gold) !important;
    transition: box-shadow 0.15s, border-color 0.15s;
  }

  /* Invalid-input feedback (audit R-09/R-15): a brief --ruby ring applied by markInvalid(el),
     cleared on the next valid input or after ~1.5s. !important overrides the gold :focus /
     :focus-visible styling so the flag shows even while focused; the box-shadow ring is the
     dependable affordance for borderless inputs (e.g. the expense cells). Theme-scoped
     --ruby / --ruby-dim keep it correct in both dark and light. */
  .input-invalid,
  .input-invalid:focus,
  .input-invalid:focus-visible {
    border-color: var(--ruby) !important;
    box-shadow: 0 0 0 2px var(--ruby-dim), 0 0 0 1px var(--ruby) !important;
    outline-color: var(--ruby) !important;
    transition: box-shadow 0.15s, border-color 0.15s;
  }

  .growth-toggle {
    display: flex;
    gap: 0;
    margin-bottom: 18px;
    border: 1px solid var(--border);
    border-radius: var(--radius-ctl);
    overflow: hidden;
  }
  .growth-toggle button {
    flex: 1;
    padding: 8px 12px;
    font-family: var(--mono);
    font-size: 0.66rem;
    font-weight: 500;
    color: var(--text-dim);
    background: var(--card);
    border: none;
    cursor: pointer;
    transition: background 0.15s, color 0.15s;
  }
  .growth-toggle button:first-child { border-right: 1px solid var(--border); }
  .growth-toggle button.active { background: var(--marker); color: var(--marker-ink); }
  .growth-toggle button:hover:not(.active) { color: var(--text-secondary); }

  .per-year-fields {
    display: none;
    gap: 6px;
    flex-direction: column;
    margin-bottom: 20px;
  }
  .per-year-fields.visible { display: flex; }
  .per-year-row {
    display: flex;
    align-items: center;
    gap: 10px;
  }
  .per-year-row .yr-label {
    font-family: var(--mono);
    font-size: 0.66rem;
    font-weight: 400;
    color: var(--text-dim);
    min-width: 36px;
  }
  .per-year-row input[type=range] { flex: 1; }
  .per-year-row .yr-val {
    font-family: var(--mono);
    font-size: 0.76rem;
    font-weight: 600;
    font-variant-numeric: tabular-nums;
    color: var(--gold);
    min-width: 38px;
    text-align: right;
  }

  /* Slider */
  .slider-field { margin-bottom: 20px; }
  .slider-row {
    display: flex;
    justify-content: space-between;
    align-items: center;
    margin-bottom: 10px;
  }
  .slider-row .field-label { margin-bottom: 0; }
  .slider-val {
    font-family: var(--mono);
    font-size: 0.94rem;
    font-weight: 600;
    font-variant-numeric: tabular-nums;
    color: var(--gold);
    min-width: 56px;
    text-align: right;
  }
  /* R2-04: brand gold (#8f7300) as slider-value text fails AA in light theme
     (4.28:1 on card, 3.64:1 on the exit-strip surface). The darker gold-dim token
     clears AA on every light surface (6.39:1 card, 5.44:1 surface, 5.91:1 ink)
     while staying recognizably gold. Dark theme keeps the bright brand gold. */
  [data-theme="light"] .slider-val { color: var(--gold-dim); }
  [data-theme="light"] .biz-val-sale .biz-val-num { color: var(--gold-dim); }

  /* Range — square marker thumb */
  input[type=range] {
    -webkit-appearance: none;
    appearance: none;
    width: 100%;
    height: 2px;
    background: var(--border);
    border-radius: var(--radius-ctl);
    outline: none;
    cursor: pointer;
  }
  input[type=range]::-webkit-slider-thumb {
    -webkit-appearance: none;
    width: 13px;
    height: 13px;
    border-radius: var(--radius-ctl);
    background: var(--marker);
    border: 2px solid var(--ink);
    box-shadow: 0 0 0 1px var(--gold-dim);
    cursor: pointer;
    transition: transform 0.12s;
  }
  input[type=range]::-webkit-slider-thumb:hover { transform: scale(1.18) rotate(45deg); }
  input[type=range]::-moz-range-thumb {
    width: 13px; height: 13px; border-radius: var(--radius-ctl);
    background: var(--marker); border: 2px solid var(--ink);
    box-shadow: 0 0 0 1px var(--gold-dim); cursor: pointer;
  }

  /* Exit year strip */
  .exit-strip {
    display: flex;
    align-items: center;
    gap: 20px;
    padding: 16px 30px;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius-ctl);
  }
  .exit-strip .field-label { margin-bottom: 0; white-space: nowrap; }
  .exit-strip input[type=range] { flex: 1; }
  /* Two labelled sliders plus the What-If button need ~485px laid out in a row, which scrolled
     the whole Plan page sideways on a phone. Let the row wrap, and drop the divider/auto-margin
     that only make sense while the controls sit on one line. */
  @media (max-width: 560px) {
    .exit-strip { flex-wrap: wrap; gap: 10px 14px; padding: 14px 16px; }
    .exit-strip input[type=range] { flex: 1 1 100%; }
    .exit-strip .field-label { margin-left: 0 !important; padding-left: 0 !important; border-left: none !important; }
    .exit-strip .whatif-enter { margin-left: 0 !important; flex: 1 1 100%; }
  }
  .exit-strip .slider-val { font-size: 1.05rem; }

  /* Data gate — hides all financial data until a JSON file is connected/approved at load */
  body.gated .side-nav,
  body.gated .wrapper,
  body.gated .sticky-bar,
  body.gated .whatif-bar,
  body.gated .sample-bar { visibility: hidden; }
  #_dataGate button { font-family: var(--sans); }

  /* Global keyboard focus indicator (mouse clicks don't trigger :focus-visible) */
  a:focus-visible,
  button:focus-visible,
  input:focus-visible,
  select:focus-visible,
  [tabindex]:focus-visible,
  .tab-btn:focus-visible,
  .kpi-info:focus-visible,
  .theme-toggle:focus-visible {
    outline: 2px solid var(--gold);
    outline-offset: 2px;
    border-radius: var(--radius-ctl);
  }

  /* Financial Independence card */
  .fi-card {
    background: var(--card);
    border: 1px solid var(--border);
    border-radius: var(--radius-card);
    padding: 24px 30px;
    margin-bottom: 12px;
  }
  .fi-card-head {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 16px;
    margin-bottom: 18px;
    flex-wrap: wrap;
  }
  .fi-card-title {
    font-family: var(--sans);
    font-size: 0.66rem;
    font-weight: 700;
    color: var(--text);
    padding-left: 12px;
    border-left: 3px solid var(--emerald);
  }
  .fi-bar-track { height: 10px; background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius-ctl); overflow: hidden; }
  .fi-bar-fill { height: 100%; border-radius: var(--radius-ctl); transition: width 0.4s ease; }
  .fi-stats { display: grid; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); gap: 22px; margin-top: 20px; }
  .fi-stat-label { font-family: var(--mono); font-size: 0.66rem; color: var(--text-dim); margin-bottom: 6px; }
  .fi-stat-val {
    font-family: var(--serif);
    font-size: 1.35rem;
    font-weight: 750;
    font-stretch: 116%;
    font-variant-numeric: tabular-nums;
    font-feature-settings: "tnum";
    letter-spacing: -0.01em;
  }
  .fi-note { font-family: var(--mono); font-size: 0.66rem; color: var(--text-dim); margin-top: 16px; line-height: 1.55; }

  /* Coach insights (Phase E) */
  .coach-row { display: flex; align-items: flex-start; gap: 10px; padding: 10px 0; border-top: 1px solid var(--border); }
  .coach-row:first-child { border-top: none; }
  .coach-dot { flex: 0 0 auto; width: 8px; height: 8px; border-radius: var(--radius-ctl); margin-top: 6px; }
  .coach-icon { flex: 0 0 auto; font-size: 0.95rem; line-height: 1.3; }
  .coach-text { flex: 1 1 auto; min-width: 0; }
  .coach-title { font-family: var(--sans); font-size: 0.8rem; font-weight: 650; color: var(--text); }
  .coach-detail { font-family: var(--mono); font-size: 0.66rem; color: var(--text-dim); line-height: 1.55; margin-top: 2px; }
  .coach-action {
    flex: 0 0 auto; align-self: center; background: transparent; border: 1px solid var(--border);
    color: var(--text-secondary); font-family: var(--mono); font-size: 0.66rem; font-weight: 500;
    padding: 5px 10px; border-radius: var(--radius-ctl); cursor: pointer; white-space: nowrap;
    transition: background 0.15s, border-color 0.15s, color 0.15s;
  }
  .coach-action:hover { background: var(--marker); border-color: var(--marker); color: var(--marker-ink); }
  .coach-math {
    font-family: var(--mono); font-size: 0.66rem; color: var(--text-secondary);
    margin-top: 6px; padding: 6px 9px; background: var(--surface); border-left: 2px solid var(--border);
    line-height: 1.5;
  }
  .coach-viewall { align-self: flex-start; margin-top: 4px; }
  /* "Try it" levers — a wrapped row of gold-accented action buttons under the insight text.
     Same base .coach-action metrics/hover (gold marker fill + dark ink) as the jump/connect
     buttons; only the resting accent and natural-case label differ (matching the What-If
     starter chips). Sit inside .coach-text so they flow below the detail and wrap cleanly. */
  .coach-tryit-row { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 8px; }
  .coach-tryit {
    align-self: flex-start; text-transform: none; letter-spacing: 0.02em;
    border-color: rgba(212, 160, 60, 0.45); color: var(--gold);
  }
  /* "Compare →" — the same metrics as its Try sibling with the gold accent deliberately left off,
     so it inherits the plain .coach-action border and secondary text colour. Try is the primary
     move (see the change, decide, keep it); Compare is the second thought (park it and look at it
     later), and the resting accent is the only thing that needs to say so. */
  .coach-compare { align-self: flex-start; text-transform: none; letter-spacing: 0.02em; }
  .coach-group-label {
    font-family: var(--mono); font-size: 0.66rem; font-weight: 600; color: var(--text-dim); padding: 16px 0 4px; margin-top: 6px;
    border-top: 1px solid var(--border);
  }
  #coachPageBody .coach-group-label:first-child { border-top: none; margin-top: 0; padding-top: 2px; }
  @media (max-width: 640px) {
    .coach-action:not(.coach-viewall) { display: none; }
    /* Keep the "Try it" levers — the flagship Coach action — visible on mobile; they wrap in
       their own row, so they don't crowd the insight the way the end-of-row jump button would.
       Equal specificity to the hide rule above, later in source, so this wins. */
    .coach-action.coach-tryit { display: inline-block; }
    /* Compare stays too. It is half of a pair — a lever you can try is a lever you can park —
       and showing one without the other below 640px reads as a bug, not as a smaller design.
       .coach-tryit-row wraps, and "Compare →" is short, so on a 375px screen it drops onto its
       own line rather than squeezing the Try label. */
    .coach-action.coach-compare { display: inline-block; }
  }

  /* What-If quick starters — a wrapped row of one-click experiment buttons (btn-export variants;
     natural-case labels so the longer preset names read cleanly). */
  .starter-row { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; }
  .whatif-starter { text-transform: none; letter-spacing: 0.02em; }

  /* Retirement timeline band (N3) */
  .retire-timeline { display: flex; height: 34px; border: 1px solid var(--border); overflow: hidden; }
  .rt-seg { display: flex; align-items: center; justify-content: center; min-width: 0; overflow: hidden; }
  .rt-seg span {
    font-family: var(--mono); font-size: 0.66rem; font-weight: 600; white-space: nowrap; padding: 0 6px;
  }
  .rt-accum { background: var(--emerald-dim); color: var(--emerald); border-right: 2px solid var(--emerald); }
  .rt-draw  { background: var(--gold-glow); color: var(--gold); }
  .rt-under { background: var(--ruby-dim); color: var(--ruby); border-left: 2px solid var(--ruby); }
  .rt-axis { position: relative; height: 20px; margin-top: 6px; }
  .rt-axis span {
    position: absolute; top: 0; font-family: var(--mono); font-size: 0.66rem;
    color: var(--text-dim); letter-spacing: 0.04em; white-space: nowrap;
  }
  .rt-axis .rt-start { left: 0; }
  .rt-axis .rt-end { right: 0; }
  .rt-axis .rt-retire { transform: translateX(-50%); color: var(--gold); }
  .goal-input {
    background: var(--card);
    border: 1px solid var(--border);
    color: var(--text);
    font-family: var(--mono);
    font-size: 0.78rem;
    font-variant-numeric: tabular-nums;
    padding: 6px 10px;
    border-radius: var(--radius-ctl);
    width: 130px;
  }
  .goal-input:focus { outline: 1px solid var(--gold); }

  /* Help guide modal */
  #helpModal {
    position: fixed;
    inset: 0;
    z-index: 99990;
    background: rgba(0,0,0,0.62);
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 24px;
  }
  .help-box {
    background: var(--card);
    border: 1px solid var(--text);
    border-radius: var(--radius-card);
    width: 100%;
    max-width: 760px;
    max-height: 86vh;
    display: flex;
    flex-direction: column;
    box-shadow: var(--shadow-hard), 0 24px 70px rgba(0,0,0,0.4);
  }
  .help-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 20px 28px 14px;
    border-bottom: 1px solid var(--border);
  }
  .help-title {
    font-family: var(--serif);
    font-size: 1.3rem;
    font-weight: 800;
    letter-spacing: -0.01em;
    color: var(--text);
  }
  .help-title span { color: var(--gold); }
  .help-close {
    background: none;
    border: 1px solid var(--border);
    color: var(--text-secondary);
    font-size: 1.15rem;
    line-height: 1;
    padding: 4px 11px;
    border-radius: var(--radius-ctl);
    cursor: pointer;
    transition: background 0.15s, color 0.15s, border-color 0.15s;
  }
  .help-close:hover { background: var(--ruby); border-color: var(--ruby); color: #fff; }
  .help-toc {
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
    padding: 14px 28px;
    border-bottom: 1px solid var(--border-subtle);
  }
  .help-toc a {
    font-family: var(--mono);
    font-size: 0.66rem;
    font-weight: 500;
    color: var(--text-secondary);
    text-decoration: none;
    border: 1px solid var(--border);
    border-radius: var(--radius-ctl);
    padding: 4px 11px;
    transition: background 0.15s, color 0.15s, border-color 0.15s;
  }
  .help-toc a:hover { background: var(--marker); color: var(--marker-ink); border-color: var(--marker); }
  .help-body {
    overflow-y: auto;
    padding: 10px 28px 28px;
    font-family: var(--sans);
    font-size: 0.88rem;
    line-height: 1.65;
    color: var(--text-secondary);
  }
  .help-body section { padding-top: 18px; scroll-margin-top: 12px; }
  .help-body section + section { border-top: 1px solid var(--border-subtle); margin-top: 18px; }
  .help-body h3 {
    font-family: var(--serif);
    font-size: 0.95rem;
    font-weight: 750;
    font-stretch: 114%;
    color: var(--text);
    margin: 0 0 8px;
    padding-left: 12px;
    border-left: 3px solid var(--marker);
  }
  .help-body p { margin: 0 0 10px; }
  .help-body ul { margin: 0 0 10px; padding-left: 20px; }
  .help-body li { margin-bottom: 6px; }
  .help-body strong { color: var(--text); font-weight: 650; }
  .help-body .help-tip {
    font-family: var(--mono);
    font-size: 0.68rem;
    color: var(--text-dim);
    background: var(--surface);
    border: 1px solid var(--border-subtle);
    border-left: 3px solid var(--gold-dim);
    border-radius: var(--radius-ctl);
    padding: 10px 14px;
  }
  .help-body section:target h3,
  .help-body section.help-hilite h3 { border-left-color: var(--emerald); }

  /* Context help FAB — opens the guide at the active tab's section */
  #tabHelpFab {
    position: fixed;
    right: 22px;
    bottom: 22px;
    z-index: 9990;
    width: 40px;
    height: 40px;
    border-radius: var(--radius-ctl);
    background: var(--card);
    border: 1px solid var(--text);
    color: var(--text);
    font-family: var(--serif);
    font-size: 1.05rem;
    font-weight: 800;
    cursor: pointer;
    box-shadow: var(--shadow-hard-sm);
    transition: background 0.15s, color 0.15s, transform 0.15s, box-shadow 0.15s;
  }
  #tabHelpFab:hover {
    background: var(--gold);
    color: #fff;
    border-color: var(--gold);
    transform: translateY(-2px);
    box-shadow: var(--shadow-hard-sm);
  }
  body.gated #tabHelpFab { display: none; }
  /* R2-18: on phones the fixed FAB at the bottom-RIGHT sat over KPI cards' info icons (which
     live at each card's bottom-right), so a tap meant for a "More info" icon hit the FAB
     instead (elementFromPoint resolved to it). Move it to the bottom-LEFT — where no info icon
     ever sits — and shrink its footprint, so it can no longer intercept those taps. */
  @media (max-width: 560px) {
    #tabHelpFab {
      right: auto;
      left: 14px;
      bottom: 14px;
      width: 34px;
      height: 34px;
      font-size: 0.92rem;
    }
  }

  /* Scenario card attention pulse (after "View" jump) */
  @keyframes scenarioPulse {
    0% { border-color: var(--blue); box-shadow: 0 0 0 3px var(--blue-dim); }
    100% { border-color: var(--border); box-shadow: none; }
  }
  .scenario-hilite { animation: scenarioPulse 2s ease-out; }

  /* Derived (AUTO) payment rows — read-only mirrors of asset loans & standalone debts, shown in
     the Expenses list and the Debts page. Not stored; rendered from the source of truth each time.
     A distinct class (not .holding-row) keeps them out of the input/drag/focus selectors. Grid
     columns are set inline per context. Theme-aware via vars. */
  .derived-row {
    display: grid;
    gap: 12px;
    align-items: center;
    background: var(--surface);
    border: 1px dashed var(--border);
    border-radius: var(--radius-ctl);
    opacity: 0.75;
  }
  .derived-row .derived-name {
    display: flex; align-items: center; flex-wrap: wrap; gap: 6px;
    font-family: var(--mono); font-size: 0.82rem; color: var(--text-secondary);
  }
  .derived-row .derived-amt {
    font-family: var(--mono); font-size: 0.85rem; font-weight: 500;
    text-align: right; font-variant-numeric: tabular-nums; color: var(--ruby);
  }
  .derived-lock { color: var(--text-dim); font-size: 0.7rem; line-height: 1; }
  .derived-src {
    font-family: var(--mono); font-size: 0.66rem; color: var(--text-dim);
    text-decoration: none; border-bottom: 1px dotted var(--text-dim);
  }
  .derived-src:hover { color: var(--gold); border-color: var(--gold); }
  .derived-auto-chip {
    font-family: var(--mono); font-size: 0.66rem; font-weight: 700; letter-spacing: 0.08em;
    color: var(--gold); background: var(--gold-glow); border: 1px solid var(--gold-dim);
    border-radius: var(--radius-ctl); padding: 2px 6px; white-space: nowrap;
  }
  /* A derived AUTO row inside the Expenses list now borrows the EXPENSE GRID (see _derivedRowHtml)
     so its cells land under the header labels instead of everything being appended to the name.
     These rules are two classes deep, so they beat .expense-line's single-class defaults on
     specificity regardless of source order: the row keeps its dashed, dimmed, un-clickable
     treatment while inheriting the .vl-cols-expense track template. */
  .derived-row.expense-line {
    border: 1px dashed var(--border);
    padding: 9px 16px;
    margin-bottom: 4px;
    cursor: default;
    opacity: 0.75;
  }
  .derived-row.expense-line:hover,
  .derived-row.expense-line:focus-visible { border-color: var(--border); }
  .derived-row.expense-line .expense-line-name { color: var(--text-secondary); }
  /* Matches .drag-slot exactly (14px) so the lock sits in the same track width as a real row's
     drag handle — otherwise every derived row's text would start ~2px left of its neighbours. */
  .derived-row.expense-line .derived-lock { width: 14px; text-align: center; }
  .derived-row.expense-line .derived-auto-chip { justify-self: end; }
  /* The quiet qualifier under the name ("loan payment" / "payment") — what used to be glued to
     the name with an em dash. flex-basis:100% puts it on its own line ALWAYS (the same trick
     .view-line-main .price-fetch-note uses). Left to wrap naturally it sat inline on the short
     names and dropped below on the long ones, so the AUTO rows in one list came out at two
     different heights. Deterministic beats compact here. */
  .derived-kind {
    flex-basis: 100%;
    font-family: var(--mono); font-size: 0.66rem; color: var(--text-dim);
    letter-spacing: 0.04em; white-space: nowrap;
  }
  @media (max-width: 900px) {
    /* Spans both wrapped rows, exactly like the EDIT chip it stands in for. */
    .derived-row.expense-line.vl-cols .derived-auto-chip { grid-row: 1 / span 2; }
    /* Wrapped mode places items automatically, and because the name cell carries an explicit
       grid-row it gets placed FIRST — into the content-sized `auto` track meant for the drag
       cell. Measured at 375px that made the derived row's first track 151px and starved the
       wrapped cells run (category + source link) to LITERALLY 0px: the two columns this whole
       change exists to expose would have been invisible on a phone. Placing the four cells
       explicitly puts the lock back in the narrow track and gives name + cells the 1fr one. */
    .derived-row.expense-line.vl-cols .derived-lock { grid-column: 1; grid-row: 1; }
    .derived-row.expense-line.vl-cols .expense-line-main { grid-column: 2; grid-row: 1; }
    .derived-row.expense-line.vl-cols .expense-line-amt { grid-column: 3; grid-row: 1 / span 2; }
    .derived-row.expense-line .derived-kind { white-space: normal; }
  }
  @media (max-width: 560px) {
    /* Three tracks at this width and the EDIT chip is dropped from real rows; the AUTO chip goes
       the same way. The lock glyph and the dashed border still say the row is derived. */
    .derived-row.expense-line .derived-auto-chip { display: none; }
    /* The base .derived-row.expense-line padding above is two classes deep, so it would otherwise
       beat the single-class ≤560 step-down and leave every derived amount 4px left of the real
       ones — a visibly broken money column. Restate it at the same specificity. */
    .derived-row.expense-line { padding: 9px 12px; }
  }

  /* Dim nudge on a manual "Debt Pmt" mirror row — payments now come from the source automatically. */
  .debtpmt-hint {
    display: inline-block; margin-top: 4px;
    font-family: var(--mono); font-size: 0.66rem; line-height: 1.3;
    color: var(--gold); background: var(--gold-glow);
    border: 1px solid var(--gold-dim); border-radius: var(--radius-ctl); padding: 2px 6px;
  }
  /* Task B: brief gold flash so a newly-added expense is easy to spot at the bottom of the list. */
  @keyframes expenseNewFlash {
    0% { box-shadow: 0 0 0 2px var(--gold); }
    100% { box-shadow: 0 0 0 2px transparent; }
  }
  .holding-row.expense-row-new { animation: expenseNewFlash 1.5s ease-out; }

  /* Expenses sort/filter toolbar — a quiet strip above the list. Deliberately lighter than the
     editor fields (this narrows the view, it doesn't change the plan). Wraps freely; the
     separator pushes sort controls to the right edge on wide viewports and collapses to a plain
     wrap point on narrow ones. */
  .expense-toolbar {
    display: flex; flex-wrap: wrap; align-items: center; gap: 8px;
    padding: 0 0 12px; margin-bottom: 12px;
    border-bottom: 1px solid var(--border-subtle);
  }
  .expense-toolbar-sep { flex: 1 1 auto; min-width: 0; }
  .expense-toolbar input[type=text],
  .expense-toolbar select {
    background: var(--card); color: var(--text);
    border: 1px solid var(--border); border-radius: var(--radius-ctl);
    font-family: var(--mono); font-size: 0.72rem;
    padding: 6px 10px; outline: none;
    transition: border-color 0.15s;
    -webkit-appearance: none; appearance: none;
  }
  .expense-toolbar input[type=text] { min-width: 190px; flex: 0 1 240px; }
  .expense-toolbar input[type=text]::placeholder { color: var(--text-dim); }
  .expense-toolbar select {
    padding-right: 26px;
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6'%3E%3Cpath d='M0 0l5 6 5-6z' fill='%23807d72'/%3E%3C/svg%3E");
    background-repeat: no-repeat; background-position: right 8px center;
  }
  .expense-toolbar input[type=text]:focus,
  .expense-toolbar select:focus { border-color: var(--gold); box-shadow: 0 0 0 3px var(--gold-glow); }
  #expFilterNote {
    font-family: var(--mono); font-size: 0.66rem; color: var(--gold);
    letter-spacing: 0.03em; white-space: nowrap;
  }
  #expSortDir { min-width: 30px; line-height: 1; }
  /* Inert stand-in for the drag handle when reordering is paused (sorted/filtered view) — keeps
     every row's first grid track the same width so nothing shifts between views. */
  .drag-slot { width: 14px; height: 16px; }  /* = the 10px handle SVG + its 2px side padding */
  /* Grand total under the list — the one number that nets nothing out (expenses + all loan and
     debt payments). Heavier than a category subtotal, and rule-topped so it reads as a sum. */
  .expense-total-row {
    display: grid;
    grid-template-columns: auto minmax(0, 1fr) auto;
    align-items: baseline;
    gap: 12px;
    margin-top: 4px;
    padding: 14px 16px 0;
    border-top: 2px solid var(--border);
  }
  .expense-total-label {
    font-family: var(--mono); font-size: 0.72rem; font-weight: 600; color: var(--text);
  }
  .expense-total-sub {
    font-family: var(--mono); font-size: 0.66rem; color: var(--text-dim); letter-spacing: 0.03em;
  }
  .expense-total-val {
    font-family: var(--mono); font-size: 1.05rem; font-weight: 600;
    font-variant-numeric: tabular-nums; color: var(--ruby);
    text-align: right; white-space: nowrap; letter-spacing: -0.01em;
  }
  @media (max-width: 700px) {
    .expense-total-row { grid-template-columns: 1fr auto; }
    .expense-total-sub { grid-column: 1 / -1; order: 3; }
  }
  .expense-empty {
    display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
    font-family: var(--mono); font-size: 0.75rem; color: var(--text-dim);
    padding: 18px 0;
  }

  /* ===== READ-FIRST DISPLAY LINES (.view-line) =====
     Every editor list — stocks, crypto, savings, real assets, income, debts, expenses, life
     events, business equity — renders its rows as scannable data lines (drag cell · name +
     dim single-line meta · value hard-right · EDIT chip); the dense field editor only appears
     for the ONE row being edited (editingRow / editingExpenseId in app.js). The fixed 52px
     trailing track matches the derived rows' AUTO chip so every amount across manual and
     derived rows right-aligns into one shared column. Every rule below is shared with the
     original .expense-line names, which shipped first and stay valid selectors (app.js and
     scripts/layout-check.js both target `.expense-line`). Theme-aware via vars, zero radius. */
  .view-line,
  .expense-line {
    display: grid;
    grid-template-columns: auto minmax(0, 1fr) auto 52px;
    gap: 12px;
    align-items: center;
    padding: 9px 16px;
    margin-bottom: 4px;
    background: var(--surface);
    border: 1px solid var(--border-subtle);
    border-radius: var(--radius-ctl);
    cursor: pointer;
    transition: border-color 0.15s;
  }
  .view-line:hover, .view-line:focus-visible,
  .expense-line:hover, .expense-line:focus-visible { border-color: var(--gold-dim); outline: none; }
  .view-line:hover .view-line-edit,
  .view-line:focus-visible .view-line-edit,
  .expense-line:hover .expense-line-edit,
  .expense-line:focus-visible .expense-line-edit { color: var(--gold); border-color: var(--gold-dim); }
  .view-line.is-dim,
  .expense-line.is-debtpmt { opacity: 0.6; }
  /* HOUSEHOLD VIEWS: a row excluded from the currently-active lens still shows (the ledger
     never hides data, it edits it) but reads as quieter — same idiom as .is-debtpmt above.
     Applies across every list, including the ones with their own row shell (.derived-row,
     .business-card, .event-row's view line already goes through .view-line). */
  .view-line.is-offview,
  .expense-line.is-offview,
  .derived-row.is-offview,
  .business-card.is-offview { opacity: 0.55; }
  .view-line-main,
  .expense-line-main { min-width: 0; display: flex; align-items: baseline; column-gap: 10px; row-gap: 2px; flex-wrap: wrap; }
  .view-line-name,
  .expense-line-name {
    font-family: var(--mono); font-size: 0.84rem; color: var(--text);
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 100%;
  }
  .view-line-unnamed,
  .expense-line-unnamed { color: var(--text-dim); font-style: italic; }
  /* The due day used to be folded into the name cell as a quiet "due Nth" hint, because the grid
     was fixed at 4 data columns. It is now a real 5th column so it can be scanned down and sorted
     on (the toolbar's Due day sort was already there but had no column to line up with). Three
     things move in step and will shear the grid if they ever drift: VIEW_COLS.expense, the cells
     array in the expense display line, and the track list below — plus the derived AUTO row, which
     must emit the same number of placeholder cells. */
  /* 0.62rem text-dim was unreadable on the light theme (owner: "not easy to see at all") —
     the meta line is DATA, not decoration. Secondary color + a step up in size. */
  .view-line-meta,
  .expense-line-meta { font-family: var(--mono); font-size: 0.7rem; color: var(--text-secondary); letter-spacing: 0.02em; }

  /* === COLUMNS MODE (.vl-cols) — homogeneous lists render their fields as aligned grid
     columns under a labeled header strip; scanning a column beats parsing per-row prose.
     The template classes are shared by the row AND the .vl-head strip so labels sit exactly
     over their columns. .vl-cells is display:contents on desktop (its spans become real grid
     tracks of the row) and collapses to a wrapping flex meta-line ≤900px. Templates:
     drag | name | <cells…> | value | edit-chip. */
  /* Rebalanced when the price-age suffix landed in POSITION. Measured at 1440, the widest content
     each track must hold is name 77px, position ~200 (a long quantity, a price and "2d ago"),
     account 250, adding 54, growth 56 — while ADDING was sitting on 124px of slack and ACCOUNT was
     72px SHORT, clipping "American Century · Traditional IRA" before this change ever existed.
     Dropping the unused width lets the same flexible space cover all of it, so the age fits AND a
     pre-existing truncation is gone. The floors keep a PRICE from ever being clipped at narrow
     widths — a half-rendered price is a misread figure, where a clipped account name is only an
     inconvenience (same rule as .vl-cols-savingsSp). The floors are the MEASURED maxima at 1024
     (stocks 156px, crypto 193px for ETH's six-decimal quantity) plus a little slack, not round
     numbers: a floor guessed at 150px still clipped every row, which is a floor that does nothing
     except look like it works. */
  .vl-cols-stock   { grid-template-columns: auto minmax(0, 0.55fr) minmax(162px, 1.25fr) minmax(0, 1.5fr) minmax(56px, 0.6fr) minmax(58px, 0.4fr) minmax(110px, auto) 52px; }
  /* Crypto's position track already had 67px spare at 1440, so only the price floor is added. */
  .vl-cols-crypto  { grid-template-columns: auto minmax(0, 0.8fr) minmax(196px, 1.2fr) 1fr 0.6fr minmax(110px, auto) 52px; }
  .vl-cols-savings { grid-template-columns: auto minmax(0, 1fr) 1.2fr 0.5fr 1.2fr minmax(110px, auto) 52px; }
  /* Savings, plus the SPENDABLE column — drawn only when some account actually reports one
     (VIEW_COLS.savingsSp). Same eight-track shape as .vl-cols-stock so the two lists still read
     as one system.
     The new column is paid for out of ADDING and APY, never out of the name. Measured at 1440:
     the widest cell each track must hold is name 248px, bank·type 169, APY 37, adding 59,
     spendable 82 (the "SPENDABLE" header itself, wider than any figure under it), value 63 — so
     adding/APY were carrying ~170px of slack while a 29-character account name was clipped to
     161px. Account names are the one column a reader scans to find a row, so they get the space.

     The floors matter at 1024, where eight columns genuinely do not fit and something must give.
     A clipped NAME or bank label is merely inconvenient — "Hand-entered Emer…" still identifies
     its row. A clipped NUMBER is a misreading: at 0.3fr the APY track collapsed to 24px and
     rendered "0.01%" as "0.0…", which reads as a different rate. So every numeric track carries a
     min that fits its widest content (APY 40px, adding 60px for "+$500/mo", spendable 82px for the
     "SPENDABLE" header, which is wider than any figure beneath it) and the prose tracks absorb the
     shortfall. Above ~1200px every floor is slack and the fr ratios alone decide, so the 1440
     layout is untouched. The existing .vl-cols-stock clips its prose columns at 1024 the same way. */
  .vl-cols-savingsSp { grid-template-columns: auto minmax(0, 1.4fr) minmax(0, 1fr) minmax(40px, 0.3fr) minmax(60px, 0.8fr) minmax(82px, 0.8fr) minmax(110px, auto) 52px; }
  .vl-cols-asset   { grid-template-columns: auto minmax(0, 0.9fr) 0.8fr 0.7fr 1.1fr 0.7fr minmax(110px, auto) 52px; }
  .vl-cols-income  { grid-template-columns: auto minmax(0, 1fr) 1fr 0.7fr 0.8fr 1fr minmax(110px, auto) 52px; }
  .vl-cols-debt    { grid-template-columns: auto minmax(0, 1fr) 0.8fr 0.9fr 1.2fr minmax(110px, auto) 52px; }
  .vl-cols-expense { grid-template-columns: auto minmax(0, 1fr) 0.85fr 0.75fr 0.85fr 0.45fr 0.5fr minmax(96px, auto) 52px; }
  .vl-cells { display: contents; }
  .vl-cell {
    font-family: var(--mono); font-size: 0.72rem; color: var(--text-secondary);
    min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
    font-variant-numeric: tabular-nums;
  }
  .vl-cell b { font-weight: 600; color: var(--text); }
  .vl-cell-empty { color: var(--text-dim); }
  /* Price age, sitting immediately after the price it describes. Quiet on purpose: this is metadata
     about a figure, not a figure, so it must never compete with the price for the eye — dim, a size
     down, and not tabular (it is words, and tabular-nums would space "2h ago" oddly). Goes gold only
     past a day, where it stops being metadata and becomes a caveat worth acting on. */
  .vl-cell .px-age {
    margin-left: 7px; font-size: 0.66rem; color: var(--text-dim);
    font-variant-numeric: normal; cursor: help;
  }
  .vl-cell .px-age.is-stale { color: var(--gold); }
  .vl-head {
    display: grid; gap: 12px; align-items: end;
    padding: 0 16px 6px; margin-bottom: 6px;
    /* Transparent side borders, not decoration: rows carry a 1px border all round, so without
       these the strip's content box is 2px wider and every fr track resolves ~0.5px off its
       column. box-sizing is border-box globally, so this costs no width. */
    border: 1px solid transparent;
    border-bottom: 1px solid var(--border-subtle);
  }
  .vl-head span {
    font-family: var(--mono); font-size: 0.66rem; font-weight: 700; color: var(--text-dim);
    min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  }
  /* The leading spacer stands in for each row's drag cell and must be exactly as wide (.drag-slot
     = 14px). Left empty it collapsed the `auto` track to 0, which shifted every label ~14px left
     of the column it names and let the error compound across the remaining tracks — measured at
     1440 the strip resolved 0/194.2/174.8/… against the rows' 14/190.4/171.3/…. Pre-existing on
     all seven columns-mode lists; it only became obvious once the derived AUTO rows joined the
     grid and there was something to compare against.
     `:empty` is load-bearing, not tidiness: the Accounts page reuses .vl-head for strips whose
     FIRST cell is a real label ("Account", "Position") over rows that have no drag cell. Without
     the guard this rule would crush those labels to 14px. */
  .vl-head span:first-child:empty { width: 14px; }
  .vl-head .h-val { text-align: right; }
  @media (max-width: 900px) {
    .vl-head { display: none; }
    /* Rows fall back to the standard line shape; the cells become a wrapping meta line
       under the name, spanning the name column. */
    .view-line.vl-cols, .expense-line.vl-cols { grid-template-columns: auto minmax(0, 1fr) minmax(90px, auto) 52px !important; }
    .view-line.vl-cols .vl-cells, .expense-line.vl-cols .vl-cells {
      display: flex; flex-wrap: wrap; column-gap: 12px; row-gap: 2px;
      grid-column: 2; grid-row: 2;
    }
    /* Inline-wrapped cells don't need placeholders — an em-dash only earns its keep when it
       is holding a column open. */
    .view-line.vl-cols .vl-cell-empty, .expense-line.vl-cols .vl-cell-empty { display: none; }
    /* The COLUMN is as load-bearing as the row here. With only a row given, the name cell (which
       has a definite row, so grid places it first) landed in track 1 — the content-sized `auto`
       track meant for the drag cell — and the drag cell was pushed into the minmax(0,1fr) track
       the wrapped cells run needs. A long name then took the whole row and the cells collapsed:
       measured at 375, "Health Insurance" left its category/pay/growth run 13.9px wide, rendering
       as "H… A… +…". Naming the columns puts the drag cell back in track 1 (it auto-places into
       the one free cell) and gives name + cells the flexible track they were always meant to
       share. The trailing chip is display:none at ≤560, where there is no track 4. */
    .view-line.vl-cols .view-line-main, .expense-line.vl-cols .expense-line-main { grid-column: 2; grid-row: 1; }
    .view-line.vl-cols .view-line-amt, .expense-line.vl-cols .expense-line-amt { grid-column: 3; grid-row: 1 / span 2; }
    .view-line.vl-cols .view-line-edit, .expense-line.vl-cols .expense-line-edit { grid-column: 4; grid-row: 1 / span 2; }
  }
  @media (max-width: 560px) {
    .view-line.vl-cols, .expense-line.vl-cols { grid-template-columns: auto minmax(0, 1fr) minmax(80px, auto) !important; }
  }
  .view-line-amt,
  .expense-line-amt {
    font-family: var(--mono); font-size: 0.88rem; font-weight: 500;
    font-variant-numeric: tabular-nums; text-align: right; color: var(--ruby); white-space: nowrap;
  }
  /* Value colorization by semantic: what you own reads emerald (or gold for business equity /
     orange for real-asset market value), what you owe or spend reads ruby, and a plain
     quantity stays in body text. .expense-line-amt keeps its ruby default above. */
  .view-line-amt.tone-emerald { color: var(--emerald); }
  .view-line-amt.tone-gold    { color: var(--gold); }
  .view-line-amt.tone-orange  { color: var(--orange); }
  .view-line-amt.tone-ruby    { color: var(--ruby); }
  .view-line-amt.tone-neutral { color: var(--text); }
  .view-line-per,
  .expense-line-per { font-size: 0.66rem; color: var(--text-dim); font-weight: 400; }
  /* Second line under the value — the derived companion figure (asset equity, annualized
     income, a business's at-sale take). Dim and smaller so the primary number still leads. */
  .view-line-sub {
    display: block;
    font-family: var(--mono); font-size: 0.66rem; font-weight: 400;
    color: var(--text-dim); letter-spacing: 0.02em; margin-top: 2px;
  }
  /* Live price-fetch microcopy carried onto the display line (stocks/crypto). It is a block
     element inside the baseline flex row, so it wraps to its own line when non-empty and
     collapses to nothing when idle. */
  .view-line-main .price-fetch-note { flex-basis: 100%; }
  .view-line-main .price-fetch-note:empty { display: none; }
  .view-line-main .price-fetch-note:not(:empty) { margin-top: 0; }
  .view-line-edit,
  .expense-line-edit {
    justify-self: end;
    font-family: var(--mono); font-size: 0.66rem; font-weight: 700; color: var(--text-dim);
    border: 1px solid var(--border); border-radius: var(--radius-ctl); padding: 2px 6px;
    transition: color 0.15s, border-color 0.15s;
  }
  @media (max-width: 560px) {
    .view-line, .expense-line { grid-template-columns: auto minmax(0, 1fr) auto; padding: 9px 12px; }
    .view-line-edit, .expense-line-edit { display: none; }
  }

  /* Group subtotal header above a run of display lines (Real Assets "with loans", Income
     "active now", …). Same typography as the Expenses category heads, factored out so every
     page's grouping reads identically. */
  .view-group { margin-bottom: 16px; }
  .view-group-head {
    display: flex; justify-content: space-between; align-items: center; gap: 12px;
    padding: 8px 0; border-bottom: 1px solid var(--border-subtle); margin-bottom: 8px;
  }
  .view-group-label {
    font-family: var(--mono); font-size: 0.7rem; font-weight: 500; color: var(--text);
  }
  .view-group-label.is-dim { color: var(--text-dim); }
  .view-group-label .view-group-note { font-weight: 400; text-transform: none; letter-spacing: 0; }
  .view-group-total {
    font-family: var(--mono); font-size: 0.8rem; font-weight: 500;
    font-variant-numeric: tabular-nums; white-space: nowrap;
  }

  /* Life event rows */
  .event-row {
    display: grid;
    grid-template-columns: 1.5fr 1fr 0.9fr 1fr auto auto auto;
    gap: 10px;
    align-items: center;
    padding: 9px 0;
    border-bottom: 1px solid var(--border-subtle);
  }
  /* Labelled cells: the label sits above its control, and the row's align-items:center then
     centres the whole stack, keeping the action buttons aligned with the group. */
  .event-row .field { display: flex; flex-direction: column; gap: 3px; margin-bottom: 0; }
  .event-row input, .event-row select {
    background: var(--card);
    border: 1px solid var(--border);
    color: var(--text);
    font-family: var(--mono);
    font-size: 0.76rem;
    padding: 6px 8px;
    border-radius: var(--radius-ctl);
    width: 100%;
  }
  .event-row input:focus, .event-row select:focus { outline: 1px solid var(--gold); }
  /* HOUSEHOLD VIEWS: Done/Duplicate/Remove + the owner select share ONE of the row's three
     trailing `auto` grid tracks (grid-template-columns above is unchanged) instead of each
     button keeping its own — a 4th trailing item would either need a 4th track (forbidden; see
     the .owner-select comment near .holding-header) or spill into the wrong column. */
  .event-row-actions { display: flex; align-items: center; gap: 4px; }
  @media (max-width: 640px) { .event-row { grid-template-columns: 1fr 1fr; } }
  /* Life Events empty state: collapse the card to the head row + a slim inline note */
  .life-events-empty-note {
    display: none;
    font-family: var(--mono);
    font-size: 0.68rem;
    color: var(--text-dim);
    letter-spacing: 0.02em;
    margin-right: auto;
  }
  #lifeEventsCard.is-empty .life-events-empty-note { display: inline; }
  #lifeEventsCard.is-empty .fi-card-head { margin-bottom: 0; }

  /* Scenario outcome table (Phase F) — inherits base table styles inside .table-scroll */
  .scenario-table { width: 100%; border-collapse: collapse; }
  .scenario-table td { vertical-align: middle; }
  .scenario-table .btn-dup, .scenario-table .btn-remove { white-space: nowrap; }
  .scenario-current td { background: var(--emerald-dim); color: var(--text); }
  .scenario-current td:first-child { border-left: 3px solid var(--emerald); }
  .scenario-meta { font-family: var(--mono); font-size: 0.66rem; color: var(--text-dim); margin-top: 2px; font-weight: 400; }
  .scenario-swatch { display: inline-block; width: 9px; height: 9px; border-radius: var(--radius-ctl); margin-right: 8px; vertical-align: baseline; }
  .scenario-table tbody tr[data-scenario-id]:hover td { background: var(--row-hover); }
  .scenario-detail-row td { background: var(--surface); padding: 14px 18px; white-space: normal; }
  .scen-diff-head { font-family: var(--mono); font-size: 0.66rem; font-weight: 600; color: var(--text-dim); margin-bottom: 10px; }
  .scen-diff-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: 6px 20px; }
  .scen-diff-item { font-family: var(--mono); font-size: 0.68rem; color: var(--text-secondary); line-height: 1.5; }
  .scen-diff-area { display: inline-block; min-width: 62px; color: var(--gold); font-size: 0.66rem; margin-right: 6px; }

  /* Nominal / Today's-$ segmented toggle on the net worth chart */
  .nw-mode-toggle { display: inline-flex; border: 1px solid var(--border); border-radius: var(--radius-ctl); overflow: hidden; }
  .nw-mode-toggle button {
    background: transparent;
    border: none;
    color: var(--text-dim);
    font-family: var(--mono);
    font-size: 0.66rem;
    font-weight: 500;
    padding: 5px 12px;
    cursor: pointer;
    transition: background 0.15s, color 0.15s;
  }
  .nw-mode-toggle button.active { background: var(--marker); color: var(--marker-ink); }
  .nw-mode-toggle button:not(.active):hover { color: var(--text-secondary); }
  /* SHOW RANGE chip: single-button toggle sharing the mode-toggle look; .active state set in JS */
  #mcBandToggle button.active { background: var(--emerald); color: var(--marker-ink); }

  /* === PLAN CONFIDENCE (Monte Carlo) === */
  .mc-intro-text { font-family: var(--mono); font-size: 0.68rem; color: var(--text-dim); line-height: 1.6; margin: 0 0 16px; max-width: 62ch; }
  .mc-run-btn { font-size: 0.66rem; padding: 9px 18px; }
  .mc-headline { display: flex; align-items: baseline; gap: 14px; flex-wrap: wrap; margin-bottom: 4px; }
  .mc-pct {
    font-family: var(--serif);
    font-size: 3.4rem;
    font-weight: 800;
    font-stretch: 116%;
    line-height: 1;
    letter-spacing: -0.02em;
    font-variant-numeric: tabular-nums;
    font-feature-settings: "tnum";
    color: var(--emerald);
  }
  .mc-pct.warn { color: var(--gold); }
  .mc-pct.bad { color: var(--ruby); }
  .mc-pct-sub { font-family: var(--mono); font-size: 0.74rem; color: var(--text-secondary); letter-spacing: 0.02em; }
  /* Historical stress tests. Same card vocabulary as .mc-* deliberately — they answer neighbouring
     questions and should read as one family. Buttons carry a 44px floor because they are real
     controls and mobile-check measures them. */
  .stress-row { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 16px; }
  .stress-btn { display: inline-flex; flex-direction: column; align-items: flex-start; gap: 2px; min-height: 44px; text-align: left; }
  .stress-btn span { font-family: var(--mono); font-size: 0.6rem; font-weight: 400; color: var(--text-dim); }
  .stress-btn.active { border-color: var(--gold); color: var(--gold); }
  .stress-btn.active span { color: var(--gold); }
  .stress-verdict {
    font-family: var(--sans); font-size: 0.92rem; font-weight: 600; line-height: 1.5;
    padding: 12px 14px; border-radius: var(--radius-ctl); border-left: 3px solid var(--border);
    background: var(--card-hover); max-width: 70ch;
  }
  .stress-verdict.good { border-left-color: var(--emerald); }
  .stress-verdict.warn { border-left-color: var(--gold); }
  .stress-verdict.bad { border-left-color: var(--ruby); }
  .stress-stats { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 22px; margin-top: 18px; }
  .stress-l { font-family: var(--mono); font-size: 0.66rem; color: var(--text-dim); margin-bottom: 6px; }
  .stress-v { font-family: var(--serif); font-size: 1.2rem; font-weight: 700; font-variant-numeric: tabular-nums; }
  .stress-s { font-family: var(--mono); font-size: 0.62rem; color: var(--text-secondary); margin-top: 3px; line-height: 1.4; }

  /* Big questions (question-first solvers). Deliberately borrows .stress-verdict / .stress-stats /
     .stress-l / .stress-v / .stress-s rather than restyling those shapes: this card answers a
     neighbouring question to Stress tests and Plan Confidence, and a second visual vocabulary for
     an identical verdict-plus-figures layout would only make the three read as unrelated. Only what
     genuinely differs lives here — a question button that has to wrap a whole sentence, and an
     action button that must not look like stress's scenario selector, since clicking it changes the
     sandbox instead of choosing what to display. Both carry a 44px floor because they are real
     controls and mobile-check measures them. */
  .bq-row { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 16px; }
  .bq-btn { min-height: 44px; text-align: left; white-space: normal; max-width: 34ch; }
  .bq-btn.active { border-color: var(--gold); color: var(--gold); }
  .bq-tryit { min-height: 44px; margin-top: 18px; border-color: var(--gold); color: var(--gold); }

  .mc-substats { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 22px; margin-top: 20px; }
  .mc-substat-label { font-family: var(--mono); font-size: 0.66rem; color: var(--text-dim); margin-bottom: 6px; }
  .mc-substat-val {
    font-family: var(--serif);
    font-size: 1.2rem;
    font-weight: 700;
    font-variant-numeric: tabular-nums;
    font-feature-settings: "tnum";
    color: var(--text);
    letter-spacing: -0.01em;
  }
  .mc-substat-sub { font-family: var(--mono); font-size: 0.66rem; color: var(--text-dim); margin-top: 3px; }
  .mc-actions { margin-top: 22px; display: flex; align-items: center; gap: 14px; flex-wrap: wrap; }
  .mc-stale-badge {
    display: inline-flex; align-items: center; gap: 6px;
    font-family: var(--mono); font-size: 0.66rem; font-weight: 600;
    color: var(--gold); border: 1px solid var(--gold);
    padding: 4px 10px; margin-bottom: 16px;
  }
  .mc-compare {
    font-family: var(--mono); font-size: 0.82rem; color: var(--text-secondary);
    margin-top: 16px; letter-spacing: 0.02em;
  }
  .mc-compare .mc-arrow { color: var(--text-dim); margin: 0 8px; }
  .mc-compare .mc-up { color: var(--emerald); }
  .mc-compare .mc-down { color: var(--ruby); }
  .mc-progress-track { height: 10px; background: var(--surface); border: 1px solid var(--border); overflow: hidden; margin-bottom: 12px; max-width: 420px; }
  .mc-progress-fill { height: 100%; background: var(--emerald); width: 0%; transition: width 0.12s linear; }
  .mc-progress-text { font-family: var(--mono); font-size: 0.66rem; color: var(--text-secondary); letter-spacing: 0.04em; }
  /* Info icon on the card head — same behavior as the KPI (i) affordance, both themes via vars */
  .mc-info-icon {
    position: relative;
    display: inline-flex; align-items: center; justify-content: center;
    width: 15px; height: 15px;
    font-family: var(--mono); font-size: 0.66rem; font-style: italic; font-weight: 700;
    color: var(--text-dim); border: 1px solid var(--border); border-radius: 50%;
    cursor: help; flex: 0 0 auto;
  }
  .mc-info-tip {
    display: none;
    position: absolute; top: calc(100% + 8px); right: 0; z-index: 50;
    width: min(320px, 78vw);
    background: var(--card); border: 1px solid var(--border);
    padding: 12px 14px;
    font-family: var(--mono); font-size: 0.66rem; font-style: normal; font-weight: 400;
    color: var(--text-secondary); line-height: 1.6; letter-spacing: 0.01em;
    text-align: left; text-transform: none;
    box-shadow: 0 6px 20px rgba(0,0,0,0.28);
  }
  .mc-info-icon:hover .mc-info-tip,
  .mc-info-icon:focus .mc-info-tip,
  .mc-info-icon:focus-within .mc-info-tip { display: block; }

  /* Toast (undo, etc.) */
  #_toast {
    position: fixed;
    left: 50%;
    bottom: 28px;
    transform: translateX(-50%) translateY(16px);
    z-index: 100001;
    display: flex;
    align-items: center;
    gap: 18px;
    padding: 12px 20px;
    background: var(--card);
    border: 1px solid var(--text);
    border-radius: var(--radius-card);
    box-shadow: var(--shadow-hard);
    font-family: var(--sans);
    font-size: 0.83rem;
    color: var(--text);
    opacity: 0;
    visibility: hidden;
    transition: opacity 0.2s ease, transform 0.2s ease;
    max-width: 90vw;
  }
  #_toast.show { opacity: 1; visibility: visible; transform: translateX(-50%) translateY(0); }
  ._toast-action {
    background: var(--marker);
    border: none;
    color: var(--marker-ink);
    font-weight: 700;
    font-size: 0.72rem;
    font-family: var(--sans);
    cursor: pointer;
    padding: 4px 10px;
    white-space: nowrap;
  }
  ._toast-action:hover { filter: brightness(1.08); }

  /* Duplicate row button (pairs with .btn-remove) */
  .btn-dup {
    background: none;
    border: 1px solid var(--border);
    color: var(--text-secondary);
    font-family: var(--mono);
    font-size: 0.66rem;
    font-weight: 500;
    padding: 5px 10px;
    border-radius: var(--radius-ctl);
    cursor: pointer;
    transition: background 0.15s, color 0.15s, border-color 0.15s;
  }
  .btn-dup:hover { background: var(--marker); color: var(--marker-ink); border-color: var(--marker); }

  /* "Done" — collapses an open expense editor back to its display line (pairs with .btn-dup). */
  .btn-done {
    background: none;
    border: 1px solid var(--gold-dim);
    color: var(--gold);
    font-family: var(--mono);
    font-size: 0.66rem;
    font-weight: 700;
    padding: 5px 10px;
    border-radius: var(--radius-ctl);
    cursor: pointer;
    transition: background 0.15s, color 0.15s, border-color 0.15s;
  }
  .btn-done:hover { background: var(--gold); border-color: var(--gold); color: var(--marker-ink); }

  /* Respect reduced-motion: disable decorative loops and entrance animations */
  @media (prefers-reduced-motion: reduce) {
    *, *::before, *::after {
      animation-duration: 0.001ms !important;
      animation-iteration-count: 1 !important;
      transition-duration: 0.001ms !important;
      scroll-behavior: auto !important;
    }
  }

  /* Business sale controls */
  .sale-field { margin-top: 18px; padding-top: 16px; border-top: 1px solid var(--border); }
  .sale-year-select {
    width: 100%;
    background: var(--card);
    border: 1px solid var(--border);
    color: var(--text);
    font-family: var(--mono);
    font-size: 0.8rem;
    padding: 7px 10px;
    border-radius: var(--radius-ctl);
    margin-top: 4px;
  }
  .sale-year-select:focus { outline: 1px solid var(--gold); }
  .sale-year-select.is-selling { border-color: var(--gold); color: var(--gold); }
  /* R2-04: the selected-year text on a "selling" select was brand gold on the ink
     surface — 3.95:1, below AA. Gold-dim clears it (5.91:1); the gold border stays
     (borders aren't a WCAG text criterion). Dark theme unchanged. */
  [data-theme="light"] .sale-year-select.is-selling { color: var(--gold-dim); }

  /* Sale Structure row (exit-structure v1): full-width block under the sale fields holding the
     proceeds-payout select, the Revenue/EBITDA basis toggle (+ margin), and the optional multiple
     range. Not a .field, so the .field input[type=number] { width:100% } floor doesn't stretch the
     small boxes — each control sets its own width (all well above the 40px layout-check floor). */
  .biz-sale-structure {
    grid-column: 1 / -1;
    margin-top: 4px;
    padding-top: 16px;
    border-top: 1px solid var(--border-subtle);
  }
  .biz-ss-row {
    display: flex;
    flex-wrap: wrap;
    gap: 14px 22px;
    align-items: flex-end;
    margin-top: 10px;
  }
  .biz-ss-cell { display: flex; flex-direction: column; gap: 6px; }
  .biz-ss-sublabel {
    font-family: var(--mono);
    font-size: 0.66rem;
    font-weight: 400;
    color: var(--text-dim);
  }
  .biz-payout-select {
    min-width: 120px;
    background: var(--card);
    border: 1px solid var(--border);
    color: var(--text);
    font-family: var(--mono);
    font-size: 0.8rem;
    padding: 7px 10px;
    border-radius: var(--radius-ctl);
  }
  .biz-payout-select:focus { outline: 1px solid var(--gold); }
  .biz-basis-toggle { margin-bottom: 0; min-width: 170px; }
  .biz-margin-input {
    width: 88px;
    background: var(--card);
    border: 1px solid var(--border);
    color: var(--text);
    font-family: var(--mono);
    font-size: 0.8rem;
    padding: 7px 10px;
    border-radius: var(--radius-ctl);
  }
  .biz-margin-input:focus { outline: none; border-color: var(--gold); box-shadow: 0 0 0 3px var(--gold-glow); }
  .biz-range-inputs { display: flex; align-items: center; gap: 8px; }
  .biz-range-input {
    width: 74px;
    background: var(--card);
    border: 1px solid var(--border);
    color: var(--text);
    font-family: var(--mono);
    font-size: 0.8rem;
    padding: 7px 8px;
    border-radius: var(--radius-ctl);
  }
  .biz-range-input:focus { outline: none; border-color: var(--gold); box-shadow: 0 0 0 3px var(--gold-glow); }
  .biz-range-input::placeholder { color: var(--text-dim); font-size: 0.66rem; letter-spacing: 0.03em; }
  .biz-range-dash { color: var(--text-dim); font-family: var(--mono); }
  .biz-range-echo { color: var(--text-dim); }

  /* Exit-range note under the Net Worth Projection chart (exit-structure v1) — a numeric note,
     not a chart band (Monte Carlo owns bands). Theme-aware tokens work in both light and dark. */
  .nw-exit-range {
    font-family: var(--mono);
    font-size: 0.72rem;
    color: var(--text-secondary);
    margin-top: 10px;
    padding: 8px 12px;
    background: var(--surface);
    border-left: 2px solid var(--gold);
    letter-spacing: 0.01em;
  }
  [data-theme="light"] .nw-exit-range { border-left-color: var(--gold-dim); }

  .sale-settings {
    padding: 20px 30px;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius-card);
    margin-bottom: 36px;
  }
  .sale-settings-title {
    font-family: var(--sans);
    font-size: 0.66rem;
    font-weight: 700;
    color: var(--text);
    margin-bottom: 16px;
    padding-left: 12px;
    border-left: 3px solid var(--marker);
  }
  .sale-settings-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
    gap: 28px;
  }
  .sale-settings-note {
    margin-top: 16px;
    font-family: var(--mono);
    font-size: 0.66rem;
    color: var(--text-dim);
    line-height: 1.55;
  }
  @media (max-width: 640px) { .sale-settings-grid { grid-template-columns: 1fr; } }

  /* === ASSET SECTION PANELS === */
  .section-panel {
    display: grid;
    gap: 0;
    overflow: hidden;
  }
  .section-panel.cols-3 { grid-template-columns: 1fr 1fr 1fr; }
  .section-panel.cols-2 { grid-template-columns: 1fr 1fr; }
  .section-panel.cols-1 { grid-template-columns: 1fr; }

  .asset-col {
    padding: 26px 30px;
    background: var(--card);
    position: relative;
  }
  .asset-col:not(:last-child) { border-right: 1px solid var(--border); }
  .asset-col .col-header {
    display: flex;
    align-items: baseline;
    gap: 12px;
    margin-bottom: 22px;
    padding-bottom: 14px;
    border-bottom: 1px solid var(--border-subtle);
  }
  .asset-col .col-name {
    font-family: var(--serif);
    font-size: 1.02rem;
    font-weight: 750;
    font-stretch: 116%;
    color: var(--text);
  }
  .asset-col .col-tag {
    font-family: var(--mono);
    font-size: 0.66rem;
    font-weight: 500;
    letter-spacing: 0.06em;
    padding: 3px 8px;
    border-radius: var(--radius-ctl);
  }
  .tag-emerald { color: var(--emerald); background: var(--emerald-dim); border: 1px solid var(--emerald); }
  .tag-blue { color: var(--blue); background: var(--blue-dim); border: 1px solid var(--blue); }
  .tag-purple { color: var(--purple); background: var(--purple-dim); border: 1px solid var(--purple); }
  .tag-orange { color: var(--orange); background: var(--orange-dim); border: 1px solid var(--orange); }
  .tag-gold { color: var(--gold); background: var(--gold-glow); border: 1px solid var(--gold); }
  .tag-ruby { color: var(--ruby); background: var(--ruby-dim); border: 1px solid var(--ruby); }

  /* Light theme only: the category pills put an accent colour on its own pale tint, which lands
     at 3.9-4.4:1 — under AA for text this size. Darkening the TEXT alone keeps each pill's border
     and fill (and so its identity) while making the label legible; darkening the accent tokens
     themselves would shift every figure, chart series and badge that shares them. Dark theme
     already clears AA on all four and is left untouched. */
  [data-theme="light"] .tag-emerald,
  [data-theme="light"] .derived-auto-chip { color: color-mix(in srgb, var(--emerald) 72%, #000); }
  [data-theme="light"] .tag-ruby   { color: color-mix(in srgb, var(--ruby) 78%, #000); }
  [data-theme="light"] .tag-gold   { color: color-mix(in srgb, var(--gold) 62%, #000); }
  [data-theme="light"] .tag-purple { color: color-mix(in srgb, var(--purple) 84%, #000); }

  /* Debt rows */
  .debt-list { display: flex; flex-direction: column; gap: 10px; }
  .debt-row {
    display: grid;
    grid-template-columns: 1.5fr 1fr 0.8fr 0.8fr auto;
    gap: 12px;
    align-items: end;
    padding: 0 18px 14px;
    background: var(--surface);
    border: 1px solid var(--border-subtle);
    border-radius: var(--radius-ctl);
  }
  .debt-row .field { margin-bottom: 0; }
  .debt-row input[type=text] {
    width: 100%;
    background: var(--card);
    color: var(--text);
    border: 1px solid var(--border);
    border-radius: var(--radius-ctl);
    padding: 9px 12px;
    font-family: var(--mono);
    font-size: 0.83rem;
    outline: none;
    transition: border-color 0.2s;
  }
  .debt-row input[type=text]:focus { border-color: var(--gold); box-shadow: 0 0 0 3px var(--gold-glow); }
  .btn-remove {
    background: none;
    border: 1px solid var(--border);
    color: var(--ruby);
    font-family: var(--mono);
    font-size: 0.66rem;
    font-weight: 500;
    padding: 8px 13px;
    border-radius: var(--radius-ctl);
    cursor: pointer;
    transition: background 0.15s, color 0.15s, border-color 0.15s;
    white-space: nowrap;
  }
  .btn-remove:hover { background: var(--ruby); border-color: var(--ruby); color: #fff; }
  .btn-remove:active { transform: translateY(1px); }

  /* Drag handle */
  .drag-handle {
    cursor: grab;
    color: var(--text-dim);
    font-size: 1rem;
    line-height: 1;
    padding: 4px 2px;
    user-select: none;
    display: flex;
    align-items: center;
    align-self: center;
    opacity: 0.5;
    transition: opacity 0.2s, color 0.2s;
  }
  .drag-handle:hover { opacity: 1; color: var(--text-secondary); }
  .drag-handle:active { cursor: grabbing; }
  .holding-row.dragging, .debt-row.dragging { opacity: 0.4; }
  .holding-row.drag-over, .debt-row.drag-over { border-color: var(--gold); box-shadow: 0 0 0 3px var(--gold-glow); }

  /* Primary add button — solid marker */
  .btn-add {
    background: var(--gold);
    border: 1px solid var(--gold);
    /* Dark ink on the light aurora accent (11.8:1). The light theme's sage accent is dark enough
       to carry white, and keeps it in the override below. */
    color: var(--ink);
    font-family: var(--sans);
    font-size: 0.78rem;
    font-weight: 600;
    padding: 10px 20px;
    border-radius: var(--radius-ctl);
    cursor: pointer;
    transition: transform 0.12s, box-shadow 0.12s, filter 0.12s;
    margin-top: 8px;
  }
  [data-theme="light"] .btn-add { color: #fff; }
  .btn-add:hover { transform: translateY(-1px); box-shadow: var(--shadow-hard-sm); filter: brightness(1.06); }
  .btn-add:active { transform: translateY(0); box-shadow: none; }

  /* Holding rows (stocks/crypto) */
  .holding-row {
    display: grid;
    grid-template-columns: 1.4fr 0.8fr 0.7fr 1fr 1fr auto;
    gap: 12px;
    align-items: end;
    padding: 0 18px 14px;
    background: var(--surface);
    border: 1px solid var(--border-subtle);
    border-radius: var(--radius-ctl);
  }
  .holding-header {
    grid-column: 1 / -1;
    display: flex; align-items: center; gap: 8px;
    padding: 10px 0;
    border-bottom: 1px solid var(--border-subtle);
    margin-bottom: 2px;
  }
  .holding-header-name {
    font-family: var(--sans); font-size: 0.76rem; font-weight: 750;
    color: var(--text);
  }
  .holding-header-value {
    margin-left: auto;
    font-family: var(--mono); font-size: 0.95rem; font-weight: 600;
    font-variant-numeric: tabular-nums;
    color: var(--gold); letter-spacing: -0.01em;
  }
  .holding-header .btn-remove { flex-shrink: 0; }
  .holding-header .drag-handle { margin: 0; }
  .holding-header-detail {
    font-family: var(--mono); font-size: 0.68rem; font-weight: 500;
    color: var(--text-dim); margin-right: 4px;
  }
  .holding-header-value--debt { color: var(--ruby); }
  /* HOUSEHOLD VIEWS: the owner select rides in the editor's HEADER flex row, beside
     Done/Duplicate/Remove — never as its own grid column, because every editor's
     grid-template-columns string above is load-bearing (layout-check asserts it) and a squeezed
     extra column would break it. `.holding-row select` (below) sizes it for a `.field`'s grid
     cell (width:100%, 12ch+ floor); that would blow up a flex header, so these two-class
     selectors (specificity beats the single-class one regardless of source order) reset it back
     to an inline, content-sized control here and in the two other header/action shells that
     carry it (businesses have no editingRow-style header, life events' row is a flat grid). */
  .holding-header .owner-select,
  .biz-card-actions .owner-select,
  .expense-row-actions .owner-select,
  .event-row-actions .owner-select {
    width: auto; min-width: 0; flex: 0 0 auto;
    background: var(--card); color: var(--text-secondary);
    border: 1px solid var(--border); border-radius: var(--radius-ctl);
    padding: 5px 22px 5px 8px;
    font-family: var(--mono); font-size: 0.68rem;
    outline: none; -webkit-appearance: none; appearance: none;
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6'%3E%3Cpath d='M0 0l5 6 5-6z' fill='%23807d72'/%3E%3C/svg%3E");
    background-repeat: no-repeat; background-position: right 8px center;
  }
  .holding-header .owner-select:focus,
  .biz-card-actions .owner-select:focus,
  .expense-row-actions .owner-select:focus,
  .event-row-actions .owner-select:focus { border-color: var(--gold); box-shadow: 0 0 0 3px var(--gold-glow); }
  .holding-fields-drag { display: none; }
  .holding-row .field { margin-bottom: 0; }
  /* R-04: per-row live-price feedback under the price cell (theme-aware via vars; zero-height when idle) */
  .price-fetch-note { font-family: var(--mono); font-size: 0.66rem; letter-spacing: 0.04em; line-height: 1.35; color: var(--text-dim); }
  .price-fetch-note:not(:empty) { margin-top: 5px; }
  .price-fetch-note.is-failed { color: var(--ruby); }
  /* Inline recovery for a transient price-service failure, so the fix is where the problem is
     reported rather than only under the sidebar's Refresh. */
  .price-retry {
    background: none; border: none; padding: 0; margin-left: 6px;
    font-family: var(--mono); font-size: 0.66rem; letter-spacing: 0.04em;
    color: var(--gold); text-decoration: underline; cursor: pointer;
  }
  .price-retry:hover { color: var(--text); }
  .holding-row input[type=text],
  .holding-row select {
    width: 100%;
    background: var(--card);
    color: var(--text);
    border: 1px solid var(--border);
    border-radius: var(--radius-ctl);
    padding: 9px 12px;
    font-family: var(--mono);
    font-size: 0.83rem;
    outline: none;
    transition: border-color 0.2s;
    -webkit-appearance: none;
    appearance: none;
  }
  .holding-row input[type=text]:focus,
  .holding-row select:focus { border-color: var(--gold); box-shadow: 0 0 0 3px var(--gold-glow); }
  .holding-row select {
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6'%3E%3Cpath d='M0 0l5 6 5-6z' fill='%23807d72'/%3E%3C/svg%3E");
    background-repeat: no-repeat;
    background-position: right 10px center;
    padding-right: 28px;
  }
  /* === SHARED EDITOR-FIELD FLOORS (R-06, extended in R2) =========================
     Single source of truth for the minimum width of every field in every editor row
     (.holding-row = stocks/crypto/savings/assets/income/expenses; .debt-row = debt).
     Each row still sets its grid-template-columns inline in app.js; these type-keyed
     floors keep any track that holds an input/select from collapsing when a sibling's
     width or the field order changes — the grid-track-starvation class of bug. box-sizing
     is border-box, so each min-width bakes in the field's own horizontal padding via
     calc(). Rows wrap below their breakpoint (further down), so the floors never force
     horizontal overflow. Widest-wins by specificity: a bare field type < base; the
     per-data-field rules (attribute selectors) outrank the base element rule. */

  /* Base floor — NOTHING collapses. Catches the drag-shifted `auto` track and any field
     added later that lacks its own rule (selects get their own, wider, base just below). */
  .holding-row input,
  .debt-row input { min-width: calc(6ch + 26px); }

  /* numeric-percent — short values like "5.25"; kept narrow so the dense rows still fit. */
  .holding-row input[data-field="annualRate"],
  .holding-row input[data-field="loanRate"],
  .holding-row input[data-field="annualReturn"],
  .holding-row input[data-field="annualRaise"],
  .holding-row input[data-field="annualGrowth"],
  .holding-row input[data-field="apy"],
  .holding-row input[data-field="saleTaxRate"],
  .debt-row input[data-field="rate"] { min-width: calc(6ch + 26px); }

  /* numeric-money — compact (unformatted) money/quantity inputs. R2-01/R2-02: `shares` and
     the Expenses `amount` had no floor and starved to 0–48px; `amount` is type-qualified so
     the *formatted* (.num-fmt) Income amount below keeps its wider 10ch floor instead. */
  .holding-row input[data-field="shares"],
  .holding-row input[data-field="price"],
  .holding-row input[type="number"][data-field="amount"],
  .holding-row input[data-field="stake"],
  .holding-row input[data-field="perYear"],
  .holding-row input[data-field="monthlyContrib"],
  .holding-row input[data-field="employerMatch"],
  .holding-row input[data-field="loanPayment"],
  .debt-row input[data-field="payment"] { min-width: calc(8ch + 26px); }

  /* formatted money (.num-fmt) — grouped digits like "1,200,000". */
  .holding-row .num-fmt,
  .debt-row .num-fmt { min-width: calc(10ch + 26px); }

  /* text symbols — short tickers/coins ("VTSAX", "BTC"). */
  .holding-row input[data-field="ticker"],
  .holding-row input[data-field="coin"] { min-width: calc(8ch + 26px); }

  /* text-name — account/asset/income/debt names ("Partner Fund", "Primary Home"). R2-10:
     the old 8ch floor clipped 12-char names; widened to 12ch. */
  .holding-row input[data-field="name"],
  .debt-row input[data-field="name"] { min-width: calc(12ch + 26px); }

  /* selects — 12ch base, with wider floors where the option text runs longer. */
  .holding-row select,
  .debt-row select { min-width: calc(12ch + 42px); }
  .holding-row select[data-field="startYear"] { min-width: calc(7ch + 42px); }
  .holding-row select[data-field="endYear"] { min-width: calc(13ch + 42px); }
  .holding-row select[data-field="category"] { min-width: calc(14ch + 34px); }
  .holding-total {
    font-family: var(--mono);
    font-size: 0.72rem;
    font-variant-numeric: tabular-nums;
    color: var(--text-secondary);
    text-align: right;
    padding: 4px 0;
  }
  .holding-total strong {
    color: var(--text);
    font-weight: 600;
  }
  .holdings-summary {
    display: flex;
    justify-content: flex-end;
    align-items: center;
    gap: 24px;
    padding: 12px 18px;
    border-top: 1px solid var(--border);
    background: var(--surface);
    border-radius: var(--radius-ctl);
  }
  .holdings-summary .hs-label {
    font-family: var(--mono);
    font-size: 0.66rem;
    font-weight: 500;
    color: var(--text-dim);
  }
  .holdings-summary .hs-val {
    font-family: var(--mono);
    font-size: 0.95rem;
    font-weight: 600;
    font-variant-numeric: tabular-nums;
  }

  /* R-06: the dense editor rows (8-11 fields) only fit on one line on wide desktops.
     Below ~1400px they wrap so every field keeps a readable width. Each row sets its column
     template inline (in app.js), which outranks a normal rule, so these responsive overrides
     need !important; narrower breakpoints are ordered last so they win on equal specificity. */
  @media (max-width: 1439px) {
    .holding-row { grid-template-columns: repeat(3, 1fr) !important; }
  }
  @media (max-width: 900px) {
    .holding-row { grid-template-columns: 1fr 1fr !important; }
  }
  @media (max-width: 560px) {
    .holding-row { grid-template-columns: 1fr !important; }
  }

  /* R2-01: the Expenses and Real-Assets rows are the densest editors — 8–9 fields plus a
     visible drag handle, and (expenses) inline Duplicate/Remove buttons. Even with the floors
     above they can't sit on one line inside the 60px-padded panel at any viewport without
     starving a track or overflowing the panel (which clipped the Amount input and the row
     buttons), so they always wrap: 3 columns on desktop, collapsing with the shared
     breakpoints on mobile. Id-scoped so they also override the rows' inline single-line
     template at ≥1440; the matching ≤900/≤560 rules preserve mobile stacking (a bare #id
     rule outranks the .holding-row breakpoints above and would otherwise freeze these rows
     at 3 columns on phones). Ordered widest→narrowest so the narrower query wins. */
  #expensesList .holding-row,
  #assetsList .holding-row { grid-template-columns: repeat(3, 1fr) !important; }
  @media (max-width: 900px) {
    #expensesList .holding-row,
    #assetsList .holding-row { grid-template-columns: 1fr 1fr !important; }
  }
  @media (max-width: 560px) {
    #expensesList .holding-row,
    #assetsList .holding-row { grid-template-columns: 1fr !important; }
  }

  /* === KPI ROW — exposed hairline grid === */
  .kpi-row {
    display: grid;
    gap: 1px;
    background: var(--border);
    border: 1px solid var(--border);
    border-radius: var(--radius-ctl);
    margin-bottom: 36px;
    overflow: visible;
    position: relative;
  }
  /* Reachable on the Accounts balance strips: a plan with loans but no cards, or investments but no
     bank accounts, yields a single tile. Stated explicitly rather than leaning on the implicit
     single-column default, so the row cannot silently change shape if the base rule gains columns. */
  .kpi-row.cols-1 { grid-template-columns: 1fr; }
  /* Fills the tail of a wrapped strip. .kpi-row paints var(--border) behind a 1px gap to draw the
     hairlines between tiles, which works perfectly while every cell is occupied — but the Accounts
     balance strips carry one tile per account, so the last row is usually part empty and that border
     colour showed through as a lighter slab beside the total. A filler is a .kpi with the card
     background and nothing in it: the hairline trick is preserved and the strip ends flush. */
  .kpi.is-filler { background: var(--card); animation: none; pointer-events: none; }

  /* --- account tiles: the NAME is the identity, so it leads ------------------------------------
     A .kpi-label is normally dim micro-caps, which is right when the label is a category ("Cards")
     and wrong when it is the answer to the tile's first question. Chase returns "CREDIT CARD" as the
     name of every card it issues, so on this strip the name plus its mask is the only thing telling
     two tiles apart — it gets heading weight, real contrast, and no uppercasing, because a name the
     owner typed ("Sapphire Reserve") should render as they typed it. */
  .kpi-label.is-acct {
    text-transform: none; letter-spacing: 0; margin-bottom: 5px;
    font-size: 0.8rem; line-height: 1.3; color: var(--text);
  }
  .kpi-acct {
    display: inline-flex; align-items: baseline; gap: 6px; flex-wrap: wrap;
    cursor: text; border-radius: var(--radius-ctl);
    border-bottom: 1px dotted transparent;   /* reserved so hover does not shift the baseline */
    transition: color 0.15s ease, border-bottom-color 0.15s ease;
  }
  .kpi-acct-name { font-weight: 700; }
  /* A disambiguator, not the name: attached but clearly secondary. */
  .kpi-acct-mask { font-weight: 400; font-size: 0.68rem; color: var(--text-dim); }
  .kpi-acct:hover, .kpi-acct:focus-visible {
    color: var(--gold); border-bottom-color: var(--gold); outline: none;
  }
  .kpi-acct:hover .kpi-acct-mask, .kpi-acct:focus-visible .kpi-acct-mask { color: var(--gold); }

  /* Sized and coloured to sit exactly where the name was, so committing a rename does not make the
     tile jump. font: inherit keeps the mono face — a bare input would fall back to the UA default. */
  .kpi-acct-input {
    width: 100%; max-width: 100%; box-sizing: border-box;
    font: inherit; font-weight: 700; color: var(--text);
    background: var(--surface); border: 1px solid var(--gold); border-radius: var(--radius-ctl);
    padding: 1px 5px; margin: -2px 0;
  }
  .kpi-acct-input:focus { outline: none; }
  .kpi-acct-input::placeholder { color: var(--text-dim); font-weight: 400; }
  .kpi-row.cols-2 { grid-template-columns: repeat(2, 1fr); }
  .kpi-row.cols-3 { grid-template-columns: repeat(3, 1fr); }
  .kpi-row.cols-5 { grid-template-columns: repeat(5, 1fr); }
  .kpi-row.cols-6 { grid-template-columns: repeat(6, 1fr); }
  /* 7 is the widest strip in the app (Expenses). It wraps to 4+3 below 1200 rather than
     squeezing seven money values into ~150px each. */
  .kpi-row.cols-7 { grid-template-columns: repeat(7, 1fr); }
  @media (max-width: 1200px) { .kpi-row.cols-7 { grid-template-columns: repeat(4, 1fr); } }
  .kpi-row.cols-4 { grid-template-columns: repeat(4, 1fr); }
  .kpi {
    background: var(--card);
    padding: 18px 20px;
    transition: background 0.2s, box-shadow 0.2s;
    position: relative;
    min-width: 0; /* let 1fr columns shrink to equal width instead of growing to fit wide values/badges */
    animation: fadeSlideUp 0.45s ease-out backwards;
  }
  .kpi:nth-child(1) { animation-delay: 0.02s; }
  .kpi:nth-child(2) { animation-delay: 0.06s; }
  .kpi:nth-child(3) { animation-delay: 0.10s; }
  .kpi:nth-child(4) { animation-delay: 0.14s; }
  .kpi:nth-child(5) { animation-delay: 0.18s; }
  .kpi:nth-child(6) { animation-delay: 0.22s; }
  .kpi:hover { background: var(--card-hover); box-shadow: inset 3px 0 0 var(--marker); }
  /* Info icon on KPI cards with data-tip — only the icon triggers the tooltip. */
  .kpi-info {
    position: absolute;
    bottom: 10px;
    right: 10px;
    width: 18px;
    height: 18px;
    border-radius: var(--radius-ctl);
    border: 1px solid var(--border);
    background: transparent;
    color: var(--text-dim);
    font-family: var(--mono);
    font-style: normal;
    font-size: 0.72rem;
    font-weight: 600;
    line-height: 16px;
    text-align: center;
    cursor: help;
    z-index: 5;
    transition: color 0.15s, border-color 0.15s, background 0.15s;
  }
  .kpi-info:hover { color: var(--marker-ink); border-color: var(--marker); background: var(--marker); }
  /* The tip lives INSIDE .kpi-info, and `position:absolute` + `z-index:5` makes that element a
     stacking context — so the tip's own z-index is resolved within it and the whole icon still
     stacks at 5. The fixed .side-nav sits at 900, so a tip wide enough to overhang the rail was
     painted underneath it. Lift the icon above the nav only while the tip is showing. */
  .kpi-info:hover, .kpi-info:focus-visible, .kpi-info:focus-within { z-index: 950; }
  .kpi-info .kpi-tip {
    position: absolute;
    bottom: calc(100% + 10px);
    right: -6px;
    background: var(--card);
    color: var(--text-secondary);
    font-family: var(--mono);
    font-style: normal;
    font-size: 0.7rem;
    font-weight: 400;
    line-height: 1.6;
    padding: 12px 16px;
    border-radius: var(--radius-ctl);
    border: 1px solid var(--text);
    white-space: normal;
    width: 280px;
    text-align: left;
    pointer-events: none;
    opacity: 0;
    visibility: hidden;
    transform: translateY(-4px);
    transition: opacity 0.15s, transform 0.15s, visibility 0.15s;
    z-index: 10000;
    box-shadow: var(--shadow-hard-sm);
  }
  .kpi-info:hover .kpi-tip,
  .kpi-info:focus-visible .kpi-tip,
  .kpi-info:focus .kpi-tip { opacity: 1; visibility: visible; transform: translateY(0); }
  /* R2-19: keyboard-dismissed state (Esc / second Enter). Same specificity as the show rules
     above but later in source, so it wins and keeps the tip hidden while the icon is still
     focused. Cleared on blur (JS), so re-focusing reopens the tip. */
  .kpi-info.tip-dismissed .kpi-tip { opacity: 0; visibility: hidden; }
  .kpi-label {
    font-family: var(--mono);
    font-size: 0.72rem;
    font-weight: 500;
    color: var(--text-dim);
    margin-bottom: 9px;
  }
  .kpi-val {
    font-family: var(--serif);
    font-size: 1.5rem;
    font-weight: 780;
    font-stretch: 118%;
    font-variant-numeric: tabular-nums;
    font-feature-settings: "tnum";
    line-height: 1;
    letter-spacing: -0.015em;
  }
  /* R-10: graduated shrink steps applied by fitKpiValues() before it falls back to an
     abbreviated value. Compound selector beats the base .kpi-val regardless of source order. */
  .kpi-val.kpi-val--tight   { font-size: 1.28rem; letter-spacing: -0.02em; }
  .kpi-val.kpi-val--tighter { font-size: 1.08rem; letter-spacing: -0.025em; }
  .kpi-delta {
    font-family: var(--mono);
    font-size: 0.76rem;
    font-weight: 400;
    font-variant-numeric: tabular-nums;
    margin-top: 7px;
    color: var(--text-secondary);
  }
  /* R-05: 1d/7d/30d net-worth delta chips beneath the Net Worth KPI value. Reuse the good/bad
     chip color vocabulary (emerald/ruby, matching .kpi-status); a flat (zero) move reads as dim
     text. Hover title carries the per-class attribution. Theme-aware via existing CSS vars. */
  .nw-delta-chips { display: flex; flex-wrap: wrap; gap: 5px; margin-top: 8px; }
  .nw-chip {
    font-family: var(--mono);
    font-size: 0.66rem;
    font-weight: 600;
    letter-spacing: 0.02em;
    font-variant-numeric: tabular-nums;
    padding: 2px 6px;
    border: 1px solid var(--border);
    background: var(--card-hover);
    color: var(--text-secondary);
    white-space: nowrap;
    cursor: default;
  }
  .nw-chip.good { background: var(--emerald-dim); color: var(--emerald); border-color: var(--emerald); }
  .nw-chip.bad  { background: var(--ruby-dim);    color: var(--ruby);    border-color: var(--ruby); }
  /* Light theme: brand emerald text on the tinted chip clears AA only in the darker variant
     (mirrors the .kpi-status.good treatment). */
  [data-theme="light"] .nw-chip.good { color: #0c6b41; }
  .kpi-status {
    position: absolute;
    top: 10px;
    right: 11px;
    font-family: var(--mono);
    font-size: 0.7rem;
    font-weight: 600;
    padding: 1px 5px;
    border-radius: var(--radius-ctl);
    letter-spacing: 0.03em;
    text-transform: uppercase;
    display: inline-flex;
    align-items: center;
    gap: 3px;
    line-height: 1.15;
  }
  .kpi-status::before {
    content: '';
    display: inline-block;
    width: 4px;
    height: 4px;
    border-radius: var(--radius-ctl);
    background: currentColor;
  }
  /* Only the LABEL reserves room for the top-right badge; the value/delta sit on the line
     below the (short) badge band and use the full cell width — keeps wide $ values from
     colliding with percentile badges in the tight 6-column grid. */
  .kpi:has(.kpi-status) .kpi-label,
  .kpi:has(.kpi-status-stack) .kpi-label { padding-right: 84px; }
  .kpi-status.good { background: var(--emerald-dim); color: var(--emerald); border: 1px solid var(--emerald); }
  .kpi-status.warn { background: var(--gold-glow); color: var(--gold); border: 1px solid var(--gold); }
  .kpi-status.bad  { background: var(--ruby-dim); color: var(--ruby); border: 1px solid var(--ruby); }
  /* Light theme: brand emerald/gold text on the tinted chip backgrounds sits below
     4.5:1 (4.17 / 3.80 measured); darker text-only variants clear AA (5.4 / 5.2).
     Borders keep the brand colors — only text contrast is a WCAG criterion. */
  [data-theme="light"] .kpi-status.good { color: #0c6b41; }
  [data-theme="light"] .kpi-status.warn { color: #755e00; }
  /* R2-04: .bad (bottom-quartile percentile chip) was brand ruby on the ruby-dim tint at
     4.69:1 — just over AA and axe-flagged. #a5251a clears it (5.90:1); border stays ruby. */
  [data-theme="light"] .kpi-status.bad  { color: #a5251a; }
  [data-theme="light"] .nav-item.active .nav-badge { color: #755e00; }
  .kpi-status-region {
    font-size: 0.66rem;
    font-weight: 500;
    opacity: 0.72;
    margin-left: 4px;
    letter-spacing: 0.05em;
  }
  .kpi-status-stack {
    position: absolute;
    top: 10px;
    right: 11px;
    display: flex;
    flex-direction: column;
    align-items: flex-end;
    gap: 1px;
    max-width: 84px;
  }
  .kpi-status-stack .kpi-status {
    position: static;
    top: auto;
    right: auto;
  }

  /* === CHARTS === */
  .chart-grid {
    display: grid;
    gap: 1px;
    background: var(--border);
    border: 1px solid var(--border);
    border-radius: var(--radius-ctl);
    overflow: hidden;
    margin-bottom: 36px;
  }
  .chart-grid.cols-2 { grid-template-columns: 1.2fr 0.8fr; }
  .chart-grid.even { grid-template-columns: 1fr 1fr; }
  .chart-grid.cols-1 { grid-template-columns: 1fr; }
  .chart-cell {
    background: var(--card);
    padding: 22px;
    border-radius: var(--radius-card);
  }
  .chart-cell h3 {
    font-family: var(--sans);
    font-size: 0.66rem;
    font-weight: 700;
    color: var(--text-secondary);
    margin-bottom: 16px;
    padding-left: 12px;
    border-left: 3px solid var(--border);
  }
  .chart-cell canvas { width: 100% !important; }

  /* Per-page History card (Stocks/Crypto/Savings/Real Assets/Debts/Business Equity/Income/
     Expenses). A quiet, compact strip that sits directly under each page's KPI row — it reports
     what already happened, so it stays deliberately shorter and lighter than the projection
     charts below it. The fixed-height box lets the chart run with maintainAspectRatio:false
     instead of deriving a very tall height from a full-page-width aspect ratio. */
  .page-history-cell { margin-bottom: 28px; }
  .page-history-canvas { position: relative; height: 190px; }
  .page-history-cell canvas { height: 100% !important; }
  .page-history-empty { margin: 0; padding: 2px 0 0; }
  @media (max-width: 700px) { .page-history-canvas { height: 150px; } }

  /* === TABLE === */
  .data-section {
    border: 1px solid var(--border);
    border-radius: var(--radius-card);
    overflow: hidden;
    margin-bottom: 36px;
  }
  .data-section-header {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    padding: 16px 22px;
    background: var(--card);
    border-bottom: 1px solid var(--border);
  }
  .data-section-header h2 {
    font-family: var(--serif);
    font-size: 1rem;
    font-weight: 650;
    color: var(--text);
  }
  .data-section-header .section-note {
    font-family: var(--sans);
    font-size: 0.72rem;
    color: var(--text-dim);
    letter-spacing: 0.06em;
  }

  table {
    width: 100%;
    border-collapse: collapse;
    background: var(--card);
  }
  /* R2-10: the wide equity/projection tables scroll horizontally, but macOS overlay
     scrollbars stay hidden until you scroll, so the last column read as clipped with no
     affordance. Force a thin, always-present horizontal scrollbar (theme-aware) so it's
     obvious the table scrolls; styling ::-webkit-scrollbar opts out of overlay behavior. */
  .table-scroll { overflow-x: auto; scrollbar-width: thin; scrollbar-color: var(--border) transparent; }
  .table-scroll::-webkit-scrollbar { height: 8px; }
  .table-scroll::-webkit-scrollbar-thumb { background: var(--border); border-radius: var(--radius-ctl); }
  .table-scroll::-webkit-scrollbar-track { background: transparent; }
  th {
    text-align: left;
    padding: 12px 16px;
    font-family: var(--sans);
    font-size: 0.74rem;
    font-weight: 600;
    color: var(--text-dim);
    border-bottom: 1px solid var(--border);
    background: var(--surface);
    position: sticky;
    top: 0;
    white-space: nowrap;
  }
  /* R2-04: table headers (text-dim #69665b on the surface tone) sat at 4.61:1 in light
     theme — over AA but on the line and flagged by axe on the projection/equity tables.
     text-secondary is the next darker existing token and clears it with margin (5.37:1).
     Dark theme headers are unchanged. */
  [data-theme="light"] th { color: var(--text-secondary); }
  th.right, td.right { text-align: right; }
  td {
    padding: 10px 16px;
    font-family: var(--mono);
    font-size: 0.76rem;
    font-weight: 400;
    font-variant-numeric: tabular-nums;
    color: var(--text-secondary);
    border-bottom: 1px solid var(--border-subtle);
    white-space: nowrap;
  }
  tr:last-child td { border-bottom: none; }
  tr:hover td { background: var(--row-hover); }
  tr.row-exit td { background: var(--emerald-dim); color: var(--text); font-weight: 500; }
  tr.row-exit td:first-child { border-left: 3px solid var(--emerald); }
  tr.row-now td { color: var(--text); }
  tr.row-now td:first-child { border-left: 3px solid var(--marker); }
  td.val-gold { color: var(--gold); font-weight: 500; }
  td.val-green { color: var(--emerald); }
  td.val-ruby { color: var(--ruby); }
  td.val-primary { color: var(--text); font-weight: 500; }

  /* === MILESTONES === */
  .milestones-grid {
    display: grid;
    grid-template-columns: repeat(6, 1fr);
    gap: 10px;
  }
  .milestone {
    background: var(--card);
    border: 1px solid var(--border);
    border-radius: var(--radius-card);
    padding: 22px 16px;
    text-align: center;
    transition: transform 0.18s, box-shadow 0.18s, border-color 0.18s;
    position: relative;
    overflow: hidden;
  }
  .milestone::before {
    content: '';
    position: absolute;
    top: 0; left: 0; right: 0;
    height: 4px;
    background: var(--border);
    transition: background 0.25s;
  }
  .milestone.hit::before { background: var(--emerald); }
  .milestone.reached::before { background: var(--marker); }
  .milestone:hover { border-color: var(--text-dim); transform: translate(-2px, -2px); box-shadow: var(--shadow-hard-sm); }
  .milestone-amount {
    font-family: var(--serif);
    font-size: 1.3rem;
    font-weight: 780;
    font-stretch: 118%;
    font-variant-numeric: tabular-nums;
    color: var(--text);
    margin-bottom: 12px;
    line-height: 1;
    letter-spacing: -0.01em;
  }
  .milestone-year {
    font-family: var(--mono);
    font-size: 0.95rem;
    font-weight: 600;
    font-variant-numeric: tabular-nums;
    padding: 5px 12px;
    border-radius: var(--radius-ctl);
    display: inline-block;
  }
  .milestone.hit .milestone-year { color: var(--emerald); background: var(--emerald-dim); }
  .milestone.reached .milestone-year { color: var(--marker-ink); background: var(--marker); }
  .milestone.miss .milestone-year { color: var(--text-dim); background: var(--surface); }
  .milestone-note {
    font-family: var(--mono);
    font-size: 0.66rem;
    font-weight: 400;
    color: var(--text-secondary);
    margin-top: 10px;
    letter-spacing: 0.03em;
  }
  .milestone.miss .milestone-note { color: var(--text-dim); }
  /* Reached milestones collapse into one compact full-width summary line */
  .milestones-reached {
    grid-column: 1 / -1;
    font-family: var(--mono);
    font-size: 0.72rem;
    font-weight: 500;
    color: var(--text-secondary);
    letter-spacing: 0.04em;
    padding: 9px 14px;
    background: var(--surface);
    border: 1px solid var(--border);
    border-left: 3px solid var(--emerald);
    margin-bottom: 2px;
  }
  /* Hero tile — the next unreached milestone leads, larger and gold */
  .milestone.hero {
    grid-column: span 2;
    border-color: var(--gold-dim);
    background: var(--gold-glow);
  }
  .milestone.hero::before { background: var(--gold); }
  .milestone.hero .milestone-amount { font-size: 2rem; color: var(--gold); }

  /* === STICKY BAR (spans the main column, right of the rail) === */
  .sticky-bar {
    position: fixed;
    top: 0;
    left: var(--nav-w);
    right: 0;
    z-index: 1000;
    background: color-mix(in srgb, var(--ink) 96%, transparent);
    border-bottom: 1px solid var(--text);
    backdrop-filter: blur(10px);
    -webkit-backdrop-filter: blur(10px);
    transform: translateY(-100%);
    opacity: 0;
    transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1), opacity 0.3s ease;
    pointer-events: none;
  }
  .sticky-bar.visible {
    transform: translateY(0);
    opacity: 1;
    pointer-events: auto;
  }
  .sticky-bar-inner {
    max-width: 1340px;
    margin: 0 auto;
    padding: 0 36px;
  }
  .sticky-kpis {
    display: grid;
    grid-template-columns: repeat(6, 1fr);
    gap: 1px;
    background: var(--border);
    border-left: 1px solid var(--border);
    border-right: 1px solid var(--border);
  }
  .sticky-kpi {
    background: var(--card);
    padding: 12px 16px;
  }
  .sticky-kpi-label {
    font-family: var(--mono);
    font-size: 0.72rem;
    font-weight: 500;
    color: var(--text-dim);
    margin-bottom: 6px;
  }
  .sticky-kpi-val {
    font-family: var(--serif);
    font-size: 1.15rem;
    font-weight: 780;
    font-stretch: 118%;
    font-variant-numeric: tabular-nums;
    line-height: 1;
    letter-spacing: -0.01em;
  }
  .sticky-kpi-delta {
    font-family: var(--mono);
    font-size: 0.76rem;
    font-weight: 400;
    margin-top: 5px;
    color: var(--text-secondary);
  }
  @media (max-width: 900px) {
    .sticky-bar-inner { padding: 0 16px; }
    .sticky-kpis { grid-template-columns: repeat(3, 1fr); }
  }
  @media (max-width: 560px) {
    .sticky-kpis { grid-template-columns: repeat(2, 1fr); }
    .sticky-kpi { padding: 10px 12px; }
    .sticky-kpi-val { font-size: 1rem; }
  }

  /* === LIVE PRICE STATUS === */
  .price-status {
    font-family: var(--mono);
    font-size: 0.66rem;
    font-weight: 400;
    color: var(--text-dim);
    letter-spacing: 0.06em;
    display: flex;
    align-items: center;
    gap: 6px;
  }
  .price-status .dot {
    width: 6px;
    height: 6px;
    border-radius: var(--radius-ctl);
    background: var(--text-dim);
  }
  .price-status .dot.loading {
    background: var(--gold);
    animation: pulse 1s ease-in-out infinite;
  }
  .price-status .dot.done { background: var(--emerald); }
  .price-status .dot.error { background: var(--ruby); }
  @keyframes pulse { 0%,100% { opacity: 1; } 50% { opacity: 0.3; } }

  /* === FOOTER === */
  .footer {
    margin-top: 56px;
    padding-top: 20px;
    border-top: 1px solid var(--border);
    font-family: var(--sans);
    font-size: 0.75rem;
    color: var(--text-dim);
    display: flex;
    justify-content: space-between;
  }

  /* === ANIMATIONS === */
  @keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }
  /* fadeSlideUp users take fill-mode `backwards`, NEVER `both`/`forwards`. A forwards fill keeps
     applying every property the keyframes mention, so `transform` stayed set (as the identity
     matrix) forever — and a transformed element is both a stacking context AND a containing
     block, which trapped the KPI tooltips beneath the fixed side-nav no matter their z-index.
     Dropping `transform` from the `to` keyframe does NOT help: a property named in any keyframe
     is still applied during the fill. `backwards` keeps the from-state during the delay (no
     flash) and lets the element fall back to its own `transform: none` once the entrance ends. */
  @keyframes fadeSlideUp { from { opacity: 0; transform: translateY(14px); } to { opacity: 1; transform: translateY(0); } }

  .kpi-row { animation: fadeSlideUp 0.45s ease-out backwards; }
  .chart-grid { animation: fadeSlideUp 0.45s ease-out 0.1s backwards; }
  .data-section { animation: fadeSlideUp 0.45s ease-out 0.15s backwards; }

  /* === RESPONSIVE === */
  @media (max-width: 900px) {
    :root { --nav-w: 0px; }
    /* The rail used to reflow here into a wrapping horizontal top bar. Measured on a real
       375x812 iPhone viewport that cost 294px — 36% of the screen, five wrapped rows — before
       a single figure of the page appeared, and it had to hide .nav-badge and .nav-sep to fit,
       which threw away the rail's whole point (its live per-page $ badges read as a running
       balance sheet). It is now a bottom sheet behind the "More" tab instead; every rule that
       built the old top bar lives in the MOBILE section at the end of this file.
       Nothing rail-related belongs in this block any more — put it in the MOBILE section, where
       source order wins the equal-specificity ties that .side-nav.rail (0,2,0) creates. */

    .wrapper { padding: 20px 16px 60px; }
    .sticky-bar { left: 0; }
    /* NOTE: the What-If banner's mobile overrides live in a media query at the END of the
       file — they must come after the base .whatif-bar / body.whatif-active rules (defined
       later in source) to win on equal specificity. */
    .inputs-panel, .section-panel.cols-3, .section-panel.cols-2 { grid-template-columns: 1fr; }
    .company-col:first-child, .asset-col:not(:last-child) { border-right: none; border-bottom: 1px solid var(--border); }
    .kpi-row.cols-5, .kpi-row.cols-6, .kpi-row.cols-7 { grid-template-columns: repeat(3, 1fr); }
    .chart-grid.cols-2, .chart-grid.even { grid-template-columns: 1fr; }
    .milestones-grid { grid-template-columns: repeat(3, 1fr); }
    .debt-row { grid-template-columns: 1fr 1fr !important; }
  }
  @media (max-width: 560px) {
    .brand b { font-size: 1.1rem; }
    .kpi-row.cols-5, .kpi-row.cols-6, .kpi-row.cols-7, .kpi-row.cols-4 { grid-template-columns: 1fr 1fr; }
    .kpi-row.cols-3 { grid-template-columns: 1fr; }
    .kpi-row.cols-2 { grid-template-columns: 1fr; }
    .milestones-grid { grid-template-columns: repeat(2, 1fr); }
    .debt-row { grid-template-columns: 1fr !important; }
  }

  /* R2-06: between the ≤900px collapse-to-3-cols above and unbounded width, the dashboard's two
     6-col KPI rows were never re-checked — at 1024px each card is only ~150px wide, too narrow
     for the .kpi-status badge's reserved 84px to clear "NET WORTH"-length labels, and too narrow
     for some values (e.g. "$187K/yr") even after the fitKpiValues abbreviation step. Reuse the
     same proven 3-column treatment the ≤900px breakpoint already ships, scoped to just the
     dashboard's two rows so other pages' KPI grids are untouched. */
  @media (min-width: 901px) and (max-width: 1180px) {
    #dashKpis1, #dashKpis2 { grid-template-columns: repeat(3, 1fr); }
  }

  /* === EXPORT/IMPORT BUTTONS — ghost, marker on hover === */
  .btn-export {
    background: transparent;
    border: 1px solid var(--border);
    color: var(--text-secondary);
    font-family: var(--sans);
    font-size: 0.72rem;
    font-weight: 600;
    padding: 6px 12px;
    border-radius: var(--radius-ctl);
    cursor: pointer;
    transition: background 0.15s, color 0.15s, border-color 0.15s;
  }
  .btn-export:hover { background: var(--marker); color: var(--marker-ink); border-color: var(--marker); }

  /* === WHAT IF MODE — highlighter banner over the main column === */
  .whatif-bar {
    position: fixed;
    top: 0;
    left: var(--nav-w);
    right: 0;
    z-index: 9998;
    display: none;
    align-items: center;
    justify-content: center;
    gap: 16px;
    padding: 9px 24px;
    background: var(--marker);
    border-bottom: 2px solid var(--marker-ink);
    font-family: var(--mono);
    font-size: 0.68rem;
    font-weight: 500;
    color: var(--marker-ink);
    letter-spacing: 0.04em;
  }
  .whatif-bar.visible { display: flex; }
  .whatif-bar .whatif-label {
    background: var(--marker);
    color: var(--marker-ink);
    padding: 3px 10px;
    border-radius: var(--radius-ctl);
    font-size: 0.66rem;
    font-weight: 700;
  }
  .whatif-bar .whatif-exit-btn {
    background: none;
    border: 1px solid var(--marker-ink);
    color: var(--marker-ink);
    font-family: var(--mono);
    font-size: 0.66rem;
    font-weight: 600;
    padding: 4px 12px;
    border-radius: var(--radius-ctl);
    cursor: pointer;
    transition: background 0.15s, color 0.15s;
  }
  .whatif-bar .whatif-exit-btn:hover { background: var(--marker-ink); color: var(--marker); }
  .whatif-bar .whatif-apply-btn {
    background: var(--marker);
    border: 1px solid var(--marker-ink);
    color: var(--marker-ink);
    font-family: var(--mono);
    font-size: 0.66rem;
    font-weight: 700;
    padding: 4px 12px;
    border-radius: var(--radius-ctl);
    cursor: pointer;
    transition: opacity 0.15s;
  }
  .whatif-bar .whatif-apply-btn:hover { opacity: 0.85; }
  .whatif-bar .whatif-save-btn {
    background: none;
    border: 1px solid var(--marker-ink);
    color: var(--marker-ink);
    font-family: var(--mono);
    font-size: 0.66rem;
    font-weight: 600;
    padding: 4px 12px;
    border-radius: var(--radius-ctl);
    cursor: pointer;
    transition: background 0.15s, color 0.15s;
  }
  .whatif-bar .whatif-save-btn:hover { background: var(--marker-ink); color: var(--marker); }
  body.whatif-active { border-top: 0; }
  body.whatif-active .wrapper { padding-top: 52px; }
  body.whatif-active .sticky-bar.visible { top: 40px; }

  /* === SAMPLE MODE — the same strip as What-If, in the accent rather than the wash ===
     Same geometry deliberately: both say "what you are looking at is not your saved plan", so
     they should read as the same kind of object. The accent ground separates them, because a
     sample session and a What-If sandbox can be live at once and must not be mistaken for
     each other. No .visible toggle — sample mode lasts the session. */
  .sample-bar {
    position: fixed;
    top: 0;
    left: var(--nav-w);
    right: 0;
    z-index: 9998;
    display: none;
    align-items: center;
    justify-content: center;
    gap: 16px;
    padding: 9px 24px;
    background: var(--gold);
    border-bottom: 2px solid var(--gold-dim);
    font-family: var(--mono);
    font-size: 0.68rem;
    font-weight: 500;
    color: var(--ink);
    letter-spacing: 0.04em;
  }
  body.sample-active .sample-bar { display: flex; }
  .sample-bar .sample-label {
    background: var(--ink);
    color: var(--gold);
    padding: 3px 10px;
    border-radius: var(--radius-ctl);
    font-size: 0.6rem;
    font-weight: 700;
  }
  .sample-bar .sample-keep-btn,
  .sample-bar .sample-exit-btn {
    background: none;
    border: 1px solid var(--ink);
    color: var(--ink);
    font-family: var(--mono);
    font-size: 0.6rem;
    font-weight: 600;
    padding: 4px 12px;
    border-radius: var(--radius-ctl);
    cursor: pointer;
    transition: background 0.15s, color 0.15s;
  }
  .sample-bar .sample-keep-btn { font-weight: 700; }
  .sample-bar .sample-keep-btn:hover,
  .sample-bar .sample-exit-btn:hover { background: var(--ink); color: var(--gold); }
  body.sample-active .wrapper { padding-top: 52px; }
  body.sample-active .sticky-bar.visible { top: 40px; }
  /* Both bars up at once: the sample bar owns the top strip (it is the outer context) and the
     What-If bar sits directly beneath it, so neither covers the other's escape hatch. */
  body.sample-active .whatif-bar.visible { top: 40px; }
  body.sample-active.whatif-active .wrapper { padding-top: 92px; }
  body.sample-active.whatif-active .sticky-bar.visible { top: 80px; }

  /* What-If banner on the collapsed (≤900px) horizontal nav. Placed here, AFTER the base
     .whatif-bar / body.whatif-active rules above, so these win on equal specificity: flow
     the banner in-page above the nav instead of a fixed overlay that covers the nav. */
  @media (max-width: 900px) {
    .whatif-bar { position: static; left: auto; }
    body.whatif-active .wrapper { padding-top: 20px; }
    body.whatif-active .sticky-bar.visible { top: 0; }
  }

  /* ===== CONNECTED ACCOUNTS (Plaid) =====
     A proposal row (.pl-line) is NOT a .view-line, and that is deliberate: a view line means
     "click me to edit this plan row", and nothing on this page is a plan row yet. It borrows the
     same column vocabulary — the .vl-head strip, .vl-cell typography, tabular figures, hairline
     borders, zero radius — so the page reads as part of the ledger rather than as a bolted-on
     integration screen. */
  .pl-panel { border: 1px solid var(--border); border-radius: var(--radius-card); margin-bottom: 28px; }
  .pl-sec-head {
    display: flex; align-items: baseline; justify-content: space-between; gap: 16px; flex-wrap: wrap;
    margin-bottom: 14px; padding-bottom: 12px; border-bottom: 1px solid var(--border-subtle);
  }
  .pl-sec-title { display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; min-width: 0; }
  .pl-sec-actions { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
  .pl-sec-actions .btn-add { margin-top: 0; }
  /* `.pl-note` is gone deliberately, not by oversight. It wrapped every section note in .fi-note
     (0.66rem / --text-dim) plus `max-width: 74ch` measured AT that size — 498px. Every note here is
     now a .pl-brief block at 0.75rem, so that inherited cap silently squeezed the definitions to
     ~37ch and made the prose wrap on almost every line. The lead and the glossary carry their own
     max-widths, in their own units, where they can actually be read. */
  /* The one thing on this page a privacy-conscious reader actually wants to find, previously set in
     the same near-invisible type as everything else. Nudged up to the ledger's own size/contrast. */
  .pl-footnote {
    margin-top: -12px; margin-bottom: 32px;
    font-size: 0.7rem; color: var(--text-secondary); max-width: 82ch;
  }

  /* --- section brief: one readable line, with the glossary behind a toggle -------------------
     The old pattern put ~430 words of .fi-note (0.66rem, --text-dim) above the Accounts rows.
     Two things are fixed here. SIZE AND CONTRAST: the lead is 0.75rem in --text-secondary, the
     same pairing the ledger rows use, because the sentence that matters most in this whole page
     ("nothing reaches your plan until you press Apply") was set in the least legible type in the
     app. STRUCTURE: the rest is a dt/dd glossary, closed by default — definitions in paragraph
     form must be re-read to be used, since the reader cannot see where one ends. */
  .pl-brief { margin-bottom: 16px; }
  .pl-lead {
    font-family: var(--mono); font-size: 0.75rem; line-height: 1.65;
    color: var(--text-secondary); max-width: 78ch; margin: 0 0 2px;
  }
  .pl-lead strong { color: var(--text); font-weight: 600; }
  .pl-lead + .pl-lead { margin-top: 8px; }

  .pl-guide-btn {
    display: inline-flex; align-items: center; gap: 6px;
    margin-top: 8px; padding: 3px 0; border: 0; background: none; cursor: pointer;
    font-family: var(--mono); font-size: 0.66rem;
    color: var(--text-dim); transition: color 0.15s ease;
  }
  .pl-guide-btn:hover, .pl-guide-btn:focus-visible { color: var(--gold); }
  .pl-guide-btn.is-open { color: var(--text-secondary); }
  .pl-guide-caret { font-size: 0.66rem; line-height: 1; }
  /* The count is the point: it tells the reader the size of what they are about to open. */
  .pl-guide-n {
    font-size: 0.66rem; padding: 1px 5px; border: 1px solid var(--border-subtle);
    color: var(--text-dim); line-height: 1.4;
  }
  .pl-guide-btn:hover .pl-guide-n { border-color: var(--gold); color: var(--gold); }

  /* Terms in a fixed column so the eye can run down them; a section-toned rule ties the block to
     the header chip it sits under (gold = proposals, ruby = rates & minimums). */
  .pl-guide {
    margin: 10px 0 0; padding: 12px 0 2px 14px;
    border-left: 2px solid var(--border-subtle);
    display: grid; gap: 11px; max-width: 82ch;
  }
  .pl-guide.tone-gold { border-left-color: color-mix(in srgb, var(--gold) 45%, transparent); }
  .pl-guide.tone-ruby { border-left-color: color-mix(in srgb, var(--ruby) 45%, transparent); }
  .pl-guide-row { display: grid; grid-template-columns: 15ch 1fr; gap: 14px; align-items: baseline; }
  .pl-guide dt {
    font-family: var(--mono); font-size: 0.68rem; line-height: 1.45;
    color: var(--text); font-weight: 600;
  }
  .pl-guide-vs { display: block; font-weight: 400; font-size: 0.66rem; color: var(--text-dim); }
  /* Capped on the dd itself, not the dl: `ch` here resolves against this element's own 0.72rem, so
     70ch is genuinely 70 characters of the font the reader sees. Freed from the old wrapper the
     column ran to ~81ch, past the range where the eye reliably finds the next line. */
  .pl-guide dd {
    margin: 0; font-family: var(--mono); font-size: 0.72rem; line-height: 1.6;
    color: var(--text-secondary); max-width: 70ch;
  }
  .pl-guide dd strong { color: var(--text); font-weight: 600; }
  .pl-guide dd em { color: var(--text); font-style: italic; }

  /* Stacked below the ledger's own breakpoint: a 15ch term column plus prose does not survive a
     phone, and a term wrapped onto three lines is worse than a term above its definition. */
  @media (max-width: 780px) {
    .pl-guide-row { grid-template-columns: 1fr; gap: 3px; }
    .pl-guide dt { color: var(--gold); }
  }

  /* --- connections --- */
  .pl-item { border: 1px solid var(--border-subtle); background: var(--surface); padding: 12px 14px; margin-bottom: 8px; }
  .pl-item.is-bad { border-color: var(--ruby); }
  .pl-item-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; flex-wrap: wrap; }
  .pl-item-id { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; min-width: 0; }
  .pl-item-name {
    font-family: var(--mono); font-size: 0.86rem; font-weight: 600; color: var(--text);
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 100%;
  }
  .pl-item-acts { display: flex; gap: 6px; flex-wrap: wrap; }
  .pl-item-meta { font-family: var(--mono); font-size: 0.66rem; color: var(--text-dim); margin-top: 6px; }
  .pl-item-accts { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 8px; }
  .pl-mini {
    font-family: var(--mono); font-size: 0.66rem; color: var(--text-secondary);
    border: 1px solid var(--border-subtle); padding: 2px 6px; white-space: nowrap;
  }
  .pl-mini b { color: var(--text-dim); font-weight: 500; }
  .pl-state {
    font-family: var(--mono); font-size: 0.66rem; font-weight: 700; padding: 2px 6px; white-space: nowrap;
  }
  .pl-state.is-ok { color: var(--emerald); background: var(--emerald-dim); border: 1px solid var(--emerald); }
  .pl-state.is-warn { color: var(--gold); background: var(--gold-glow); border: 1px solid var(--gold-dim); }
  .pl-state.is-bad { color: var(--ruby); background: var(--ruby-dim); border: 1px solid var(--ruby); }
  .pl-warn {
    font-family: var(--mono); font-size: 0.66rem; line-height: 1.5; color: var(--ruby);
    background: var(--ruby-dim); border-left: 2px solid var(--ruby); padding: 6px 10px; margin-top: 8px;
  }
  /* A connection scheduled to stop is NOT the same as one that already has. Ruby is reserved for
     "this is broken now"; a dated warning gets gold, or the two become indistinguishable and the
     colour stops meaning anything. */
  .pl-warn.is-soon { color: var(--gold); background: var(--gold-glow); border-left-color: var(--gold); }

  /* --- proposal rows --- */
  .pl-line {
    display: grid; gap: 12px; align-items: center;
    padding: 10px 14px; margin-bottom: 4px;
    background: var(--surface); border: 1px solid var(--border-subtle); border-radius: var(--radius-ctl);
  }
  .pl-line.is-bad { border-color: var(--ruby); }
  .pl-line.is-done { opacity: 0.72; }
  /* The "At institution" track is wider than its neighbours because it carries a STACK, not a
     number: both balance figures your bank reports, each labelled, plus the age of the read. One
     unlabelled number in that column is what made the app disagree with Chase's website. */
  .pl-cols-acct { grid-template-columns: minmax(0, 1.35fr) 0.7fr minmax(132px, 1.05fr) minmax(84px, 0.7fr) minmax(76px, 0.65fr) minmax(230px, 1.1fr); }
  .pl-cols-hold { grid-template-columns: minmax(0, 1.5fr) 0.7fr 0.75fr minmax(84px, 0.75fr) minmax(84px, 0.75fr) minmax(250px, 1.2fr); }
  .pl-cols-rec  { grid-template-columns: minmax(0, 1.5fr) 0.75fr minmax(120px, 0.9fr) minmax(120px, 0.9fr) minmax(84px, 0.7fr) minmax(210px, 1fr); }
  .pl-cols-liab { grid-template-columns: minmax(0, 1.6fr) 0.6fr minmax(84px, 0.7fr) minmax(96px, 0.9fr) minmax(96px, 0.9fr) minmax(200px, 1fr); }
  .vl-head .h-act { text-align: left; }
  /* An askable column heading. Most of what used to be section prose on the Accounts page now lives
     in these titles, so the affordance has to read as askable without becoming six loud icons on one
     strip: a dotted rule under the words only, warming to gold on hover. Inline-block so the rule
     hugs the label rather than running the width of the grid track. */
  .vl-head .h-tip {
    display: inline-block; cursor: help;
    border-bottom: 1px dotted var(--text-dim);
    transition: color 0.15s ease, border-bottom-color 0.15s ease;
  }
  .vl-head .h-tip:hover { color: var(--text-secondary); border-bottom-color: var(--gold); }
  .pl-name { min-width: 0; display: flex; flex-direction: column; gap: 2px; }
  .pl-name-main {
    font-family: var(--mono); font-size: 0.82rem; color: var(--text);
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  }
  .pl-mask { color: var(--text-dim); }
  .pl-name-sub {
    font-family: var(--mono); font-size: 0.66rem; color: var(--text-dim);
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  }
  .pl-hint {
    font-family: var(--mono); font-size: 0.66rem; line-height: 1.45; color: var(--gold);
    white-space: normal;
  }
  /* Same shape, lower voice — the precedent is .pl-warn.is-soon. A pending debit explaining why
     `available` sits below `current` is the bank working normally, not a problem: painting it gold
     alongside the real warnings is how an eye learns to skip both. */
  .pl-hint.is-quiet { color: var(--text-secondary); }

  /* A row-level caveat as a CHIP rather than a paragraph. Same job as .pl-hint, but bounded: these
     two facts (no ticker to merge on; that ticker is also held elsewhere in the plan) used to wrap to
     several lines inside a name cell, dragging that one row taller than its neighbours so the figures
     no longer scanned as a column. The chip states the fact; `title` carries the reasoning. */
  .pl-flag {
    display: inline-block; width: fit-content; max-width: 100%; cursor: help;
    font-family: var(--mono); font-size: 0.66rem; line-height: 1.3; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
    padding: 2px 5px; margin-top: 3px;
    color: var(--gold); border: 1px solid color-mix(in srgb, var(--gold) 40%, transparent);
  }
  .pl-flag.is-quiet {
    color: var(--text-secondary); border-color: var(--border-subtle);
  }
  .pl-flag:hover { border-color: var(--gold); color: var(--gold); }
  .pl-num { text-align: right; }
  .pl-num b { color: var(--text); }
  .pl-sub { font-size: 0.66rem; color: var(--text-dim); }
  /* Two labelled figures and a freshness line inside one grid cell. Right-aligned and tabular so
     the digits still line up down the column the way every other ledger figure does. */
  .pl-stack {
    display: flex; flex-direction: column; align-items: flex-end; gap: 1px;
    min-width: 0; font-variant-numeric: tabular-nums;
  }
  .pl-fig { white-space: nowrap; }
  .pl-fig.is-alt { color: var(--text-dim); }
  .pl-age {
    font-size: 0.66rem; letter-spacing: 0.04em; color: var(--text-dim);
    white-space: normal; text-align: right; margin-top: 2px;
  }
  .pl-age.is-stale { color: var(--gold); }
  .pl-none { color: var(--text-dim); }
  .pl-delta { font-weight: 600; }
  .pl-delta.is-up { color: var(--gold); }
  .pl-delta.is-down { color: var(--blue); }
  .pl-actions { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; justify-content: flex-end; }
  .pl-actions .pl-none { font-family: var(--mono); font-size: 0.66rem; }
  .pl-btn-sm { margin-top: 0; padding: 5px 10px; font-size: 0.66rem; }
  .pl-btn-caution { border-color: var(--gold-dim); color: var(--gold); }
  select.pl-bind {
    flex: 1 1 auto; min-width: 130px; max-width: 100%;
    background: var(--card); color: var(--text);
    border: 1px solid var(--border); border-radius: var(--radius-ctl);
    font-family: var(--mono); font-size: 0.7rem;
    padding: 6px 24px 6px 8px; outline: none;
    -webkit-appearance: none; appearance: none;
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6'%3E%3Cpath d='M0 0l5 6 5-6z' fill='%23807d72'/%3E%3C/svg%3E");
    background-repeat: no-repeat; background-position: right 8px center;
  }
  select.pl-bind:focus { border-color: var(--gold); box-shadow: 0 0 0 3px var(--gold-glow); }
  .pl-empty { font-family: var(--mono); font-size: 0.74rem; line-height: 1.65; color: var(--text-secondary); max-width: 76ch; }
  .pl-empty p { margin-bottom: 14px; }
  .pl-empty strong { color: var(--text); }
  .pl-empty-cta { color: var(--text-dim); }
  .pl-empty .btn-add { margin-top: 4px; }
  .pl-empty-row { font-family: var(--mono); font-size: 0.7rem; color: var(--text-dim); padding: 12px 2px; }
  .pl-ignored { margin-top: 10px; padding-top: 10px; border-top: 1px dashed var(--border); }
  .pl-ignored-note { font-family: var(--mono); font-size: 0.66rem; color: var(--text-dim); padding: 8px 2px 0; }

  /* The two options in the Connect chooser ("Bank or credit card" / "Investment or retirement
     account"). The box itself is styled inline in app.js like every other Plaid dialog; only the
     hover/focus accent needs a real rule, because an inline style cannot express a pseudo-class. */
  .pl-kind-opt:hover, .pl-kind-opt:focus-visible {
    border-color: var(--gold); background: var(--gold-glow); outline: none;
  }
  .pl-kind-opt:hover > span:first-child, .pl-kind-opt:focus-visible > span:first-child { color: var(--gold); }

  /* Provenance chip on a plan row whose figure has an outside source. Same vocabulary as the AUTO
     chip on derived rows — but emerald, because AUTO means "computed for you and read-only" while
     LINKED means "you own this row, something outside just informs it". */
  .plaid-chip {
    display: inline-flex; align-items: center; gap: 5px;
    font-family: var(--mono); font-size: 0.66rem; font-weight: 700; letter-spacing: 0.08em;
    color: var(--emerald); background: var(--emerald-dim); border: 1px solid var(--emerald);
    border-radius: var(--radius-ctl); padding: 2px 6px; white-space: nowrap; flex: 0 0 auto;
  }
  .plaid-chip-src {
    font-weight: 500; letter-spacing: 0.02em; text-transform: none;
    color: var(--text-secondary); max-width: 12ch; overflow: hidden; text-overflow: ellipsis;
  }

  /* HOUSEHOLD VIEWS: whose row this is, on the read-first line — only rendered when the owner
     isn't 'me' (see _ownerChipHtml), so the common single-person case stays chip-free. Same
     small-mono-caps shape as .plaid-chip but deliberately quieter (dim text/border, no color):
     ownership is a fact about the row, not a provenance claim worth escalating visually. */
  .owner-chip {
    display: inline-flex; align-items: center;
    font-family: var(--mono); font-size: 0.66rem; font-weight: 700; letter-spacing: 0.08em;
    color: var(--text-dim); background: transparent; border: 1px solid var(--border-subtle);
    border-radius: var(--radius-ctl); padding: 2px 6px; white-space: nowrap; flex: 0 0 auto;
    text-transform: uppercase;
  }

  /* ≤900px the proposal rows stop being tables and become stacked records: the figures wrap into
     a labelled run under the name and the decision control gets the full width. Same collapse the
     .vl-cols lists make at this breakpoint. */
  @media (max-width: 900px) {
    .pl-line { grid-template-columns: minmax(0, 1fr) !important; gap: 7px; padding: 12px 14px; }
    /* .vl-head is already display:none at this width, so each figure carries its own label from
       data-l — an unlabelled column of money is worse than no column at all. */
    .pl-line .vl-cell {
      display: flex; justify-content: space-between; align-items: baseline; gap: 10px;
      text-align: right; white-space: normal;
    }
    .pl-line .vl-cell[data-l]::before {
      content: attr(data-l);
      font-size: 0.66rem; font-weight: 700;
      color: var(--text-dim); text-align: left; flex: 0 0 auto;
    }
    .pl-line .vl-cell:not([data-l]) { justify-content: flex-start; text-align: left; }
    .pl-actions { justify-content: flex-start; }
    .pl-actions .btn-dup, .pl-actions .btn-add { flex: 0 0 auto; }
    select.pl-bind { flex: 1 1 100%; min-width: 0; }
    .pl-sec-head { align-items: flex-start; }
  }
  @media (max-width: 560px) {
    .plaid-chip-src { display: none; }
  }

  /* ============================================================
     Quiet breadcrumb line above each page: current page name left,
     the long date right. Stays in the DOM (app.js writes to it) but
     carries none of the old masthead's report styling — no rule,
     no letterspacing, just a calm sentence-case line.
     ============================================================ */

  /* Page-name + date breadcrumb is now the sticky .topline itself (Stage 1) — see the
     .topline.report-masthead rule above; nothing further needed here. */

  /* ---------- headline story (shared by Home's .headrow and every page's .page-headline) ---------- */
  .headline-story {
    font-size: 0.98rem;
    line-height: 1.55;
    color: var(--text-secondary);
    max-width: 46rem;
    margin-top: 14px;
  }
  .headline-story strong { color: var(--text); font-weight: 700; }
  /* Money figures in the headline sentence read green; years, ages and the rest of the
     emphasis stay in --text so only the amounts pop. */
  .headline-story strong.money { color: var(--emerald); }

  @media (max-width: 900px) {
    .topline.report-masthead { flex-direction: column; align-items: flex-start; gap: 8px; }
  }

  /* ============================================================
     Per-page headline + the open KPI band. Every page opens with a
     lead figure and a one-line story; the KPI "tile wall" from the
     older brutalist pass demotes to bare columns under a single
     hairline rule (spec §Surfaces — KPI band border-top 1px).
     ============================================================ */

  /* ---------- per-page headline ---------- */
  .page-headline {
    padding: 4px 0 22px;
    margin-bottom: 26px;
    border-bottom: 1px solid var(--border-subtle);
  }
  .page-figure {
    font-family: var(--serif);
    font-weight: 800;
    font-size: clamp(1.7rem, 3vw, 2.3rem);
    letter-spacing: -0.02em;
    line-height: 1.08;
    margin: 10px 0 0;
    color: var(--text);
  }
  .page-figure .mark { color: var(--text); }
  [data-theme="dark"] .page-figure .mark,
  :root:not([data-theme]) .page-figure .mark {
    background: var(--aurora);
    -webkit-background-clip: text;
    background-clip: text;
    color: transparent;
    filter: drop-shadow(0 0 24px rgba(94,230,168,.22));
  }
  .page-headline .headline-story { margin-top: 12px; font-size: 0.92rem; max-width: 44rem; }

  /* ============================================================
     STAGE 3 — interior pages panelized (design/restructure-investments.html).
     .pagehead puts the existing headline block left and a primary action
     button right; .panel-head restyles .invest-section-head/.data-section-header
     to a title + tag + right-aligned note row; .band-in-panel is the mockup's
     .band adapted to sit inside a .section-panel's .asset-col, directly under
     the list it summarizes.
     ============================================================ */
  .page-headline.pagehead {
    display: flex;
    align-items: flex-end;
    justify-content: space-between;
    gap: 20px;
    flex-wrap: wrap;
  }
  .pagehead .primary {
    background: var(--gold);
    border: none;
    color: #fff;
    font-family: var(--sans);
    font-size: 0.86rem;
    font-weight: 650;
    border-radius: var(--radius-ctl);
    padding: 9px 16px;
    cursor: pointer;
    box-shadow: var(--shadow-hard-sm);
    white-space: nowrap;
  }
  [data-theme="dark"] .pagehead .primary { background: var(--aurora); color: #fff; }
  .pagehead .primary:hover { opacity: 0.92; }

  .invest-section-head.panel-head,
  .data-section-header.panel-head {
    flex-wrap: wrap;
  }
  .panel-head .section-note,
  .panel-head .note {
    margin-left: auto;
    font-family: var(--sans);
    font-size: 0.76rem;
    color: var(--text-dim);
  }

  .kpi-row.band-in-panel {
    display: grid;
    border: none;
    border-top: 1px solid var(--border-subtle);
    margin: 18px -30px -26px;
    padding-top: 0;
    background: transparent;
  }
  .kpi-row.band-in-panel .kpi { padding: 14px 20px 14px; border-left: 1px solid var(--border-subtle); }
  .kpi-row.band-in-panel .kpi:first-child { border-left: none; }
  @media (max-width: 900px) { .kpi-row.band-in-panel { margin-left: -20px; margin-right: -20px; } }

  /* ---------- subgroup header inside a panel (Stage 5: Holdings > Equities / Crypto) ---------- */
  .subhead {
    display: flex;
    align-items: baseline;
    gap: 12px;
    margin: 22px 0 14px;
    padding-top: 18px;
    border-top: 1px solid var(--border-subtle);
  }
  .subhead:first-child { margin-top: 0; padding-top: 0; border-top: none; }
  .subhead .col-name {
    font-family: var(--serif);
    font-size: 0.9rem;
    font-weight: 650;
  }

  /* ---------- section jumpers ---------- */
  .section-jumper { display: flex; gap: 8px; margin-top: 14px; flex-wrap: wrap; }
  .section-jumper a {
    font-family: var(--sans);
    font-size: 0.74rem;
    font-weight: 600;
    text-decoration: none;
    color: var(--gold);
    background: var(--gold-glow);
    border-radius: var(--radius-pill);
    padding: 5px 12px;
    transition: filter 0.15s;
  }
  .section-jumper a:hover { filter: brightness(1.1); }
  /* Jump targets clear the sticky bar */
  .invest-section-head, #goalCard, #sec-yearbyyear, #sec-milestones { scroll-margin-top: 72px; }

  /* ---------- KPI strips: open band, bare columns ---------- */
  /* Anatomy (label / value / delta / status chip / info icon) is untouched — only the chrome. */
  .kpi-row {
    background: transparent;
    border: none;
    border-top: 1px solid var(--border);
    gap: 26px;
    padding-top: 2px;
    margin-bottom: 40px;
  }
  .kpi { background: transparent; padding: 14px 2px 0 0; }
  .kpi:hover { background: transparent; box-shadow: none; }
  .kpi.is-filler { background: transparent; }
  .kpi-val { font-family: var(--serif); font-weight: 650; font-size: 1.35rem; }
  .kpi-status { top: 14px; right: 0; }
  .kpi-info { bottom: 0; }

  /* Display numerals: Schibsted's tabular figures make commas full-width, which reads as
     "$3 , 240 , 248" at hero sizes. Tables and bands keep body-level tnum for alignment;
     the big display numbers opt back into proportional figures. */
  .big, .page-figure, .nav-nw-val, .headline-story,
  .fi-stat-val, .kpi-val {
    font-variant-numeric: proportional-nums;
    font-feature-settings: 'tnum' 0;
  }

/* ===== ⌘K command palette (trigger + overlay) ===== */
.cmdk-hint {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  margin-left: 12px;
  padding: 5px 10px;
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: var(--radius-ctl);
  color: var(--text-dim);
  font-family: inherit;
  font-size: .8rem;
  cursor: pointer;
  transition: border-color .15s ease, color .15s ease;
}
.cmdk-hint:hover { color: var(--text); border-color: var(--gold); }
.cmdk-hint kbd {
  font-family: inherit;
  font-size: .72rem;
  padding: 1px 5px;
  border: 1px solid var(--border);
  border-radius: 5px;
  color: var(--text-dim);
  background: transparent;
}

.cmdk-overlay {
  position: fixed;
  inset: 0;
  z-index: 9999;
  background: rgba(0,0,0,.35);
  backdrop-filter: blur(3px);
  display: flex;
  align-items: flex-start;
  justify-content: center;
  padding-top: 14vh;
}
.cmdk-overlay[hidden] { display: none; }

.cmdk-panel {
  width: min(560px, 92vw);
  max-height: 60vh;
  display: flex;
  flex-direction: column;
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: var(--radius-card);
  box-shadow: var(--shadow-hard);
  overflow: hidden;
}

.cmdk-input {
  width: 100%;
  border: none;
  border-bottom: 1px solid var(--border);
  background: transparent;
  color: var(--text);
  font-family: inherit;
  font-size: 1rem;
  padding: 14px 16px;
  outline: none;
  box-sizing: border-box;
}
.cmdk-input::placeholder { color: var(--text-dim); }

.cmdk-list {
  list-style: none;
  margin: 0;
  padding: 6px;
  overflow-y: auto;
}
.cmdk-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 10px 14px;
  border-radius: 8px;
  cursor: pointer;
  color: var(--text);
  font-size: .92rem;
}
.cmdk-row.active {
  background: var(--gold-glow);
  color: var(--gold);
}
.cmdk-row-hint {
  font-size: .74rem;
  color: var(--text-dim);
  white-space: nowrap;
}
.cmdk-row.active .cmdk-row-hint { color: var(--gold); opacity: .8; }
.cmdk-empty {
  padding: 18px 16px;
  color: var(--text-dim);
  font-size: .88rem;
  text-align: center;
}

  /* Two pinned bars can't share top:0 — the sticky KPI bar slides in UNDER the topline. */
  .sticky-bar { top: 61px; }
  /* Coach row emoji breathing room */
  .coach-row .coach-icon { margin-right: 8px; }

  /* Panel-head children flow from the left; only the note is pushed to the right edge —
     keeps the live-price tag glued to its section title like the mockup. */
  .panel-head, .invest-section-head.panel-head, .data-section-header.panel-head { justify-content: flex-start; }
  .panel-head .section-note, .panel-head .note { margin-left: auto; }
  .panel-head a.goto { margin-left: auto; }

  /* The restructured Home introduced new display-numeral classes; opt them out of the
     body-level tabular figures like the old ones (full-width commas read as gaps). */
  .headrow .big, #homeBand .cell .v, #homePortfolio td, .rfacts b, .ring .mid,
  .pagehead .big {
    font-variant-numeric: proportional-nums;
    font-feature-settings: 'tnum' 0;
  }

/* ============================================================
   STAGE 4 FOLDS + ANATOMY — the big always-open detail tables
   (Year-by-Year, Brokerage Breakdown, Debt Payoff Schedule, Loan
   Payoff Dates, Income Projection, Outflow Summary, Business
   Equity Detail, projections' Year-by-Year Projection and
   Milestones) collapse into a closed-by-default details/summary
   styled like a .panel with a .panel-head-ish summary row. Tables
   only inside a fold — never a canvas.
   ============================================================ */
.fold {
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: var(--radius-card);
  box-shadow: var(--shadow-hard-sm);
  padding: 0;
  margin: 24px 0;
  overflow: hidden;
}
.fold > .table-scroll,
.fold > div {
  padding: 0 20px 20px;
}
summary.fold-summary::-webkit-details-marker { display: none; }
.fold-summary {
  list-style: none;
  display: flex;
  align-items: baseline;
  justify-content: flex-start;
  gap: 12px;
  flex-wrap: wrap;
  padding: 16px 20px;
  cursor: pointer;
  font-family: var(--serif);
  font-size: 0.95rem;
  font-weight: 700;
  color: var(--text);
  transition: color 0.15s;
}
.fold-summary::before {
  content: '\25B8';
  display: inline-block;
  color: var(--gold);
  font-size: 0.8em;
  transition: transform 0.15s;
  flex: none;
}
.fold[open] > .fold-summary::before { transform: rotate(90deg); }
.fold-summary:hover { color: var(--gold); }
.fold-summary .note {
  margin-left: auto;
  font-family: var(--sans);
  font-size: 0.76rem;
  font-weight: 500;
  color: var(--text-dim);
}
.fold[open] > .fold-summary {
  border-bottom: 1px solid var(--border);
  margin-bottom: 4px;
}

/* ============================================================
   MOBILE — iOS
   ============================================================
   Appended LAST on purpose. Many base rules in this file are
   two-class selectors (.side-nav.rail, .kpi-row.cols-6,
   .section-panel.cols-3), so a single-class override inside a
   media query LOSES the specificity tie rather than winning it.
   That exact bug already shipped once here — the whole ≤900px
   rail block was silently dead for months (see the note kept at
   the old RESPONSIVE block). Landing last means source order
   settles every equal-specificity tie in this section's favour
   without a single !important.

   DESKTOP SAFETY. Everything below is either (a) inside a
   max-width media query, or (b) a property that provably cannot
   render differently on a pointer:fine desktop — the two
   exceptions are marked individually. scripts/desktop-fingerprint.js
   proves this empirically: it hashes geometry + computed styles
   for all ten pages at 1440/1280/1024/901 and must report zero
   drift. 901 is in that list precisely to catch an off-by-one in
   a breakpoint below.

   Measured against scripts/mobile-check.js, which asserts the
   four things that actually break iOS: content clipped by
   body{overflow-x:hidden} with no scroller, controls under 16px
   (Safari zooms on focus and never returns), tap targets under
   44x44, and document-level horizontal scroll.
   ============================================================ */

/* GLOBAL, DESKTOP-NEUTRAL (exception a). text-size-adjust only ever fires in a mobile
   text-inflation algorithm; desktop engines have none, so this is inert there. Without it iOS
   inflates text in landscape and the KPI fit pass measures against the wrong metrics. */
html {
  -webkit-text-size-adjust: 100%;
  text-size-adjust: 100%;
}

/* GLOBAL, DESKTOP-NEUTRAL (exception b). Both properties are touch-only: the grey flash box is
   drawn by mobile WebKit alone, and touch-action governs gesture handling, not layout. Setting
   manipulation opts out of the legacy 300ms double-tap-to-zoom delay, which is what makes every
   button in the app feel laggy on an iPhone. */
button, a, summary, [role="button"], .view-line, .expense-line, .nav-item {
  -webkit-tap-highlight-color: transparent;
  touch-action: manipulation;
}
/* The reorder handle is the one control that must NOT get `manipulation`: the touch reorder in
   app.js (_initTouchReorder) needs the browser to hand over the vertical gesture instead of
   scrolling the page with it, and `none` is what surrenders it. Touch-only either way — a mouse
   drag is unaffected by touch-action. */
.drag-handle {
  -webkit-tap-highlight-color: transparent;
  touch-action: none;
}

/* The bottom tab bar does not exist on desktop. Declared outside the query so the default is
   "absent" and the media query below is what turns it on — never the other way round. */
.mnav, .mnav-scrim { display: none; }

@media (max-width: 900px) {
  :root {
    --mnav-h: 56px;                       /* tab bar height, excluding the home-indicator inset */
    --safe-b: env(safe-area-inset-bottom, 0px);
    --safe-t: env(safe-area-inset-top, 0px);
    --safe-l: env(safe-area-inset-left, 0px);
    --safe-r: env(safe-area-inset-right, 0px);
  }

  /* ---------- 1. VIEWPORT UNITS ----------
     100vh on iOS Safari is the height with the URL bar COLLAPSED, so a 100vh box is always
     taller than what you can actually see and the page starts life scrolled-looking. dvh
     tracks the live viewport. Scoped to mobile rather than edited at the base rule so the
     desktop fingerprint stays byte-identical. */
  body { min-height: 100dvh; }
  .main-col { min-height: 100dvh; }

  /* Rubber-banding the whole document past its ends looks broken behind fixed chrome, and on
     the horizontal table scrollers it steals the gesture and can trigger back-navigation. */
  body { overscroll-behavior-y: contain; }
  .table-scroll { overscroll-behavior-x: contain; }

  /* Landscape puts the notch on one side; without this, content hides under it. */
  .wrapper { padding-left: calc(16px + var(--safe-l)); padding-right: calc(16px + var(--safe-r)); }

  /* ---------- 2. THE RAIL, AS A BOTTOM SHEET ----------
     The rail is reused verbatim rather than duplicated into a mobile menu: it already renders
     every destination with its live $ badge, the Data & backups flyout and the price-status
     line, and duplicating that markup would mean duplicating the state that keeps it truthful. */
  .side-nav.rail {
    position: fixed;
    top: auto;
    left: 0;
    right: 0;
    bottom: 0;
    width: 100%;
    height: auto;
    max-height: 82dvh;
    flex-direction: column;
    flex-wrap: nowrap;
    align-items: stretch;
    gap: 2px;
    border-right: none;
    border-top: 1px solid var(--border);
    border-radius: 18px 18px 0 0;
    box-shadow: var(--shadow-hard);
    overflow-y: auto;
    overflow-x: hidden;
    padding: 8px 12px calc(14px + var(--safe-b));
    z-index: 10001;                        /* above .mnav so the sheet covers the bar it came from */
    /* Closed state. visibility (not just transform) is what takes the sheet out of the tab
       order and the accessibility tree while it is off-screen — a transform alone would leave
       ten invisible but focusable buttons behind the page. */
    transform: translateY(101%);
    visibility: hidden;
    transition: transform 0.26s cubic-bezier(0.32, 0.72, 0, 1), visibility 0.26s;
    animation: none;                       /* the desktop rail's fadeIn entrance, cancelled */
  }
  body.mnav-open .side-nav.rail { transform: none; visibility: visible; }

  /* A grab bar, so the sheet reads as a sheet. */
  .side-nav.rail::before {
    content: '';
    display: block;
    flex: none;
    width: 36px;
    height: 4px;
    margin: 2px auto 10px;
    border-radius: 999px;
    background: var(--border);
  }

  /* Restore everything the old wrapping top bar had to throw away to fit. */
  .brand { border: none; padding: 2px 10px 12px; flex: 0 0 auto; }
  .nav-groups { flex-direction: column; flex-wrap: nowrap; gap: 2px; padding: 0; }
  .nav-item { width: 100%; margin: 0; padding: 12px 12px; font-size: 1rem; }
  .nav-item.sub { padding-left: 34px; font-size: 0.95rem; }
  .nav-badge { display: block; font-size: 0.85rem; }
  .nav-sep { display: block; font-size: 0.75rem; padding: 14px 10px 6px; }
  .rail-bottom {
    flex: 0 0 auto; max-width: 100%; min-width: 0;
    margin-top: 8px; border-top: 1px solid var(--border-subtle); border-left: none;
    padding: 10px 10px 0;
  }
  .data-flyout { padding: 12px 10px; font-size: 0.95rem; }
  .sync { padding: 10px 2px 0; font-size: 0.78rem; }
  .nav-data-actions { gap: 8px; }
  .nav-data-actions .btn-export { font-size: 0.72rem; padding: 11px 14px; }
  .data-pop .price-status { padding: 10px 2px; font-size: 0.8rem; }

  .mnav-scrim {
    display: block;
    position: fixed;
    inset: 0;
    z-index: 10000;
    background: rgba(0, 0, 0, 0.45);
    opacity: 0;
    pointer-events: none;
    transition: opacity 0.26s;
  }
  .mnav-scrim[hidden] { display: block; }   /* [hidden] is the closed state, not "not rendered" */
  body.mnav-open .mnav-scrim { opacity: 1; pointer-events: auto; }

  /* ---------- 3. BOTTOM TAB BAR ---------- */
  .mnav {
    display: flex;
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    z-index: 9995;
    align-items: stretch;
    justify-content: space-around;
    gap: 2px;
    height: calc(var(--mnav-h) + var(--safe-b));
    padding: 0 calc(4px + var(--safe-l)) var(--safe-b) calc(4px + var(--safe-r));
    background: var(--surface);
    border-top: 1px solid var(--border);
    /* The bar sits over scrolling content; on iOS the blur is what keeps it legible. */
    -webkit-backdrop-filter: saturate(1.6) blur(12px);
    backdrop-filter: saturate(1.6) blur(12px);
  }
  .mnav-item {
    flex: 1 1 0;
    min-width: 0;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 3px;
    padding: 6px 2px;
    background: none;
    border: none;
    border-radius: 10px;
    cursor: pointer;
    font-family: var(--sans);
    font-size: 0.66rem;
    font-weight: 600;
    letter-spacing: 0.01em;
    color: var(--text-dim);
    -webkit-tap-highlight-color: transparent;
  }
  .mnav-item svg { width: 20px; height: 20px; flex: none; }
  .mnav-item span { max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .mnav-item.active { color: var(--gold); }
  .mnav-item:active { background: var(--card); }
  body.mnav-open .mnav-more { color: var(--gold); }

  /* Room for the bar (and the home indicator) under the last thing on every page, so the final
     row of a table or the Add button is never permanently parked behind it. */
  .wrapper { padding-bottom: calc(var(--mnav-h) + var(--safe-b) + 28px); }

  /* ---------- 4. THE OTHER FIXED CHROME ----------
     Three fixed elements previously assumed nothing else was pinned to the bottom. */
  /* Lifted clear of the tab bar. Deliberately kept on the LEFT, honouring R2-18 above: at the
     bottom-RIGHT this button sat over the KPI cards' info icons and swallowed their taps. The
     34px footprint R2-18 chose was part of that same fix, but it was a bottom-right remedy —
     nothing sits at the bottom-left to intercept — so it can take the 44px HIG minimum here.
     left/right are both stated so the anchor is a decision, not an over-constrained accident. */
  #tabHelpFab {
    bottom: calc(var(--mnav-h) + var(--safe-b) + 14px);
    left: calc(14px + var(--safe-l));
    right: auto;
    width: 44px;
    height: 44px;
  }
  /* Sit under the topline at its ACTUAL height, not the 61px the base rule assumes for one
     desktop row. _syncToplineHeight() in app.js publishes the measured value; the 61px fallback
     only applies before the first measurement. Without this the two pinned bars overlapped by
     40px and the KPI strip covered the search field and Profile control. */
  .sticky-bar { padding-top: var(--safe-t); top: var(--topline-h, 61px); }
  /* The rail sheet and its scrim outrank the help FAB and the sticky KPI bar while open. */
  body.mnav-open #tabHelpFab, body.mnav-open .sticky-bar { visibility: hidden; }

  /* ---------- 4a. THE TWO STICKY HEADERS, COMPACTED ----------
     Both of these are pinned, so whatever they cost is taken off every screenful of the page
     for the whole session — the most expensive vertical space in the app.

     The masthead stacks into a column below 900px (title / date / search / Profile / theme on
     five lines) and measured 195px. Reflowed to a wrapping row it is ~52px, handing ~145px back
     to the page on every single scroll position. */
  .topline.report-masthead {
    flex-direction: row;
    flex-wrap: wrap;
    align-items: center;
    gap: 6px 10px;
    padding: 8px calc(16px + var(--safe-r)) 8px calc(16px + var(--safe-l));
    padding-top: calc(8px + var(--safe-t));
  }
  .topline .masthead-page { flex: 0 0 auto; }
  .topline .topline-date { flex: 0 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  /* The search field is the only flexible item, so it takes the slack and the controls after it
     stay on the same line instead of being pushed onto their own. */
  .cmdk.cmdk-hint { margin-top: 0; margin-left: auto; max-width: none; flex: 0 1 auto; min-width: 0; }
  .cmdk.cmdk-hint span, .cmdk-hint { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

  /* The KPI bar reproduces up to six figures; at ≤560 that is a 2-column grid three rows deep,
     measured at 206px — a quarter of an iPhone screen permanently occupied by a duplicate of
     what is already on the page. One horizontally-scrollable row keeps every figure reachable
     at ~56px. */
  .sticky-bar-inner { padding: 0 calc(16px + var(--safe-l)) 0 calc(16px + var(--safe-r)); }
  .sticky-kpis {
    display: flex;
    flex-wrap: nowrap;
    overflow-x: auto;
    overscroll-behavior-x: contain;
    border-left: none;
    border-right: none;
    scrollbar-width: none;
  }
  .sticky-kpis::-webkit-scrollbar { display: none; }
  .sticky-kpi { flex: 0 0 auto; min-width: 42vw; padding: 8px 14px; }
  .sticky-kpi-val { font-size: 0.95rem; }

  /* The command palette is centred on desktop; on a phone it should start near the top so the
     on-screen keyboard does not cover the list it is filtering. */
  .cmdk-overlay { padding-top: calc(6vh + var(--safe-t)); align-items: flex-start; }
  .cmdk-panel { width: min(560px, 94vw); max-height: 70dvh; }
  .cmdk-row { padding: 14px; }

  /* The help guide is a full-height modal on a phone rather than a centred box with 24px of
     dead margin on all four sides. */
  #helpModal { padding: 0; align-items: stretch; }
  #helpModal .help-box {
    max-height: 100dvh;
    height: 100dvh;
    border-radius: 0;
    padding-bottom: calc(var(--safe-b) + 12px);
    padding-top: var(--safe-t);
  }

  /* ---------- 5. FORM CONTROLS — the iOS focus-zoom fix ----------
     Mobile Safari zooms the viewport whenever a text-entry control with a computed font-size
     below 16px receives focus, and never zooms back out; the user is left panning a magnified
     page. The fix is the font size itself, not user-scalable=no.
     16px is a FLOOR, applied with max(): fields already larger keep their size. Range,
     checkbox, radio and button controls never trigger the zoom and are excluded so their
     layout is untouched.

     !important is deliberate, and is the difference between a fix and a suggestion. This is a
     platform constraint, not a preference: dozens of scattered rules set control font sizes at
     higher specificity than any bare-element selector can reach (.holding-row select is 0,2,0
     and sets 0.83rem; .owner-select lands at 10.88px), and a floor that the next new rule can
     silently undercut would quietly re-break focus on one page. scripts/mobile-check.js asserts
     the floor holds on every control of every page, so the rule and its test agree.
     max() keeps it a FLOOR — anything already larger is left alone. 1em resolves against the
     parent, so inheriting-larger contexts stay proportional. */
  input:not([type="range"]):not([type="checkbox"]):not([type="radio"]):not([type="button"]):not([type="submit"]):not([type="reset"]),
  select,
  textarea {
    font-size: max(16px, 1em) !important;
    min-height: 44px;                     /* Apple HIG minimum hit target */
  }
  /* Bigger text in the same box would clip, so trade horizontal padding for it. The editor
     grids are already one column per row at this width, so the width is there to spend.
     LONGHAND, not the `padding` shorthand: `.holding-row select` paints its own chevron at
     `right 10px center` and reserves a 28px right gutter for it. This rule has the same (0,1,1)
     specificity and comes later, so a shorthand here silently reset that gutter to 10px and ran
     16px option text underneath the chevron. */
  .holding-row input, .holding-row select,
  .debt-row input, .debt-row select,
  .business-card input, .business-card select,
  .field input, .field select {
    padding-top: 10px;
    padding-bottom: 10px;
    padding-left: 10px;
    line-height: 1.25;
  }
  .holding-row input, .debt-row input,
  .business-card input, .field input { padding-right: 10px; }
  /* Selects keep their caret gutter, widened a little because the option text is now 16px. */
  .holding-row select, .debt-row select,
  .business-card select, .field select { padding-right: 30px; }
  /* Native iOS controls otherwise render with their own chrome and ignore the app's. */
  input[type="text"], input[type="number"], input[type="search"], textarea {
    -webkit-appearance: none;
    appearance: none;
    border-radius: var(--radius-ctl);
  }
  /* The date/number spinners iOS draws are unusable at this size and the app formats its own. */
  input[type="number"]::-webkit-outer-spin-button,
  input[type="number"]::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }

  /* Range sliders: the base track is 2px tall, which is a 2px-tall touch target. Grow the
     ELEMENT to 44px so there is something to hit, and repaint the track on the pseudo-element
     that actually draws it — the base rule paints the track as the input's own `background`, so
     a transparent 44px box would leave the thumb floating on nothing (measured: the Projection
     Year and Inflation sliders rendered as two empty rounded squares). */
  input[type="range"] {
    height: 44px;
    touch-action: pan-y;                  /* horizontal drags adjust; vertical still scrolls */
    -webkit-appearance: none;
    appearance: none;
    background: transparent;
  }
  input[type="range"]::-webkit-slider-runnable-track {
    height: 4px;
    background: var(--border);
    border-radius: var(--radius-ctl);
  }
  input[type="range"]::-moz-range-track {
    height: 4px;
    background: var(--border);
    border-radius: var(--radius-ctl);
  }
  /* Keeps the base rule's marker look (fill, border, ring, radius) — only the size changes.
     margin-top re-centres it on the 4px track: -(thumb - track) / 2. */
  input[type="range"]::-webkit-slider-thumb { width: 22px; height: 22px; margin-top: -9px; }
  input[type="range"]::-moz-range-thumb { width: 22px; height: 22px; }

  /* ---------- 6. TAP TARGETS ----------
     Small controls grow their HIT box with an absolutely-positioned ::after rather than their
     paint box, so the typeset density of the page survives. mobile-check.js measures this
     pseudo-element expansion, so the assertion and the technique agree. */
  /* NB .kpi-info is deliberately NOT in this list. It is already `position: absolute`
     (bottom/right, pinned into its card's corner), which is just as good a containing block for
     the ::after below — and setting `relative` on it dropped it back into normal flow, where an
     inline <span> ignores its own width/height and collapsed from 18x18 to 5x16 in the corner of
     every KPI card. Only add a selector here if it is currently `static`. */
  .mc-info-icon, .drag-handle, .derived-src,
  .btn-dup, .btn-remove, .btn-done, .btn-add, .goto, .coach-action,
  .btn-export, .view-line .vl-cell button,
  .section-jumper a, .growth-toggle button, .nw-mode-toggle button, .field a {
    position: relative;
  }
  .kpi-info::after, .mc-info-icon::after, .drag-handle::after, .derived-src::after,
  .btn-dup::after, .btn-remove::after, .btn-done::after, .btn-add::after,
  .goto::after, .coach-action::after,
  /* Segmented toggles (Overall/Per Year, Revenue/EBITDA, Nominal/Today's $) and the in-page
     jump links are 21-28px tall by design — they are typographic controls, and growing them to
     44px would turn a quiet strip into a row of slabs. They get the hit area without the bulk.
     The expander is 44px wide and centred, so on these 61px+ wide controls neighbouring
     expanders cannot overlap horizontally. */
  .section-jumper a::after, .growth-toggle button::after, .nw-mode-toggle button::after,
  .field a::after {
    content: '';
    position: absolute;
    top: 50%;
    left: 50%;
    width: 44px;
    height: 44px;
    transform: translate(-50%, -50%);
    /* Purely a hit area — it must never paint, and must never intercept a scroll gesture that
       began on it. pointer-events:auto is inherited from the host; the parent stays clickable. */
    background: transparent;
    pointer-events: auto;
  }
  /* Primary actions are wide already and simply read better at full size, so these grow their
     real box rather than a hidden one. */
  .btn-add, .btn-dup, .btn-remove, .btn-done, .btn-export, button.primary, .whatif-bar button,
  .allchecks, .whatif {
    min-height: 44px;
  }
  .drag-handle { min-width: 44px; min-height: 44px; display: flex; align-items: center; justify-content: center; }

  /* ---------- 7. HOVER-ONLY INFORMATION ----------
     A touch device has no hover, so anything revealed only by :hover is unreachable. Both info
     bubbles were already built for this: .kpi-info (created in render()) and .mc-info-icon (in
     index.html) each carry role="button" tabindex="0", and their show rules already include
     :focus alongside :hover — so a tap focuses them and the tip appears. Nothing to add here.
     What tapping does NOT do on iOS is take the focus back off again when you tap ordinary page
     content, which would leave the tip covering the figures underneath; app.js dismisses it on
     an outside tap (see the _tapDismissTips handler). */
  .kpi-info, .mc-info-icon { cursor: pointer; }

  /* ---------- 8. TABLES ----------
     The wide projection tables are genuinely wide data (the year-by-year table is ~970px), so
     they keep scrolling horizontally rather than being restacked into cards — a card per year
     for 20 years is worse, not better. What they get instead is a first column that stays put,
     so the year label is still there when the figures scroll. */
  .table-scroll { -webkit-overflow-scrolling: touch; }
  /* `left: 0` pins to the scroll container's PADDING edge, and .fold gives its scroller
     `padding: 0 20px 20px`. That left a 20px gutter outside the pinned column through which the
     scrolled-away columns were visible — fragments of other years' figures sliding along the
     left margin, directly beside the year they did not belong to. Zeroing the horizontal
     padding pins the column flush; the cells' own 12px padding still keeps the text off the
     edge, and a table that reaches the card edge reads as scrollable, which it is. */
  .fold > .table-scroll { padding-left: 0; padding-right: 0; }
  .table-scroll th:first-child,
  .table-scroll td:first-child {
    position: sticky;
    left: 0;
    z-index: 1;
  }
  .table-scroll th:first-child { z-index: 3; background: var(--surface); }
  .table-scroll td:first-child { background: var(--card); }
  /* The highlighted rows tint with a TRANSLUCENT token in dark theme, which a sticky cell
     cannot use — the scrolled columns would slide visibly under it. Compositing the tint over
     an opaque base as a gradient layer keeps the exact colour and makes it opaque. */
  .table-scroll tr.row-exit td:first-child {
    background: linear-gradient(var(--emerald-dim), var(--emerald-dim)), var(--card);
  }
  .table-scroll tr.row-now td:first-child {
    background: linear-gradient(var(--row-hover), var(--row-hover)), var(--card);
  }
  /* A hairline that reads as an edge where the sticky column ends. */
  .table-scroll th:first-child::after,
  .table-scroll td:first-child::after {
    content: '';
    position: absolute;
    top: 0;
    right: 0;
    bottom: -1px;
    width: 1px;
    background: var(--border);
  }
  th { padding: 10px 12px; }
  td { padding: 10px 12px; font-size: 0.8rem; }

  /* The Loan Payoff Dates schedule is built from independent CSS grids rather than a <table>
     (app.js renders one grid per row against a shared `fr` template), so it never inherited the
     .table-scroll treatment every real table gets, and nothing made it scroll.
     Two things were wrong on a phone. The data rows sized to their content — 479px inside a
     293px column — and were cut off at the screen edge with no way to reach the last columns.
     And because the header row is a SEPARATE grid, it squeezed to 293px while the data rows
     stayed at 479px, so the column headings did not line up with the figures beneath them.
     Giving both the same floor inside a scroller fixes the alignment and the reachability with
     one rule. 580px is the widest natural row (measured: 568px header, 479px data) rounded up. */
  #debtPayoffSchedule {
    overflow-x: auto;
    overscroll-behavior-x: contain;
    -webkit-overflow-scrolling: touch;
  }
  #debtPayoffSchedule > div,
  #debtPayoffSchedule > div > div { min-width: 580px; }

  /* ---------- 9. LAYOUT OVERFLOW ----------
     Measured clipping: every editor page laid its open row editor out wider than the screen
     with no scrollable ancestor, so body{overflow-x:hidden} cut it off silently — .asset-col
     ran to 575px inside a 320px viewport on Assets, 577px on Businesses, 538px on Savings.
     The cause is fixed-width grid tracks and min-width floors that were sized for a desktop
     column. Below 900px every one of these becomes a single column. */
  .asset-col, .company-col, .business-list, .business-card, .biz-card-grid,
  .panel, .panel-head, .fold, .section-panel, .inputs-panel {
    min-width: 0;
    max-width: 100%;
  }
  .asset-col, .company-col { grid-template-columns: 1fr; }
  .biz-card-grid { grid-template-columns: 1fr; }
  .biz-card-head, .biz-card-actions, .biz-value-strip { flex-wrap: wrap; }
  .panel-head { flex-wrap: wrap; }
  .panel-head .section-note, .panel-head .note, .panel-head a.goto { margin-left: 0; }
  /* Long unbroken figures and names are the other half of the overflow: a nowrap cell in a
     one-column grid still pushes past the viewport. */
  .field, .field input, .field select, .vl-cell { min-width: 0; }
  .vl-cell { overflow-wrap: anywhere; }
  .holding-header, .holding-fields, .holding-row, .debt-row { min-width: 0; max-width: 100%; }

  /* An open editor's header packs handle + name + value + owner select + Done + Duplicate +
     Remove onto one non-wrapping flex line. That is seven items; on a phone the last three ran
     up to 236px past the screen edge and were unreachable — you could open a row but not delete
     it. Let the line wrap, and let the name (not the buttons) absorb the slack. */
  .holding-header { flex-wrap: wrap; row-gap: 8px; }
  .holding-header-name { flex: 1 1 auto; min-width: 0; overflow-wrap: anywhere; }
  .holding-header-value { margin-left: auto; }
  /* Once wrapped, the three action chips read as a group on their own line. */
  .holding-header .btn-done,
  .holding-header .btn-dup,
  .holding-header .btn-remove { flex: 0 0 auto; }
  .expense-row-actions { flex-wrap: wrap; }
}

/* ---------- 9b. EDITOR-FIELD WIDTH FLOORS, RETIRED ON ONE-COLUMN ROWS ----------
   The per-data-field min-width floors near .holding-row above exist to stop grid-track
   starvation — a track collapsing toward 0 when a sibling's width changes — and their own
   comment records the invariant that made them safe: "Rows wrap below their breakpoint, so the
   floors never force horizontal overflow."

   Lifting every control to 16px for iOS broke that invariant. The floors are written in `ch`,
   so a bigger font makes each floor wider in px; at 320px the sum pushed the editor 236px past
   the viewport, where body{overflow-x:hidden} cut it off with no scrollbar.

   Below 560px each row is a SINGLE column (the !important rules above), so no track has a
   sibling to be starved by and the floors protect nothing — they only overflow. Retiring them
   here restores the original invariant at the new font size.

   !important is required and is the established idiom in this file for this exact conflict: the
   floors are beaten only at equal-or-higher specificity, and the set runs from (0,2,0) to
   (0,3,1) — matching each one individually would be a selector list that silently rots the next
   time a data-field is added. The neighbouring grid-template-columns overrides use !important
   for the same reason, documented at R-06. */
@media (max-width: 560px) {
  .holding-row input, .holding-row select, .holding-row .num-fmt,
  .debt-row input, .debt-row select, .debt-row .num-fmt {
    min-width: 0 !important;
  }
}

/* ---------- 10. NARROW PHONES ---------- */
@media (max-width: 560px) {
  /* Four tab labels plus More at 320px: shrink the label, never the icon. */
  .mnav-item { font-size: 0.66rem; }
  .mnav-item svg { width: 19px; height: 19px; }
  /* The masthead's controls are a single row on desktop; on a phone they wrap, and the
     command-palette hint is redundant next to a bar with no keyboard. */
  .topline .cmdk-hint kbd { display: none; }
}

/* ---------- 11. TOUCH-CAPABILITY RULES ----------
     Keyed to the pointer itself, not the width: an iPad at 1024px is a coarse pointer on a wide
     screen, and a small desktop window is a fine pointer on a narrow one. Nothing here can
     apply to a mouse-driven desktop, which is always (hover: hover) and (pointer: fine). */
@media (hover: none) and (pointer: coarse) {
  /* Row actions that fade in on hover are simply invisible on a touch device. */
  .view-line .vl-actions, .expense-line .vl-actions, .holding-row .row-actions {
    opacity: 1;
    visibility: visible;
  }
  /* Hover tints never clear after a tap on iOS — the element keeps the hover state until you
     tap elsewhere, which reads as a stuck selection. */
  tr:hover td { background: transparent; }
  .nav-item:hover { background: transparent; color: var(--text-secondary); }
  .nav-item.active:hover { background: var(--gold-glow); color: var(--gold); }
  .fold-summary:hover { color: var(--text); }
  .btn-export:hover { background: transparent; color: var(--text-secondary); border-color: var(--border); }
}

/* ---------- 12. KPI TOOLTIPS ----------
   Measured on a 375px viewport: the tip is a fixed 280px wide and anchored to its card's right
   edge (.kpi-info .kpi-tip, right:-6px). Once the KPI grid drops to two columns, a card is only
   ~165px, so a left-column tip starts at x=-233 — 83% of the explanation sitting outside the
   document, eaten by body{overflow-x:hidden} with nothing to scroll it back. Every "(i)" on the
   app's most-used surface was effectively blank on a phone.

   Pinning it to the viewport makes its width independent of which column the card is in. It has
   to clear the fixed tab bar both geometrically and in paint order: .mnav is after </main> in
   the DOM, so an equal z-index would lose the tie to it.

   Lands at the end of the file because the base rules are .kpi-info .kpi-tip (0,2,0) and a later
   .kpi-info { bottom: 0 } (0,1,0) — an equal-specificity override only wins on source order. */
@media (max-width: 900px) {
  /* The fixed positioning below only holds if no ancestor is a containing block for it, and a
     transformed element is one. .kpi-row / .kpi / .chart-grid / .data-section all carry the
     fadeSlideUp entrance, whose transform was measured still applied on .kpi long after the
     450ms window — every render rebuilds these nodes via innerHTML and restarts the animation,
     so there is no quiet moment to rely on. Measured effect: the tip resolved against the CARD
     instead of the viewport and came out 124px wide instead of ~351px.
     This is the same trap the fadeSlideUp comment near the keyframes already documents (a
     `forwards` fill once pinned these tooltips under the rail). Dropping the entrance on mobile
     removes the containing block entirely rather than racing it — and a slide-up on every
     re-render is decoration a phone can do without. */
  .kpi-row, .chart-grid, .data-section, .kpi { animation: none; transform: none; }

  .kpi-info .kpi-tip {
    position: fixed;
    left: calc(12px + var(--safe-l));
    right: calc(12px + var(--safe-r));
    bottom: calc(var(--mnav-h) + var(--safe-b) + 12px);
    top: auto;
    width: auto;
    max-height: 45dvh;
    overflow-y: auto;
    /* The base rule is pointer-events:none (it must not block the card underneath on hover).
       A tip that can scroll has to receive the gesture, and on touch it is opened by an explicit
       tap rather than a passing cursor, so it can afford to. */
    pointer-events: auto;
    transform: none;
    font-size: 0.76rem;
  }
  .kpi-info:hover .kpi-tip,
  .kpi-info:focus-visible .kpi-tip,
  .kpi-info:focus .kpi-tip { transform: none; }
  /* .kpi-info is position:absolute with a z-index, so it is a stacking context and the tip's own
     z-index:10000 resolves INSIDE it — the icon's z-index is what actually competes with the
     page chrome. 9996 clears #tabHelpFab (9990) and .mnav (9995) while staying under
     .mnav-scrim (10000) and the rail sheet (10001), so an open nav sheet still covers the tip. */
  .kpi-info:hover,
  .kpi-info:focus,
  .kpi-info:focus-visible,
  .kpi-info:focus-within { z-index: 9996; }
}

/* ---------- 13. BACKDROP BLUR ON iOS ----------
   Safari needed -webkit-backdrop-filter until version 18, and drops the unprefixed property
   entirely before that. .sticky-bar and .mnav already ship both; .topline and .cmdk-overlay ship
   only the unprefixed one, so on iOS 15-17 they render as flat translucency with the scrolling
   page fully legible through them. .topline is the worst place for it: sticky at top:0 over the
   scrolling page at 82% opacity, and on a phone it is the only thing naming the current page.

   Scoped to the mobile range on purpose. Adding the prefix unscoped would also switch on a blur
   that desktop Safari 16/17 does not draw today — a real rendering change on a desktop, which is
   exactly what this work is not allowed to do. The trade is that desktop Safari <18 keeps its
   existing unblurred topline; matching Chrome there is a separate, deliberate decision. */
@media (max-width: 900px) {
  .topline.report-masthead { -webkit-backdrop-filter: blur(12px); }
  .cmdk-overlay { -webkit-backdrop-filter: blur(3px); }
}

/* ---------- 14. THE BOTTOM STACK ----------
   Three fixed elements were each written when nothing else was pinned to the bottom of a phone
   screen. They now have to queue above the tab bar instead of landing on top of it. Ordered
   bottom-up: .mnav (0) < .whatif-bar < #_toast, with the help FAB beside them. */
@media (max-width: 900px) {
  /* The What-If banner carries the ONLY Exit / Apply / Save controls for a mode in which every
     figure on screen is provisional. It was set `position: static` so it could sit above the old
     wrapping top-nav strip — but that strip is gone, so static now parks the escape hatch at the
     very top of the document, where the first swipe scrolls it away for the rest of the session.
     Pin it to the bottom stack, where the thumb already is. */
  .whatif-bar {
    position: fixed;
    top: auto;
    left: 0;
    right: 0;
    bottom: calc(var(--mnav-h) + var(--safe-b));
    /* Below .mnav (9995) and well below .mnav-scrim (10000) and the sheet (10001), so opening
       the nav sheet dims this rather than fighting it for the same strip. */
    z-index: 9993;
    gap: 8px;
    padding: 8px calc(12px + var(--safe-r)) 8px calc(12px + var(--safe-l));
    flex-wrap: wrap;
    justify-content: center;
    /* The banner's ground is var(--marker) — a 16%-alpha wash. That reads fine on desktop, where
       it is a strip across the top of the page with little behind it, and is illegible here,
       where it is pinned over the densest part of a scrolling page: figures and (i) icons showed
       straight through the Exit button. Composite the exact same tint over an opaque base as a
       gradient layer, so the colour is unchanged and the bar is solid. */
    background: linear-gradient(var(--marker), var(--marker)), var(--surface);
    border-top: 1px solid var(--border);
    box-shadow: var(--shadow-hard);
  }
  /* The explanatory sentence has no room beside three 44px buttons; the gold What-If chip and
     the buttons themselves carry the meaning. The full sentence is untouched on desktop. */
  .whatif-bar > span:not(.whatif-label) { display: none; }
  .whatif-bar .whatif-save-btn,
  .whatif-bar .whatif-apply-btn,
  .whatif-bar .whatif-exit-btn { flex: 1 1 96px; font-size: 0.7rem; padding: 10px 8px; }
  /* The banner is no longer in flow, so the top padding it used to justify is wrong; the bottom
     has to clear the banner AND the bar beneath it. The three buttons wrap to two rows at 320px,
     hence 108px rather than a single-row guess. */
  body.whatif-active .wrapper {
    padding-top: 20px;
    padding-bottom: calc(var(--mnav-h) + var(--safe-b) + 108px);
  }
  body.whatif-active #tabHelpFab { bottom: calc(var(--mnav-h) + var(--safe-b) + 108px); }

  /* Sample bar joins the same bottom stack rather than fighting for the top strip, for the same
     reason What-If does: "Start my own plan" is the only way out of a sample session that keeps
     the work, and at the top of the document the first swipe scrolls it away. When both bars are
     up the sample bar sits above the What-If bar, preserving the desktop stacking order. */
  .sample-bar {
    position: fixed;
    top: auto;
    left: 0;
    right: 0;
    bottom: calc(var(--mnav-h) + var(--safe-b));
    z-index: 9993;
    gap: 8px;
    padding: 8px calc(12px + var(--safe-r)) 8px calc(12px + var(--safe-l));
    flex-wrap: wrap;
    justify-content: center;
    border-top: 1px solid var(--border);
    border-bottom: none;
    box-shadow: var(--shadow-hard);
  }
  body.sample-active.whatif-active .sample-bar { bottom: calc(var(--mnav-h) + var(--safe-b) + 108px); }
  /* The confidence pill is the only tappable thing in the pill row, and as a <span> it is
     invisible to mobile-check's tap-target sweep. Give it the 44px floor by hand. It ends up
     taller than the static pills beside it; that is the right trade — a 26px target on a phone
     is a miss waiting to happen, and the height difference reads as "this one is a control". */
  .dp-act { display: inline-flex; align-items: center; min-height: 44px; padding: 4px 14px; }

  /* No room for the sentence beside two 44px buttons; the chip and the buttons carry it. */
  .sample-bar > span:not(.sample-label) { display: none; }
  .sample-bar .sample-keep-btn,
  .sample-bar .sample-exit-btn { flex: 1 1 96px; font-size: 0.7rem; padding: 10px 8px; }
  body.sample-active .wrapper {
    padding-top: 20px;
    padding-bottom: calc(var(--mnav-h) + var(--safe-b) + 72px);
  }
  body.sample-active #tabHelpFab { bottom: calc(var(--mnav-h) + var(--safe-b) + 72px); }
  body.sample-active.whatif-active .wrapper { padding-bottom: calc(var(--mnav-h) + var(--safe-b) + 180px); }
  body.sample-active.whatif-active #tabHelpFab { bottom: calc(var(--mnav-h) + var(--safe-b) + 180px); }

  /* The toast is the app's only undo affordance — every destructive row delete routes through
     showToast() — and it was pinned at bottom:28px, which now lands squarely on the tab bar. It
     paints at z-index 100001 against the bar's 9995, so it did not just overlap the bar, it
     covered it and swallowed taps meant for navigation. It also has a 6s lifetime, so a mis-hit
     is unrecoverable. Stack it above the bar, and clear the help FAB beside it
     (14px gutter + 44px FAB + 10px gap) rather than covering it. */
  #_toast {
    left: calc(12px + var(--safe-l));
    right: calc(12px + var(--safe-r));
    max-width: none;
    bottom: calc(var(--mnav-h) + var(--safe-b) + 68px);
    /* left/right do the positioning now, so the centring translateX must come out of BOTH the
       resting and shown transforms — otherwise the toast sits half a screen to the left. */
    transform: translateY(16px);
    justify-content: space-between;
    padding: 14px 16px;
    font-size: 0.95rem;
  }
  #_toast.show { transform: translateY(0); }
  /* Undo is the highest-stakes 22px button in the app. */
  ._toast-action { font-size: 0.85rem; padding: 11px 16px; border-radius: var(--radius-ctl); }
  body.whatif-active #_toast { bottom: calc(var(--mnav-h) + var(--safe-b) + 176px); }
}

/* ---------- 15. TABLE SCROLL CUE ----------
   The only affordance saying a table scrolls sideways is the ::-webkit-scrollbar styled near
   .table-scroll, and its own comment explains it was forced visible because macOS overlay
   scrollbars stay hidden until you scroll. iOS Safari paints NO persistent scrollbar for a touch
   scroller at all, so on a phone that cue does not exist — a table simply looks like it ends at
   the screen edge, which is exactly the impression the ≤900px sticky-column work is trying to
   avoid.

   Pure CSS, no scroll listener: the classic background-attachment pair. The `local` layer scrolls
   WITH the content and covers the shadow when there is nothing further that way; the `scroll`
   layer is fixed to the element and shows through when there is. Self-updating by construction,
   so there is no observer to keep in sync with re-renders.

   Only the RIGHT edge gets one — the left is already unambiguous, because the sticky first column
   and its hairline sit there. */
@media (max-width: 900px) {
  .table-scroll {
    background:
      linear-gradient(to left, var(--card) 40%, transparent) right center / 26px 100% no-repeat local,
      radial-gradient(farthest-side at 100% 50%, rgba(0, 0, 0, 0.26), transparent) right center / 13px 100% no-repeat scroll;
  }
  [data-theme="light"] .table-scroll {
    background:
      linear-gradient(to left, var(--card) 40%, transparent) right center / 26px 100% no-repeat local,
      radial-gradient(farthest-side at 100% 50%, rgba(60, 50, 30, 0.2), transparent) right center / 13px 100% no-repeat scroll;
  }
}

/* ---------- 16. HOME PORTFOLIO — DROP THE DUPLICATED COLUMN ----------
   Column 3 is "Share" as a percentage; column 4 is the same figure again as a .share-bar, with a
   blank header. On desktop the bar earns its place — it is the proportion-at-a-glance read. On a
   375px phone it is 90px of the widest column in the table spent restating the number 8px to its
   left, inside a 301px scroller. Keep the number, drop the second rendering.
   This does NOT stop the table scrolling, and is not meant to: .table-scroll is this app's
   deliberate pattern for wide tables and the document itself never scrolls sideways. */
@media (max-width: 900px) {
  #homePortfolio thead th:nth-child(4),
  #homePortfolio tbody td:nth-child(4) { display: none; }
}

/* ---------- 17. STICKY TABLE HEADERS, ACTIVATED ----------
   `th { position: sticky; top: 0 }` has been in this file all along and has never once worked.
   Per CSS Overflow 3, when one axis is `visible` and the other is not, `visible` computes to
   `auto` — so `.table-scroll { overflow-x: auto }` already makes the element a scroll container
   on BOTH axes, and that container is the sticky header's nearest scrollport. It simply has no
   height constraint, so it never scrolls vertically and the header never has anything to stick
   to. The year-by-year and withdrawal tables run 25-30+ rows; on a phone their column headings
   were off screen for the entire read.

   Scoped to `.fold > .table-scroll`, which is exactly the set of big always-open detail tables
   (Year-by-Year, Debt Payoff Schedule, Income Projection, Business Equity Detail) — the ones long
   enough to need it. Short in-panel tables keep plain page scroll and never become a nested
   scroller, which is the failure mode this treatment is worth avoiding elsewhere.

   svh, not dvh: a max-height that grew and shrank as the iOS URL bar collapsed would reflow the
   table under the reader's finger mid-scroll. svh is the stable (URL-bar-shown) height. */
@media (max-width: 900px) {
  .fold > .table-scroll {
    max-height: 70svh;
    overscroll-behavior-y: contain;   /* don't hand a flick at the end back to the page */
  }
  /* border-collapse:collapse makes the th's border-bottom a TABLE border, which does not travel
     with the pinned cell — the header would scroll over the rows with no rule under it. Redraw
     it as an inset shadow on the cell itself, which does travel. */
  .fold > .table-scroll th { box-shadow: inset 0 -1px 0 var(--border); }
  /* The base ::-webkit-scrollbar rule sets only `height`, so the now-vertical scrollbar would
     have zero width — and styling it at all opted this element out of overlay scrollbars. */
  .fold > .table-scroll::-webkit-scrollbar { width: 8px; height: 8px; }
  /* The corner where the sticky header meets the sticky first column must outrank both. */
  .fold > .table-scroll th:first-child { z-index: 4; }
}
