:root {
  color-scheme: light;
  --bg: #f7f7f5; --card: #ffffff; --border: #e1e0d9; --text: #1a1a1a;
  --muted: #6b6b66; --accent: #2a6f4b; --accent-dark: #1f5138; --danger: #c0392b;
  /* Theme tokens added in v2.5.0 (dark mode). Every colour that used to be
     a literal hex further down this file is now one of these, so a theme is
     just a different set of values for this list -- see the
     html[data-theme="dark"] block right below, and js/theme.js for how the
     attribute gets set (System / Light / Dark, remembered per browser). */
  --input-bg: #ffffff;
  --surface: #f0f0ec;      /* table heads, input add-ons, plan picker bar */
  --surface-2: #fafaf7;    /* status cards, quick-adjust panel */
  --tint: #f0f6f3;         /* summary cards, narration box */
  --tint-2: #f4f8f5;       /* hover / selected rows and cards */
  --track: #ece9e2;        /* progress-bar track */
  --text-soft: #333333;
  --rule: #dddddd;         /* thin separators drawn inline in the app */
  --faint: #6e6e6e;        /* footer / de-emphasised small print */
  --btn-bg: #2a6f4b; --btn-bg-hover: #1f5138; --on-accent: #ffffff;
  --danger-solid: #c0392b;
  --danger-bg: #fdf0ee; --danger-border: #e8b4ac; --danger-text: #8a2e1f;
  --fatal-bg: #fde8e8; --fatal-text: #7a1f1f;
  --ok-bg: #eaf3ee;
  --log-bg: #fbfbf9; --warn-text: #8a5a00;
  --hit-bg: #ffe8a3; --hit-cur-bg: #ff9d3d; --hit-cur-text: #1a1200; --hit-cur-ring: #b5650a;
  --shadow: rgba(0,0,0,0.04); --shadow-strong: rgba(0,0,0,0.25); --overlay: rgba(0,0,0,0.45);
  --chart-text: #444444; --chart-grid: rgba(0,0,0,0.1);
  --info: #2c5aa0; --caution: #8a6500;
  --notice-bg: #fff6e0; --notice-border: #e3b45a; --notice-text: #5e3d00;
  --c-green: #2a6f4b; --c-red: #c0392b; --c-blue: #2c5aa0; --c-amber: #c98a1e; --c-ira: #e8484a; --c-roth: #1baf7a; --c-taxable: #2a78d6;
  --select-arrow: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%236b6b66' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><polyline points='6 9 12 15 18 9'></polyline></svg>");
}
html[data-theme="dark"] {
  color-scheme: dark;
  --bg: #121513; --card: #1b1f1d; --border: #2f3632; --text: #e8ebe9;
  --muted: #9aa39e; --accent: #5cc592; --accent-dark: #8fdcb4; --danger: #ef7b6e;
  --input-bg: #141816;
  --surface: #252b28; --surface-2: #1f2421; --tint: #1d2a24; --tint-2: #22312a;
  --track: #2a312d; --text-soft: #cfd5d1; --rule: #3a423e; --faint: #8b948f;
  --btn-bg: #2e7d57; --btn-bg-hover: #24654a; --on-accent: #ffffff;
  --danger-solid: #c0392b;
  --danger-bg: #3a1f1c; --danger-border: #7a3d36; --danger-text: #f2b4aa;
  --fatal-bg: #3d1c1c; --fatal-text: #f5b5b5;
  --ok-bg: #1d2a24;
  --log-bg: #161a18; --warn-text: #e0b04a;
  --hit-bg: #6b5a1a; --hit-cur-bg: #d9822b; --hit-cur-text: #1a1200; --hit-cur-ring: #f0b36a;
  --shadow: rgba(0,0,0,0.4); --shadow-strong: rgba(0,0,0,0.6); --overlay: rgba(0,0,0,0.65);
  --chart-text: #c9d0cc; --chart-grid: rgba(255,255,255,0.12);
  --info: #7fb0ee; --caution: #e0b04a;
  --notice-bg: #2d2410; --notice-border: #8f6d1f; --notice-text: #f2d38a;
  --c-green: #3fae7a; --c-red: #e5675c; --c-blue: #6aa5f0; --c-amber: #e0a94a; --c-ira: #f06a6c; --c-roth: #2fcf95; --c-taxable: #5b9df0;
  --select-arrow: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%239aa39e' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><polyline points='6 9 12 15 18 9'></polyline></svg>");
}
* { box-sizing: border-box; }
body {
  margin: 0; font-family: -apple-system, "Segoe UI", Roboto, Arial, sans-serif;
  background: var(--bg); color: var(--text);
  /* Safety net, not a fix for any specific element: if ANYTHING on the
     page ever ends up wider than the viewport (a stray wide table, a
     long unbreakable string, etc.), mobile browsers don't just show a
     horizontal scrollbar for it -- by default they can widen the whole
     page's effective layout viewport to fit that one element, which
     shrinks the apparent (zoomed-out) size of EVERYTHING else on the
     page along with it. Clipping overflow at the html/body level keeps
     one oversized descendant from ever doing that; elements that
     legitimately need their own horizontal scrolling (like
     .results-table below) still get it via their own overflow-x:auto,
     which isn't affected by a hidden overflow further up the tree. */
  overflow-x: hidden;
}
html { overflow-x: hidden; }
.container { max-width: 480px; margin: 80px auto; padding: 0 20px; }
.container.wide { max-width: 980px; margin: 40px auto; }
.auth-brand { text-align: center; margin-bottom: 24px; }
.auth-brand img { height: 48px; }
.card {
  background: var(--card); border: 1px solid var(--border); border-radius: 12px;
  padding: 32px; box-shadow: 0 1px 3px var(--shadow);
}
h1 { font-size: 22px; margin: 0 0 4px; }
.subtitle { color: var(--muted); font-size: 14px; margin-bottom: 24px; }
label { display: block; font-size: 13px; font-weight: 600; margin: 14px 0 4px; }
input, select, textarea {
  width: 100%; padding: 9px 11px; border: 1px solid var(--border); border-radius: 8px;
  font-size: 14px; background: var(--input-bg); color: var(--text);
}
textarea { font-family: inherit; resize: vertical; }
/* input, select above (bare tag selector) also matches checkboxes/radios,
   which is wrong for them: width:100% gives a checkbox a huge flex-basis
   inside any flex-laid-out label (e.g. the disclaimer-gate acknowledgment
   checkbox in plan-app.js), squeezing the adjacent label text down to a
   few characters per line while the native checkbox glyph itself stays
   visually tiny (browsers largely ignore box-model width on the actual
   control). This attribute selector has higher specificity than the bare
   `input` above, so it wins regardless of source order. */
input[type="checkbox"], input[type="radio"] {
  width: auto;
  height: 16px;
  padding: 0;
  border-radius: 3px;
  accent-color: var(--accent);
}
/* Safari (and other browsers) render <select> with their own tight native
   chrome and dropdown arrow that our normal input padding doesn't account
   for, which is what made this look cramped next to the custom-styled
   money/percent fields around it. Stripping the native appearance and
   drawing our own arrow makes it consistent everywhere. */
select {
  appearance: none; -webkit-appearance: none; -moz-appearance: none;
  padding: 11px 40px 11px 12px;
  background-image: var(--select-arrow);
  background-repeat: no-repeat;
  background-position: right 12px center;
  background-size: 15px;
  line-height: 1.3;
  cursor: pointer;
}
fieldset {
  border: 1px solid var(--border); border-radius: 10px; margin-top: 18px; padding: 14px 16px;
  /* Browsers give <fieldset> an implicit `min-width: min-content` in
     their default stylesheet -- meaning a fieldset REFUSES to shrink
     below the intrinsic width of its widest content, no matter what
     overflow rules a descendant has. This silently overrides
     .table-scroll's overflow-x:auto on any wide table inside: the
     table wrapper was working exactly as intended, but the fieldset
     ANCESTOR was forcing itself (and everything else inside it, like
     an ordinary paragraph next to that table) wider than the viewport
     regardless. This is what caused the Social Security optimization
     and Income & Expenses sections to visually "truncate" -- the
     WHOLE fieldset was rendering wider than the phone screen, right
     up until a wide table inside it (the "Full comparison" grid, or
     the budget items table) was actually expanded/populated with real
     data, since that's what made its min-content width exceed the
     viewport. Resetting min-width to 0 is the standard, well-known fix
     -- it makes <fieldset> behave like a normal block container that
     actually respects overflow-x:auto on its children again. */
  min-width: 0;
}
legend { font-weight: 600; padding: 0 6px; font-size: 13px; color: var(--accent-dark); }
.grid-2 { display: grid; grid-template-columns: 1fr 1fr; gap: 0 16px; }
button {
  margin-top: 22px; width: 100%; padding: 11px; border: none; border-radius: 8px;
  background: var(--btn-bg); color: var(--on-accent); font-size: 15px; font-weight: 600; cursor: pointer;
}
button:hover { background: var(--btn-bg-hover); }
button.secondary { background: transparent; color: var(--accent); border: 1px solid var(--accent); }
/* Every one of this app's async actions (Calculate, Export, Monte Carlo,
   the Social Security optimizer, longevity check, mortality-weighted
   optimizer, admin refresh/load-older, ...) disables its own button while
   in flight and swaps its label to something like "Generating
   spreadsheet…" -- but until now, nothing here ever styled `:disabled`
   at all, so the cursor stayed a plain pointer and the button looked
   fully clickable throughout, with the label change as the only (easy to
   miss) sign anything was happening. This single rule fixes that
   everywhere at once, rather than patching each button's JSX
   individually: `not-allowed` is the conventional cursor for "this
   control is temporarily unavailable," and the dimmed opacity gives a
   second, glance-able signal alongside it. The click-blocking itself was
   already correct (each handler already guards against re-entry and the
   disabled attribute already stops the browser from dispatching the
   click at all) -- this only fixes the missing visual feedback. */
button:disabled, select:disabled {
  cursor: not-allowed;
  opacity: 0.6;
}
button:disabled:hover { background: var(--btn-bg); } /* keep-disabled buttons from getting the :hover darken above -- disabled means disabled, even mid-hover */
button.secondary:disabled:hover { background: transparent; }
.error { color: var(--danger); font-size: 13px; margin-top: 10px; display: none; }
.link { text-align: center; margin-top: 16px; font-size: 13px; }
.link a { color: var(--accent); text-decoration: none; }
nav {
  display: flex; justify-content: space-between; align-items: center;
  padding: 14px 24px; background: var(--card); border-bottom: 1px solid var(--border);
}
nav .brand { font-weight: 700; display: flex; align-items: center; }
nav .brand img { height: 26px; display: block; }
nav button { width: auto; margin: 0; padding: 7px 14px; font-size: 13px; }
/* Small, unobtrusive version badge -- used in nav bars (plan screens)
   and standalone below the login card. Muted so it doesn't compete with
   real content, but visible enough to check at a glance. */
.app-version { color: var(--muted); font-size: 12px; }
nav .app-version { margin-left: 10px; font-weight: 400; }
/* Support contact line shown on every pre-login auth page (login,
   register, forgot/reset password, verify-email) -- these are exactly
   the screens where someone can be genuinely stuck with no way to reach
   the in-app Feedback form, since that requires already being signed in.
   Deliberately styled as a quiet footer note, not a call-to-action button
   -- same visual weight as .app-version, which it normally sits next to. */
.auth-support { text-align: center; font-size: 12px; color: var(--muted); margin-top: 10px; }
.auth-support a { color: var(--accent); text-decoration: none; }
.auth-support a:hover { text-decoration: underline; }
.results-table { width: 100%; border-collapse: collapse; margin-top: 16px; font-size: 13px; }
.results-table th, .results-table td { padding: 6px 8px; border-bottom: 1px solid var(--border); text-align: right; }
.results-table th:first-child, .results-table td:first-child { text-align: left; }
.results-table thead { background: var(--surface); }
/* A fixed 3-column grid rather than flex-wrap: SummaryCards always
   renders exactly 9 items (see plan-app.jsx), so 3 columns means 3 full,
   even rows every time -- no ragged trailing row, and no dependency on
   how many 160px-ish cards happen to fit at the current container width
   (which is what flex-wrap here used to do, and why the last row could
   end up shorter AND its cards a different width than the rows above --
   flex:1 stretches leftover space across however many cards land in that
   final row). If a future edit changes the item count away from a
   multiple of 3, the trailing row will simply end in blank grid cells
   rather than mismatched card widths -- prefer that over deleting a real
   figure (like Total IRMAA surcharge) just to force a particular count. */
.summary-row { display: grid; grid-template-columns: repeat(3, 1fr); gap: 12px; margin: 20px 0; }
.summary-card { background: var(--tint); border-radius: 10px; padding: 12px 16px; }
.summary-card .label { font-size: 12px; color: var(--muted); }
.summary-card .value { font-size: 18px; font-weight: 700; color: var(--accent-dark); }
.chart-wrap {
  background: var(--card); border: 1px solid var(--border); border-radius: 10px;
  padding: 16px; margin-top: 20px;
  /* Chart.js needs an explicit height on the container when
     maintainAspectRatio:false (see plan-app.js/plan-classic.js) --
     without one, "fill the container" has no container size to fill.
     position:relative is required too: Chart.js absolutely-positions
     the canvas inside its parent to do that filling. */
  position: relative; height: 340px;
}

.input-group {
  display: flex; align-items: stretch; border: 1px solid var(--border);
  border-radius: 8px; overflow: hidden; background: var(--input-bg);
}
.input-group input {
  border: none; flex: 1; min-width: 0; padding: 9px 11px; font-size: 14px;
}
.input-group input:focus { outline: none; }
.input-group span {
  display: flex; align-items: center; padding: 0 12px; font-size: 14px;
  color: var(--muted); background: var(--surface); white-space: nowrap;
}
.input-group.money span { border-right: 1px solid var(--border); }
.input-group.percent span { border-left: 1px solid var(--border); }

.field-block {
  margin-top: 18px; padding-top: 16px; border-top: 1px dashed var(--border);
}
.field-block:first-child { margin-top: 0; padding-top: 0; border-top: none; }
.field-block > label:first-child { margin-top: 0; margin-bottom: 8px; }
.field-hint {
  font-size: 12px; color: var(--muted); line-height: 1.55; margin: 8px 0 0;
}

/* Guided setup wizard (GuidedSetup in plan-app.jsx). Deliberately reuses
   .field-block/.grid-2/.field-hint/button.secondary for everything a
   step's fields need -- only the wizard-only chrome (progress bar, the
   Yes/No/itemized choice row, the goal-picker radio list, and the
   Back/Next footer) gets its own rules here. */
.wizard-progress {
  height: 6px; border-radius: 3px; background: var(--track); overflow: hidden; margin-top: 18px;
}
.wizard-progress-fill {
  height: 100%; background: var(--accent); transition: width 0.2s ease;
}
.wizard-step-title { font-size: 1.05em; margin: 0 0 4px; color: var(--accent-dark); }
.wizard-choice-row {
  display: flex; flex-wrap: wrap; gap: 8px; margin-top: 8px;
}
.wizard-choice-row button { width: auto; margin-top: 0; padding: 9px 14px; font-size: 14px; }

/* Selected-state for the wizard's Yes/No choice buttons and goal cards
   (v2.5.0). Before this, a chosen choice was the app's solid-green primary
   button -- the exact look of the Next / Build my plan button right below
   it -- and an unchosen one was a green outline, so colour was the only
   clue and "selected" read as "press me". Now:
     - Next stays the ONLY solid-green control on the screen;
     - a selected choice gets a tinted fill, a thicker accent border and an
       explicit check mark (so it never relies on colour alone), and is
       announced to screen readers via aria-pressed / the radio's checked state;
     - unselected choices are a quiet neutral outline. */
.choice-btn {
  position: relative; display: inline-flex; align-items: center; gap: 8px;
  background: var(--card); color: var(--text); border: 2px solid var(--border);
  font-weight: 600;
}
.choice-btn:hover { background: var(--tint-2); border-color: var(--accent); }
.choice-btn .choice-mark {
  display: inline-flex; align-items: center; justify-content: center; flex: none;
  width: 18px; height: 18px; border-radius: 50%; border: 2px solid var(--muted);
  font-size: 12px; line-height: 1; color: transparent;
}
.choice-btn.selected {
  background: var(--tint-2); color: var(--accent-dark); border-color: var(--accent);
}
.choice-btn.selected:hover { background: var(--tint-2); }
.choice-btn.selected .choice-mark {
  background: var(--btn-bg); border-color: var(--btn-bg); color: var(--on-accent);
}
.choice-btn:focus-visible, .wizard-goal-option:focus-within {
  outline: 3px solid var(--accent); outline-offset: 2px;
}

.wizard-goal-list { display: flex; flex-direction: column; gap: 10px; margin-top: 10px; }
.wizard-goal-option {
  position: relative; display: flex; align-items: flex-start; gap: 10px; padding: 12px 44px 12px 14px;
  border: 2px solid var(--border); border-radius: 8px; cursor: pointer; background: var(--card);
}
.wizard-goal-option:hover { background: var(--tint-2); }
.wizard-goal-option.selected { border-color: var(--accent); background: var(--tint-2); }
/* Check badge at the right edge -- empty ring when not chosen, filled
   check when chosen. Driven by the .selected class (set from React state)
   rather than :has(input:checked), which older Firefox/Safari builds lack. */
.wizard-goal-option::after {
  content: ""; position: absolute; right: 14px; top: 14px; box-sizing: border-box;
  width: 20px; height: 20px; border-radius: 50%; border: 2px solid var(--muted);
  display: flex; align-items: center; justify-content: center;
  font-size: 13px; line-height: 16px; text-align: center; color: transparent;
}
.wizard-goal-option.selected::after {
  content: "\2713"; background: var(--btn-bg); border-color: var(--btn-bg); color: var(--on-accent);
}
.wizard-goal-option input[type="radio"] { margin-top: 3px; }
.wizard-goal-option .field-hint { margin-top: 2px; }
.wizard-review-list { margin: 10px 0 0; padding-left: 20px; line-height: 1.7; }
.wizard-nav {
  display: flex; justify-content: space-between; margin-top: 24px; padding-top: 16px;
  border-top: 1px dashed var(--border);
}
.wizard-nav button { width: auto; margin-top: 0; padding: 10px 18px; }

/* Top-level Wizard / Form View / Dashboard switcher (App component).
   Same segmented-button look as .wizard-choice-row, reused rather than
   inventing a second "selected tab" visual language. */
.view-switcher { display: flex; gap: 8px; margin-bottom: 16px; }
.view-switcher button { width: auto; margin-top: 0; padding: 9px 16px; }

/* Dashboard (Dashboard/QuickAdjustPanel/DashboardStatusCard in
   plan-app.jsx). The headline strip is deliberately a *summary above* the
   existing, more detailed SummaryCards grid -- not a replacement for it;
   see the Dashboard component's own comment for why nothing here
   reimplements ChartsPanel/MonteCarloPanel/SSOptimizerPanel/
   ComparisonPanel/ResultsTable. */
.dashboard-status-strip {
  display: grid; grid-template-columns: repeat(4, 1fr); gap: 12px; margin: 18px 0;
}
.dashboard-status-card {
  border: 1px solid var(--border); border-radius: 10px; padding: 14px 16px; background: var(--surface-2);
}
.dashboard-status-label { font-size: 12px; color: var(--muted); font-weight: 600; text-transform: uppercase; letter-spacing: 0.03em; }
.dashboard-status-value { font-size: 1.6em; font-weight: 700; margin-top: 4px; color: var(--accent-dark); }
.dashboard-status-sub { font-size: 12px; color: var(--muted); margin-top: 4px; }

/* Which saved plan the Dashboard is showing. */
.dashboard-plan-line { display: flex; flex-wrap: wrap; align-items: center; gap: 6px 10px; margin: 0 0 8px; font-size: 15px; }
.dashboard-plan-label { color: var(--muted); }
.dashboard-plan-name { color: var(--text); overflow-wrap: anywhere; }
.dashboard-plan-badge { font-size: 12px; padding: 2px 9px; border-radius: 999px; background: var(--notice-bg); border: 1px solid var(--notice-border); color: var(--notice-text); }
.dashboard-plan-change { width: auto; flex: 0 0 auto; margin: 0; padding: 3px 12px; font-size: 13px; }
.dashboard-status-prompt {
  display: flex; flex-direction: column; align-items: flex-start; justify-content: center;
  cursor: pointer; font: inherit; text-align: left;
  /* This is a <button>, unlike its three sibling <div className="dashboard-status-card">
     cards, so it also inherits the global `button` rule's `margin-top: 22px`
     (nothing here previously overrode it). A CSS Grid item with a non-auto
     margin gets that margin subtracted from its stretched cross-axis size,
     so left as-is this card ends up a full 22px shorter than the others in
     the same row -- reset to 0 so all four stretch to the same height. */
  margin-top: 0;
}
.dashboard-status-cta { font-size: 1.05em; font-weight: 700; margin-top: 6px; color: var(--accent); }

/* 3 equal tracks + 12px gap -- the exact same column unit .summary-row
   above uses (repeat(3, 1fr), gap: 12) -- rather than an independent
   1fr/320px split. That makes .dashboard-rail-col (and the
   quick-adjust-panel/explore-further it holds) always exactly as wide as
   one SummaryCards card, at every viewport, instead of a fixed 320px
   guess that happened to be close on some screens and off on others. */
.dashboard-columns { display: grid; grid-template-columns: repeat(3, 1fr); gap: 12px; align-items: start; margin-top: 8px; }
.dashboard-main-col { grid-column: span 2; min-width: 0; } /* min-width: 0 so Chart.js canvases can shrink inside the grid track instead of overflowing it */
.dashboard-rail-col { grid-column: span 1; min-width: 0; }

.quick-adjust-panel {
  border: 1px solid var(--border); border-radius: 10px; padding: 16px; background: var(--surface-2);
}
.quick-adjust-panel .field-block:first-of-type { margin-top: 0; padding-top: 0; border-top: none; }

.explore-further { margin-top: 20px; }
.explore-card {
  display: flex; flex-direction: column; align-items: flex-start; gap: 4px; width: 100%;
  text-align: left; font: inherit; background: var(--card); color: inherit;
  border: 1px solid var(--border); border-radius: 8px; padding: 12px 14px; margin-top: 8px; cursor: pointer;
}
.explore-card:hover { border-color: var(--accent); background: var(--tint-2); }
.explore-card.active { border-color: var(--accent); background: var(--tint-2); }
.explore-card .field-hint { margin-top: 0; }

@media (max-width: 900px) {
  .dashboard-status-strip { grid-template-columns: 1fr 1fr; }
  /* Single stacked column below 900px -- both spans collapse to the
     full-width track so main/rail stack top-to-bottom instead of trying
     to honor their wide-layout span (2 and 1) inside just one column. */
  .dashboard-columns { grid-template-columns: 1fr; }
  .dashboard-main-col, .dashboard-rail-col { grid-column: span 1; }
  .summary-row { grid-template-columns: repeat(2, 1fr); }
}
@media (max-width: 480px) {
  .dashboard-status-strip { grid-template-columns: 1fr; }
  .summary-row { grid-template-columns: 1fr; }
}
.state-tax-warning {
  background: var(--danger-bg); border: 1px solid var(--danger-border); color: var(--danger-text);
  border-radius: 8px; padding: 10px 14px; font-size: 13px; line-height: 1.5;
  margin-bottom: 16px;
}
/* Same visual language as .state-tax-warning, under a name that isn't
   tied to that one specific feature -- for any action failure that
   needs to actually stop the person (a wrong current password, a failed
   save), where the plain, small .error text elsewhere in the app reads
   as too easy to miss. */
.error-banner {
  background: var(--danger-bg); border: 1px solid var(--danger-border); color: var(--danger-text);
  border-radius: 8px; padding: 10px 14px; font-size: 13px; line-height: 1.5;
  margin-top: 10px;
}
/* "This plan runs short of money": a RESULT of the numbers entered, not an application error, so it uses
   the calm amber notice palette -- red is kept for real errors (failed requests, invalid fields). */
.tool-section { scroll-margin-top: 12px; }
.plan-shortfall-notice {
  background: var(--notice-bg); border: 1px solid var(--notice-border); color: var(--notice-text);
  border-radius: 10px; padding: 14px 18px; margin-bottom: 16px; font-size: 13px; line-height: 1.6;
}
.plan-shortfall-notice h3 { margin: 0 0 6px; font-size: 15px; color: var(--notice-text); }
.plan-shortfall-notice p { margin: 0 0 8px; }
.plan-shortfall-notice ul { margin: 4px 0 12px 18px; padding: 0; }
.plan-shortfall-notice .notice-actions { display: flex; gap: 8px; flex-wrap: wrap; }
.plan-shortfall-notice .notice-actions button { width: auto; margin-top: 0; }
/* "Explain this plan" -- see AiNarrationService / the Plan Narration PRD.
   Same background family as .summary-card (this app's other "generated
   from the plan's own numbers" surface) rather than a brand-new color, so
   it reads as belonging to the same Dashboard rather than a bolted-on
   widget. The italic AI-generated line is deliberately its own paragraph,
   not baked into .narration-result's own ::before or similar, so it can
   never be hidden by truncating or restyling the box later without
   someone having to notice they're also removing a disclaimer. */
.narration-result {
  background: var(--tint); border-radius: 10px; padding: 14px 16px; margin-top: 10px;
}
.narration-result .ai-disclaimer {
  color: var(--muted); font-size: 12px; font-style: italic; margin: 0 0 8px 0;
}
.narration-result p:not(.ai-disclaimer) { white-space: pre-wrap; line-height: 1.6; }


.modal-overlay {
  position: fixed; inset: 0; background: var(--overlay);
  display: flex; align-items: center; justify-content: center;
  z-index: 1000; padding: 24px;
}
.modal {
  background: var(--card); border-radius: 12px; max-width: 720px; width: 100%;
  max-height: 85vh; display: flex; flex-direction: column;
  box-shadow: 0 8px 32px var(--shadow-strong);
}
.modal-header {
  display: flex; justify-content: space-between; align-items: center;
  padding: 18px 24px; border-bottom: 1px solid var(--border); flex-shrink: 0;
}
.modal-header h2 { margin: 0; font-size: 18px; }
.modal-close {
  background: none; border: none; font-size: 26px; line-height: 1; cursor: pointer;
  color: var(--muted); width: auto; margin: 0; padding: 0 4px;
}
.modal-close:hover { color: var(--text); background: none; }
.modal-body { padding: 8px 24px 24px; overflow-y: auto; font-size: 14px; line-height: 1.6; }

/* "View spreadsheet in browser": a large modal whose body is one sandboxed iframe. */
.modal.sheet-modal { max-width: 1280px; height: 92vh; max-height: 92vh; }
.modal.sheet-modal .modal-header { flex-wrap: wrap; gap: 8px; }
.sheet-frame { flex: 1; width: 100%; border: 0; border-radius: 0 0 12px 12px; background: var(--card); }
.modal-body h3 { font-size: 14px; margin: 20px 0 6px; color: var(--accent-dark); }
.modal-body h3:first-child { margin-top: 14px; }
.modal-body p, .modal-body ul { margin: 0 0 4px; color: var(--text-soft); }
.modal-body ul { padding-left: 20px; }
.modal-body li { margin-bottom: 6px; }

.plan-selector {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  margin: 14px 0 4px; padding: 12px 14px; background: var(--surface); border-radius: 10px;
}
.plan-selector select { width: auto; flex: 1; min-width: 180px; margin: 0; }
.plan-selector button { width: auto; margin: 0; padding: 8px 14px; font-size: 13px; }
button.danger { background: transparent; color: var(--danger); border: 1px solid var(--danger); }
button.danger:hover { background: var(--danger-solid); color: #fff; }
button.danger:disabled:hover { background: transparent; color: var(--danger); }

/* Wraps any table that might be wider than its container (results
   summaries, comparison grids, the year-by-year schedule) so it scrolls
   horizontally in its own lane instead of overflowing the page or
   getting crushed unreadably narrow. Deliberately a single, universal
   rule (not gated to a mobile media query) -- overflow-x:auto is a
   no-op on a container whose content already fits, so this is inert on
   desktop and only engages when a table is genuinely too wide for the
   viewport, at any screen size.

   This used to be implemented as `.results-table { display: block;
   overflow-x: auto }` targeting the <table> element itself, gated to a
   max-width media query. Two problems with that: (1) several tables
   ALSO had their own dedicated overflow-x:auto wrapper <div> already
   (the "Full comparison" and "Sensitivity results" tables), so the two
   overflow contexts ended up nested and fighting each other, which is
   what broke their scrolling; (2) forcing display:block directly onto
   a <table> element is a known-inconsistent hack across browsers for
   how it then treats the table's internal thead/tbody/tr/td layout.
   Using a plain wrapper <div> with this class -- the same pattern the
   original developer already used for most of these tables -- avoids
   both problems and needs no media query at all. */
.table-scroll { overflow-x: auto; -webkit-overflow-scrolling: touch; }

/* Item-builder table columns (BudgetBuilder/IncomeBuilder's "Item",
   "Whose income"/"Category", "Frequency") use a CSS class instead of an
   inline width so a SINGLE field can be a different width on desktop vs.
   mobile. Desktop values here are sized to a tight but real budget: the
   whole row (all columns + cell padding) needs to fit within a fieldset's
   available content width (~716px on this app's .container.wide layout)
   so the trailing "Remove" button doesn't require a horizontal scroll to
   reach on an ordinary desktop window. Mobile doesn't have that
   constraint -- the row already exceeds a phone's viewport regardless of
   these three columns, so .table-scroll's horizontal scroll is already
   the expected way to reach the remaining columns (same pattern as the
   Schedule table and every other wide results table in this app) --
   which means mobile is free to widen just these three text-heavy
   columns enough that "Household"/"Part-time consulting"/"Monthly" all
   render without clipping at the 16px font mobile uses. */
.item-col-name { width: 98px; }
.item-col-owner { width: 120px; }
.item-col-amount { width: 118px; }
.item-col-freq { width: 104px; }

/* HoldingsBuilder's own columns -- deliberately NOT reusing the
   .item-col-* classes above. That table has one more column than
   Budget/Income's (Holding, Account, Category, Current Value, Cost
   Basis, Gain %, Remove), and the two money columns need real room:
   an 8-figure balance ("$10,000,000") plus the "$" prefix box overflows
   .item-col-amount's 118px pretty much immediately, since .input-group
   clips overflow rather than wrapping it. Widening the shared classes
   instead would have thrown off Budget/Income's own tight, already-
   tuned desktop budget (see the comment above), so these get their own
   sizing instead. Same desktop-tight/mobile-wide split as the shared
   classes, same reasoning -- .table-scroll's horizontal scroll is the
   fallback if a very long account/holding name still doesn't fit. */
.holding-col-name { width: 100px; }
.holding-col-account { width: 130px; }
.holding-col-category { width: 110px; }
.holding-col-amount { width: 135px; }

/* Headers for the Holdings table specifically -- unlike the rest of
   .results-table's uses (mostly numeric schedules where a right-aligned
   header matches the right-aligned numbers below it), a 7-column entry
   table reads better with left-aligned labels above its inputs, closer
   to how Budget/Income's own 4-column headers read once .table-scroll's
   first-child rule already puts "Holding"/"Item" flush left. Scoped to
   .holdings-table rather than changing .results-table's default, which
   the numeric results tables elsewhere still rely on. */
.holdings-table th { text-align: left; }

/* Builder-table selects (owner/frequency dropdowns in BudgetBuilder and
   IncomeBuilder) inherit the global select rule's 11px top/bottom padding,
   2px more than a plain input's 9px -- a visible ~4px height mismatch
   against every input in the same table row. Scoped to .results-table so
   selects elsewhere (login/register/advanced-assumptions forms) keep
   their normal sizing; only the horizontal padding needs to stay
   different here (40px right, for the dropdown arrow). */
.results-table select { padding-top: 9px; padding-bottom: 9px; }

/* The Feedback admin table is all text (timestamps, emails, free-form
   messages) with nothing numeric to right-align -- unlike most of
   .results-table's other uses elsewhere (dollar amounts, percentages),
   where right-alignment is the sensible default. Scoped to this one
   table rather than changing .results-table's own default, which other
   screens still rely on. */
.feedback-table th, .feedback-table td { text-align: left; }

@media (max-width: 640px) {
  .item-col-name { width: 170px; }
  .item-col-owner { width: 160px; }
  .item-col-amount { width: 150px; }
  .item-col-freq { width: 130px; }

  .holding-col-name { width: 150px; }
  .holding-col-account { width: 170px; }
  .holding-col-category { width: 150px; }
  .holding-col-amount { width: 180px; }
}

/* -------------------------------------------------------------------
   Mobile responsive overrides.

   Everything below is scoped to @media (max-width: 640px), so none of
   it can change anything at desktop/tablet widths above that -- purely
   additive. Combined with the viewport meta tag now on every page
   (mobile browsers otherwise render at desktop width and shrink the
   whole page to fit, which is what made everything tiny before this).

   This is also written with an eventual native mobile app in mind: the
   backend already exposes a plain JSON REST API (PlanRequest in,
   ProjectionResultDto out) completely decoupled from this static
   frontend, so a future iOS/Android/React Native client can call the
   same endpoints directly without any backend changes -- this pass is
   about making the EXISTING browser-based UI usable on a phone
   in the meantime, not a prerequisite for that.
   ------------------------------------------------------------------- */
@media (max-width: 640px) {
  .container { margin: 24px auto; padding: 0 14px; }
  .container.wide { margin: 16px auto; padding: 0 10px; }
  .card { padding: 18px; }

  /* Two-column form sections (spouse fields, account balances, Advanced
     assumptions, etc.) stack instead of squeezing two columns into a
     phone-width screen. */
  .grid-2 { grid-template-columns: 1fr; gap: 0; }

  nav { padding: 10px 14px; flex-wrap: wrap; gap: 8px; }
  nav .brand { font-size: 15px; }
  nav button { padding: 9px 14px; }

  .plan-selector { flex-direction: column; align-items: stretch; }
  .plan-selector select { min-width: 0; }
  .plan-selector button { width: 100%; }

  .summary-row { gap: 8px; }

  .modal-overlay { padding: 10px; }
  .modal { max-width: 100%; max-height: 90vh; }

  /* iOS Safari auto-zooms the page when focusing an input/select/button
     with a computed font-size under 16px -- bumping these to exactly
     16px on mobile prevents that jarring zoom-in on every tap, without
     changing anything on desktop. */
  input, select, button { font-size: 16px; }

  /* Charts are wide (landscape-friendly) on desktop by default; on a
     narrow portrait phone a 340px-tall chart is a lot of scrolling for
     not much visible width, so trim it down. Landscape phones are
     comfortably above 640px wide, so this doesn't apply there --
     landscape gets the full desktop chart height, which is the point. */
  .chart-wrap { height: 240px; padding: 10px; }
}

/* ---- Home / landing page (index.html) ---- */
.container.wide.home { max-width: 900px; }
.home-hero { text-align: center; padding: 48px 0 40px; }
.home-hero-logo { height: 56px; margin-bottom: 28px; }
.home-hero h1 { font-size: 30px; line-height: 1.25; margin: 0 0 16px; color: var(--text); }
.home-hero-sub {
  max-width: 620px; margin: 0 auto; color: var(--muted); font-size: 16px; line-height: 1.55;
}
.home-cta { margin-top: 28px; display: flex; justify-content: center; gap: 12px; }
.btn-primary, .btn-secondary {
  display: inline-block; padding: 12px 24px; border-radius: 8px; font-size: 15px;
  font-weight: 600; text-decoration: none;
}
.btn-primary { background: var(--btn-bg); color: var(--on-accent); }
.btn-primary:hover { background: var(--btn-bg-hover); }
.btn-secondary { background: transparent; color: var(--accent); border: 1px solid var(--accent); }
.btn-secondary:hover { background: var(--btn-bg); color: var(--on-accent); }

.home-features {
  display: grid; grid-template-columns: repeat(3, 1fr); gap: 20px;
  padding: 8px 0 40px; border-top: 1px solid var(--border);
}
.home-feature { padding-top: 28px; }
.home-feature h3 { font-size: 15px; margin: 0 0 8px; color: var(--text); }
.home-feature p { font-size: 13.5px; line-height: 1.55; color: var(--muted); margin: 0; }

.home-footer {
  display: flex; justify-content: center; align-items: center; gap: 16px;
  padding: 20px 0 40px; border-top: 1px solid var(--border); font-size: 13px;
}
.home-footer a { color: var(--accent); text-decoration: none; }

@media (max-width: 640px) {
  .home-features { grid-template-columns: 1fr; }
  .home-cta { flex-direction: column; align-items: stretch; }
  .home-hero h1 { font-size: 24px; }
}

/* ---- Admin panel tab bar ---- */
.admin-tabs {
  display: flex; gap: 4px; padding: 0 24px; background: var(--card);
  border-bottom: 1px solid var(--border);
}
.admin-tab {
  width: auto; margin: 0; padding: 12px 18px; font-size: 14px; font-weight: 600;
  background: transparent; color: var(--muted); border: none; border-bottom: 2px solid transparent;
  border-radius: 0; cursor: pointer;
}
.admin-tab:hover { color: var(--text); background: transparent; }
.admin-tab-active { color: var(--accent); border-bottom-color: var(--accent); }

/* ---- Admin Log Viewer ---- */
.log-viewer-pane { max-height: 600px; overflow-y: auto; border: 1px solid var(--border); border-radius: 8px; padding: 10px; background: var(--log-bg); }
.log-viewer-content { margin: 0; font-family: ui-monospace, "SF Mono", Consolas, monospace; font-size: 12px; line-height: 1.5; white-space: pre-wrap; word-break: break-all; }
.log-line-error { color: var(--danger); font-weight: 600; }
.log-line-warn { color: var(--warn-text); }
/* In-window search highlights (see LogViewerSection's renderLineContent).
   Two distinct colors, matching the usual browser find-bar convention:
   every match gets the dimmer shade, and whichever one is "current" (the
   one just scrolled to) gets the brighter, more attention-grabbing one so
   it's obvious at a glance which occurrence you just jumped to. */
.log-search-hit { background: var(--hit-bg); color: inherit; }
.log-search-current { background: var(--hit-cur-bg); color: var(--hit-cur-text); box-shadow: 0 0 0 1px var(--hit-cur-ring); }

/* The two wordmark logos (img/logo-nav.svg, img/logo-lockup.svg) draw their
   lettering in near-black, which vanishes on the dark background. They are
   loaded through <img>, so CSS variables can't reach inside them; instead,
   in dark mode the image is lightness-inverted and hue-rotated back, which
   turns the dark lettering light while keeping the green/amber mark's hues.
   (The icon-only favicon/apple-touch-icon is untouched.) */
html[data-theme="dark"] nav .brand img,
html[data-theme="dark"] .auth-brand img,
html[data-theme="dark"] .home-hero-logo { filter: invert(1) hue-rotate(180deg); }
@media print { html[data-theme="dark"] nav .brand img { filter: none; } }

/* ---- Theme toggle (System / Light / Dark) -- see js/theme.js ----
   In the app's nav bars the toggle is a plain `button.secondary` (the React
   component adds that class), so it is styled exactly like Help / Feedback /
   Log out and picks up nav button sizing and the hover state for free.
   Only the floating variant -- injected by theme.js on pages with no nav of
   their own (sign-in, register, ...) -- needs rules here, and they simply
   mirror button.secondary. */
.theme-toggle.floating {
  position: fixed; top: 12px; right: 12px; z-index: 50;
  width: auto; margin: 0; padding: 7px 14px; font-size: 13px;
  background: var(--card); color: var(--accent); border: 1px solid var(--accent);
}
.theme-toggle.floating:hover { background: var(--btn-bg); color: var(--on-accent); }
@media (max-width: 640px) { .theme-toggle.floating { padding: 9px 14px; } }

/* ---- Per-chart "How to read this chart" notes (v2.5.0) ---- */
.chart-explainer {
  margin: 8px 0 0; border: 1px solid var(--border); border-radius: 8px;
  background: var(--surface-2); font-size: 13px; line-height: 1.55;
}
.chart-explainer summary { cursor: pointer; padding: 8px 12px; font-weight: 600; color: var(--accent-dark); }
.chart-explainer .chart-explainer-body { padding: 0 12px 10px; color: var(--text-soft); }
.chart-explainer .chart-explainer-body p { margin: 6px 0 0; }

/* ---- "Was this summary helpful?" thumbs under the AI explanation ---- */
.narration-feedback { display: flex; align-items: center; gap: 8px; margin-top: 10px; flex-wrap: wrap; }
.narration-feedback .field-hint { margin: 0; }
.thumb-btn {
  width: auto; margin: 0; padding: 6px 12px; font-size: 14px; background: var(--card);
  color: var(--text); border: 2px solid var(--border); border-radius: 8px;
}
.thumb-btn:hover { background: var(--tint-2); border-color: var(--accent); }
.thumb-btn.selected { background: var(--tint-2); border-color: var(--accent); color: var(--accent-dark); }
.thumb-btn:disabled { opacity: 0.7; }

/* ---- Printing the Dashboard (Print / Save as PDF button) ----
   Screen: the .print-* blocks are hidden. Print: app chrome and
   interactive controls are hidden, the 3-column dashboard grid becomes one
   full-width column, charts print from their PNG snapshots (see
   ChartsPanel), and the year-by-year schedule prints always-expanded in a
   small font. Landscape because the schedule has 17 columns. */
.print-only { display: none; }

@media print {
  @page { size: landscape; margin: 0.45in; }

  /* Paper is always light: re-declare the light palette over the dark one. */
  html, html[data-theme="dark"] {
    color-scheme: light;
    --bg: #f7f7f5; --card: #ffffff; --border: #e1e0d9; --text: #1a1a1a;
    --muted: #6b6b66; --accent: #2a6f4b; --accent-dark: #1f5138; --danger: #c0392b;
    --input-bg: #ffffff;
    --surface: #f0f0ec;      /* table heads, input add-ons, plan picker bar */
    --surface-2: #fafaf7;    /* status cards, quick-adjust panel */
    --tint: #f0f6f3;         /* summary cards, narration box */
    --tint-2: #f4f8f5;       /* hover / selected rows and cards */
    --track: #ece9e2;        /* progress-bar track */
    --text-soft: #333333;
    --rule: #dddddd;         /* thin separators drawn inline in the app */
    --faint: #6e6e6e;        /* footer / de-emphasised small print */
    --btn-bg: #2a6f4b; --btn-bg-hover: #1f5138; --on-accent: #ffffff;
    --danger-solid: #c0392b;
    --danger-bg: #fdf0ee; --danger-border: #e8b4ac; --danger-text: #8a2e1f;
    --fatal-bg: #fde8e8; --fatal-text: #7a1f1f;
    --ok-bg: #eaf3ee;
    --log-bg: #fbfbf9; --warn-text: #8a5a00;
    --hit-bg: #ffe8a3; --hit-cur-bg: #ff9d3d; --hit-cur-text: #1a1200; --hit-cur-ring: #b5650a;
    --shadow: rgba(0,0,0,0.04); --shadow-strong: rgba(0,0,0,0.25); --overlay: rgba(0,0,0,0.45);
    --chart-text: #444444; --chart-grid: rgba(0,0,0,0.1);
    --info: #2c5aa0; --caution: #8a6500;
    --notice-bg: #fff6e0; --notice-border: #e3b45a; --notice-text: #5e3d00;
    --c-green: #2a6f4b; --c-red: #c0392b; --c-blue: #2c5aa0; --c-amber: #c98a1e; --c-ira: #e8484a; --c-roth: #1baf7a; --c-taxable: #2a78d6;
    --select-arrow: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%236b6b66' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><polyline points='6 9 12 15 18 9'></polyline></svg>");
  }

  .print-only { display: block; }
  .print-charts.print-only { display: flex; gap: 12px; margin-top: 8px; }
  .no-print,
  nav, .view-switcher, .plan-selector, .modal-overlay,
  .chart-wrap, .explore-further, .dashboard-rail-col,
  button { display: none !important; }

  html, body { background: #fff !important; }
  body { color: #000; font-size: 11pt; }
  /* Keep status-card / summary-card tints and chart colours on paper. */
  * { -webkit-print-color-adjust: exact; print-color-adjust: exact; }

  .container, .container.wide { max-width: none; margin: 0; padding: 0; }
  .card { box-shadow: none !important; border: none !important; padding: 0 !important; margin: 0 !important; }

  .dashboard-columns { display: block; }
  .dashboard-main-col { grid-column: auto; }

  .dashboard-status-strip, .summary-row, .summary-card, .dashboard-status-card,
  .print-charts, .print-assumptions { break-inside: avoid; page-break-inside: avoid; }

  .print-header { margin-bottom: 10px; }
  .print-title { font-size: 16pt; font-weight: 700; }
  .print-subtitle { font-size: 10pt; color: #444; margin: 2px 0 8px; }
  .print-assumptions { border-collapse: collapse; font-size: 9pt; width: 100%; }
  .print-assumptions th { text-align: left; width: 22%; padding: 2px 8px 2px 0; color: #333; vertical-align: top; }
  .print-assumptions td { padding: 2px 0; }

  /* Charts side by side, each half the printable width, so the whole
     overview (header, status, summary, both charts) fits on page 1. */
  img.print-chart-img { width: calc(50% - 6px); height: auto; }
  .summary-row { grid-template-columns: repeat(5, 1fr); gap: 6px; margin: 8px 0; }
  .summary-card { padding: 5px 8px; }
  .summary-card .label { font-size: 8pt; }
  .summary-card .value { font-size: 11pt; }
  .dashboard-status-strip { margin: 6px 0; }
  .dashboard-status-card { padding: 6px 10px; }
  .dashboard-status-value { font-size: 14pt; }

  .print-schedule { break-before: page; page-break-before: always; }
  .print-schedule h2 { font-size: 12pt; margin: 0 0 6px; }
  .print-schedule .table-scroll { overflow: visible !important; display: block !important; }
  .print-schedule .results-table { font-size: 7pt; width: 100%; border-collapse: collapse; }
  .print-schedule .results-table th,
  .print-schedule .results-table td { padding: 2px 3px; border: 1px solid #bbb; white-space: nowrap; }
  .print-schedule .results-table thead { display: table-header-group; } /* repeat header on each page */
  .print-schedule .results-table tr { break-inside: avoid; page-break-inside: avoid; }

  footer { margin: 12px 0 0 !important; padding: 8px 0 0 !important; max-width: none !important; font-size: 8pt !important; }
}

/* Optional early-death scenario: a calm informational note, and shaded survivor-year rows in the schedule. */
.survivor-notice {
  background: var(--tint); border: 1px solid var(--border); border-left: 4px solid var(--info); color: var(--text);
  border-radius: 8px; padding: 10px 14px; margin-bottom: 16px; font-size: 13px; line-height: 1.6;
}
.results-table tr.survivor-row td { background: var(--tint-2); }
