/* RetireSure WPS — frontend styles. Vanilla CSS, no external fonts, no CDN. */

:root {
  --bg: #f4f6f8;
  --panel: #ffffff;
  --ink: #1c2733;
  --muted: #5b6b7b;
  --line: #d6dde4;
  --line-strong: #b3bfca;
  --accent: #1f5fa8;
  --green: #1a7f37;
  --green-soft: #bfe0c6;
  --red: #c0392b;
  --red-soft: #e7b7b1;
  --amber: #8a6100;
  --amber-bg: #fdf3d7;
  --error: #b3261e;
  --error-bg: #fdecea;
  --assume-bg: #eef4fb;
}

* { box-sizing: border-box; }

html, body {
  margin: 0;
  padding: 0;
  background: var(--bg);
  color: var(--ink);
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  font-size: 14px;
  line-height: 1.45;
}

h1, h2, h3, h4 { margin: 0 0 .5em; line-height: 1.25; }
h1 { font-size: 1.25rem; }
h2 { font-size: 1.02rem; letter-spacing: .01em; }
h3 { font-size: .95rem; }
p { margin: .35em 0; }

.app-header {
  background: #12324f;
  color: #fff;
  padding: 10px 18px;
  display: flex;
  align-items: baseline;
  gap: 14px;
  flex-wrap: wrap;
}
.app-header h1 { margin: 0; font-size: 1.15rem; }
.app-header .sub { color: #b9cadb; font-size: .82rem; }

main { padding: 14px; max-width: 1500px; margin: 0 auto; }

.panel {
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: 8px;
  padding: 12px 14px;
  margin-bottom: 14px;
}
.panel > h2 {
  border-bottom: 1px solid var(--line);
  padding-bottom: 6px;
  margin-bottom: 10px;
}
.panel .hint { color: var(--muted); font-size: .8rem; }

/* ---- the four result panes, laid out to fit one screen ---- */
/* The pane grid is given an explicit height derived from the viewport, and each panel scrolls
   INTERNALLY. That is what keeps the whole simulator on one screen: the page itself does not
   scroll to reach the charts, only down to the input parameters and assumptions beneath it.
   The clamp keeps it usable on a short window (floor 420px) and stops it stretching absurdly
   on a tall one (ceiling 1200px). */
.pane-grid {
  position: relative;
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  grid-auto-rows: minmax(0, 1fr);
  gap: 10px;
  height: clamp(430px, calc(100vh - 132px), 1200px);
  margin-bottom: 10px;
}
/* A parameter has moved, so every figure in these four panes describes the PREVIOUS run. Dimmed
   and captioned rather than left looking current -- numbers that no longer answer the question
   on screen are the one thing this page must never quietly show. */
.pane-grid.stale > .panel { opacity: .4; filter: saturate(.55); transition: opacity .15s ease; }
.pane-grid.stale::after {
  content: attr(data-stale-note);
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  z-index: 3;
  padding: 10px 18px;
  border: 1px solid #e0b070;
  border-radius: 8px;
  background: #fff8ec;
  color: #8a5a00;
  font-size: .88rem;
  font-weight: 600;
  text-align: center;
  max-width: min(90%, 440px);
  box-shadow: 0 4px 14px rgba(0, 0, 0, .12);
}

.pane-grid > .panel {
  display: flex;
  flex-direction: column;
  min-height: 0;
  margin-bottom: 0;
  overflow: auto;
}
.pane-grid > .panel > h2 { flex: 0 0 auto; }
.pane-grid > .panel > .chart-wrap { flex: 1 1 auto; min-height: 72px; }
/* height:100% + preserveAspectRatio="xMidYMid meet" lets the chart scale to whichever of the
   panel's width or height is tighter, instead of overflowing the fixed-height pane. */
.pane-grid svg.chart { height: 100%; width: 100%; }

/* ---- info icons: explanation text lives in a tooltip, not on the page ---- */
.panel > h2 { display: flex; align-items: center; gap: 2px; flex-wrap: wrap; }
.info {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: 0 0 auto;
  width: 16px;
  height: 16px;
  padding: 0;
  margin-left: 6px;
  border: 1px solid var(--line-strong);
  border-radius: 50%;
  background: #eef2f6;
  color: var(--muted);
  font-family: Georgia, "Times New Roman", serif;
  font-size: .68rem;
  font-style: italic;
  font-weight: 700;
  line-height: 1;
  cursor: help;
  vertical-align: 2px;
}
.info:hover, .info:focus-visible, .info.open {
  background: var(--accent);
  border-color: var(--accent);
  color: #fff;
}

/* One shared, fixed-position tooltip. Fixed rather than a ::after on the icon because the
   panes scroll internally and would clip anything overflowing them. */
#tip {
  position: fixed;
  z-index: 200;
  display: none;
  max-width: 330px;
  padding: 8px 10px;
  border-radius: 6px;
  background: rgba(18, 40, 62, .97);
  color: #fff;
  font-size: .78rem;
  line-height: 1.42;
  box-shadow: 0 4px 16px rgba(0, 0, 0, .3);
  pointer-events: none;
}
#tip.show { display: block; }
/* Generated tooltips (the extraction report, the solver's reasoning) are plain text with
   newlines, so the line breaks have to be honoured rather than collapsed. */
#tip { white-space: pre-line; max-width: 420px; }

/* ---- language switcher ---- */
.lang-switch {
  margin-left: auto;
  display: inline-flex;
  border: 1px solid #3b5a78;
  border-radius: 6px;
  overflow: hidden;
}
.lang-switch button {
  border: none;
  border-radius: 0;
  background: transparent;
  color: #cfe0f2;
  padding: 3px 11px;
  font-size: .78rem;
  cursor: pointer;
}
.lang-switch button + button { border-left: 1px solid #3b5a78; }
.lang-switch button.active { background: #fff; color: #12324f; font-weight: 700; }

/* ---- pane-level controls ---- */
.pane-controls { display: flex; gap: 10px; flex-wrap: wrap; align-items: center; margin-bottom: 4px; }
.link-btn {
  align-self: flex-start;
  border: none;
  background: none;
  padding: 2px 0;
  color: var(--accent);
  font-size: .78rem;
  text-decoration: underline;
  cursor: pointer;
}
.link-btn:hover { color: #1a5292; }
.short-read { font-size: .82rem; margin: 8px 0 4px; }
.modal-body { max-height: 68vh; overflow: auto; }
.modal-body > .table-scroll { max-height: none; }

/* ---- charts ---- */
.chart-wrap { position: relative; }
svg.chart { display: block; width: 100%; height: auto; }
svg.chart text { font-family: inherit; fill: var(--ink); }

.legend { display: flex; flex-wrap: wrap; gap: 4px 18px; margin: 6px 0 2px; font-size: .8rem; }
.legend .item { display: inline-flex; align-items: center; gap: 6px; }
.legend .swatch { width: 20px; height: 0; border-top-width: 3px; border-top-style: solid; display: inline-block; }
.legend .block { width: 14px; height: 10px; display: inline-block; border: 1px solid rgba(0,0,0,.15); }

.chart-tooltip {
  position: absolute;
  pointer-events: none;
  background: rgba(18, 40, 62, .96);
  color: #fff;
  padding: 7px 9px;
  border-radius: 6px;
  font-size: .78rem;
  line-height: 1.35;
  white-space: nowrap;
  z-index: 5;
  display: none;
}
.chart-tooltip .tt-title { font-weight: 700; margin-bottom: 3px; }
.chart-tooltip .tt-row span:first-child { display: inline-block; width: 62px; color: #b9cadb; }

/* ---- summary ---- */
.summary-table { width: 100%; border-collapse: collapse; }
.summary-table th {
  text-align: left;
  color: var(--muted);
  font-weight: 600;
  padding: 5px 6px;
  border-bottom: 1px solid var(--line);
  font-size: .82rem;
  /* Without a width the label column collapses to its longest word and breaks labels like
     "Top 5% portfolio value" across three lines while the value column sits half empty. */
  width: 46%;
}
.summary-table td { padding: 6px; border-bottom: 1px solid #eef1f4; vertical-align: top; }
.summary-table tr:last-child td { border-bottom: none; }
.summary-table td.num { text-align: right; font-variant-numeric: tabular-nums; font-weight: 600; white-space: nowrap; }
.summary-table td.num.neg { color: var(--red); }
.summary-table .sub { color: var(--muted); font-size: .78rem; font-weight: 400; }

.outlook-cell { display: flex; gap: 10px; align-items: flex-start; }
.outlook-emoji { font-size: 1.9rem; line-height: 1; }
.outlook-label { font-weight: 700; }
.outlook-msg { color: var(--ink); }
.outlook-caveat { color: var(--muted); font-size: .78rem; margin-top: 3px; }
.outlook-band-name { color: var(--muted); font-size: .74rem; font-weight: 400; }

/* ---- forms ---- */
.groups {
  display: grid;
  grid-template-columns: repeat(3, minmax(220px, 1fr));
  gap: 18px;
}
.group { min-width: 0; }
.group > h3 { color: var(--accent); }
.field { margin-bottom: 9px; }
.field label { display: block; font-size: .82rem; color: var(--muted); margin-bottom: 3px; }
.field .row { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
input[type="number"], input[type="text"], input[type="search"], select {
  font: inherit;
  padding: 5px 7px;
  border: 1px solid var(--line-strong);
  border-radius: 5px;
  background: #fff;
  color: var(--ink);
  min-width: 0;
}
input[type="number"] { width: 110px; text-align: right; font-variant-numeric: tabular-nums; }
input:focus, select:focus, button:focus { outline: 2px solid var(--accent); outline-offset: 1px; }
input:disabled, select:disabled { background: #eef1f4; color: #8a97a3; cursor: not-allowed; }

button {
  font: inherit;
  padding: 6px 12px;
  border: 1px solid var(--line-strong);
  border-radius: 5px;
  background: #f7f9fb;
  color: var(--ink);
  cursor: pointer;
}
button:hover:not(:disabled) { background: #eef2f6; }
button:disabled { opacity: .55; cursor: not-allowed; }
button.primary { background: var(--accent); border-color: var(--accent); color: #fff; font-weight: 600; }
button.primary:hover:not(:disabled) { background: #1a5292; }
button.danger { border-color: var(--red-soft); color: var(--error); background: #fff; }
button.tiny { padding: 2px 7px; font-size: .78rem; }

/* Busy state for a control the user pressed. The label stays in the flow but transparent, so
   the button keeps EXACTLY its width and nothing beside it shifts as the spinner appears and
   goes -- a growing button would jog the whole row for the second it is up. */
@keyframes wps-spin { to { transform: rotate(360deg); } }
button.busy {
  position: relative;
  color: transparent;
  cursor: progress;
}
button.busy:disabled { opacity: 1; }   /* busy is working, not unavailable -- do not dim it */
button.busy::before {
  content: '';
  position: absolute;
  left: 50%;
  top: 50%;
  width: 12px;
  height: 12px;
  margin: -8px 0 0 -8px;               /* -(12px + 2x2px border) / 2 */
  border: 2px solid var(--ink);
  border-top-color: transparent;       /* the gap is what makes the rotation readable */
  border-radius: 50%;
  animation: wps-spin .7s linear infinite;
}
button.primary.busy::before { border-color: #fff; border-top-color: transparent; }

@media (prefers-reduced-motion: reduce) {
  /* A spinner here is a status indicator, not decoration, so it stays -- but slowed right
     down so it does not read as motion for someone who asked for less of it. */
  button.busy::before { animation-duration: 2.4s; }
}

.seg { display: inline-flex; border: 1px solid var(--line-strong); border-radius: 6px; overflow: hidden; }
.seg button { border: none; border-right: 1px solid var(--line-strong); border-radius: 0; background: #fff; }
.seg button:last-child { border-right: none; }
.seg button.active { background: var(--accent); color: #fff; font-weight: 600; }

.note { font-size: .78rem; color: var(--muted); }
.note.warn { color: var(--amber); background: var(--amber-bg); border: 1px solid #f0e0b0; border-radius: 5px; padding: 5px 8px; }
.note.assume { color: #2a4a6b; background: var(--assume-bg); border: 1px solid #cfe0f2; border-radius: 5px; padding: 5px 8px; }

/* ---- holdings ---- */
.holding-row {
  display: grid;
  grid-template-columns: minmax(90px, 1.3fr) 74px 84px 74px auto;
  gap: 6px;
  align-items: end;
  padding: 7px 0;
  border-bottom: 1px dashed #e3e9ee;
}
.holding-row.header { border-bottom: 1px solid var(--line); font-size: .76rem; color: var(--muted); }
.holding-row .cell { min-width: 0; }
.holding-row input { width: 100%; }
.holding-row input.name { text-align: left; }
.holding-row .badge {
  display: inline-block;
  font-size: .68rem;
  padding: 1px 5px;
  border-radius: 3px;
  margin-top: 2px;
  font-weight: 600;
  letter-spacing: .02em;
}
.badge.historical { background: #e2efe4; color: #175c2c; border: 1px solid #bfe0c6; }
.badge.user { background: #eceff2; color: #4a5a68; border: 1px solid #d6dde4; }
.growth-note { font-size: .72rem; color: var(--muted); margin-top: 2px; }

.weight-total { font-weight: 700; }
.weight-total.ok { color: var(--green); }
.weight-total.bad { color: var(--error); }

.portfolio-footer { display: flex; flex-wrap: wrap; gap: 10px; align-items: center; margin-top: 8px; }

/* ---- error banner ---- */
#error-banner {
  display: none;
  background: var(--error-bg);
  border: 1px solid #f0b6b0;
  color: var(--error);
  border-radius: 6px;
  padding: 9px 12px;
  margin-bottom: 12px;
  font-weight: 600;
}
#error-banner.show { display: block; }

/* ---- solver ---- */
/* ---- describe your situation (rule-based, not an LLM) ---- */
.describe { margin-bottom: 10px; }
.describe > label {
  display: block;
  font-size: .78rem;
  font-weight: 700;
  color: var(--muted);
  margin-bottom: 4px;
}
/* Four rows: the generated sentence is long, and this shows most of it while keeping
   the pane's own controls in reach. It scrolls internally for anything longer, and
   the user can still drag the corner.
 */
#situation-text {
  width: 100%;
  min-height: 81px;
  resize: vertical;
  padding: 7px 9px;
  border: 1px solid var(--line-strong);
  border-radius: 6px;
  background: #fff;
  color: var(--ink);
  font: inherit;
  font-size: .86rem;
  line-height: 1.4;
}
#situation-text::placeholder { color: #98a6b4; }
#situation-text:focus { outline: 2px solid var(--accent); outline-offset: -1px; }
.describe-bar {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
  margin-top: 6px;
}
.qs-label {
  margin-left: 6px;
  padding-left: 10px;
  border-left: 1px solid var(--line-strong);
  color: var(--muted);
  font-size: .7rem;
  letter-spacing: .06em;
  text-transform: uppercase;
}
.chip {
  border: 1px solid var(--line-strong);
  border-radius: 999px;
  background: #fff;
  color: var(--ink);
  padding: 3px 11px;
  font-size: .8rem;
  cursor: pointer;
}
.chip:hover { border-color: var(--accent); color: var(--accent); }
.chip.active { background: var(--accent); border-color: var(--accent); color: #fff; font-weight: 700; }
.describe-bar .link-btn { margin-left: auto; }
#situation-restore { margin-left: 10px; }

/* The extraction report, now one compact line: what was read, with the phrase each value came
   from on the info icon. It sat as a list before, which pushed the ANSWER beside it into a
   scroll -- and the answer is the thing the pane exists to show. */
.extract { margin-top: 0; font-size: .8rem; }
.extract:empty { display: none; }
#extract-result { max-height: 62px; overflow-y: auto; padding-right: 4px; }
.ex-line { display: flex; flex-wrap: wrap; align-items: baseline; gap: 6px; }
.ex-vals { display: flex; flex-wrap: wrap; gap: 6px; }
.ex-k { color: var(--muted); font-size: .74rem; flex: 0 0 auto; }
.ex-v {
  border: 1px solid var(--line);
  border-radius: 4px;
  padding: 1px 6px;
  background: #f4f7fa;
  font-size: .76rem;
  white-space: nowrap;
}
.ex-skip { margin: 6px 0 0; font-size: .76rem; line-height: 1.35; }
.ex-head { margin: 0 0 4px; font-size: .78rem; font-weight: 700; }
.ex-list { margin: 0 0 6px; padding: 0; list-style: none; }
.ex-list li { display: flex; flex-wrap: wrap; align-items: baseline; gap: 6px; padding: 2px 0; }
.ex-k { flex: 0 0 auto; min-width: 92px; color: var(--muted); font-size: .74rem; }
.ex-src {
  flex: 1 1 auto;
  min-width: 0;
  overflow-wrap: anywhere;
  color: var(--muted);
  font-size: .74rem;
  font-style: italic;
}
.ex-band {
  border: 1px solid var(--line-strong);
  border-radius: 999px;
  padding: 1px 6px;
  color: var(--muted);
  font-size: .68rem;
  letter-spacing: .04em;
  text-transform: uppercase;
}
.ex-notes li .ex-k { color: var(--accent); }
.ex-dropped li { color: var(--muted); }

/* Half the pane for the controls, half for the answer. A fixed cap on the controls column
   looked fine on a wide window and starved the answer on a narrower one -- at a 1100px
   window the answer was squeezed into 101px while the controls kept all 380. Half each
   means the two shrink together, which is the honest reading of "the answer gets half". */
.solver-grid { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 20px; align-items: start; }
.solver-output-col { display: flex; flex-direction: column; gap: 10px; min-width: 0; }
/* Chinese and Cantonese option text is wider than the English it replaces, and a <select>
   clips rather than wraps — so the control fills its column instead of sizing to itself. */
#solver .field select { width: 100%; }
.solver-result { border-left: 1px solid var(--line); padding-left: 18px; }
.solver-value { font-size: 1.6rem; font-weight: 800; }
.solver-band { font-weight: 600; color: var(--ink); }
.solver-notes { margin: 8px 0 0; padding-left: 18px; color: var(--muted); font-size: .82rem; }
.solver-notes li { margin-bottom: 4px; }
.solver-na { font-weight: 700; color: var(--error); }

/* The answer, sized so it reads without scrolling: the value and what it is. Everything else
   the solver used to print -- parameter, achieved vs target, sensitivity band, notes -- is on
   the info icon beside the value. */
.solver-answer { padding: 2px 0; }
.solver-value {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
  font-size: 1.85rem;
  font-weight: 800;
  line-height: 1.15;
  letter-spacing: -.01em;
}
.solver-answer.na .solver-value { font-size: 1.15rem; color: var(--error); }
.solver-label { margin-top: 2px; color: var(--muted); font-size: .82rem; }

/* ---- assumptions ---- */
.assume-list { margin: 0; }
.assume-list dt { font-weight: 700; margin-top: 8px; }
.assume-list dd { margin: 0 0 2px; color: var(--ink); }
.bands-table { width: 100%; border-collapse: collapse; font-size: .82rem; margin-top: 8px; }
.bands-table th, .bands-table td { border-bottom: 1px solid #eef1f4; padding: 5px 6px; text-align: left; vertical-align: top; }
.bands-table th { color: var(--muted); }
.bands-table td:first-child { white-space: nowrap; }

/* ---- fund picker ---- */
.modal-overlay {
  display: none;
  position: fixed;
  inset: 0;
  background: rgba(16, 28, 40, .55);
  z-index: 50;
  padding: 24px;
  overflow: auto;
}
.modal-overlay.open { display: block; }
.modal {
  background: #fff;
  border-radius: 10px;
  max-width: 1080px;
  margin: 0 auto;
  padding: 13px 16px 14px;
  border: 1px solid var(--line-strong);
}
.modal-head { display: flex; justify-content: space-between; align-items: flex-start; gap: 12px; }
.modal-controls { display: flex; gap: 10px; align-items: center; flex-wrap: wrap; margin: 10px 0; }
.modal-controls input[type="search"] { flex: 1 1 260px; text-align: left; }
.fund-table-wrap { max-height: 52vh; overflow: auto; border: 1px solid var(--line); border-radius: 6px; }
table.fund-table { width: 100%; border-collapse: collapse; font-size: .82rem; }
table.fund-table th, table.fund-table td { padding: 5px 7px; border-bottom: 1px solid #eef1f4; text-align: left; }
table.fund-table thead th { position: sticky; top: 0; background: #f7f9fb; z-index: 1; cursor: pointer; white-space: nowrap; }
table.fund-table td.num, table.fund-table th.num { text-align: right; font-variant-numeric: tabular-nums; white-space: nowrap; }
table.fund-table tr.selectable { cursor: pointer; }
table.fund-table tr.selectable:hover { background: #eef4fb; }
table.fund-table tr.unavailable { color: #94a1ad; }
table.fund-table tr.short { background: #fffdf3; }
.reliab-yes { color: var(--green); font-weight: 600; }
.reliab-no { color: var(--amber); font-weight: 600; }

#fee-prompt { display: none; margin-top: 12px; border: 1px solid #f0e0b0; background: var(--amber-bg); border-radius: 6px; padding: 10px 12px; }
#fee-prompt.show { display: block; }

/* ---- data tables (accessible chart alternative) ---- */
details.dtable { margin-top: 8px; }
details.dtable summary { cursor: pointer; color: var(--accent); font-size: .82rem; }
.table-alt { width: 100%; border-collapse: collapse; font-size: .78rem; margin-top: 6px; }
.table-alt th, .table-alt td { border-bottom: 1px solid #eef1f4; padding: 3px 6px; text-align: right; }
.table-alt th:first-child, .table-alt td:first-child { text-align: left; }
.table-scroll { max-height: 240px; overflow: auto; }

.row-inline { display: flex; gap: 14px; flex-wrap: wrap; align-items: flex-end; }
.row-inline .field { margin-bottom: 0; }

.run-bar { display: flex; align-items: center; gap: 14px; margin-top: 14px; flex-wrap: wrap; }
.status-line { color: var(--muted); font-size: .8rem; }
.status-line.stale { color: #8a5a00; font-weight: 600; }

/* The Re-run button is the ONLY way to bring the panes back in step with the parameters, so
   while the result is stale it is the one control that should be asking to be pressed. Made to
   look like the primary action it has become, rather than adding another button beside it. */
#run-btn.attention {
  background: var(--accent);
  border-color: var(--accent);
  color: #fff;
  font-weight: 600;
  box-shadow: 0 0 0 3px rgba(31, 95, 168, .16);
}
#run-btn.attention:hover:not(:disabled) { background: #1a5292; }
#run-btn.attention.busy::before { border-color: #fff; border-top-color: transparent; }

.pl-stale { color: #8a5a00; font-size: .86rem; font-weight: 600; }

/* ================================================================================
   ④ INPUT PARAMETERS — restyle
   Scoped to #inputs so nothing here can leak into the summary, solver or fund picker.
   Later in the file than the base form rules, so it overrides them at equal specificity.
   ================================================================================ */

#inputs .hint { margin-bottom: 14px; max-width: 78ch; }

/* --- section cards --- */
.input-card {
  border: 1px solid var(--line);
  border-radius: 10px;
  /* Tighter than it looks like it needs to be: this card is twice as tall as anything else in
     the dialog, and the dialog has to fit a 1280x800 window without scrolling. */
  padding: 11px 14px;
  background: #fbfcfe;
  margin-bottom: 14px;
}
.input-card > h3 {
  margin: 0 0 2px;
  font-size: .8rem;
  font-weight: 800;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--accent);
}
.input-card > .card-hint {
  margin: 0 0 8px;
  font-size: .78rem;
  color: var(--muted);
  max-width: 82ch;
}
.input-card > .card-hint code {
  background: #eef2f6;
  border-radius: 3px;
  padding: 0 4px;
  font-size: .95em;
}

/* --- fields --- */
#inputs .field { margin-bottom: 14px; }
#inputs .field:last-child { margin-bottom: 0; }
#inputs .field > label {
  display: block;
  font-size: .8rem;
  font-weight: 600;
  color: var(--ink);
  margin-bottom: 5px;
}
#inputs .field > .note { margin-top: 5px; }
#inputs .field > input[type="number"],
#inputs .field > select { width: 100%; }
#inputs .field-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 220px), 1fr));
  gap: 14px 18px;
}
#inputs .field-grid .field { margin-bottom: 0; }

/* The editor dialog is NOT inside #inputs, so the rule above never reached it -- Plan basics,
   Current and Retirement were all rendering as single columns of fields, which is most of why
   the dialog always opened with a scrollbar. Two columns is what the width affords. */
.params-grid .field-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 180px), 1fr));
  gap: 10px 14px;
}
.params-grid .field-grid .field { margin-bottom: 0; }

/* Single-column cards: a number input does not need to span 680px to hold "40" — the value
   would sit marooned at the far right of an otherwise empty box, disconnected from its
   label. Cap the width so the label and the value read as one unit. Selects keep the full
   width, because their option text is long enough to need it. */

/* A small tag naming the unit convention, e.g. "today's dollars". */
.unit-tag {
  display: inline-block;
  font-size: .68rem;
  font-weight: 700;
  letter-spacing: .02em;
  text-transform: uppercase;
  color: #2a4a6b;
  background: var(--assume-bg);
  border: 1px solid #cfe0f2;
  border-radius: 3px;
  padding: 0 4px;
  vertical-align: 1px;
}

/* --- unit affixes: the unit belongs on screen, not only in the label --- */
.affix-input {
  display: flex;
  align-items: stretch;
  border: 1px solid var(--line-strong);
  border-radius: 6px;
  background: #fff;
  overflow: hidden;
}
.affix-input:focus-within {
  border-color: var(--accent);
  box-shadow: 0 0 0 3px rgba(31, 95, 168, .18);
}
.affix-input > input {
  flex: 1 1 auto;
  width: 100%;
  min-width: 0;
  border: none;
  border-radius: 0;
  background: transparent;
  padding: 8px 10px;
}
.affix-input > input:focus { outline: none; box-shadow: none; }
.affix-input > .affix {
  display: flex;
  align-items: center;
  padding: 0 10px;
  background: #f2f6f9;
  color: var(--muted);
  font-size: .78rem;
  font-weight: 700;
  white-space: nowrap;
}
.affix-input.prefix > .affix { border-right: 1px solid var(--line); }
.affix-input.suffix > .affix { border-left: 1px solid var(--line); }

/* --- the two-asset mix (Fund / Bond) --- */
.mix-row { padding: 9px 0; border-bottom: 1px dashed #e3e9ee; }
.mix-row:last-child { border-bottom: none; padding-bottom: 4px; }

.mix-head { display: flex; align-items: center; gap: 10px; margin-bottom: 2px; flex-wrap: wrap; }
.mix-name { font-weight: 700; font-size: .95rem; min-width: 3.2em; }
.mix-meta { flex: 1 1 auto; }
.mix-out {
  font-weight: 800;
  font-size: 1.1rem;
  font-variant-numeric: tabular-nums;
  color: var(--accent);
  min-width: 3.2em;
  text-align: right;
}

/* A range input needs its own track/thumb styling or the browser default looks nothing
   like the rest of the pane. */
.mix-slider {
  -webkit-appearance: none;
  appearance: none;
  display: block;
  width: 100%;
  height: 26px;
  margin: 2px 0 0;
  background: transparent;
  cursor: pointer;
}
.mix-slider::-webkit-slider-runnable-track {
  height: 6px;
  border-radius: 3px;
  background: linear-gradient(to right, var(--accent), #9ec2e6);
}
.mix-slider::-moz-range-track {
  height: 6px;
  border-radius: 3px;
  background: linear-gradient(to right, var(--accent), #9ec2e6);
}
.mix-slider::-webkit-slider-thumb {
  -webkit-appearance: none;
  appearance: none;
  width: 22px;
  height: 22px;
  margin-top: -8px;
  border-radius: 50%;
  background: #fff;
  border: 2px solid var(--accent);
  box-shadow: 0 1px 3px rgba(0, 0, 0, .25);
}
.mix-slider::-moz-range-thumb {
  width: 18px;
  height: 18px;
  border-radius: 50%;
  background: #fff;
  border: 2px solid var(--accent);
  box-shadow: 0 1px 3px rgba(0, 0, 0, .25);
}
.mix-slider:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }

.mix-params {
  display: grid;
  /* Fixed 3 fields + the button on ONE row. `auto-fit` could not know the button was coming:
     with three 126px fields filling the card there was no room left, so it wrapped to a second
     row every time -- two rows of controls per asset, and the editor dialog scrolled because
     of it. */
  grid-template-columns: repeat(3, minmax(0, 1fr)) auto;
  gap: 10px 14px;
  align-items: end;
  margin-top: 8px;
}
.mix-params .field { margin-bottom: 0; }
.mix-params .field > label { font-size: .74rem; }
.mix-params .field > select { width: 100%; }
.mix-params > button { align-self: end; justify-self: start; }
.mix-row .growth-note { margin-top: 8px; }

/* --- the compact read-only parameter strip (front page) --- */
.param-strip {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 232px), 1fr));
  gap: 0 20px;
  margin-bottom: 12px;
}
.param-item {
  display: flex;
  justify-content: space-between;
  gap: 10px;
  align-items: baseline;
  border-bottom: 1px dotted var(--line);
  padding: 5px 0;
  min-width: 0;
}
.param-k { color: var(--muted); font-size: .78rem; white-space: nowrap; }
.param-v {
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  text-align: right;
  overflow-wrap: anywhere;
}
.param-foot {
  grid-column: 1 / -1;
  margin: 8px 0 0;
  font-size: .76rem;
  color: var(--muted);
}

/* --- the parameter editor dialog --- */
.modal-wide { max-width: 1240px; }

/* The editor is a 2x2 grid rather than a stack of four cards. Stacked, the body was ~1100px
   tall against a 62vh cap, so it ALWAYS opened with a scrollbar and the last card was never
   visible without rolling. Two columns halve it, and pairing Portfolio with Plan basics puts
   the Fund/Bond split next to the capital and horizon it has to carry -- the comparison you
   actually make. */
.params-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 12px;
  align-items: start;
}
.params-grid > .input-card { margin-bottom: 0; }

.params-body {
  /* Sized from the viewport so the dialog's head and foot stay put; the scroll is a safety
     net for short windows, not the intended way to reach the bottom card. */
  max-height: calc(100vh - 160px);
  overflow-y: auto;
  padding: 2px 4px 2px 2px;
}
.params-foot {
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
  align-items: center;
  justify-content: space-between;
  border-top: 1px solid var(--line);
  margin-top: 12px;
  padding-top: 12px;
}
.params-foot-btns { display: flex; gap: 10px; flex-wrap: wrap; }
.params-live { display: flex; flex-wrap: wrap; gap: 8px 18px; align-items: baseline; }
.pl-item { display: inline-flex; gap: 6px; align-items: baseline; font-variant-numeric: tabular-nums; }
.pl-k { color: var(--muted); font-size: .78rem; }

/* The front-page pane is now a short read-only strip, so the action bar no longer needs to
   be sticky — pinning it would only overlay content for no benefit. */
#inputs .run-bar {
  display: flex;
  align-items: center;
  gap: 12px 16px;
  flex-wrap: wrap;
  margin: 6px -16px -14px;
  padding: 12px 16px 0;
  border-top: 1px solid var(--line);
}
#inputs .run-bar .primary {
  padding: 9px 22px;
  font-size: .95rem;
}
#inputs .status-line { font-size: .8rem; flex: 1 1 260px; }

/* --- touch: buttons and inputs get a comfortable target on coarse pointers --- */
@media (pointer: coarse) {
  #inputs button,
  #inputs select,
  #inputs .affix-input > input { min-height: 44px; }
  #inputs button.tiny { min-height: 38px; }
}

/* ================================================================================
   Responsive: the input pane reflows as one column well before the charts do, and
   the holding grid turns into stacked, individually-labelled cards on a phone.
   ================================================================================ */

@media (max-width: 780px) {
  .params-grid { grid-template-columns: minmax(0, 1fr); }
  .mix-params { grid-template-columns: minmax(0, 1fr); }
  #inputs .run-bar {
    margin: 6px -14px -12px;
    padding: 12px 14px 0;
  }
  #inputs .run-bar .primary { flex: 1 1 100%; }
  .params-foot { flex-direction: column; align-items: stretch; }
  .params-foot-btns > button { flex: 1 1 auto; }
  .params-body { max-height: 58vh; }
}

@media (max-width: 480px) {
  main { padding: 10px; }
  .panel { padding: 12px; }
  .input-card { padding: 12px; }
  #inputs .field-grid { grid-template-columns: minmax(0, 1fr); }
}

.sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0,0,0,0); white-space: nowrap; border: 0;
}

/* Short windows. The roomy card padding is comfortable on a tall display and is exactly what
   pushed the last card past the fold on a 768px-high one -- so tighten it there rather than
   making the desktop dialog cramped for everyone. Keyed on HEIGHT, not width: this is about
   vertical room, and a wide-but-short window has the same problem as a narrow one. */
@media (max-height: 819px) {
  .input-card { padding: 9px 12px; }
  .input-card > .card-hint { margin-bottom: 6px; }
  .params-grid { gap: 10px; }
  .mix-row { padding: 7px 0; }
  .mix-slider { height: 22px; }
  .mix-params { margin-top: 6px; }
  /* Derived from the real chrome, not guessed: modal = overlay padding (48) + head + foot +
     card padding ~= 133px, so the body must stay under 100vh - 184 to keep the WHOLE
     dialog on screen. Clipping is worse than scrolling here -- a clipped dialog can put
     Done and Reset out of reach entirely, whereas a scroll at least reaches them. */
  .params-body { max-height: calc(100vh - 184px); }
}

@media (max-width: 940px) {
  /* Below this the two-up grid cannot hold two readable charts side by side, so it becomes a
     single column and the page scrolls normally — fitting one screen is a desktop affordance. */
  .pane-grid {
    grid-template-columns: minmax(0, 1fr);
    grid-auto-rows: auto;
    height: auto;
  }
  .pane-grid > .panel { overflow: visible; }
  .pane-grid > .panel > .chart-wrap { min-height: 250px; }
  .groups { grid-template-columns: minmax(0, 1fr); }
  .solver-grid { grid-template-columns: minmax(0, 1fr); }
  .solver-result { border-left: none; padding-left: 0; border-top: 1px solid var(--line); padding-top: 12px; }

