:root {
  --accent: #e25822;
  --bg: #f7f5f2;
  --card: #ffffff;
  --text: #2b2b2b;
  --muted: #777;
  --border: #ddd;
  --error: #c0392b;
}

* { box-sizing: border-box; }

body {
  margin: 0 auto;
  padding: 1rem;
  max-width: 1100px;
  font-family: system-ui, -apple-system, sans-serif;
  background: var(--bg);
  color: var(--text);
}

header {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
}

h1 { margin: 0.2rem 0; font-size: 1.6rem; white-space: nowrap; }

.home-link { color: var(--muted); text-decoration: none; }
.home-link:hover { color: var(--accent); }

/* Plan bar pins to the very top so the active plan + Save are always in view.
   Only Save lives here; the rest of the plan actions hide in the ⋯ menu. */
.plan-bar {
  position: sticky;
  top: 0;
  z-index: 60;
  display: flex;
  flex-wrap: nowrap;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem;
  margin: 0.75rem 0;
  padding: 0.5rem 0.6rem;
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: 12px;
}

.plan-left {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  min-width: 0;
}

.plan-bar select {
  padding: 0.4rem 0.6rem;
  font-size: 1rem;
  font-weight: 600;
  min-width: 0;
  max-width: 11rem;
  border: 1px solid var(--border);
  border-radius: 8px;
  background: var(--bg);
  color: var(--text);
}

/* Generic chip button (used inside the ⋯ menu and for the icon button) */
.plan-bar button {
  padding: 0.4rem 0.8rem;
  font-size: 0.85rem;
  font-weight: 500;
  border: 1px solid var(--border);
  border-radius: 8px;
  background: var(--bg);
  color: var(--text);
  cursor: pointer;
  transition: background 0.12s ease, border-color 0.12s ease, color 0.12s ease;
}

.plan-bar button:hover { border-color: var(--accent); color: var(--accent); }

/* ⋯ overflow menu */
.plan-menu { position: relative; }

.icon-btn {
  padding: 0.3rem 0.55rem !important;
  font-size: 1.15rem !important;
  line-height: 1;
}

.menu-pop {
  position: absolute;
  top: calc(100% + 6px);
  right: 0; /* ⋯ sits at the far right — open the menu aligned to that edge */
  z-index: 70;
  display: flex;
  flex-direction: column;
  gap: 0.1rem;
  min-width: 12rem;
  padding: 0.3rem;
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: 10px;
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.16);
}

.menu-pop[hidden] { display: none; }

.menu-pop button {
  text-align: left;
  width: 100%;
  padding: 0.5rem 0.6rem !important;
  border: none !important;
  border-radius: 6px !important;
  background: transparent !important;
  font-weight: 500;
}

.menu-pop button:hover { background: var(--bg) !important; color: var(--accent); }

/* Primary action (Save). In the plan bar it's only live when there are unsaved
   changes — disabled + muted otherwise; accent + clickable when dirty. The modal
   reuses .btn-primary but is never disabled, so it always reads as accent. */
.btn-primary {
  background: var(--accent) !important;
  border-color: var(--accent) !important;
  color: #fff !important;
  font-weight: 600 !important;
}

.btn-primary:not(:disabled):hover { filter: brightness(0.94); }

.btn-primary:disabled {
  background: #ece9e5 !important;
  border-color: var(--border) !important;
  color: #9a958f !important;
  cursor: default;
  filter: none;
}

/* Single chart panel, sticky (just under the plan bar) so it stays in view
   while adjusting inputs below */
.chart-panel {
  position: sticky;
  top: var(--planbar-h, 0);
  z-index: 50;
  background: var(--bg);
  /* graph + view toggle are one compact unit (the toggle hugs the card just
     below it), kept close to the footprint the graph alone used to have */
  padding: 0.25rem 0 0.3rem;
}

/* Segmented toggle that swaps which of the three charts is shown — sits below
   the graph, centred, naming the active view (there's no on-screen title). */
.chart-toggle {
  display: flex;
  width: fit-content;
  max-width: 100%;
  margin: 0.4rem auto 0;
  border: 1px solid var(--border);
  border-radius: 999px;
  overflow: hidden;
  background: var(--card);
}

.chart-toggle button {
  padding: 0.34rem 0.95rem;
  font-size: 0.8rem;
  font-weight: 600;
  border: none;
  border-right: 1px solid var(--border);
  background: transparent;
  color: var(--muted);
  cursor: pointer;
  transition: background 0.12s ease, color 0.12s ease;
}

.chart-toggle button:last-child { border-right: none; }
.chart-toggle button:hover:not(.active) { color: var(--accent); }
.chart-toggle button.active { background: var(--accent); color: #fff; }
/* A hidden toggle button (not in the active methodology's set) must not occupy
   space or keep a stray right-border in the segmented control. */
.chart-toggle button[hidden] { display: none; }

/* Little text toggle that collapses the chart legends + the backtest slider's
   caption to free vertical space for the inputs. It's an absolute overlay in the
   plot's lower-left corner — so it costs no extra height — pinned to the x-axis
   "Age" label's baseline. The `bottom` here is only a first-paint fallback:
   positionLegendToggle() (app.js) sets it inline to track the label as the
   legend is shown/hidden. Quiet styling so it never competes with the chart. */
.legend-toggle {
  position: absolute;
  left: 0.6rem;
  bottom: 0.45rem;
  z-index: 5;
  border: none;
  background: transparent;
  color: var(--muted);
  font-size: 0.7rem;
  font-weight: 600;
  cursor: pointer;
  padding: 0.05rem 0.2rem;
  border-radius: 6px;
}
.legend-toggle::before {
  content: "▾ ";
  font-size: 0.64rem;
}
.legend-toggle[aria-pressed="true"]::before { content: "▸ "; }
.legend-toggle:hover { color: var(--accent); }

/* Backtest headline above the chart: reported FIRE ages + historical success. */
/* Results line, shown for both methodologies below the inputs (the FIRE ages +
   whether the plan works / its historical success). */
.results-summary {
  margin: 1rem auto 0.25rem;
  max-width: 720px;
  padding: 0.6rem 0.9rem;
  border: 1px solid var(--border);
  border-radius: 10px;
  background: var(--card);
  text-align: center;
  line-height: 1.45;
}
.results-summary:empty { display: none; }
.results-summary .res-headline { font-size: 1.05rem; }
.results-summary .res-sub { color: var(--muted); font-size: 0.85rem; }
.results-summary b { color: var(--accent); }

/* Simulation scrubber for the backtest views (below the chart toggle). Column
   layout with a wrapping label so a long caption never widens the page (which
   previously forced mobile to zoom out). */
.sim-slider {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: 0.2rem;
  width: 100%;
  max-width: 520px;
  margin: 0.4rem auto 0;
  box-sizing: border-box;
}
.sim-slider[hidden] { display: none; }
.sim-slider input[type="range"] { width: 100%; box-sizing: border-box; margin: 0; }
.sim-slider .sim-slider-label {
  color: var(--muted);
  font-size: 0.78rem;
  /* Three-column grid (year | scenario | values) kept to TWO rows: the year
     sits in column 1 of the first row only, so the Barista / Full FIRE names
     share one left edge in column 2 and their values align in column 3. The
     grid is sized to its content and centered. */
  display: grid;
  grid-template-columns: auto auto auto;
  column-gap: 0.4rem;
  row-gap: 0.05rem;
  justify-content: center;
  white-space: normal;       /* wrap instead of overflowing the frame */
  overflow-wrap: anywhere;
}
/* The start year sits in the first column of the first row only. */
.sim-slider .sim-slider-label .sim-year {
  grid-column: 1;
  grid-row: 1;
  text-align: right;
}
.sim-slider .sim-slider-label .sim-scen { grid-column: 2; text-align: left; font-weight: 600; }
.sim-slider .sim-slider-label .sim-vals { grid-column: 3; text-align: left; }

/* Negative net-worth values in the slider label are flagged red. */
.sim-slider .sim-slider-label .neg-val { color: var(--error); }

/* A <select> input styled like the text inputs. */
.field-select {
  width: 100%;
  padding: 0.4rem 0.5rem;
  border: 1px solid var(--border);
  border-radius: 8px;
  background: var(--card);
  color: inherit;
  font: inherit;
  cursor: pointer;
}

/* A <select> input styled like the text inputs. */
.field-select {
  width: 100%;
  padding: 0.4rem 0.5rem;
  border: 1px solid var(--border);
  border-radius: 8px;
  background: var(--card);
  color: inherit;
  font: inherit;
  cursor: pointer;
}

/* The combined Model selector and its live plain-language description, shown at
   the top of the Model & assumptions section. */
/* Sits above the assumptions grid; the bottom margin matches the grid's row gap
   so the gap between the Model selector and the first assumption (Real return) is
   the same as the gap between the assumptions themselves. */
.model-field { max-width: 32rem; margin-bottom: 0.6rem; }
.model-desc {
  margin: 0.45rem 0 0;
  font-size: 0.82rem;
  line-height: 1.4;
  color: var(--muted);
}

/* The simulation-model group: one box collecting every simulation option (model
   selector, its assumptions, and the asset-mix sub-group) so the section reads
   as "tax rate" + "how the simulation runs". */
.sim-group {
  margin-top: 0.6rem;
  padding: 0.75rem 0.85rem 0.85rem;
  border: 1px solid var(--border);
  border-radius: 12px;
  background: color-mix(in srgb, var(--card) 70%, transparent);
}
/* The "Simulation model" sub-heading stays locked just below its section heading
   ("Model & assumptions") while any of the simulation fields are on screen, so
   both headings are visible as you scroll the section. --section-h (the section
   h3's measured height) is published in JS alongside --section-top. The opaque
   background is the flattened equivalent of the .sim-group's translucent fill, so
   the bar reads as part of the box at rest and stays readable when content
   scrolls under it. */
.sim-head {
  position: sticky;
  top: calc(var(--section-top, 0px) + var(--section-h, 0px));
  z-index: 39;
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 0.5rem;
  margin-bottom: 0.6rem;
  padding: 0.25rem 0;
  background: color-mix(in srgb, var(--card) 70%, var(--bg));
}
.sim-title { font-size: 0.85rem; font-weight: 600; color: var(--accent); }

/* Fixed-mix allocation: the three sleeves grouped under one heading with a live
   total, so it's clear they're a single 100% split (normalised by the model).
   Nested inside .sim-group as a sub-group. */
.alloc-group {
  margin-top: 0.6rem;
  padding: 0.6rem 0.75rem 0.75rem;
  border: 1px solid var(--border);
  border-radius: 10px;
  background: color-mix(in srgb, var(--card) 55%, transparent);
}
.alloc-group[hidden] { display: none; }
.alloc-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 0.5rem;
  margin-bottom: 0.55rem;
}
.alloc-title { font-size: 0.82rem; font-weight: 600; color: var(--accent); }
.alloc-total { font-size: 0.78rem; color: var(--muted); font-variant-numeric: tabular-nums; }
.alloc-total.warn { color: var(--error); font-weight: 600; }
.alloc-auto {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  margin-bottom: 0.55rem;
  font-size: 0.8rem;
  color: var(--muted);
  cursor: pointer;
}
.alloc-auto input { cursor: pointer; }

/* On-page methodology: each methodology section, the active one highlighted. */
.methodology-doc { border-top: 1px solid var(--border); margin-top: 0.6rem; padding-top: 0.2rem; }
.methodology-doc.active { background: color-mix(in srgb, var(--accent) 7%, transparent); border-radius: 8px; padding: 0.2rem 0.6rem; }
.methodology-active-badge { color: var(--accent); font-weight: 600; margin: 0.3rem 0; }

/* Scenario switch: a compact pill overlaid in the top-right corner of the chart
   tile, so it costs no vertical space. Sits over the usually-empty upper-right
   of the income/expense plots. */
.scenario-toggle {
  position: absolute;
  top: 0.95rem;
  right: 0.95rem;
  z-index: 5;
  display: flex;
  border: 1px solid var(--border);
  border-radius: 999px;
  overflow: hidden;
  background: var(--card);
  font-size: 0.72rem;
  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.08);
}
.scenario-toggle[hidden] { display: none; }
.scenario-toggle button {
  padding: 0.22rem 0.66rem;
  border: none;
  border-right: 1px solid var(--border);
  background: transparent;
  color: var(--muted);
  font-weight: 600;
  cursor: pointer;
  transition: background 0.12s ease, color 0.12s ease;
}
.scenario-toggle button:last-child { border-right: none; }
.scenario-toggle button:hover:not(.active):not(:disabled) { color: var(--accent); }
.scenario-toggle button.active { background: var(--accent); color: #fff; }
.scenario-toggle button:disabled { opacity: 0.4; cursor: not-allowed; }

.chart-wrap {
  position: relative;
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 0.5rem;
  height: 284px;
}

/* Legend collapsed (store.collapseLegends): drop the in-chart legends + the
   backtest slider caption and shrink the plot so the inputs gain room. The
   chart's legend.display is also turned off in app.js (applyLegendCollapse) so
   the freed space goes to the plot rather than empty padding. */
.legends-collapsed .chart-wrap { height: 234px; }
.legends-collapsed .sim-slider .sim-slider-label { display: none; }

/* Red error overlay covering the plot when the fixed asset mix doesn't total
   100% and auto-adjust is off — the chart behind it would be misleading. */
.chart-error {
  position: absolute;
  inset: 0;
  z-index: 6;
  display: flex;
  align-items: center;
  justify-content: center;
  text-align: center;
  padding: 1.5rem;
  border-radius: 10px;
  background: #fdecea;
  color: var(--error);
  font-weight: 600;
  line-height: 1.4;
}
.chart-error[hidden] { display: none; }

/* Dragging a finger across the plot must keep the tooltip "sliding" — block the
   browser's touch text-selection / callout so the gesture is never hijacked
   into selecting the canvas element (which would freeze the hover info). */
.chart-wrap canvas {
  -webkit-user-select: none;
  user-select: none;
  -webkit-touch-callout: none;
}

/* All three charts share the wrap; only the active slot is shown (toggled in
   app.js). The toggle lives on the slot, not the <canvas>, because Chart.js
   writes an inline display:block on the canvas that would override a CSS rule. */
.chart-slot { display: none; height: 100%; }
.chart-slot.active { display: block; }

.info {
  display: inline-block;
  position: relative;
  margin-left: 0.3rem;
  color: #aaa;
  cursor: help;
  font-style: normal;
}

.info:hover, .info:focus { color: var(--accent); outline: none; }

.info .tip {
  display: none;
  position: absolute;
  left: 0;
  top: 1.5em;
  z-index: 100;
  width: 280px;
  padding: 0.6rem 0.75rem;
  background: #2b2b2b;
  color: #f3f3f3;
  font-size: 0.8rem;
  line-height: 1.45;
  border-radius: 8px;
  box-shadow: 0 4px 14px rgba(0, 0, 0, 0.25);
}

.info:hover .tip, .info:focus .tip { display: block; }

.inputs { margin-top: 0.5rem; }

.input-section { margin-top: 1.25rem; }
.input-section:first-child { margin-top: 0.5rem; }

/* Each subsection heading sticks just below the chart panel while any of its
   fields are on screen; the next section's heading then pushes it out. The
   --section-top offset (plan bar + chart panel height) is kept current in JS. */
.input-section h3 {
  position: sticky;
  top: var(--section-top, 0);
  z-index: 40;
  margin: 0 0 0.6rem;
  font-size: 0.95rem;
  color: var(--accent);
  background: var(--bg);
  border-bottom: 1px solid var(--border);
  padding: 0.35rem 0 0.25rem;
}

.section-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(310px, 1fr));
  gap: 0.6rem 1.2rem;
}

/* Income schedule: a full-time income baseline plus optional break points.
   Mirrors the housing schedule below (same layout), differing only in the value
   unit ($/yr) and the wider nudge step. */
.income-rows { display: flex; flex-direction: column; gap: 0.5rem; }

.income-row {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  gap: 0.5rem 0.75rem;
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 0.5rem 0.75rem;
}

.income-cell {
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
  flex: 1 1 8rem;
  min-width: 7rem;
}

.income-cell .row-slider { min-width: 0; }
.income-row .inc-remove { align-self: flex-start; }

.income-from {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  min-width: 6.5rem;
}

.income-from label,
.income-base-label { font-size: 0.8rem; color: var(--muted); }

.income-row input[type="number"] {
  padding: 0.25rem 0.4rem;
  font-size: 16px; /* 16px keeps iOS from auto-zooming on focus (see housing) */
  border: 1px solid var(--border);
  border-radius: 5px;
}

.income-from .inc-from { width: 3.5rem; }
.income-amount { display: flex; align-items: center; gap: 0.3rem; }
.income-amount .inc-annual { width: 5.5rem; }
.income-amount .affix { color: var(--muted); font-size: 0.9rem; }

.income-add {
  display: block;
  width: fit-content;
  margin: 0.6rem auto;   /* centered between the income schedule and barista income */
  padding: 0.3rem 0.7rem;
  font-size: 0.85rem;
  border: 1px solid var(--border);
  border-radius: 6px;
  background: var(--card);
  cursor: pointer;
}

.income-add:hover { border-color: var(--accent); color: var(--accent); }

.inc-remove {
  margin-left: auto;
  border: 1px solid var(--border);
  border-radius: 6px;
  background: var(--card);
  color: var(--muted);
  cursor: pointer;
  padding: 0.2rem 0.5rem;
  line-height: 1;
}

.inc-remove:hover { border-color: var(--error); color: var(--error); }

/* Housing schedule: a baseline row plus optional break points */
.housing-rows { display: flex; flex-direction: column; gap: 0.5rem; }

.housing-row {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  gap: 0.5rem 0.75rem;
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 0.5rem 0.75rem;
}

/* Each row splits into an age cell and a cost cell: the value box on top, its
   own half-width nudge slider below. The two cells share the width evenly. */
.housing-cell {
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
  flex: 1 1 8rem;
  min-width: 7rem;
}

.housing-cell .row-slider { min-width: 0; }

.housing-row .h-remove { align-self: flex-start; }

.housing-from {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  min-width: 6.5rem;
}

.housing-from label,
.housing-base-label { font-size: 0.8rem; color: var(--muted); }

.housing-row input[type="number"] {
  padding: 0.25rem 0.4rem;
  /* 16px keeps iOS from auto-zooming (and scroll-jumping) when the field gains
     focus — anything smaller triggers the zoom. Applies to every editable box. */
  font-size: 16px;
  border: 1px solid var(--border);
  border-radius: 5px;
}

.housing-from .h-from { width: 3.5rem; }
.housing-cost { display: flex; align-items: center; gap: 0.3rem; }
.housing-cost .h-monthly { width: 4.5rem; }
.housing-cost .affix { color: var(--muted); font-size: 0.9rem; }

/* Inline nudge slider that shares the row with the edit boxes. Reuses .slider-wrap
   (position/touch handling); takes the leftover width but can wrap to its own line
   on a narrow row. */
.row-slider { min-width: 8rem; }
.row-slider input[type="range"] {
  flex: 1;
  width: 100%;
  accent-color: var(--accent);
}

.housing-add {
  margin-top: 0.6rem;
  padding: 0.3rem 0.7rem;
  font-size: 0.85rem;
  border: 1px solid var(--border);
  border-radius: 6px;
  background: var(--card);
  cursor: pointer;
}

.housing-add:hover { border-color: var(--accent); color: var(--accent); }

.h-remove {
  margin-left: auto;
  border: 1px solid var(--border);
  border-radius: 6px;
  background: var(--card);
  color: var(--muted);
  cursor: pointer;
  padding: 0.2rem 0.5rem;
  line-height: 1;
}

.h-remove:hover { border-color: var(--error); color: var(--error); }

/* Children list: a row per child (just the birth age), mirroring housing. The
   shared per-child cost fields render below in a normal section grid. */
.children-rows { display: flex; flex-direction: column; gap: 0.5rem; }

.child-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem 0.75rem;
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 0.5rem 0.75rem;
}

.child-from { display: flex; align-items: center; gap: 0.4rem; }
.child-from label { font-size: 0.8rem; color: var(--muted); }

.child-row input[type="number"] {
  width: 4rem;
  padding: 0.25rem 0.4rem;
  font-size: 16px;
  border: 1px solid var(--border);
  border-radius: 5px;
}

.child-row input.invalid {
  border-color: var(--error);
  background: #fdecea;
}

.children-add {
  margin: 0.6rem 0 0.9rem;
  padding: 0.3rem 0.7rem;
  font-size: 0.85rem;
  border: 1px solid var(--border);
  border-radius: 6px;
  background: var(--card);
  cursor: pointer;
}

.children-add:hover { border-color: var(--accent); color: var(--accent); }

.c-remove {
  margin-left: auto;
  border: 1px solid var(--border);
  border-radius: 6px;
  background: var(--card);
  color: var(--muted);
  cursor: pointer;
  padding: 0.2rem 0.5rem;
  line-height: 1;
}

.c-remove:hover { border-color: var(--error); color: var(--error); }

/* Lump-sum list (one-time income / expense). Mirrors the housing break point —
   an "at age" cell and an amount cell, each with a nudge slider — but adds an
   editable NAME on its own line above, styled like a row title with a ✎ icon. */
/* Separate the lump section from the fields above it (it's appended straight
   after the section's field grid, which has no bottom margin). */
.lump { margin-top: 1rem; }

.lump-rows { display: flex; flex-direction: column; gap: 0.5rem; }

.lump-row {
  position: relative; /* anchors the absolutely-placed ✕ remove button */
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  gap: 0.4rem; /* one gap value everywhere: title↔cells, between cells, etc. */
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 0.5rem 0.75rem;
}

/* The name line spans the full row width (forces the cells onto the next line).
   The whole group (title + ✎) is scaled to 0.8 so it renders at the 0.8rem
   label size while the title stays a real 16px input — see .lump-title. Scaling
   the GROUP (not just the input) keeps the ✎ icon right after the title text and
   at the same size. transform-origin keeps it left-aligned / vertically centred. */
.lump-name {
  flex: 1 1 100%;
  display: flex;
  align-items: center;
  gap: 0.4rem;
  padding-right: 3rem; /* reserve a lane on the right so the ✕ has its own space */
  transform: scale(0.8);
  /* Scale from the top-left so the only leftover space sits at the bottom, then
     pull the next line up with a negative margin — otherwise the ×0.8 box
     reserves its full unscaled height and the title↔cells gap reads larger than
     every other gap. The compensation is `0.2 × the title's fixed height` (set
     below), so it's the same on every browser instead of depending on the UA's
     intrinsic input height (which is what left the gap uneven on iOS Safari). */
  transform-origin: left top;
  margin-bottom: -0.35rem;
}

/* The name reads like every other field label/title — small and muted, not bold
   — at rest (borderless). The ✎ edit icon beside it (or clicking the text)
   focuses it, which is when the input box appears (border + page background), so
   it only looks like an edit box while editing; blurring drops the border and
   it's a plain title again. Matches `.field label` (0.8rem, var(--muted)).

   Mobile: iOS Safari auto-zooms the page when you focus any input with a
   font-size < 16px, which made tapping the title zoom in oddly. The app sizes
   inputs at 16px everywhere to avoid that, so this stays a real 16px input and
   the parent .lump-name is scaled ×0.8 to render it at the label size — focusing
   it never zooms, and the size is identical at rest and while editing.

   Width is BOUNDED: where supported, field-sizing makes the box hug the text so
   the ✎ sits right after a short name, but max-width caps it (and caps the
   fallback width on browsers without field-sizing) so a long name never pushes
   the ✎ into the ✕ — it gets clipped inside the box and you scroll/focus to read
   it (renderLump resets scrollLeft on blur so the first letter always shows).
   The explicit height keeps the ×0.8 gap math identical on every browser. */
.lump-title {
  flex: 0 1 auto;
  min-width: 2.5rem;
  max-width: calc(100% - 2.6rem); /* reserve room for the ✎ + a clear gap before the ✕ */
  field-sizing: content;
  box-sizing: border-box;
  height: 1.75rem;     /* fixed so the scaled-height compensation is deterministic */
  line-height: 1.25;
  font-size: 16px;
  color: var(--muted);
  background: transparent;
  border: 1px solid transparent;
  border-radius: 5px;
  padding: 0.2rem 0.45rem;
}
.lump-title::placeholder { color: var(--muted); }
.lump-title:focus { border-color: var(--accent); background: var(--bg); outline: none; }

.lump-edit {
  flex: 0 0 auto;
  color: var(--muted);
  font-size: 20px;     /* a bit bigger than the title text (both scaled ×0.8) */
  font-weight: 700;    /* bold — the trailing U+FE0E forces text (not emoji) so weight applies */
  line-height: 1;
  cursor: pointer;
  user-select: none;
}
.lump-edit:hover { color: var(--accent); }

.lump-cell {
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
  flex: 1 1 8rem;
  min-width: 7rem;
}
.lump-cell .row-slider { min-width: 0; }

/* Both cells share one layout: a 0.8rem muted label/affix (kept on one line),
   the 16px number input, and a 0.4rem gap throughout — keep them in lock-step. */
.lump-from { display: flex; align-items: center; gap: 0.4rem; }
.lump-from label { font-size: 0.8rem; color: var(--muted); white-space: nowrap; }
.lump-from .l-at { width: 3.5rem; }

.lump-amount { display: flex; align-items: center; gap: 0.4rem; }
.lump-amount .l-amount { width: 5.5rem; }
.lump-amount .affix { color: var(--muted); font-size: 0.8rem; white-space: nowrap; }

.lump-row input[type="number"] {
  padding: 0.25rem 0.4rem;
  font-size: 16px; /* prevent iOS focus auto-zoom (see housing input note) */
  border: 1px solid var(--border);
  border-radius: 5px;
}
.lump-row input.invalid { border-color: var(--error); background: #fdecea; }

.lump-row .l-remove {
  position: absolute;
  top: 0.5rem;
  right: 0.75rem;
  border: 1px solid var(--border);
  border-radius: 6px;
  background: var(--card);
  color: var(--muted);
  cursor: pointer;
  padding: 0.2rem 0.5rem;
  line-height: 1;
}
.lump-row .l-remove:hover { border-color: var(--error); color: var(--error); }

.lump-add {
  margin: 0.6rem 0 0.2rem;
  padding: 0.3rem 0.7rem;
  font-size: 0.85rem;
  border: 1px solid var(--border);
  border-radius: 6px;
  background: var(--card);
  cursor: pointer;
}
.lump-add:hover { border-color: var(--accent); color: var(--accent); }

.field {
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 0.5rem 0.75rem;
}

.field .label-row {
  display: flex;
  align-items: baseline;
  margin-bottom: 0.25rem;
}

.field label {
  font-size: 0.8rem;
  color: var(--muted);
}

.field .row {
  display: flex;
  align-items: center;
  gap: 0.6rem;
}

.field input[type="text"] {
  width: 110px;
  padding: 0.25rem 0.4rem;
  font-size: 16px; /* prevent iOS focus auto-zoom (see housing input note) */
  border: 1px solid var(--border);
  border-radius: 5px;
}

/* When a just-edited box is scrolled back into view (e.g. after the mobile
   keyboard closes), land it just below the sticky chart + its section heading so
   it's the top visible thing rather than hidden behind the graph. */
.field,
.income-row,
.housing-row,
.child-row,
.lump-row { scroll-margin-top: calc(var(--section-top, 0px) + 2.75rem); }

.field input[type="text"].invalid {
  border-color: var(--error);
  background: #fdecea;
}

.slider-wrap {
  position: relative;
  flex: 1;
  display: flex;
  align-items: center;
  /* Stop iOS from treating a long-press as text selection (the magnifying-glass
     loupe / callout); we want the hold to "select" the slider instead. */
  -webkit-touch-callout: none;
  -webkit-user-select: none;
  user-select: none;
}

.field input[type="range"] {
  flex: 1;
  accent-color: var(--accent);
}

/* Transparent overlay that intercepts stray taps on touch devices. pan-y lets
   vertical page scrolls through; a long-press adds .selected, which lifts it. */
.slider-guard {
  position: absolute;
  inset: -6px 0;
  touch-action: pan-y;
  z-index: 2;
  cursor: pointer;
}

.slider-wrap.selected .slider-guard {
  /* Selected: we steer the slider from the held touch via the guard, so it must
     keep receiving events. Lock scrolling so the drag isn't read as a pan. */
  touch-action: none;
}

/* Visible cue that the slider is selected and ready to drag. */
.slider-wrap.selected input[type="range"] {
  filter: drop-shadow(0 0 0.5px var(--accent));
}

.slider-wrap.selected::after {
  content: "";
  position: absolute;
  inset: -7px -4px;
  border: 1.5px solid var(--accent);
  border-radius: 8px;
  pointer-events: none;
  opacity: 0.6;
}

/* Value bubble shown above the thumb while a slider is selected. */
.slider-bubble {
  position: absolute;
  bottom: 100%;
  margin-bottom: 8px;
  transform: translateX(-50%);
  padding: 0.15rem 0.45rem;
  background: var(--accent);
  color: #fff;
  font-size: 0.78rem;
  font-weight: 600;
  white-space: nowrap;
  border-radius: 6px;
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.12s ease;
  z-index: 3;
}

.slider-bubble::after {
  content: "";
  position: absolute;
  top: 100%;
  left: 50%;
  transform: translateX(-50%);
  border: 4px solid transparent;
  border-top-color: var(--accent);
}

.slider-wrap.selected .slider-bubble { opacity: 1; }

/* ---------------------------------------------------------------------------
   Modal dialog (save-before-switch prompt)
--------------------------------------------------------------------------- */

.modal-backdrop {
  position: fixed;
  inset: 0;
  z-index: 1000;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 1rem;
  background: rgba(20, 20, 20, 0.45);
}

.modal {
  background: var(--card);
  border-radius: 14px;
  box-shadow: 0 12px 40px rgba(0, 0, 0, 0.25);
  padding: 1.25rem 1.25rem 1rem;
  max-width: 24rem;
  width: 100%;
}

.modal-title { margin: 0 0 0.4rem; font-size: 1.05rem; }
.modal-msg { margin: 0 0 0.75rem; color: var(--text); font-size: 0.92rem; line-height: 1.45; }

.modal-list {
  margin: 0 0 0.75rem;
  padding-left: 1.1rem;
  color: var(--text);
  font-size: 0.9rem;
  line-height: 1.5;
}

.modal-list li { margin: 0.1rem 0; }

.modal-sub {
  margin: 0 0 1rem;
  color: var(--muted);
  font-size: 0.85rem;
}

.share-row {
  display: flex;
  gap: 0.4rem;
  margin: 0 0 1rem;
}

.share-url {
  flex: 1;
  min-width: 0; /* let the field shrink instead of overflowing the row */
  box-sizing: border-box;
  padding: 0.45rem 0.6rem;
  /* 16px keeps mobile Safari from auto-zooming when the field is focused/selected */
  font-size: 16px;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  color: var(--text);
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 8px;
}

.copy-btn {
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2.6rem;
  border: 1px solid var(--border);
  border-radius: 8px;
  background: var(--bg);
  color: var(--text);
  cursor: pointer;
  transition: border-color 0.15s, color 0.15s, background 0.15s;
}

/* Hover styling only on real hover devices — on touch, :hover sticks after a
   tap and would otherwise paint the button with the accent colour once the
   copied (green) state reverts. The copied state stays green even while hovered. */
@media (hover: hover) {
  .copy-btn:hover { border-color: var(--accent); color: var(--accent); }
  .copy-btn.copied:hover { border-color: #2e9e5b; color: #2e9e5b; }
}

.copy-btn .ic-check { display: none; }

/* Copied state: green button + the check pops in (ic-copy hides). */
.copy-btn.copied {
  border-color: #2e9e5b;
  color: #2e9e5b;
  background: rgba(46, 158, 91, 0.08);
}
.copy-btn.copied .ic-copy { display: none; }
.copy-btn.copied .ic-check {
  display: block;
  animation: copy-pop 0.32s ease;
}

@keyframes copy-pop {
  0%   { transform: scale(0.3); opacity: 0; }
  60%  { transform: scale(1.25); }
  100% { transform: scale(1); opacity: 1; }
}

@media (prefers-reduced-motion: reduce) {
  .copy-btn.copied .ic-check { animation: none; }
}

.modal-actions {
  display: flex;
  flex-wrap: wrap;
  justify-content: flex-end;
  gap: 0.5rem;
}

.modal-actions button {
  padding: 0.45rem 0.9rem;
  font-size: 0.9rem;
  font-weight: 500;
  border: 1px solid var(--border);
  border-radius: 8px;
  background: var(--bg);
  color: var(--text);
  cursor: pointer;
}

.modal-actions button:hover { border-color: var(--accent); color: var(--accent); }

.modal-actions .btn-primary:hover { filter: brightness(0.94); color: #fff; }

.methodology {
  margin: 1.5rem 0;
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 0.75rem 1rem;
}

.methodology summary {
  cursor: pointer;
  font-weight: 600;
  font-size: 1.05rem;
}

.methodology code {
  background: var(--bg);
  padding: 0.1rem 0.3rem;
  border-radius: 4px;
}

.methodology pre {
  background: var(--bg);
  padding: 0.6rem;
  border-radius: 6px;
  overflow-x: auto;
}

/* ---------------------------------------------------------------------------
   PDF report (#report) — never shown on screen; when exporting, the body gets
   .printing-report and print shows ONLY the report.
--------------------------------------------------------------------------- */

.report { display: none; }

@media print {
  /* Tighter margins than the 12mm default so the four stacked plots + the
     one-line outcome clear page 1 with headroom (the page-1-must-fit invariant).
     @bottom-right adds a page number in the right margin (CSS Paged Media). Note
     Blink doesn't render margin boxes in print today, so the reliable page
     numbers come from the print pipeline's footer (footerTemplate in the render
     script / the browser's own "Headers and footers" option); this is the
     standards-correct hook for when support lands and is harmless meanwhile. */
  @page {
    margin: 10mm;
    @bottom-right { content: counter(page); font-size: 8pt; color: #777; }
  }

  body.printing-report {
    max-width: none;
    padding: 0;
    background: #fff;
  }

  body.printing-report > :not(.report) { display: none !important; }

  body.printing-report .report {
    display: block;
    font-size: 10pt;
    line-height: 1.4;
  }

  .report h1 { font-size: 15pt; margin: 0 0 0.05rem; }
  .report h2 {
    font-size: 11.5pt;
    margin: 0.45rem 0 0.3rem;
    border-bottom: 1px solid #ccc;
    padding-bottom: 0.12rem;
  }
  /* Page 1: up to four plots stacked, x-axes aligned (all canvases share the
     same width and a fixed y-axis width — see afterFit in app.js). The whole
     row + the one-line outcome must stay on one page, so the charts are capped to
     a height that leaves room for the header and outcome (max-height matches the
     REPORT_CHART_W:H aspect at full content width, so it never distorts — it is
     only a safety cap for unusually wide/short pages). */
  .report-charts-row { display: flex; flex-direction: column; gap: 1mm; break-inside: avoid; }
  .report-charts-row figure { margin: 0; }
  .report-charts-row figcaption {
    font-size: 8.5pt;
    font-weight: 600;
    text-align: center;
    margin-bottom: 0.02rem;
  }
  .report-charts-row img { width: 100%; max-height: 44mm; display: block; }

  /* Outcome: a single line — header, colon, did-it-work — to leave page 1 to the
     plots (replaces the old multi-fact strip). */
  .report-outcome {
    border: none;
    font-size: 11pt;
    margin: 0.5rem 0 0;
  }
  .report-outcome span { font-weight: 600; }

  /* Page 2: the explicit inputs list, on its own page, grouped into the same
     labelled sections as the app (Income / Lifestyle / Housing / Child / Model),
     with the share link + generation note centred beneath. */
  .report-inputs-page { break-before: page; }
  .report-footer {
    text-align: center;
    margin: 1.2rem 0 0;
    font-size: 9pt;
    color: #555;
  }
  .report-footer a { color: #e25822; font-weight: 600; }
  .report-gen { margin-top: 0.15rem; }
  /* The scan-to-open QR. image-rendering keeps the GIF's hard module edges crisp
     when scaled to print size instead of blurring them (which hurts scanning). */
  .report-qr {
    display: block;
    margin: 0 auto 0.3rem;
    width: 26mm;
    height: 26mm;
    image-rendering: pixelated;
  }
  .report-input-group { break-inside: avoid; margin-bottom: 0.5rem; }
  .report-input-group h3 {
    font-size: 9.5pt;
    color: #e25822;            /* --accent, matching the app's section headings */
    margin: 0.55rem 0 0.2rem;
    border-bottom: 1px solid #ddd;
    padding-bottom: 0.1rem;
  }
  /* two columns within each section, items filling row-wise */
  .report-kvs { display: grid; grid-template-columns: 1fr 1fr; column-gap: 8mm; }
  .report-kvs .kv {
    display: flex;
    justify-content: space-between;
    gap: 0.8rem;
    break-inside: avoid;
    padding: 0.1rem 0;
    border-bottom: 1px solid #eee;
    font-size: 9pt;
  }
  .report-kvs .kv span { color: #555; }

  /* Page 3+: methodology */
  .report-methodology-page { break-before: page; }
  .report-methodology {
    font-size: 7.5pt;
    line-height: 1.2;
    column-count: 2;
    column-gap: 7mm;
  }
  .report-methodology h1 { display: none; } /* the section already has a heading */
  .report-methodology h1 + p { display: none; } /* repo-maintenance preamble, not report content */
  .report-methodology h2 { font-size: 9pt; border: none; margin: 0.35rem 0 0.1rem; break-after: avoid; }
  .report-methodology p, .report-methodology ul { margin: 0.18rem 0; }
  .report-methodology ul { padding-left: 1.1rem; }
  .report-methodology pre, .report-methodology code {
    font-size: 7.2pt;
    background: #f4f4f4;
  }
  .report-methodology pre {
    margin: 0.18rem 0;
    padding: 0.25rem;
    break-inside: avoid;
    white-space: pre-wrap; /* code lines wider than a column wrap, not clip */
  }
}
