/* Thomas Tong — Honest Calculator. Faithful digitisation of his Excel
   dashboard. Ink + rice-paper cream, vermillion accent, green/red strictly for
   profit/loss, mono for every figure. Light content surfaces so black
   annotation ink stays visible. */

:root {
  --red-500: #C9261A; --red-600: #A81A14; --red-900: #541710; --red-50: #FFF1EC;
  --ink-900: #141110; --ink-800: #231E1B; --ink-700: #3A322D; --ink-600: #5C514A; --ink-500: #7F746C;
  --ink-300: #CABFB5; --ink-200: #E4DAD1; --ink-100: #F2EBE3; --ink-50: #FAF6F0;
  --gold-500: #C79A3A; --jade: #2F6B5B; --jade-50: #ECF4F1;
  --profit: #1F7A4D; --loss: #C9261A; --paper: #FFFFFF;

  --font-serif: "Instrument Serif", Georgia, serif;
  --font-sans: "Inter Tight", system-ui, sans-serif;
  --font-mono: "JetBrains Mono", ui-monospace, monospace;
  --r: 12px; --shadow: 0 1px 2px rgba(20,17,16,.06), 0 8px 24px rgba(20,17,16,.06);
  --toolbar-h: 58px; --ease: cubic-bezier(.2,.7,.2,1);
}

* { box-sizing: border-box; margin: 0; }
/* Tiny build stamp (bottom-right) so you can confirm which build is live past a cache. */
.build-stamp { position: fixed; right: 6px; bottom: calc(3px + env(safe-area-inset-bottom)); z-index: 1; font-family: var(--font-mono); font-size: 9.5px; line-height: 1; letter-spacing: .2px; color: var(--ink-300); pointer-events: none; user-select: none; }
[hidden] { display: none !important; }
button, input, label.field { touch-action: manipulation; } /* no double-tap zoom */
/* Transient toast (e.g. "message copied"). Centred above the safe area, fades in/out. */
.toast { position: fixed; left: 50%; bottom: calc(22px + env(safe-area-inset-bottom)); transform: translateX(-50%) translateY(8px); z-index: 3000; max-width: min(92vw, 420px); background: var(--ink-900); color: var(--ink-50); font-family: var(--font-sans); font-size: 13.5px; font-weight: 500; line-height: 1.4; text-align: center; padding: 11px 16px; border-radius: var(--r); box-shadow: var(--shadow); opacity: 0; pointer-events: none; transition: opacity .22s var(--ease), transform .22s var(--ease); }
.toast.show { opacity: 1; transform: translateX(-50%) translateY(0); }
html { -webkit-text-size-adjust: 100%; overflow-x: clip; overscroll-behavior-x: none; }
/* The real wobble fix is min-width: 0 on the form controls below (iOS gives the
   date input / selects a wide intrinsic width that, as flex items, refused to
   shrink → overflow). overflow-x: clip is a sticky-safe net (hidden would break
   the sticky toolbar by making body a scroll container).
   overscroll-behavior-x: none — the page has no horizontal scroll by design, yet
   iOS still elastic-panned the viewport sideways on TWO-finger drags (a lateral
   wobble one-finger pans axis-lock away), which read as "the page shifts left and
   right / scroll feels stuck". Opt out of horizontal overscroll entirely;
   vertical keeps its natural rubber-band. */
body { font-family: var(--font-sans); color: var(--ink-900); background: var(--ink-100); font-size: 14.5px; line-height: 1.45; -webkit-font-smoothing: antialiased; overflow-x: clip; width: 100%; max-width: 100%; overscroll-behavior-x: none; }
/* Pinch-zoom is allowed EVERYWHERE (body keeps default touch-action) — Yan's call
   after the ?tune lab: on iOS a zoomable page pays a small two-finger-scroll tax
   (every 2f pan is held as a pinch candidate; blocking pinch via touch-action
   pan-x pan-y makes 2f buttery), and no toggleable property removes the tax while
   keeping pinch. Zoom-everywhere beats buttery-2f — 1-finger and trackpad scroll
   are unaffected either way. */

/* ── Toolbar ─────────────────────────────────────────────────────────── */
.toolbar {
  position: sticky; top: 0; z-index: 2700; min-height: var(--toolbar-h);
  display: flex; align-items: center; gap: 16px; background: var(--ink-900); color: var(--ink-50);
  padding: env(safe-area-inset-top) max(16px, env(safe-area-inset-right)) 0 max(16px, env(safe-area-inset-left));
}
.brand { display: flex; align-items: center; gap: 11px; flex: 0 0 auto; }
.brand-mark { display: grid; place-items: center; width: 34px; height: 34px; border-radius: 8px; background: var(--red-500); color: var(--ink-50); font-family: var(--font-serif); font-weight: 900; font-size: 22px; line-height: 1; }
.brand-name { font-weight: 700; font-size: 14px; line-height: 1.1; }
.brand-sub { font-size: 11.5px; color: var(--ink-300); }
@media (max-width: 560px) { .brand-text { display: none; } }
.tools-right { display: flex; align-items: center; gap: 8px; margin-left: auto; }
.tools-right .act { border-color: rgba(255,255,255,.2); background: transparent; color: var(--ink-50); }
.tools-right .act:hover { background: rgba(255,255,255,.1); }
.tools-right .act[data-active="true"] { background: var(--red-500); border-color: var(--red-500); }

.act { appearance: none; border: 1px solid var(--ink-200); background: var(--ink-50); color: var(--ink-800); font-family: inherit; font-size: 13px; font-weight: 600; padding: 8px 13px; border-radius: 8px; cursor: pointer; transition: background .15s var(--ease), border-color .15s var(--ease); }
.act .ico { margin-right: 5px; }
.act:hover { background: var(--ink-100); border-color: var(--ink-300); }

/* ── Sheet ───────────────────────────────────────────────────────────── */
/* Capped at a comfortable reading width and centred: filling the full 1800px made panels
   too wide (and the spotlight zoom enormous). On big screens the excess becomes natural
   side margin; on iPad it stays full-width (below the cap) with its own side padding. */
.sheet-inner { max-width: 1200px; margin: 0 auto; padding: 10px 24px; display: flex; flex-direction: column; gap: 10px; width: 100%; transform-origin: center top; }

/* Swiping across a field (Thomas's highlight muscle memory) and drawing while
   annotating both trigger the browser's text-selection blue hue across labels and
   computed values. Suppress selection across the sheet; restore it only on editable
   inputs so select-all-on-focus (the UX Thomas likes) still works. Computed AUTO
   boxes ([readonly]) stay unselectable — they're values, not fields. */
.sheet { -webkit-user-select: none; user-select: none; -webkit-touch-callout: none; }
.sheet input:not([readonly]) { -webkit-user-select: text; user-select: text; }
/* While annotating there is nothing to select or copy — ink is the only gesture.
   A drag starting on the annotate canvas (outside .sheet) can still sweep a
   selection through the editable inputs beneath, painting the blue hue mid-pitch,
   so selection goes fully off for the whole document, inputs included. */
body.annotating, body.annotating .sheet input:not([readonly]) { -webkit-user-select: none; user-select: none; -webkit-touch-callout: none; }
/* While annotating, the vertical rubber-band is off: at a scroll boundary the
   bounce engages with zero threshold and grabbed one-finger strokes inside the
   6px slop (page scrolled instead of inking — always at the bottom-most panel,
   the one you annotate after scrolling down). Threshold-gated scrolling remains;
   two-finger scroll inside annotate just stops dead at the ends instead of
   bouncing. Normal mode keeps the native bounce. */
html.annotating, html.annotating body { overscroll-behavior-y: none; }
/* Belt for every boundary failure mode the bounce rule can't reach (iPadOS < 16.4
   ignores overscroll-behavior on the root; touches landing during scroll settle
   are scroller-owned and unpreventable; the standalone home-indicator strip owns
   the bottom ~30px): while annotating, the page gains scroll room below the last
   panel, so the bottom-most rows can always ride up to mid-screen — annotatable
   content never has to sit pinned against the physical bottom edge at max scroll. */
body.annotating .sheet { padding-bottom: 45vh; }

/* Landscape / large screens: one contained, centred screen. JS auto-fit scales
   .sheet-inner down so everything fits without scrolling (e.g. 11" iPad Pro)
   while 12.9" stays full size. Narrow screens scroll the page normally. */
/* Wide screens (iPad + desktop): app-shell with a fixed toolbar and a SHEET that scrolls
   full-size — the content fills the width and scrolls for length, with the results dash
   pinned at the top. (Replaces the old one-screen height-fit, which shrank the content —
   and its width — to fit the screen height.) */
/* Tablet portrait and up (721px+): whole-page (document) scroll — the model the mobile
   layout already uses and the one iOS standalone scrolls reliably (the app-shell internal
   sheet scroll did NOT scroll on iPad). The sticky toolbar + sticky dash keep the chrome
   and results pinned. This range (721-1023px = portrait iPad) previously had NO sticky
   dash, so the live figures scrolled away while editing holding costs below the fold. */
@media (min-width: 721px) {
  .dash { position: sticky; top: calc(var(--toolbar-h) + env(safe-area-inset-top)); z-index: 15; background: var(--ink-100); padding: 8px 0 12px; margin-top: -4px; }
  /* Side breathing room (respects iPad safe-area insets). Portrait iPad is narrower, so
     less inset here; the laptop/landscape block below doubles it. */
  .sheet-inner { padding-left: max(40px, env(safe-area-inset-left)); padding-right: max(40px, env(safe-area-inset-right)); }
}
@media (min-width: 1024px) {
  /* Landscape iPad + laptop: more side breathing room now the content fills the width. */
  .sheet-inner { padding-left: max(88px, env(safe-area-inset-left)); padding-right: max(88px, env(safe-area-inset-right)); }
}

/* Dashboard cards — ONE flex model for every width (no grid-vs-strip breakpoint).
   `flex: 1 1 158px` grows the 6 cards to fill the row when they fit (≈1126px+ of inner
   width → no scroll, equal widths like a grid) and floors each at 158px so when they DON'T
   fit (portrait/split iPad, narrow laptop) the row scrolls horizontally instead of cramming
   6 cards until labels clip. This replaces repeat(6, 1fr) — whose min-content floor either
   overflowed the viewport (11" landscape) or clipped "Breakeven price" labels (≤1024px). */
.dash {
  display: flex; gap: 10px;
  overflow-x: auto; -webkit-overflow-scrolling: touch;
  overscroll-behavior-x: contain; scroll-snap-type: x proximity;
}
.dash::-webkit-scrollbar { display: none; }
.dash-card { flex: 1 1 158px; min-width: 158px; scroll-snap-align: start; }
.dash-card { background: var(--paper); border: 1px solid var(--ink-200); border-top: 3px solid var(--ink-900); border-radius: 11px; padding: 10px 12px; box-shadow: var(--shadow); }
/* Reserve the ⓘ height (flex + min-height) so a card WITHOUT an ⓘ (e.g. Buy price)
   keeps its value on the same baseline as cards that have one. */
.dash-label { display: flex; align-items: center; min-height: 19px; white-space: nowrap; font-size: 11.5px; color: var(--ink-500); text-transform: uppercase; letter-spacing: .5px; }
/* Viewport-responsive value size: a 9-digit figure ($3,631,154) at a fixed 28px can't fit
   6 cards in a sub-1200px row, which clipped the numbers (or forced an ugly per-card JS
   down-scale that also failed on device). clamp() keeps the value fitting at every width —
   ~21px on an 11" landscape (6 cards still fit, no clip), up to 28px on a roomy laptop —
   uniform across all cards, no JS transform needed. fitDashValues stays only as a safety
   net for rare 10-digit values. */
.dash-value { font-family: var(--font-mono); font-weight: 700; font-size: clamp(20px, 1.8vw, 22px); line-height: 1.1; margin-top: 5px; font-variant-numeric: tabular-nums; white-space: nowrap; overflow: hidden; transition: color .2s var(--ease); }
.dash-value.profit { color: var(--profit); } .dash-value.loss { color: var(--loss); }
.dash-sub { font-family: var(--font-mono); font-size: 11.5px; color: var(--ink-500); margin-top: 2px; font-variant-numeric: tabular-nums; white-space: nowrap; }

/* Tappable cards (Group B): pagination dots signal the alternate view; the whole card
   taps to cycle. Verdict colour reinforces the words (the words carry the meaning). */
.dash-card.tap { position: relative; cursor: pointer; -webkit-tap-highlight-color: transparent; }
.dash-card.tap:hover { border-color: var(--ink-400); }
.dash-card.tap:focus-visible { outline: 2px solid var(--red-500); outline-offset: 2px; }
.dash-dots { position: absolute; top: 11px; right: 14px; display: flex; gap: 4px; }
.dot { width: 5px; height: 5px; border-radius: 50%; background: var(--ink-300); }
.dot.on { background: var(--ink-700); }
.card-verdict { font-weight: 800; }
.card-verdict.mr-buy { color: var(--profit); }
.card-verdict.mr-rent { color: var(--loss); }
.card-verdict.mr-neutral { color: var(--ink-500); font-weight: 600; }

/* Mobile: results pinned under the toolbar as a compact swipeable strip so the
   live figures stay visible while you edit. (The page wobble was the form-control
   overflow, now fixed with min-width: 0 — not this strip.) */
@media (max-width: 720px) {
  .dash {
    grid-template-columns: none; display: flex; gap: 8px;
    overflow-x: auto; -webkit-overflow-scrolling: touch;
    overscroll-behavior-x: contain;
    scroll-snap-type: x proximity;
    position: sticky; top: var(--toolbar-h); z-index: 15;
    padding: 8px 0 14px; margin: -4px 0 2px;
    /* Right-edge fade signalling "more cards", auto-hiding at the end. */
    background-color: var(--ink-100);
    background-image:
      linear-gradient(to left, var(--ink-100) 40%, rgba(242,235,227,0)),
      radial-gradient(farthest-side at 100% 50%, rgba(20,17,16,.16), rgba(20,17,16,0));
    background-position: 100% 0, 100% 0;
    background-size: 42px calc(100% - 14px), 16px calc(100% - 14px);
    background-repeat: no-repeat;
    background-attachment: local, scroll;
  }
  .dash::-webkit-scrollbar { display: none; }
  .dash-card { flex: 0 0 auto; min-width: 44%; scroll-snap-align: start; padding: 8px 14px; border-top-width: 2px; }
  .dash-label { font-size: 10.5px; }
  .dash-value { font-size: 22px; margin-top: 3px; }
}

/* Top/bottom split: info panels on top (2), cost panels below (3). */
.info-row { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; align-items: start; }
.cost-row { display: grid; gap: 10px; align-items: start; }
/* Buying/Selling groups stack as flex columns (keeps the 7px field gap on every screen);
   the laptop layout below lays the two side by side. */
.ot-col { display: flex; flex-direction: column; gap: 7px; min-width: 0; }
@media (max-width: 720px) { .info-row { grid-template-columns: 1fr; } }

/* Provenance footer — closes the sheet in normal flow so a reader who scrolls
   past the numbers lands on it. Distinct from .build-stamp (a fixed 9.5px
   developer marker); this one is for Thomas and whoever he shows. */
.sheet-foot { display: flex; flex-wrap: wrap; justify-content: space-between; align-items: baseline; gap: 3px 16px; padding: 2px 2px 12px; font-size: 11.5px; line-height: 1.45; color: var(--ink-500); }
.sheet-foot .sf-rates { font-family: var(--font-mono); letter-spacing: .1px; }
/* Stale state is signalled by weight, a glyph and the sentence itself — never by
   hue. The primary reader is red-green colourblind, so a red tint reads as no
   change at all; these are neutral warm greys, separated by lightness. */
/* Paper fill lifts it off the --ink-100 page (they are the same value, so a tinted
   fill would read as no box at all), and the heavy left rule borrows the dash
   cards' dark-edge idiom — salience with no hue to lose. */
.sheet-foot.stale { background: var(--paper); border: 1px solid var(--ink-200); border-left: 3px solid var(--ink-900); border-radius: 8px; padding: 9px 12px; margin-bottom: 4px; box-shadow: var(--shadow); }
.sheet-foot.stale .sf-rates { color: var(--ink-900); font-weight: 700; }
.sheet-foot .sf-warn { font-family: var(--font-sans); }
@media (max-width: 720px) { .sheet-foot { flex-direction: column; } }

/* Tablet and up (721px+): spread the cost area — One-time spans the left two columns
   (Buying | Selling side by side), Holding below it, and the tall Cost summary receipt holds
   the full-height right column. Applied from 721px so EVERY iPad mode (incl. 11" portrait
   at 834px) shares this one layout instead of a separate 3-across one. The bigger TYPE block
   below stays gated at 1024px (the narrower 11"-portrait columns keep the base type). */
@media (min-width: 721px) {
  /* 50/50 — One-time/Holding line up under Key purchase info, Cost summary under Key
     sales, so the whole sheet is one clean two-column grid (and the wider receipt reads
     better — see the font bump below). */
  .cost-row { grid-template-columns: 1fr 1fr; grid-template-areas: "onetime summary" "holding summary"; }
  .cost-row .onetime { grid-area: onetime; }
  .cost-row .holding { grid-area: holding; }
  .cost-row .receipt { grid-area: summary; }
  /* .cost-row prefix wins specificity over the later `.pbody { display: flex }`. */
  /* align-items: stretch makes the two columns equal height; pinning each section's
     subtotal to the bottom (margin-top: auto) then lines Buying subtotal up with Selling
     subtotal even though Buying has one more line item. */
  .cost-row .onetime-body { display: grid; grid-template-columns: 1fr 1fr; gap: 0 28px; align-items: stretch; }
  .cost-row .onetime-body > .ot-col:nth-child(2) { border-left: 1px solid var(--ink-200); padding-left: 28px; }
  .cost-row .onetime-body > .ot-col > .drow { margin-top: auto; } /* subtotal pinned to column bottom → aligned */
  .cost-row .onetime-body > .drow { grid-column: 1 / -1; } /* One-time subtotal spans both columns */
  /* Receipt stretches to its full column height; JS (fitReceipt) then scales the row
     FONT (via --rfs) so the text grows to fill that height — readable, no gap, and it
     re-fits per render so it tallies whatever the scenario (rental rows etc.). */
  .cost-row .receipt { align-self: stretch; }
}
/* Portrait: 2:1 instead of 1:1. A portrait screen is narrow, so an even split squeezed
   One-time's Buying|Selling sub-columns until labels wrapped ("SELLING COST from sale
   proceeds", "SSD · 0% (held past 4yr)"). Give the One-time/Holding column 2 parts and the
   Cost summary 1 part — the receipt is just label→value rows and compresses cleanly into a
   narrow column, and the taller portrait screen gives it the vertical room to stay readable. */
@media (min-width: 721px) and (orientation: portrait) {
  .cost-row { grid-template-columns: 2fr 1fr; }
}
/* Bigger type on wide screens. The cost panels read larger; the Cost summary receipt is
   sized UP to fill its column (the left One-time+Holding stack is taller, so the receipt
   had ~230px of empty space below it). Filling it costs no extra sheet height — the left
   column already sets the cost-row height — so it's free on the iPad's one-screen fit too. */
@media (min-width: 1024px) {
  .cost-row .drow { font-size: 15px; }
  .cost-row .drow .dk { font-size: 14.5px; }
  .cost-row .drow .dv { font-size: 17px; }
  .cost-row .drow.grand .dv { font-size: 19px; }
  .cost-row .pair-label { font-size: 14px; }
  .cost-row .pair-total { font-size: 16.5px; }
  .cost-row input { font-size: 16px; }
  /* Receipt rows scale by --rfs (set by fitReceipt JS) so the text grows to fill the
     column height. --rfs defaults to 1; the rsec/total bases are tuned so the whole
     receipt fills its column once scaled. Row padding scales too, so it grows evenly. */
  .cost-row .receipt h2 { font-size: 22px; }
  .cost-row .rrow { font-size: calc(15px * var(--rfs, 1)); padding: calc(4px * var(--rfs, 1)) 0; }
  .cost-row .rsec { font-size: calc(11.5px * var(--rfs, 1)); margin: calc(13px * var(--rfs, 1)) 0 calc(3px * var(--rfs, 1)); }
  .cost-row .rrow.rsub .rk, .cost-row .rrow.rsub .rv { font-size: calc(15px * var(--rfs, 1)); }
  .cost-row .rrow.rgrand .rk, .cost-row .rrow.rgrand .rv { font-size: calc(16px * var(--rfs, 1)); }
  .cost-row .rrow.rtotal { padding-top: calc(8px * var(--rfs, 1)); }
  .cost-row .rrow.rtotal .rk { font-size: calc(18px * var(--rfs, 1)); }
  .cost-row .rrow.rtotal .rv { font-size: calc(20px * var(--rfs, 1)); }
}
.panel { background: var(--paper); border: 1px solid var(--ink-200); border-radius: 10px; box-shadow: var(--shadow); overflow: hidden; }
.panel h2 { font-family: var(--font-serif); font-size: 19px; font-weight: 400; padding: 10px 17px 0; display: flex; align-items: baseline; gap: 7px; flex-wrap: wrap; cursor: pointer; }
.panel h2 .hsub { font-family: var(--font-sans); font-size: 11px; font-weight: 600; color: var(--ink-500); text-transform: uppercase; letter-spacing: .3px; }
.pbody { padding: 8px 17px 12px; display: flex; flex-direction: column; gap: 7px; }
/* Sub-section header inside a panel (e.g. Buying cost / Selling cost). A mid-panel
   header (Selling cost) gets clear space + a hairline above it so it reads as a NEW
   section start, not as a trailer under the preceding subtotal. The first header
   (Buying cost) is the panel start, so no rule/space above it. */
.psec { font-size: 11px; font-weight: 800; text-transform: uppercase; letter-spacing: .4px; color: var(--ink-900); margin: 18px 0 4px; padding-top: 14px; border-top: 1px solid var(--ink-200); display: flex; align-items: baseline; gap: 7px; }
.psec:first-child { margin-top: 0; padding-top: 0; border-top: 0; }
.psec-sub { font-size: 10.5px; font-weight: 500; text-transform: none; letter-spacing: 0; color: var(--ink-400); }
.field-row3 { display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px; }
.field-row2 { display: grid; grid-template-columns: repeat(2, 1fr); gap: 10px; }
/* Phones: stack the 3-/2-col field rows so inputs and the date picker aren't cramped.
   Must come AFTER the base rules above to win on source order. */
@media (max-width: 560px) { .field-row3, .field-row2 { grid-template-columns: 1fr; } }
.panel-note { font-size: 10.5px; color: var(--ink-500); margin-top: 3px; line-height: 1.35; }

/* Receipt-style cost summary (replaces end-of-loan; bottom panel on mobile) */
.receipt .rbody { padding: 8px 17px 14px; display: flex; flex-direction: column; }
/* Section headers (BUYING / SELLING / HOLDING) carry the emphasis — bold + ink-900 so the
   eye lands on the group it's reading, not on the subtotal rows. */
.rsec { font-size: 11px; font-weight: 800; text-transform: uppercase; letter-spacing: .4px; color: var(--ink-900); margin: 10px 0 2px; }
.rsec:first-child { margin-top: 2px; }
.rrow { display: flex; justify-content: space-between; align-items: baseline; gap: 10px; font-size: 13.5px; padding: 2px 0; }
/* Small left padding only — the numbered marker (.rnum, ~1.9em + gap) already supplies the
   item step-in, so the old 12px before the number was a double indent. */
.rrow .rk { color: var(--ink-500); padding-left: 2px; }
/* Continuous line-item index "1." "2." — set in the mono face (vs the sans labels) and
   right-aligned in a fixed box so the periods column-align and labels line up across 1- and
   2-digit numbers. Muted + tabular so it reads as an index marker, not data. Scales with the
   row font. */
.rnum { display: inline-block; min-width: 1.9em; margin-right: 8px; text-align: right; font-family: var(--font-mono); font-weight: 600; font-size: 0.9em; color: var(--ink-400); font-variant-numeric: tabular-nums; }
/* Subtotals/totals pull back to the margin so each one visually closes its group;
   only the component line items step in. Indent the label, not the row, so the
   value column stays aligned. */
.rrow.rgrand .rk, .rrow.rtotal .rk, .rrow.rnet .rk { padding-left: 0; }
/* Section subtotals (Buying/Selling) nest one step under One-time subtotal: a
   slight indent between the components (12px) and the flush grand sums (0). */
.rrow.rsub .rk { padding-left: 6px; }
.rrow .rv { font-family: var(--font-mono); font-variant-numeric: tabular-nums; color: var(--ink-500); white-space: nowrap; }
.rrow.rsub { border-top: 1px dashed var(--ink-300); margin-top: 2px; padding-top: 4px; }
/* Section subtotal (Buying/Selling): quiet now that the SECTION HEADER carries the
   emphasis — regular weight, a step up from the line items but not bold. */
.rrow.rsub .rk { color: var(--ink-600); font-weight: 500; }
.rrow.rsub .rv { font-weight: 500; color: var(--ink-600); }
.rrow.rtotal { border-top: 2px solid var(--ink-900); margin-top: 7px; padding-top: 8px; }
.rrow.rtotal .rk { font-size: 15px; font-weight: 800; color: var(--ink-900); }
.rrow.rtotal .rv { font-size: 16px; font-weight: 800; color: var(--ink-900); }
.rrow.rgrand { border-top: 1.5px solid var(--ink-700); margin-top: 3px; padding-top: 5px; }
.rrow.rgrand .rk, .rrow.rgrand .rv { font-weight: 800; color: var(--ink-900); }
.rrow.rrent .rv { color: var(--profit); }
.rrow.rnet { border-top: 1px dashed var(--ink-300); margin-top: 4px; padding-top: 6px; }
.rrow.rnet .rk, .rrow.rnet .rv { font-weight: 700; color: var(--ink-900); }
/* Price basis under a section header (Purchase price / Expected sell price): the figure each
   section's costs scale from. Flush to the margin, label quiet, value prominent; a hairline
   below sets it apart from the numbered cost items it anchors. */
.rrow.rbasis { padding-bottom: 4px; margin-bottom: 2px; border-bottom: 1px dotted var(--ink-200); }
.rrow.rbasis .rk { padding-left: 0; color: var(--ink-700); font-weight: 600; }
.rrow.rbasis .rv { color: var(--ink-900); font-weight: 700; }
.receipt h2 { justify-content: space-between; }
/* Visible "expand to spotlight" hint in each panel header's top-right (where the eye
   and finger go). The whole header toggles the spotlight; this just marks the spot. */
.panel-expand { margin-left: auto; align-self: center; color: var(--ink-300); font-size: 15px; line-height: 1; }
body.spot .is-spot .panel-expand { color: var(--ink-500); }

/* Full breakdown rendered large inside the focus popup (for video) */
.rbody-lg .rsec { font-size: 13px; margin: 22px 0 6px; }
.rbody-lg .rsec:first-child { margin-top: 4px; }
.rbody-lg .rrow { font-size: 19px; padding: 7px 0; }
.rbody-lg .rrow .rk { padding-left: 4px; }
.rbody-lg .rrow.rgrand .rk, .rbody-lg .rrow.rtotal .rk, .rbody-lg .rrow.rnet .rk, .rbody-lg .rrow.rbasis .rk { padding-left: 0; }
.rbody-lg .rrow.rsub .rk { padding-left: 9px; }
.rbody-lg .rrow.rtotal { margin-top: 10px; padding-top: 12px; }
.rbody-lg .rrow.rtotal .rk { font-size: 21px; }
.rbody-lg .rrow.rtotal .rv { font-size: 22px; }

/* Key-figures grid + input-echo spec strip on the honest-cost popup — the six dashboard
   cards and the scenario inputs, so one screenshot of this popup is self-contained.
   auto-fit keeps full rows: 3 columns in the 540px card, 2 on narrow phones. */
.rfigs { display: grid; grid-template-columns: repeat(auto-fit, minmax(130px, 1fr)); gap: 8px 10px; margin-bottom: 12px; }
.rfig { border: 1px solid var(--ink-200); border-radius: 8px; padding: 7px 10px 8px; }
.rfig-k { font-size: 10px; font-weight: 700; text-transform: uppercase; letter-spacing: .4px; color: var(--ink-500); white-space: nowrap; }
.rfig-v { font-family: var(--font-mono); font-variant-numeric: tabular-nums; font-size: 15.5px; font-weight: 700; color: var(--ink-900); margin-top: 2px; white-space: nowrap; }
.rfig-v.profit { color: var(--profit); }
.rfig-v.loss { color: var(--loss); }
.rspecs { margin-bottom: 12px; padding-bottom: 10px; border-bottom: 1px solid var(--ink-200); font-size: 12.5px; line-height: 1.55; color: var(--ink-500); }
/* The inline Cost summary panel carries the input-echo spec strip, shown ONLY when
   spotlighted — in the normal grid the panel sits beside the very inputs it echoes,
   while the spotlight dims those inputs away. NO figures grid here: the spotlight
   keeps the dash lit above the panel, so the six cards are already on screen. */
.receipt .rspecs { display: none; }
.receipt.is-spot .rspecs { display: block; padding: 2px 17px 8px; margin-bottom: 0; }

/* Fields */
label.field { display: flex; flex-direction: column; gap: 4px; font-size: 13px; font-weight: 600; color: var(--ink-700); }
.field .sub { font-weight: 500; color: var(--ink-600); font-size: 11.5px; }
.field-hint { font-weight: 600; font-size: 11.5px; line-height: 1.35; color: var(--red-600); background: var(--red-50); border-radius: 6px; padding: 5px 8px; }
/* Neutral variant: regime context past the window (no SSD charged — informational, not a warning). */
.field-hint--info { color: var(--ink-700); background: var(--ink-100); }
.ssd-cliff { display: inline-block; margin-top: 5px; appearance: none; border: 1px solid var(--red-500); background: #fff; color: var(--red-600); font-family: var(--font-sans); font-weight: 700; font-size: 11px; line-height: 1.2; padding: 4px 9px; border-radius: 999px; cursor: pointer; white-space: nowrap; transition: background .12s var(--ease), color .12s var(--ease); }
.ssd-cliff:hover, .ssd-cliff:active { background: var(--red-500); color: #fff; }
.ssd-cliff:focus-visible { outline: 2px solid var(--red-500); outline-offset: 2px; }
.iw { position: relative; display: flex; align-items: center; min-width: 0; }
.field-top { display: flex; align-items: baseline; gap: 7px; flex-wrap: wrap; }
/* K/M multiplier chips — on the label line (above the input, so visible above the
   mobile keyboard), shown only while a money field is focused */
/* Chip group sits inline at the right on wide screens; on narrow ones the field-top
   wraps it to its own full-width line (flex-basis 100%) so the chips lay out in one
   tidy row below the label instead of crowding it. */
.qmul { display: none; gap: 5px; margin-left: auto; flex-wrap: wrap; justify-content: flex-end; }
@media (max-width: 720px) {
  .qmul { flex: 1 0 100%; margin-left: 0; gap: 4px; margin-top: 2px; }
  .qmul button { padding: 3px 8px; font-size: 12px; }
}
label.field:focus-within .qmul { display: flex; }
.qmul button { font-family: var(--font-mono); font-size: 12px; font-weight: 700; color: var(--ink-700); background: var(--ink-100); border: 1px solid var(--ink-300); border-radius: 6px; padding: 2px 10px; cursor: pointer; line-height: 1.4; }
.qmul button:active { background: var(--red-50); border-color: var(--red-500); color: var(--red-600); }
@media (pointer: coarse) { .qmul button { padding: 4px 13px; font-size: 13px; } }
/* Sell price carries extra scenario chips (+30/+20/+10/BE/S) on top of K/M — give the
   group its own full-width row in ONE line, so the 7th chip never wraps to a new line. */
.qmul.qmul-wide { flex: 1 0 100%; margin-left: 0; margin-top: 5px; justify-content: flex-end; flex-wrap: nowrap; gap: 5px; }
.qmul.qmul-wide button { padding: 3px 9px; }
@media (pointer: coarse) { .qmul.qmul-wide button { padding: 4px 10px; font-size: 12.5px; } }
@media (max-width: 380px) { .qmul.qmul-wide { flex-wrap: wrap; } } /* tiny phones: wrap rather than overflow */
input { width: 100%; min-width: 0; font-family: var(--font-mono); font-size: 15px; color: var(--ink-900); background: var(--ink-50); border: 1px solid var(--ink-200); border-radius: 8px; padding: 8px 11px; outline: none; text-align: right; font-variant-numeric: tabular-nums; transition: border-color .12s var(--ease), box-shadow .12s var(--ease), background .12s var(--ease); }
input:focus { border-color: var(--red-500); background: #fff; box-shadow: 0 0 0 3px var(--red-50); }
/* Empty zero-value money fields read as "$0", not blank/unfinished. */
input::placeholder { color: var(--ink-300); opacity: 1; }
input[type="date"] { text-align: left; font-family: var(--font-sans); min-width: 0; max-width: 100%; -webkit-appearance: none; appearance: none; }

/* Select (buyer profile) and date — match the input box, left-aligned text */
select { width: 100%; min-width: 0; font-family: var(--font-sans); font-size: 15px; color: var(--ink-900); background: var(--ink-50); border: 1px solid var(--ink-200); border-radius: 8px; padding: 8px 30px 8px 11px; outline: none; cursor: pointer; -webkit-appearance: none; appearance: none; background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath d='M2 4l4 4 4-4' stroke='%237F746C' stroke-width='1.6' fill='none' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E"); background-repeat: no-repeat; background-position: right 10px center; transition: border-color .12s var(--ease), box-shadow .12s var(--ease); }
select:focus { border-color: var(--red-500); background-color: #fff; box-shadow: 0 0 0 3px var(--red-50); }

/* Read-only "auto" field: a computed value that sits among input boxes, shown
   as a locked field so it can't be mistaken for one you fill. Calm flat fill,
   dashed border, muted text. */
.field.readonly input { background: #ECECEF; color: var(--ink-900); border-style: dashed; border-color: #C2C2CB; cursor: default; }
.field.readonly input:focus { border-color: #C2C2CB; box-shadow: none; }
.auto-tag { font-size: 9px; font-weight: 700; text-transform: uppercase; letter-spacing: .4px; color: #6E6E78; background: #E2E2E7; border-radius: 4px; padding: 1px 4px; vertical-align: middle; }
/* Live tag: sell price is auto-tracking the breakeven (disappears once the user
   sets their own target). Jade = the brand's calm accent, not profit-green. */
.be-flag { font-size: 9.5px; font-weight: 700; text-transform: uppercase; letter-spacing: .3px; color: var(--jade); background: var(--jade-50); border-radius: 4px; padding: 1px 5px; vertical-align: middle; }

/* Per-unit input paired with its computed period total on one line */
.pair { display: flex; flex-direction: column; gap: 4px; margin-bottom: 4px; }
.pair-top { display: flex; justify-content: space-between; align-items: baseline; gap: 8px; }
.pair-label { font-size: 13px; font-weight: 600; color: var(--ink-700); }
.pair-cap { font-size: 12.5px; font-weight: 500; color: var(--ink-500); white-space: nowrap; }
.pair-body { display: grid; grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr); grid-template-rows: auto auto; align-items: center; column-gap: 10px; }
.pair-body .iw { grid-column: 1; grid-row: 1; }
.pair-arrow { grid-column: 2; grid-row: 1; color: var(--ink-400); font-size: 15px; text-align: center; }
.pair-total { grid-column: 3; grid-row: 1; font-family: var(--font-mono); font-size: 15px; font-weight: 600; color: var(--ink-900); font-variant-numeric: tabular-nums; text-align: right; white-space: nowrap; padding-right: 12px; }
.pair-unit { grid-column: 1; grid-row: 2; text-align: right; font-size: 11.5px; font-weight: 500; color: var(--ink-500); line-height: 1.1; margin-top: 2px; }

/* Derived rows */
.drow { display: flex; justify-content: space-between; align-items: baseline; gap: 8px; font-size: 14px; padding: 5px 0; }
.drow .dk { color: var(--ink-700); font-weight: 600; }
/* Inset by the input's right padding(11) + border(1) so ledger numbers (subtotals)
   share the exact right edge of the numbers inside the boxes above them. */
.drow .dv { font-family: var(--font-mono); font-size: 15px; font-weight: 600; font-variant-numeric: tabular-nums; white-space: nowrap; padding-right: 12px; }
.drow.subrow { padding: 2px 0 6px; }
.drow.subrow .dk { color: var(--ink-500); font-size: 12.5px; }
.drow.subrow .dv { color: var(--ink-700); font-size: 15px; }
/* Rental deductions in Key sales. ONE "Less" heading carries the subtraction for the
   whole group, so each figure stays positive and matches the receipt's identical two
   lines (both panels are on screen together on iPad/desktop — opposing signs for the
   same number is what confused Thomas). A per-line minus under a "Less" heading would
   read as the same double negative we removed from the receipt. The indent subordinates
   the group to the gross rent above it. */
.deduct-head { font-size: 11px; font-weight: 800; text-transform: uppercase; letter-spacing: .4px; color: var(--ink-500); margin-bottom: -2px; }
.deduct-group { display: flex; flex-direction: column; gap: 7px; padding-left: 12px; }
.drow.strong { border-top: 1px dashed var(--ink-300); border-bottom: 0; margin-top: 2px; padding-top: 6px; }
.drow.strong .dk { font-weight: 700; color: var(--ink-900); }
.drow.strong .dv { font-weight: 800; }
/* Buying/Selling column subtotals borrow the receipt's quiet subtotal look (muted, weight
   500 under a dashed rule) so the bold section header + grand One-time total carry the
   hierarchy, and the panel reads like the Cost summary. */
.onetime-body > .ot-col > .drow.strong .dk,
.onetime-body > .ot-col > .drow.strong .dv { color: var(--ink-600); font-weight: 500; }
/* Panel grand total (One-time total) — bigger and heavier than the subtotals */
.drow.grand { border-top: 2.5px double var(--ink-900); margin-top: 4px; padding-top: 7px; }
.drow.grand .dk { font-weight: 800; font-size: 15px; }
.drow.grand .dv { font-weight: 800; font-size: 17px; }

/* Maximum Risk — its own full-width banner panel (Thomas's "one number"): the monthly
   cost-to-own gut check vs the rent for an equivalent home. Text-led so it works for a
   colour-blind reader — the call word ("Cheaper to buy/rent") carries the meaning; the
   accent colour is a secondary cue. */
/* Sits in the right column under Key sales (its own panel, not full-width). Stacked:
   the cost-to-own number on top, the rent-vs-buy test below. */
.info-col { display: flex; flex-direction: column; gap: 10px; min-width: 0; }
.panel.mrp { border-top: 3px solid var(--ink-300); }
.mrp-body { padding: 12px 17px 15px; }
.mrp-val { font-family: var(--font-mono); font-size: 28px; font-weight: 800; color: var(--ink-900); font-variant-numeric: tabular-nums; line-height: 1; white-space: nowrap; }
.mrp-u { font-size: 14px; font-weight: 600; color: var(--ink-500); margin-left: 3px; }
.mrp-cap { font-size: 12px; color: var(--ink-500); margin-top: 5px; display: flex; align-items: center; gap: 4px; }
.mrp-vs { display: flex; align-items: center; flex-wrap: wrap; gap: 7px; margin-top: 12px; padding-top: 12px; border-top: 1px solid var(--ink-200); }
.mrp-vs-lbl { font-size: 13px; font-weight: 600; color: var(--ink-600); }
.mr-rent-iw { flex: 0 0 auto; }
.mr-rent-iw input { width: 104px; text-align: right; }
.mr-unit { font-size: 12px; color: var(--ink-500); margin-left: -3px; }
.mr-call { margin-left: 5px; font-size: 15px; font-weight: 800; white-space: nowrap; }
.panel.mrp.mr-buy { border-top-color: var(--profit); } .panel.mrp.mr-buy .mr-call { color: var(--profit); }
.panel.mrp.mr-rent { border-top-color: var(--loss); } .panel.mrp.mr-rent .mr-call { color: var(--loss); }
.panel.mrp.mr-neutral .mr-call { color: var(--ink-400); font-weight: 600; font-style: italic; }

/* ── Segmented control (Property type: Completed | New launch) ────────── */
.seg { display: inline-flex; background: var(--ink-50); border: 1px solid var(--ink-200); border-radius: 8px; padding: 2px; gap: 2px; }
.seg-btn { flex: 1; appearance: none; border: 0; background: transparent; font-family: var(--font-sans); font-size: 13.5px; font-weight: 600; color: var(--ink-600); padding: 7px 12px; border-radius: 6px; cursor: pointer; white-space: nowrap; transition: background .12s var(--ease), color .12s var(--ease); }
.seg-btn.on { background: var(--ink-900); color: var(--ink-50); }
.seg-btn:not(.on):hover { color: var(--ink-900); }
@media (pointer: coarse) { .seg-btn { min-height: 40px; font-size: 14.5px; } }

/* ── BUC progressive payment (popover: interest-gap lead + horizontal milestone timeline) ── */
/* Wider card than a normal breakdown — Thomas walks a client through it on iPad. Deliberately
   NOT 99.co's vertical ring + dotted rail: we lead with the cost comparison they never show,
   and lay the milestones out horizontally. */
.explain-card.explain-wide { max-width: 940px; }
/* Save-image action inside the honest-cost receipt popover (the Share path). */
.explain-actions { display: flex; justify-content: center; margin-top: 16px; }
.explain-actions .act.primary { font-size: 14.5px; padding: 10px 20px; }
@media (pointer: coarse) { .explain-actions .act.primary { min-height: 44px; } }
/* iPad / desktop: fill most of the screen + scale the type up, so the full timeline reads
   across a room on video (the card was only using ~79% width / ~62% height before). */
@media (min-width: 1024px) {
  .explain-card.explain-wide { max-width: 1200px; padding: 48px 56px 46px; }
  .explain-card.explain-wide .explain-title { font-size: 38px; }
  .explain-card.explain-wide .explain-sub { font-size: 16px; }
  .buc2-lead { font-size: 29px; margin: 6px 0 20px; }
  .buc2-cmp { gap: 14px 18px; }
  .buc2-cmp .cmp-k { font-size: 16px; }
  .buc2-cmp .cmp-track { height: 32px; border-radius: 8px; }
  .buc2-cmp .cmp-v { font-size: 20px; }
  .buc2-save { font-size: 17px; padding: 14px 18px; margin-top: 14px; }
  .buc2-cap { font-size: 13px; margin: 34px 0 18px; }
  .htl .when { font-size: 12px; height: 17px; }
  .htl .nm { font-size: 14px; margin-top: 7px; }
  .htl .pc { font-size: 12.5px; }
  .htl .rail { height: 24px; }
  .htl .dot { width: 15px; height: 15px; }
  .htl .dot.ms { width: 17px; height: 17px; }
  .tl-flip { margin-bottom: 18px; }
  .tl-flip-btn { font-size: 15px; padding: 8px 18px; }
  .htl .tl-ln, .htl .tl-mo, .htl .tl-cc { font-size: 10.5px; }
  .amz2-bar { height: 54px; }
  .amz2-title { font-size: 14px; }
  .amz2-legend { font-size: 14px; }
  .tl-specs { gap: 14px 40px; margin-bottom: 20px; }
  .tl-spec .k { font-size: 12px; }
  .tl-spec .v { font-size: 17px; }
  .tl-foot { margin-top: 30px; gap: 20px; padding-top: 18px; }
  .tl-stat .stat-k { font-size: 12.5px; }
  .tl-stat .stat-v { font-size: 23px; }
  .tl-stat .stat-sub { font-size: 12px; }
}
@media (max-width: 720px) { .explain-card.explain-wide { max-width: none; } }

/* Small "View timeline →" trigger sitting in the Property type label row (when New launch
   is on) — a light touch, not a full-width button that pushes the inputs down. */
.seg-top { display: flex; align-items: baseline; gap: 7px; }
/* New-launch top row: Property type toggle spans two columns, Years to TOP takes the
   third — so it lines up with Purchase date in the field-row3 below it. */
.buc-toprow > label.field:first-child { grid-column: span 2; }
/* The Years-to-TOP input FILLS its field up to the taller toggle's height, so its label
   sits right above it (no floating gap) while the input bottom still meets the toggle bottom.
   Both fields stretch to the row height (grid default); the input grows into the slack.
   (margin-top: auto instead pushed the input to the bottom and left the label floating high
   above it.) */
.buc-toprow .iw { flex: 1 1 auto; }
.buc-toprow .iw input { height: 100%; }
/* margin-right pulls the trigger clear of the Years-to-TOP column to its right (it sits at the
   far edge of the 2-col Property type span, only the grid gap from Years to TOP otherwise). */
.tl-link { margin-left: auto; margin-right: 14px; appearance: none; border: 0; background: transparent; color: var(--red-600); font-family: var(--font-sans); font-size: 12px; font-weight: 700; cursor: pointer; padding: 0; white-space: nowrap; }
.tl-link:hover { text-decoration: underline; }
/* Keep the touch tap-area (padding) but cancel it in layout (margin-block) so the link
   doesn't inflate the label line and push "Property type" below "Years to TOP". */
@media (pointer: coarse) { .tl-link { font-size: 12.5px; padding: 6px 0; margin-block: -6px; } }

/* lead: the interest gap vs a completed unit */
.buc2-lead { font-family: var(--font-serif); font-size: 21px; margin: 2px 0 12px; color: var(--ink-900); }
.buc2-cmp { display: grid; grid-template-columns: auto 1fr auto; gap: 9px 13px; align-items: center; }
.buc2-cmp .cmp-k { font-size: 13px; font-weight: 600; color: var(--ink-700); white-space: nowrap; }
.buc2-cmp .cmp-track { height: 22px; background: var(--ink-100); border-radius: 6px; position: relative; overflow: hidden; }
.buc2-cmp .cmp-fill { position: absolute; left: 0; top: 0; bottom: 0; border-radius: 6px; }
.buc2-cmp .cmp-fill.buc { background: var(--red-500); }
.buc2-cmp .cmp-fill.done { background: var(--ink-300); }
.buc2-cmp .cmp-v { font-family: var(--font-mono); font-size: 15px; font-weight: 700; font-variant-numeric: tabular-nums; white-space: nowrap; }
.buc2-save { margin-top: 10px; padding: 9px 13px; background: var(--jade-50); border-left: 4px solid var(--jade); border-radius: 7px; font-size: 14px; font-weight: 700; color: var(--jade); }
.buc2-save b { font-family: var(--font-mono); }

/* horizontal milestone timeline: timing (above) · dot on a two-tone rail · stage/%/monthly (below) */
.buc2-cap { font-size: 11.5px; font-weight: 700; text-transform: uppercase; letter-spacing: .4px; color: var(--ink-500); margin: 22px 0 8px; }
.htl-scroll { overflow-x: auto; -webkit-overflow-scrolling: touch; }
.htl { position: relative; min-width: 600px; }
.htl .row { display: flex; }
.htl .cell { flex: 1; text-align: center; padding: 0 1px; line-height: 1.18; }
.htl .when { font-family: var(--font-mono); font-size: 9.5px; color: var(--ink-500); height: 15px; }
.htl .rail { position: relative; height: 16px; display: flex; align-items: center; }
/* rail colour split: your cash/CPF stretch (ink) then the bank loan (red) at --stop */
.htl .rail::before { content: ''; position: absolute; left: 5%; right: 5%; top: 50%; transform: translateY(-50%); height: 3px; border-radius: 2px; background: linear-gradient(90deg, var(--ink-300) 0 var(--stop, 25%), var(--red-500) var(--stop, 25%) 100%); }
.htl .rcell { flex: 1; display: flex; justify-content: center; }
.htl .dot { width: 11px; height: 11px; border-radius: 50%; background: var(--red-500); border: 2px solid var(--paper); position: relative; z-index: 1; }
.htl .dot.pre { background: var(--ink-300); }                                   /* cash/CPF stage — no loan yet */
.htl .dot.ms { width: 13px; height: 13px; border-radius: 2px; transform: rotate(45deg); background: var(--ink-900); } /* TOP / CSC */
.htl .nm { font-size: 10.5px; font-weight: 700; color: var(--ink-900); margin-top: 5px; }
.htl .nm.ms { color: var(--red-600); }
.htl .pc { font-size: 10px; color: var(--ink-500); font-weight: 600; }

/* vertical timeline — mobile only (a horizontal rail scrolls badly on a phone) */
.vtl { display: none; position: relative; margin-top: 4px; }
/* rail centered on x = 7.5px; dots/diamonds centered on the same line (box-sizing
   border-box on the pseudos so the white border doesn't shift their centre right) */
.vtl::before { content: ''; position: absolute; box-sizing: border-box; left: 6px; top: 12px; bottom: 12px; width: 3px; border-radius: 2px; background: linear-gradient(180deg, var(--ink-300) 0 var(--stop, 25%), var(--red-500) var(--stop, 25%) 100%); }
.vtl-node { position: relative; display: flex; justify-content: space-between; align-items: center; gap: 12px; padding: 10px 2px 10px 28px; }
.vtl-node::before { content: ''; position: absolute; box-sizing: border-box; left: 1.5px; top: 50%; transform: translateY(-50%); width: 12px; height: 12px; border-radius: 50%; background: var(--ink-300); border: 2px solid var(--paper); z-index: 1; }
.vtl-node.drawing::before { background: var(--red-500); }
.vtl-node.ms::before { left: 0.5px; width: 14px; height: 14px; border: 0; border-radius: 2px; transform: translateY(-50%) rotate(45deg); background: var(--ink-900); }
.vtl-info { display: flex; flex-direction: column; gap: 1px; }
.vtl-when { font-family: var(--font-mono); font-size: 11.5px; color: var(--ink-500); }
.vtl-name { font-size: 15px; font-weight: 700; color: var(--ink-900); }
.vtl-node.ms .vtl-name { color: var(--red-600); }
.vtl-name .pc { font-weight: 500; color: var(--ink-500); font-size: 12.5px; }
.vtl-flag { font-size: 11px; font-weight: 700; color: var(--red-600); margin-top: 2px; }

@media (max-width: 720px) {
  .htl-scroll { display: none; }   /* horizontal rail → vertical list on phones */
  .vtl { display: block; }
}

/* ── Loan-timeline matched pair: flip, dollar splits, amortisation, footer ──────── */
/* Flip toggle (Completed ⟷ New launch) — a pill segmented control atop the popover. */
.tl-flip { display: inline-flex; gap: 2px; padding: 3px; background: var(--ink-100); border-radius: 9px; margin-bottom: 12px; }
.tl-flip-btn { font: inherit; font-size: 13px; font-weight: 700; color: var(--ink-500); background: none; border: 0; padding: 6px 15px; border-radius: 7px; cursor: pointer; white-space: nowrap; }
.tl-flip-btn.on { background: var(--paper); color: var(--ink-900); box-shadow: 0 1px 3px rgba(20,17,16,.14); }
/* Scenario spec strip — key inputs as scannable label-over-value chips. */
.tl-specs { display: flex; flex-wrap: wrap; gap: 12px 32px; margin: 2px 0 16px; }
.tl-spec .k { font-size: 11px; text-transform: uppercase; letter-spacing: .4px; color: var(--ink-500); }
.tl-spec .v { font-family: var(--font-mono); font-size: 15px; font-weight: 700; color: var(--ink-900); margin-top: 3px; }
/* Active side highlighted on the head-to-head labels. */
.buc2-cmp .cmp-k.on { color: var(--ink-900); }

/* Per-stage rows, each on its own line so nothing wraps: an optional split note (Foundation
   only) first, then loan draw (red), the P+I instalment (ink-bold), and cash/CPF (ink). */
.tl-split, .tl-ln, .tl-mo, .tl-cc { font-family: var(--font-mono); font-size: 9.5px; line-height: 1.25; white-space: nowrap; }
.tl-split { margin-top: 3px; font-weight: 700; }
.tl-split .s-cc { color: var(--ink-700); }
.tl-split .s-ln { color: var(--red-600); }
.tl-ln { margin-top: 3px; }
.tl-ln .s-ln { color: var(--red-600); font-weight: 700; }
.tl-ln.none { color: var(--ink-400); font-weight: 500; font-style: italic; }
.tl-mo { margin-top: 1px; font-weight: 700; color: var(--ink-900); }
.tl-cc { margin-top: 2px; font-weight: 700; }
.tl-cc .s-cc { color: var(--ink-600); }
.vtl .tl-split, .vtl .tl-ln, .vtl .tl-mo, .vtl .tl-cc { font-size: 11.5px; white-space: normal; }
.vtl .tl-mo.blank, .vtl .tl-split.blank { display: none; }   /* spacers only align columns in the horizontal rail */

/* Amortisation rail (completed unit): one bar of the whole loan — paid off (jade) vs still
   owed (red) at the sale year — showing how little is dented after years of paying. */
.amz2 { margin-top: 6px; }
.amz2-title { font-size: 12px; font-weight: 700; color: var(--ink-700); margin-bottom: 9px; }
.amz2-bar { display: flex; height: 40px; border-radius: 8px; overflow: hidden; background: var(--ink-100); }
.amz2-bar .seg { display: block; height: 100%; }
.amz2-bar .seg.paid { background: var(--jade); }
.amz2-bar .seg.owe { background: var(--red-500); }
.amz2-bar .seg.prin { background: var(--ink-300); }
.amz2-bar .seg.int { background: var(--red-900); }
.amz2-bar.pay { background: transparent; }
.amz2.stack { margin-top: 16px; }
.amz2-legend { display: flex; justify-content: space-between; gap: 12px; margin-top: 9px; font-size: 12px; font-weight: 600; color: var(--ink-600); }
/* Pay-split legend rides the bar's inline width; if the bar is too short for the
   two labels, hold a readable minimum and let them wrap instead of colliding. */
.amz2-legend.pay { flex-wrap: wrap; min-width: min(100%, 340px); }
.amz2-legend b { font-family: var(--font-mono); }
.amz2-legend .lg.paid b { color: var(--jade); }
.amz2-legend .lg.owe b { color: var(--red-600); }
.amz2-legend .lg.prin b { color: var(--ink-600); }
.amz2-legend .lg.int b { color: var(--red-900); }

/* Reconciling 4-card footer: Cash + Loan principal + Total outflow + Interest. */
.tl-foot { display: grid; grid-template-columns: repeat(5, 1fr); gap: 16px; margin-top: 24px; padding-top: 16px; border-top: 1px solid var(--ink-200); }
.tl-stat .stat-k { font-size: 11px; color: var(--ink-500); text-transform: uppercase; letter-spacing: .4px; }
.tl-stat .stat-v { font-family: var(--font-mono); font-size: 19px; font-weight: 700; color: var(--ink-900); font-variant-numeric: tabular-nums; margin-top: 2px; }
.tl-stat .stat-sub { font-size: 11px; color: var(--ink-500); margin-top: 3px; line-height: 1.35; }
.tl-stat-tot .stat-v { color: var(--red-600); }
.tl-stat-pl.profit .stat-v { color: var(--jade); }
.tl-stat-pl.loss .stat-v { color: var(--red-600); }
@media (max-width: 720px) {
  .tl-foot { grid-template-columns: 1fr 1fr; gap: 14px 16px; }
}

/* Keyboard focus + tick + reduced motion */
.act:focus-visible, .atool:focus-visible, .swatch:focus-visible { outline: 2px solid var(--red-500); outline-offset: 2px; border-radius: 7px; }
@keyframes tick { 0% { transform: scale(1); } 28% { transform: scale(1.05); filter: brightness(1.18); } 100% { transform: scale(1); filter: brightness(1); } }
.dash-value.tick { animation: tick .32s var(--ease); transform-origin: left center; }
@keyframes overlayIn { from { opacity: 0; transform: translateY(8px) scale(.99); } to { opacity: 1; transform: none; } }
.onboard.show .onboard-card { animation: overlayIn .2s var(--ease); }
@media (prefers-reduced-motion: reduce) { *, *::before, *::after { animation-duration: .001ms !important; transition-duration: .001ms !important; } }

/* ── Annotation overlay (light pill docked in toolbar) ───────────────── */
.annotate-bar { display: none; align-items: center; gap: 8px; margin: 0 auto; padding: 6px; border-radius: 12px; background: var(--paper); border: 1px solid var(--ink-200); box-shadow: 0 2px 10px rgba(20,17,16,.22); }
.annotate-bar.show { display: flex; min-width: 0; }
.annotate-bar .grp { display: flex; align-items: center; gap: 4px; padding-right: 8px; border-right: 1px solid var(--ink-200); }
.annotate-bar .grp:last-of-type { border-right: 0; }
/* Annotation mode is a focused takeover on EVERY screen: hide the brand + tools so the
   annotate pill owns the toolbar. On 13" landscape the brand + Reset/Annotate stayed
   visible and crammed the pill into the middle (overlapping Reset); on 11" landscape the
   pill was squeezed. Hiding them everywhere keeps the pill clean — centred at its natural
   width on wide screens (margin: 0 auto), full-width-scrolling on narrow. Done exits, so
   Reset/Annotate aren't needed mid-annotation. */
body.annotating .brand, body.annotating .tools-right { display: none; }
/* Narrow (phone + portrait/split iPad): the pill stays CENTRED at its natural width (base
   margin: 0 auto) — NOT force-stretched. flex:1 1 auto stretched it to the full toolbar on
   portrait iPad, where the controls fit with room, leaving them left-packed with a big empty
   gap on the right. Only when the controls actually overflow (narrow phone) does min-width:0
   shrink the pill and overflow-x scroll them. Contained scroll = no page wobble. */
@media (max-width: 1023px) {
  .annotate-bar.show { overflow-x: auto; overscroll-behavior-x: contain; -webkit-overflow-scrolling: touch; }
  .annotate-bar.show::-webkit-scrollbar { display: none; }
  .annotate-bar.show > * { flex: 0 0 auto; }
  /* Pin Done to the right so the exit is always reachable while the rest scrolls. */
  .annotate-bar.show .done { position: sticky; right: 0; z-index: 1; box-shadow: -8px 0 8px 2px var(--paper); }
}
.atool { appearance: none; border: 0; background: transparent; color: var(--ink-700); font-family: var(--font-sans); font-size: 15px; font-weight: 600; height: 32px; min-width: 32px; padding: 0 6px; border-radius: 8px; cursor: pointer; display: grid; place-items: center; transition: background .12s var(--ease), color .12s var(--ease); }
.atool:hover { background: var(--ink-100); }
.atool.active { background: var(--ink-900); color: #fff; }
.atool.done { padding: 0 14px; background: var(--ink-900); color: #fff; }
.swatch { width: 23px; height: 23px; border-radius: 50%; border: 2px solid rgba(20,17,16,.18); cursor: pointer; padding: 0; }
.swatch.active { border-color: #fff; box-shadow: 0 0 0 2px var(--ink-900); }
/* Pen-thickness selector: a dark horizontal stroke (thin/medium/thick) in a
   rounded-rect — clearly "stroke width", not a round colour dot. On the white pill. */
.size-swatch { display: grid; place-items: center; width: 30px; height: 24px; border-radius: 6px; border: 1px solid var(--ink-300); background: var(--ink-50); cursor: pointer; padding: 0 5px; }
.size-swatch span { display: block; width: 100%; border-radius: 3px; background: var(--ink-900); }
.size-swatch.active { border-color: var(--ink-900); background: var(--ink-100); box-shadow: inset 0 0 0 1px var(--ink-900); }

.annotate-layer { position: fixed; top: calc(var(--toolbar-h) + env(safe-area-inset-top)); left: 0; right: 0; bottom: 0; z-index: 2600; pointer-events: none; display: none; }
.annotate-layer.on { display: block; }
/* manipulation (= pan + pinch-zoom, NOT none): ALL two-finger navigation stays
   NATIVE — the same scroll and visual-viewport pinch-zoom the page has outside
   annotate mode. JS-driven scroll feeds back on iOS (touch coords re-anchor to
   content after every scroll we apply → runaway doubling), and a JS pinch
   recognizer never matched the native feel. One-finger native pan is suppressed
   per-move by the non-passive touchmove preventDefault in annotate.js, so a
   single finger inks. */
.annotate-layer.on #annotateCanvas { pointer-events: auto; cursor: crosshair; touch-action: manipulation; }
#annotateCanvas { position: absolute; inset: 0; width: 100%; height: 100%; }

/* ── Add-to-Home-Screen onboarding ───────────────────────────────────── */
.onboard { position: fixed; inset: 0; z-index: 2900; display: none; align-items: center; justify-content: center; padding: 20px; background: rgba(20,17,16,.5); -webkit-backdrop-filter: blur(2px); backdrop-filter: blur(2px); }
.onboard.show { display: flex; }
.onboard-card { background: var(--paper); border-radius: 18px; max-width: 384px; width: 100%; padding: 24px; box-shadow: 0 22px 60px rgba(0,0,0,.35); text-align: center; }
.onboard-icon { border-radius: 15px; box-shadow: var(--shadow); }
.onboard-title { font-family: var(--font-serif); font-size: 24px; font-weight: 400; margin: 12px 0 6px; text-wrap: balance; }
.onboard-sub { font-size: 13.5px; color: var(--ink-600); margin-bottom: 18px; line-height: 1.5; }
.onboard-steps { text-align: left; font-size: 13.5px; color: var(--ink-800); margin: 0 0 20px; padding: 0; list-style: none; display: flex; flex-direction: column; gap: 11px; line-height: 1.45; }
p.onboard-steps { display: block; }
.onboard-steps li { display: flex; gap: 10px; align-items: flex-start; }
.onboard-steps .step-text { flex: 1; }
.onboard-steps .ios-share { margin: 0 1px; }
.onboard-steps .num { flex: 0 0 auto; width: 22px; height: 22px; border-radius: 50%; background: var(--ink-900); color: #fff; font-size: 12px; font-weight: 700; display: grid; place-items: center; font-family: var(--font-mono); margin-top: 1px; }
.ios-share { display: inline-grid; place-items: center; width: 23px; height: 23px; border: 1px solid var(--ink-300); border-radius: 6px; color: var(--red-500); vertical-align: -6px; }
.onboard-actions { display: flex; gap: 10px; justify-content: center; }
.act.primary { background: var(--red-500); border-color: var(--red-500); color: #fff; }
.act.primary:hover { background: var(--red-600); border-color: var(--red-600); }
.act.ghost { background: transparent; }

/* Small hint after a strong label (e.g. Total expense -> one-time + variable) */
.dk-hint { font-weight: 500; font-size: 12px; color: var(--ink-500); margin-left: 6px; }

/* ── "How is this calculated?" info button + breakdown popover ──────────── */
.info-btn { position: relative; display: inline-grid; place-items: center; width: 19px; height: 19px; margin-left: 5px; padding: 0; border: 0; border-radius: 50%; background: transparent; color: var(--ink-400); cursor: pointer; vertical-align: middle; transition: color .12s var(--ease), background .12s var(--ease); }
.info-btn:hover, .info-btn:focus-visible { color: var(--red-500); background: var(--red-50); outline: none; }
.dash-label .info-btn { color: var(--ink-300); }
.dash-label .info-btn:hover { color: var(--red-500); }

/* Centered, dimmed, large type — readable when recorded for social video. */
/* Top padding clears the sticky toolbar (which sits above this popup), so the
   card centers in the area below it and its title isn't hidden on tall phones. */
.explain { position: fixed; inset: 0; z-index: 2500; display: none; align-items: center; justify-content: center; padding: calc(var(--toolbar-h) + env(safe-area-inset-top) + 14px) 20px 20px; background: rgba(20,17,16,.62); -webkit-backdrop-filter: blur(3px); backdrop-filter: blur(3px); }
.explain.show { display: flex; animation: explainIn .16s var(--ease); }
@keyframes explainIn { from { opacity: 0; } to { opacity: 1; } }
.explain-card { position: relative; background: var(--paper); border-radius: 18px; width: 100%; max-width: 540px; padding: 38px 42px 36px; box-shadow: 0 30px 80px rgba(0,0,0,.4); transform-origin: center center; }
.explain-x { position: absolute; top: 14px; right: 16px; width: 40px; height: 40px; border: 0; background: transparent; color: var(--ink-500); font-size: 28px; line-height: 1; cursor: pointer; border-radius: 10px; }
.explain-x:hover { background: var(--ink-100); color: var(--ink-900); }
.explain-title { font-family: var(--font-serif); font-size: 28px; font-weight: 400; margin: 0 36px 16px 0; line-height: 1.15; }
.explain-sub { font-family: var(--font-sans); font-size: 14px; font-weight: 600; color: var(--ink-500); text-transform: uppercase; letter-spacing: .4px; }
.erow { display: flex; justify-content: space-between; align-items: baseline; gap: 16px; padding: 9px 0; font-size: 20px; }
.erow .ek { color: var(--ink-700); }
.erow .ev { font-family: var(--font-mono); font-weight: 600; color: var(--ink-900); font-variant-numeric: tabular-nums; white-space: nowrap; }
.erow.etotal { border-top: 2px solid var(--ink-900); margin-top: 6px; padding-top: 12px; font-size: 23px; }
.erow.etotal .ek { font-weight: 700; color: var(--ink-900); }
.erow.etotal .ev { font-weight: 800; }
/* Mid-breakdown subtotal rule (e.g. "Capital returned" in the cash-proceeds split):
   a lighter divider than the grand total, with the value emphasised so the eye lands
   on "this much is just your own money back". */
.erow.esub { border-top: 1px solid var(--ink-300); margin-top: 4px; padding-top: 11px; }
.erow.esub .ek { font-weight: 700; color: var(--ink-900); }
.erow.esub .ev { font-weight: 800; color: var(--red-600); }
.erow.esub:first-child { border-top: none; margin-top: 0; padding-top: 9px; }
/* Slide-4-style subline under a row label ("Downpayment $X + loan principal paid
   down $Y") — the detail lives in the row, not as extra rows. */
.erow .ek-sub { display: block; margin-top: 5px; font-family: var(--font-sans); font-size: 14px; font-weight: 500; color: var(--ink-500); line-height: 1.35; }
/* In-card expand control + the collapsed breakdown rows it reveals (.ex-open on
   #explainBody). Text-led affordance — underline, not colour alone. */
.ex-toggle { appearance: none; background: none; border: 0; padding: 0; margin-left: 10px; font-family: var(--font-sans); font-size: 13.5px; font-weight: 700; color: var(--ink-700); text-decoration: underline; text-underline-offset: 2px; cursor: pointer; }
.erow.eitem { display: none; padding: 6px 0 6px 18px; font-size: 18px; }
.erow.eitem .ek { color: var(--ink-600); }
.erow.eitem .ev { font-size: 17px; font-weight: 500; }
#explainBody.ex-open .erow.eitem { display: flex; }
#explainBody.ex-open-own .erow.eitem.ex-own { display: flex; }
#explainBody.ex-open-sell .erow.eitem.ex-sell { display: flex; }
.enote { font-size: 14.5px; line-height: 1.45; color: var(--ink-600); margin: 16px 0 0; }
@media (prefers-reduced-motion: reduce) { .explain.show { animation: none; } }

/* Panel spotlight (fit-to-screen): single-tap a panel title → the REAL panel (still live /
   editable) floats CENTRED and scaled to FIT the viewport at a glance, over a full-screen
   dark backdrop, with the page locked so it can't scroll under the toolbar. JS sizes the
   panel's width/left/top/scale to fit (the old fixed 66.67%×1.5 was tuned for the retired
   one-screen-fit layout and went enormous once panels filled the width). The toolbar (z 2700)
   stays above so its bar frames the top edge; the annotation canvas (z 2600) sits above the
   panel so you can draw on it. The backdrop (z 2400) covers the dash + every other row. */
body.spot::before { content: ''; position: fixed; inset: 0; z-index: 2400; background: #141110; }
/* Keep the results dash lit and pinned above the backdrop so the live figures stay in view
   while a panel is spotlit. Transparent strip so only the white cards show on the dark. */
body.spot .dash { z-index: 2460; background: transparent; }
body.spot .info-row:not(.has-spot), body.spot .cost-row:not(.has-spot) { display: none; }
body.spot .has-spot { display: block; }                       /* row → block; panel lifts out fixed */
body.spot .has-spot .panel:not(.is-spot) { display: none; }   /* show only the focused panel */
/* position/left/top/width/transform are all set by JS (applySpotlight) so the whole panel
   fits the screen. transform-origin top-left pairs with the JS-computed left for centring. */
body.spot .panel.is-spot { position: fixed; z-index: 2450; transform-origin: top left; box-shadow: 0 24px 70px rgba(0,0,0,.5); cursor: default; }
@keyframes spotIn { from { opacity: .35; } to { opacity: 1; } } /* opacity only — must not override the JS scale */
body.spot .panel.is-spot.spot-enter { animation: spotIn .24s var(--ease); }

/* Phones: the focus popup fills the screen width (no narrow floating card), and the
   full receipt uses compact spacing so it fits the height with little/no scaling —
   uniform scale-to-fit would otherwise shrink the card narrow AND small. */
@media (max-width: 720px) {
  .explain { padding-left: 16px; padding-right: 16px; }
  .explain-card { max-width: none; padding: 28px 26px 28px; border-radius: 16px; }
  .explain-title { font-size: 24px; margin-bottom: 10px; }
  .rbody-lg .rsec { margin: 13px 0 3px; }
  .rbody-lg .rsec:first-child { margin-top: 2px; }
  .rbody-lg .rrow { font-size: 17px; padding: 4px 0; }
  .rbody-lg .rrow.rtotal { margin-top: 8px; padding-top: 9px; }
  .rbody-lg .rrow.rtotal .rk { font-size: 18px; }
  .rbody-lg .rrow.rtotal .rv { font-size: 19px; }
}

/* ── Touch devices: ≥44px tap targets ────────────────────────────────── */
/* Phones: compact toolbar so the buttons fit even when iOS renders the glyphs/
   fallback font wider than Chromium (a cause of the toolbar expanding past the
   viewport). brand + tools shrink instead of overflowing. */
@media (max-width: 480px) {
  .toolbar { gap: 8px; padding-left: max(12px, env(safe-area-inset-left)); padding-right: max(12px, env(safe-area-inset-right)); }
  .toolbar .act { padding: 7px 9px; font-size: 12px; }
  .tools-right { gap: 6px; min-width: 0; }
  .brand-sub { display: none; }
}

@media (pointer: coarse) {
  /* 16px stops iOS Safari auto-zooming the viewport when an input gains focus
     (it zooms any text field under 16px — including the re-focus after a chip tap). */
  input, select { min-height: 42px; font-size: 16px; }
  .act { min-height: 42px; }
  .atool { min-height: 40px; min-width: 40px; }
  .swatch { width: 28px; height: 28px; }
  .size-swatch { width: 38px; height: 28px; }
  .info-btn { width: 30px; height: 30px; }
  .dash-label { min-height: 30px; } /* match the 30px coarse ⓘ so all card values align */
  /* Transparent hit-area expander → ~44px tap target without enlarging the glyph
     (a bigger visible button would balloon the dense row heights). Vertical reach
     kept modest so adjacent rows' targets don't overlap. */
  .info-btn::after { content: ''; position: absolute; left: -8px; right: -8px; top: -7px; bottom: -7px; }
}
