/* === Deelva — warm-cream + deep-forest-green palette ===
   Brand identity:
     Dark Charcoal        #0F1F2C  (primary text)
     Deep Forest Green    #2E5A38  (accent, CTAs, accent borders)
     Light Sage Green     #A5B5A3  (accent bg, subtle highlights)
     Cream                #faf7f1  (background)
     White                #ffffff  (elevated surfaces)
   Tagline: "Signal over noise."
*/
:root {
  /* Warm ivory surfaces — softer than stark white, no yellow cast. */
  --bg: #faf7f1;
  --bg-surface: #f3eee3;
  /* Round 22 (color audit): bumped from #fdfbf5 (2-3 units brighter
     than the cream page bg) → pure white so the P&L subtotal /
     total / NOI rows visibly step out of the surrounding zebra. The
     previous #fdfbf5 was invisible against the reverted #faf7f1 bg,
     collapsing the year-1 and 10-yr P&L's structural bracketing. */
  --bg-elevated: #ffffff;
  --bg-hover: #ede7d9;
  /* Cream-toned separators so divider lines don't read as cold gray. */
  --separator: #e0d9c8;
  --separator-soft: #ece6d8;
  /* Dark Charcoal — slightly cooler than the previous warm-black to
     match the new brand identity's "Dark Charcoal" swatch. */
  --text: #0F1F2C;
  --text-secondary: #3a4250;
  /* Round 22 (color audit): darkened from #6b7280 (~3.9:1 on cream,
     fails WCAG AA for small text) → #5a6473 (~4.7:1). Used on metric
     labels and eyebrows ("CASH FLOW", "CoC", "CAP", "WHERE THE DEAL
     PENCILS") which previously read as decorative scribble on the
     reverted lighter cream bg. */
  --text-tertiary: #5a6473;
  /* Deep Forest Green as the primary accent (replaces the previous deep
     navy). Buttons, focused states, key highlights. */
  --accent: #2E5A38;
  --accent-hover: #244730;
  --accent-bg: rgba(47, 90, 63, 0.10);
  /* Light Sage Green — used for subtle accent tints (selected chip
     borders, soft callouts) in the Deelva palette. */
  --sage: #A5B5A3;
  --sage-bg: rgba(168, 184, 163, 0.18);
  /* Restrained positive/negative — slightly muted from the bright versions so they
     pair with the cream surfaces. */
  --positive: #0c6e3f;
  --positive-bg: rgba(12, 110, 63, 0.08);
  --negative: #9b2c2c;
  --negative-bg: rgba(155, 44, 44, 0.08);

  /* Round 18 brand refresh — Satoshi is the single brand typeface per
     the new Deelva Brand Identity System. Cormorant Garamond removed.
     Inter kept as a fallback for tabular-nums on data displays. */
  --font-sans: "Satoshi", "Inter", -apple-system, BlinkMacSystemFont, "Helvetica Neue", Helvetica, Arial, sans-serif;
  /* Legacy --font-serif var name preserved (now aliased to Satoshi)
     so the dozens of font-family: var(--font-serif) declarations
     across this file don't all need to be hand-edited. */
  --font-serif: "Satoshi", "Inter", -apple-system, BlinkMacSystemFont, system-ui, sans-serif;
  --font-mono: ui-monospace, "SF Mono", "JetBrains Mono", Menlo, Monaco, monospace;

  --radius-sm: 8px;
  --radius-md: 14px;
  --radius-lg: 20px;
  --radius-xl: 26px;

  /* Softer, warmer shadows — less "tech card", more "printed paper edge". */
  --shadow-xs: 0 1px 2px rgba(40, 30, 12, 0.04);
  --shadow-sm: 0 2px 10px rgba(40, 30, 12, 0.06);
  --shadow-md: 0 10px 28px rgba(40, 30, 12, 0.08);
  --shadow-lg: 0 22px 52px rgba(40, 30, 12, 0.10);

  --ease: cubic-bezier(0.4, 0, 0.2, 1);
  /* Topbar dropped 84→68 after removing the html { zoom: 0.85 } hack.
     At 100% browser zoom the 84px topbar towered; 68 matches the actual
     content density of a single row of 32px chip buttons + 40px brand. */
  /* Round 42: bumped 76 → 84 so the app topbar matches the welcome
     page topbar's visual height. Welcome uses padding-based sizing
     (16px top + 16px bottom + 52px logo = 84px); app uses this fixed
     var. Equal heights so the two surfaces feel like one product. */
  --topbar-h: 84px;
  /* Filter-bar height. Single-row on desktop (52px); wraps to ~120px on
     mobile where view-switcher takes a full row. Updated via media
     queries so .layout's vertical inset stays accurate. Financing
     scenario picker now lives inside the filter-bar as a popover —
     no separate bar height. */
  /* Was 52px when filter bar held all inputs inline; now houses just the
     Filters toggle, view switcher, and result counter (everything else
     moved into the left-rail .filter-panel below). */
  --filter-bar-h: 48px;
  /* Site footer reserves space at viewport bottom so the layout above
     never overlaps the About-this-demo strip. */
  --footer-h: 44px;
}

* { box-sizing: border-box; margin: 0; padding: 0; }
/* Inherit border-radius so focus rings on rounded pill buttons trace the
   pill rather than draw a square box (designer-audit fix). */
*:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: inherit; }

/* Type scale — replacing the previous `html { zoom: 0.85 }` hack that
   broke rem math and compounded with browser zoom. Real desktop scale:
   H1 38, H2 24, H3 18, body 15, sm 13, xs 11. Mobile rules in their own
   media block. */
html, body { height: 100%; }
body {
  background: var(--bg);
  color: var(--text);
  font-family: var(--font-sans);
  font-weight: 400;
  /* User feedback: Chrome rendered the site as feeling "zoomed in".
     Knocked the root size from 16 → 15 so headings, body text, and the
     em/rem-scaled UI all shrink proportionally without rewriting every
     declared px size. */
  font-size: 15px;
  line-height: 1.5;
  letter-spacing: -0.003em;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  overflow: hidden;
}

body.detail-body {
  /* Was `overflow: auto` — that allowed BOTH axes, and certain scraped
     listings (long unbroken Zillow source URLs, wide map iframes)
     exposed a horizontal scrollbar. Clamp X explicitly. */
  overflow-x: hidden;
  overflow-y: auto;
  background: var(--bg);
  /* Belt-and-suspenders: also constrain width so transformed children
     (e.g. an overflowing carousel that briefly bleeds to the right
     during scrollIntoView animation) cannot enlarge the page. */
  width: 100%;
  max-width: 100vw;
}
/* Detail page html-level lock — modern `overflow-x: clip` ALSO disables
   programmatic horizontal scrolling, where `hidden` only hides the
   scrollbar (the page can still be scrolled via touch / scrollIntoView /
   element.scrollLeft). Without clip, any descendant that briefly
   overflows could still shift the page on mobile Safari. */
html:has(body.detail-body) {
  overflow-x: clip;
}

a { color: inherit; text-decoration: none; }
button { font: inherit; color: inherit; background: none; border: none; cursor: pointer; }
input, select, textarea { font: inherit; color: inherit; }
input[type="number"] { -moz-appearance: textfield; }
input[type="number"]::-webkit-outer-spin-button,
input[type="number"]::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }

/* === Topbar === */
.topbar {
  position: fixed; inset: 0 0 auto 0; height: var(--topbar-h);
  /* Round 43: changed grid from `minmax(460px, auto) 1fr auto` to
     `1fr auto 1fr` so the middle (search) column is true-centered
     on the page. With equal 1fr side columns, brand sits flush-left
     and actions sit flush-right while the search stays centered
     regardless of brand/action widths. Also keeps search position
     stable across zoom levels — both 1fr columns scale identically. */
  display: grid; grid-template-columns: 1fr auto 1fr;
  align-items: center; gap: 28px;
  /* Round 43: padding 24 → 32 to match welcome page (.w-topbar uses
     `padding: 16px 32px`). Identical horizontal anchor for the brand
     across welcome → app so the lockup doesn't shift on nav. */
  padding: 0 32px;
  /* Round 40: match the welcome page's cream topbar so the brand mark
     reads as one continuous identity across welcome → app. Previously
     rgba(255,255,255,0.72) (cool white) which made app look like a
     different product. */
  background: rgba(250, 247, 241, 0.85);
  backdrop-filter: saturate(180%) blur(20px);
  -webkit-backdrop-filter: saturate(180%) blur(20px);
  border-bottom: 1px solid var(--separator);
  z-index: 20;
}
/* Make sure brand hugs the left and actions hug the right inside
   their 1fr cells (defaults to stretch in some browsers). */
.topbar > .brand { justify-self: start; }
/* Round 52: listing.html (body.detail-body) doesn't have a search bar
   in the middle. With the `1fr auto 1fr` grid, the topbar-actions
   land in the auto center column and read as centered. Override to
   a 2-column layout (brand left, actions right) on detail pages. */
body.detail-body .topbar {
  grid-template-columns: auto 1fr;
  /* Round 55: fully opaque on the detail page per user. The 85%
     translucent cream + backdrop-blur made scrolling content bleed
     through behind the bar — distracting on long property pages.
     The app page still uses translucent (looks right over the map). */
  background: #faf7f1;
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
}
body.detail-body .topbar > .topbar-actions { justify-self: end; }
/* Action buttons (Saved, Add property, etc.) always right-justified
   in their grid column. Explicit flex + flex-end so the row doesn't
   visually drift center on viewports where the search column is
   hidden (mobile) and the auto column might grow into the empty space. */
.topbar-actions {
  display: flex;
  align-items: center;
  gap: 8px;
  justify-self: end;
}

/* === Deelva brand-mark lockup ===
   Per Senior PD spec: Satoshi Medium uppercase wordmark with positive
   tracking; sentence-case tagline beneath it in muted charcoal, NO
   italic, NO green (green is reserved for the logo's "signal" tile and
   hover state — keeps the accent precious). 3×3 mark sized down to
   44px so it sits visually balanced next to the tightened wordmark
   stack, and centerline-aligned to the full text block rather than to
   the wordmark cap-height alone. */
/* Round 43: gap 14 → 12 to match welcome page's .w-brand-link
   (gap: 12px), so the logo-to-wordmark distance is pixel-identical
   across welcome → app. No visual shift when navigating between
   the two surfaces. */
.brand { display: flex; align-items: center; gap: 12px; min-width: 0; }
/* Round 82.1: the brand-with-back / btn-back-to-map styles (Round 80)
   were removed. Back-to-map now lives in the .detail-toc section-tabs
   strip — see .detail-toc-back below. */
.brand-link {
  display: flex; align-items: center; gap: 12px;
  text-decoration: none; color: inherit;
  border-radius: 12px;
}
/* Round 21: lock brand-link hover / visited / focus so the wordmark
   and tagline stay charcoal across every interaction. The previous
   .brand-link:hover .brand-name → accent rule was the source of the
   "logo turns green when I hover over it" report. Belt-and-suspenders:
   pin .brand-name + .brand-tagline color in every state. */
.brand-link:hover,
.brand-link:visited,
.brand-link:focus,
.brand-link:focus-visible {
  color: inherit;
  text-decoration: none;
}
.brand-link:hover .brand-name,
.brand-link:focus-visible .brand-name {
  color: var(--text);
}
.brand-link:hover .brand-tagline,
.brand-link:focus-visible .brand-tagline {
  color: var(--text-secondary);
}
.brand-logo {
  display: block;
  flex-shrink: 0;
  align-self: center;
  border-radius: 10px;
  /* Bumped 44 → 52 so the mark sits in visual balance with the new
     26px bold wordmark + 12px tagline lockup. */
  width: 52px !important;
  height: 52px !important;
}
.brand-text {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 2px;
  min-width: 0;
  line-height: 1;
  /* Round 43: -2px optical offset to match welcome page's
     .w-brand-text. Without this, the 2-line wordmark/tagline lockup
     sits a hair low next to the 52px logo because the wordmark's
     cap-height is shorter than the logo's vertical center. */
  margin-top: -2px;
}
/* deelva wordmark — inline SVG (lowercase letters + forest gable over the ee)
   that replaces the old wordmark text in every lockup. Height scales with the
   lockup's font-size; currentColor = letters, var(--accent) = gable. Must reset
   text-transform + letter-spacing: the brand-name spans set uppercase tracking
   for the old caps wordmark, which would otherwise uppercase/space the SVG text. */
.deelva-word {
  height: 1.8em;
  width: auto;
  display: block;
  text-transform: none;
  letter-spacing: normal;
  overflow: visible;
}
.brand-name {
  font-family: "Satoshi", var(--font-sans);
  /* Bolder per brand guide — 700 (Satoshi Bold) instead of 500. The
     wordmark needs to feel anchored at this size. */
  font-weight: 700;
  text-transform: uppercase;
  /* Positive tracking so the uppercase glyphs breathe. The
     padding-right is the optical-correction trailing space; the
     negative margin-left compensates so the visual left edge of
     "M" aligns with the tagline's first letter (without the negative
     compensation, half the M's advance shifts the wordmark right). */
  letter-spacing: 0.10em;
  padding-right: 0.10em;
  /* Round 40: 26 → 22 to match the welcome page wordmark exactly. The
     two were drifting apart and the app's larger wordmark made the
     two surfaces feel like different products. */
  font-size: 22px;
  line-height: 1;
  color: var(--text);
  margin-bottom: 0;
  transition: color 0.15s var(--ease);
}
.brand-tagline {
  font-family: "Satoshi", var(--font-sans);
  /* Bumped 9.5 → 12px so the tagline reads at a respectable scale
     against the larger wordmark (~46% ratio — classic). Slightly
     lower tracking than before (0.04em) to keep the alignment tight
     to the wordmark's left edge.
     Round 30 (Deelva brand handoff): added padding-left: 3px to
     optically align tagline first letter with wordmark M. */
  font-weight: 500;
  font-style: normal;
  text-transform: none;
  font-size: 12px;
  letter-spacing: 0.04em;
  line-height: 1.1;
  color: #5a6470;
  /* Round 31: REVERTED the Round 30 3px optical correction — user
     reported the padding pushed the tagline visibly right of the
     wordmark. Flush left across every surface. */
  padding-left: 0;
  white-space: nowrap;
}

/* === Search ===
   Centered within the topbar's 1fr middle grid column. The previous
   `position: absolute; left: 50%` approach was visually centered but
   overlaid the actions column on narrower desktops, hiding the
   Saved / Add property buttons. Staying in-grid keeps the actions
   reachable and the search optically centered enough on most sizes. */
/* Round 36: REVERTED the Round-10 panel-anchored absolute
   positioning. Anchoring the search bar's right edge to
   var(--panel-w) made the bar visibly shift left/right when the
   user zoomed via Chrome (browser pixel scaling kept the search
   at a fixed viewport offset while the brand + actions
   redistributed grid space — the bar drifted relative to
   everything else). Now back to a simple centered-in-grid layout:
   topbar's middle grid column (1fr) holds the search; margin auto
   centers it inside that column at any zoom level. */
/* Round 43: search lives in the topbar's center grid cell (auto
   column with 1fr columns on either side, so the cell is
   page-centered). Round 50: switched from `min(520, 100vw - 64)`
   to a clamp() that reserves room for brand (~230px) + actions
   (~250px) + gaps (~120px). Now the search shrinks BEFORE
   colliding with the brand on narrower viewports instead of
   crowding it off-screen. Min 220px keeps the input usable. */
.search {
  position: relative;
  width: clamp(220px, calc(100vw - 600px), 520px);
}
.search-icon {
  position: absolute; left: 16px; top: 50%; transform: translateY(-50%);
  color: var(--text-tertiary); pointer-events: none;
}
.search input {
  width: 100%; height: 40px;
  background: var(--bg-surface); border: 1px solid transparent;
  border-radius: 10px; padding: 0 158px 0 40px;
  font-size: 14px; color: var(--text);
  transition: background 0.15s var(--ease), border-color 0.15s var(--ease), box-shadow 0.15s var(--ease);
}
.search input::placeholder { color: var(--text-tertiary); }
.search input:focus {
  background: var(--bg);
  border-color: var(--separator);
  box-shadow: 0 0 0 3px rgba(47, 90, 63, 0.12);
}
.search-results {
  position: absolute; top: calc(100% + 6px); left: 0; right: 0;
  background: var(--bg); border: 1px solid var(--separator-soft);
  border-radius: var(--radius-md); box-shadow: var(--shadow-md);
  max-height: 360px; overflow-y: auto; z-index: 30;
  padding: 6px;
}
.search-result {
  padding: 10px 12px; cursor: pointer;
  border-radius: var(--radius-sm);
  display: flex; align-items: center; gap: 12px;
}
.search-result:hover, .search-result.active { background: var(--bg-surface); }
.search-result-icon { color: var(--text-tertiary); flex-shrink: 0; }
.search-result-main { font-size: 14px; color: var(--text); }
.search-result-sub { font-size: 12px; color: var(--text-tertiary); margin-top: 1px; }

/* Empty-state row in the search dropdown when no WNY matches exist.
   Tells the user the scope is intentional, not broken. */
.search-result-empty {
  padding: 12px 14px;
  border-radius: var(--radius-sm);
}
.search-result-empty-title {
  font-size: 13px;
  font-weight: 600;
  color: var(--text);
  letter-spacing: -0.005em;
}
.search-result-empty-sub {
  font-size: 12px;
  color: var(--text-tertiary);
  margin-top: 3px;
  line-height: 1.45;
}

/* Persistent scope-limit footer — always renders at the bottom of the
   search dropdown. "Not finding what you're looking for? [+ Add a
   property]" — the CTA reuses the .add-listing-toggle event-delegated
   handler so clicking opens the Add Property modal automatically. */
.search-result-scope {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  margin-top: 6px;
  padding: 10px 12px;
  border-top: 1px solid var(--separator-soft);
  background: var(--bg-surface);
  border-radius: 0 0 var(--radius-sm) var(--radius-sm);
}
.search-result-scope-text {
  font-size: 12px;
  color: var(--text-tertiary);
  letter-spacing: -0.005em;
}
.search-result-scope-cta {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 5px 10px;
  background: var(--bg);
  border: 1px solid var(--separator);
  border-radius: 980px;
  font-size: 12px;
  font-weight: 500;
  color: var(--accent);
  cursor: pointer;
  white-space: nowrap;
  transition: background 0.15s var(--ease), border-color 0.15s var(--ease);
}
.search-result-scope-cta:hover {
  background: var(--accent-bg);
  border-color: var(--accent);
}

/* Mobile search affordance — toggle button (in topbar actions) and the
   close button inside the search wrapper. Both hidden on desktop. The
   `.btn.search-toggle-mobile` combo selector beats `.btn { display: inline-flex }`
   which is otherwise cascade-later at equal specificity. */
.btn.search-toggle-mobile { display: none; }
.search-close-mobile {
  display: none;
  /* On mobile the parent .search element expands to `inset: 0` (full-screen
     sheet). top: 50% would place this in the viewport vertical center —
     orphaned mid-screen. Anchor to the input row instead. */
  position: absolute; right: 14px; top: 24px;
  width: 40px; height: 40px;
  background: transparent; border: 0; border-radius: 50%;
  color: var(--text-tertiary); cursor: pointer;
  align-items: center; justify-content: center;
  z-index: 1;
}
.search-close-mobile:hover { background: var(--bg-surface); color: var(--text); }

/* === Buttons === */
.btn {
  display: inline-flex; align-items: center; gap: 6px;
  height: 32px; padding: 0 14px;
  border-radius: 980px; font-size: 13px; font-weight: 500;
  letter-spacing: -0.005em;
  transition: all 0.15s var(--ease);
  cursor: pointer; white-space: nowrap;
}
.btn-ghost {
  background: var(--bg-surface); border: 1px solid transparent;
  color: var(--text);
}
.btn-ghost:hover { background: var(--bg-hover); }
.btn-primary {
  background: var(--accent); color: white;
  border: 1px solid var(--accent); font-weight: 500;
  height: 36px; padding: 0 18px;
}
.btn-primary:hover { background: var(--accent-hover); border-color: var(--accent-hover); }
.btn-text {
  background: transparent; color: var(--accent);
  height: auto; padding: 4px 6px; border-radius: 6px;
  font-size: 13px; font-weight: 400;
}
.btn-text:hover { background: var(--accent-bg); }
.btn-icon {
  background: transparent; width: 32px; height: 32px; padding: 0;
  border-radius: 50%; color: var(--text-secondary);
  display: grid; place-items: center;
}
.btn-icon:hover { background: var(--bg-hover); color: var(--text); }
.dot {
  width: 6px; height: 6px; border-radius: 50%;
  background: var(--accent);
}

/* === Controls bar (slim horizontal above the layout) ===
   Replaces the old multi-input .filter-bar. Holds only the Filters
   toggle, view switcher, and result counter — everything else moved
   into the left-rail .filter-panel below. Designed to give first-time
   visitors a clean orientation: one button to filter, one to switch
   views, one counter so they know how many deals are out there. */
.controls-bar {
  position: fixed;
  top: var(--topbar-h);
  left: 0; right: 0;
  height: var(--filter-bar-h);
  z-index: 9;
  display: flex; align-items: center;
  gap: 12px;
  padding: 0 16px;
  background: var(--bg);
  border-bottom: 1px solid var(--separator-soft);
  /* The pills + chips should never push the bar wider than the viewport.
     overflow: hidden + flex: 1 1 0 on the chip strip keeps the bar firmly
     locked, and the chip strip handles its own horizontal scroll inside. */
  overflow: hidden;
}
/* Pill-style button used for both Filters and Investment Assumptions
   in the controls bar. Same shape; the icon + label differentiate. */
.btn-controls-pill,
.btn-controls-filters {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  height: 32px;
  padding: 0 14px;
  background: var(--bg-surface);
  color: var(--text);
  border: 1px solid var(--separator-soft);
  border-radius: 980px;
  font-size: 13px;
  font-weight: 500;
  letter-spacing: -0.005em;
  cursor: pointer;
  white-space: nowrap;
  transition: background 0.15s var(--ease), border-color 0.15s var(--ease);
}
.btn-controls-pill:hover,
.btn-controls-filters:hover {
  background: var(--bg-hover);
  border-color: var(--separator);
}
.btn-controls-pill[aria-expanded="true"],
.btn-controls-filters[aria-expanded="true"] {
  background: var(--accent-bg);
  border-color: var(--accent);
  color: var(--accent);
}
/* Pulse animation triggered for first-time visitors when the welcome
   modal closes — draws the eye to the Filters button. */
@keyframes filters-pulse {
  0%   { box-shadow: 0 0 0 0 rgba(47, 90, 63, 0.45); }
  70%  { box-shadow: 0 0 0 12px rgba(47, 90, 63, 0); }
  100% { box-shadow: 0 0 0 0 rgba(47, 90, 63, 0); }
}
.btn-controls-pill.filters-pulse,
.btn-controls-filters.filters-pulse {
  animation: filters-pulse 1.6s ease-out 2;
}

/* Active-filter chip row — shows each non-default filter as a small
   removable pill in the controls bar next to the Filters / Investment
   Assumptions buttons. Click the × to clear just that filter. */
.active-filter-chips {
  display: flex;
  flex-wrap: nowrap;
  gap: 6px;
  overflow-x: auto;
  scrollbar-width: none;
  /* flex: 1 1 0 + min-width: 0 = chip strip claims the remaining width
     after the pills and scrolls horizontally internally instead of
     pushing the bar wider than the viewport. */
  flex: 1 1 0;
  min-width: 0;
}
.active-filter-chips::-webkit-scrollbar { display: none; }
.active-filter-chip {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  height: 26px;
  padding: 0 4px 0 10px;
  background: rgba(15, 31, 44, 0.06);
  border: 1px solid var(--separator);
  border-radius: 980px;
  font-size: 12px;
  font-weight: 500;
  color: var(--text);
  cursor: pointer;
  white-space: nowrap;
  flex-shrink: 0;
  transition: background 0.15s var(--ease), border-color 0.15s var(--ease);
}
.active-filter-chip:hover {
  background: rgba(15, 31, 44, 0.10);
  border-color: rgba(15, 31, 44, 0.22);
}
.active-filter-chip-x {
  display: inline-grid;
  place-items: center;
  width: 18px; height: 18px;
  border-radius: 50%;
  font-size: 13px;
  font-weight: 600;
  line-height: 1;
  color: var(--text-secondary);
  margin-left: 2px;
}
.active-filter-chip:hover .active-filter-chip-x {
  background: rgba(15, 31, 44, 0.12);
}
/* === Verdict variants of the active-filter chip ===
   Round 72: when a Deelva Smart Picks bucket is selected, its
   chip in the top strip mirrors the same color the user already
   associates with that verdict (Pursue forest, Hold amber, Weak
   orange, Pass red). Resting state is a soft tinted background;
   hover deepens the tint and outlines the chip. Same shape /
   layout as the default green chip — only the palette changes. */
.active-filter-chip-verdict { border-color: transparent; }
/* Grouped "Smart Picks: Pursue, Hold" chip — forest tint (the Smart Picks
   brand), mirrors the single Pursue chip's palette. */
.active-filter-chip.active-filter-chip-picks {
  background: rgba(15, 31, 44, 0.06); color: var(--text); border-color: var(--separator);
}
.active-filter-chip.active-filter-chip-picks .active-filter-chip-x { color: var(--text-secondary); }
.active-filter-chip.active-filter-chip-picks:hover {
  background: rgba(15, 31, 44, 0.10); border-color: rgba(15, 31, 44, 0.22);
}
.active-filter-chip.active-filter-chip-picks:hover .active-filter-chip-x {
  background: rgba(15, 31, 44, 0.12);
}
.active-filter-chip.verdict-chip-pursue {
  background: rgba(63, 122, 78, 0.12); color: #2E5A38;
}
.active-filter-chip.verdict-chip-pursue .active-filter-chip-x { color: #2E5A38; }
.active-filter-chip.verdict-chip-pursue:hover {
  background: rgba(63, 122, 78, 0.20); border-color: #3F7A4E;
}
.active-filter-chip.verdict-chip-pursue:hover .active-filter-chip-x {
  background: rgba(63, 122, 78, 0.18);
}
.active-filter-chip.verdict-chip-hold {
  background: rgba(166, 122, 46, 0.13); color: #6b4e15;
}
.active-filter-chip.verdict-chip-hold .active-filter-chip-x { color: #6b4e15; }
.active-filter-chip.verdict-chip-hold:hover {
  background: rgba(166, 122, 46, 0.22); border-color: #A67A2E;
}
.active-filter-chip.verdict-chip-hold:hover .active-filter-chip-x {
  background: rgba(166, 122, 46, 0.20);
}
.active-filter-chip.verdict-chip-weak {
  background: rgba(199, 106, 58, 0.13); color: #7a3611;
}
.active-filter-chip.verdict-chip-weak .active-filter-chip-x { color: #7a3611; }
.active-filter-chip.verdict-chip-weak:hover {
  background: rgba(199, 106, 58, 0.22); border-color: #C76A3A;
}
.active-filter-chip.verdict-chip-weak:hover .active-filter-chip-x {
  background: rgba(199, 106, 58, 0.20);
}
.active-filter-chip.verdict-chip-pass {
  background: rgba(162, 58, 58, 0.13); color: #6e1c1c;
}
.active-filter-chip.verdict-chip-pass .active-filter-chip-x { color: #6e1c1c; }
.active-filter-chip.verdict-chip-pass:hover {
  background: rgba(162, 58, 58, 0.22); border-color: #A23A3A;
}
.active-filter-chip.verdict-chip-pass:hover .active-filter-chip-x {
  background: rgba(162, 58, 58, 0.20);
}
/* Non-interactive "X matching" pill — same shape as the removable
   chips but visually quieter (transparent border, muted color) so it
   doesn't read as another clickable filter. */
.active-filter-chip-count {
  background: transparent;
  border-color: var(--separator);
  color: var(--text-secondary);
  cursor: default;
  padding: 0 12px;
}
.active-filter-chip-count:hover {
  background: transparent;
  border-color: var(--separator);
}
@media (max-width: 768px) {
  /* Bumped from 24px tall to 30px and the × from 16x16 to 22x22 — gives
     the chip's dismiss target enough room to land cleanly with a thumb
     without overshooting into the next chip. */
  /* Round 22 (mobile audit HIGH): chip height 30 → 36, dismiss ×
     22 → 28 so the whole interactive bar meets the 36px touch floor. */
  .active-filter-chip { height: 36px; font-size: 12px; padding: 0 4px 0 12px; }
  .active-filter-chip-x { width: 28px; height: 28px; font-size: 14px; }

  /* Mobile filter layout — single fixed 2-row bar:
       Row 1 = Filters / Investment Assumptions pills (always)
       Row 2 = chips strip (when filters active) — HORIZONTALLY scrollable
               so it never grows to a 3rd row regardless of how many
               filters the user adds. Previous design wrapped chips to
               multiple rows, pushing --filter-bar-h beyond its declared
               96px and cutting off the top of the grid view because
               .layout's top inset stayed at 96px. */
  /* Single-row controls bar. The Investment-assumptions pill moved up to the
     topbar, so Filters and the active-filter chips now share ONE row: the chips
     sit directly beside the Filters button (signaling "these are your selected
     filters") and scroll horizontally if they overflow. Holding the bar at the
     no-chips height reclaims the old second row, so the map below grows taller. */
  body.has-filter-chips { --filter-bar-h: 52px; }
  body.has-filter-chips .controls-bar {
    flex-wrap: nowrap;
    height: var(--filter-bar-h);
    min-height: var(--filter-bar-h);
    max-height: var(--filter-bar-h);
    padding: 0 12px;
    gap: 8px;
    overflow: hidden;
  }
  body.has-filter-chips .active-filter-chips {
    flex: 1 1 0;               /* claim the width left of the Filters button */
    flex-wrap: nowrap;         /* single row */
    overflow-x: auto;          /* scroll horizontally instead of wrapping */
    -webkit-overflow-scrolling: touch;
    scrollbar-width: none;
    margin-top: 0;
    min-width: 0;
  }
  body.has-filter-chips .active-filter-chips::-webkit-scrollbar { display: none; }
}

/* === Mobile inline result count ============================================
   On mobile the map is hidden behind the listings panel so the .results-pill
   overlay (top-left of the map) is invisible. Surface the count inline in
   the controls bar so the user always knows how many properties matched.
   Desktop hides this; the overlay handles it there. */
.controls-bar-results {
  display: none;
  font-size: 12px;
  color: var(--text-secondary);
  margin-left: auto;
  white-space: nowrap;
  flex-shrink: 0;
}
.controls-bar-results #results-count-inline {
  color: var(--text);
  font-weight: 600;
}
@media (max-width: 768px) {
  /* Round 17 fix: only show the inline count in GRID view. In MAP view,
     the map's .results-pill overlay already shows the same number ("79
     residences in view") — having both visible at a narrow desktop /
     tablet width is redundant and looks like double-counting. The
     map's overlay is visible whenever the map is visible; the inline
     count only earns its keep when the map is hidden behind the panel
     (grid view at narrow viewport). */
  body.view-grid .controls-bar-results { display: inline-flex; align-items: center; }
  /* Single-row controls bar: keep the count vertically centered with the pills
     and chips (it used to sit on its own second row). */
  body.view-grid.has-filter-chips .controls-bar-results {
    align-self: center;
    margin-top: 0;
    flex-shrink: 0;
  }
}

/* === iOS auto-zoom prevention ===
   Mobile Safari auto-zooms (and stays zoomed) any time it focuses an
   input with font-size < 16px. Apply a 16px font-size floor to every
   user-editable input/select on touch viewports so taps stay at 1x. We
   keep the visual proportion via padding adjustments where needed; the
   tiny absolute size bump from 11–14 → 16 reads as fine on phones. */
@media (max-width: 768px) {
  .filter-field input,
  .filter-field select,
  .filter-subfield input,
  .filter-subfield select,
  .input-with-suffix input,
  .pencils-card-target-input,
  .ed-input,
  /* Round 24 (mobile audit BLOCKER): Add Property modal inputs and
     Advanced filter range inputs were the two un-guarded paths that
     still triggered iOS Safari auto-zoom (rendered at 14px and 13px
     respectively). Paste-URL → tap field → 1.16x zoom was the
     reported regression. */
  .al-section .field input,
  .al-section .field select,
  .range-field input,
  .range-field-pair input,
  /* Round 105: the topbar search field was 14px → iOS auto-zoomed on
     focus and stayed zoomed, on a primary "find a property" action. */
  .search input {
    font-size: 16px;
  }
  /* Year-1 P&L cells stay visually at 11px when rendered as text — only
     the inline-edit input that opens on tap needs 16px to dodge iOS
     auto-zoom. Without this scoping, all .ed-cell rows ballooned to
     16px while the read-only NOI / Mortgage / Cash flow totals stayed
     at 11px → inconsistent table typography. */
}
.filter-count-badge {
  display: inline-grid;
  place-items: center;
  min-width: 18px; height: 18px;
  padding: 0 5px;
  border-radius: 9px;
  background: var(--accent);
  color: #fff;
  font-size: 11px;
  font-weight: 600;
  line-height: 1;
}
/* Location label on the right side of the controls bar — replaces the
   old "X of 79 deals · West New York, NJ" counter (deduped against the
   map-overlay pill that already shows the in-view count). */
.controls-bar-loc {
  margin-left: auto;
  font-size: 12.5px;
  color: var(--text-tertiary);
  letter-spacing: -0.005em;
}
@media (max-width: 768px) {
  .controls-bar { padding: 0 12px; gap: 8px; }
  /* Round 105: the Round-22 touch-target bump targeted .btn-controls-filters,
     a class that isn't in the markup (the buttons are .btn-controls-pill),
     so it never applied. Target the real class and use 44px to actually
     meet the touch guideline. */
  .btn-controls-pill { padding: 0 14px; height: 44px; font-size: 13px; }
  /* Round 107: per user, keep the "Investment assumptions" label visible
     on mobile (R106 had collapsed it to an icon). When filter chips are
     active the controls bar wraps to two rows — the pills on row 1, the
     chip strip on its own scrollable row 2 — so the label no longer
     competes with the chips for horizontal space. */
  .controls-bar-loc { display: none; }
}

/* === Filter panel (integrated left rail, slide-in) ===
   On desktop the panel is an integrated column of the page layout —
   when it opens, the map+listings layout shifts right to make room
   (no overlay, no backdrop blur). Feels like a sidebar on a hotel
   booking site, not a modal. On mobile it falls back to a bottom-
   sheet modal pattern with a backdrop. */
:root {
  --filter-rail-w: 320px;
}
.filter-panel-backdrop {
  position: fixed; inset: 0;
  z-index: 49;
  background: rgba(20, 35, 25, 0.32);
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.2s var(--ease);
}
.filter-panel-backdrop.open {
  opacity: 1;
  pointer-events: auto;
}
.filter-panel {
  position: fixed;
  top: calc(var(--topbar-h) + var(--filter-bar-h));
  bottom: var(--footer-h);
  left: 0;
  width: var(--filter-rail-w);
  z-index: 50;
  background: var(--bg);
  border-right: 1px solid var(--separator-soft);
  display: flex; flex-direction: column;
  transform: translateX(-100%);
  transition: transform 0.24s var(--ease);
}
.filter-panel.open {
  transform: translateX(0);
}
/* Push the main layout right so the filter rail doesn't overlay the
   map. Sync the 0.24s transition with the panel's slide. */
.layout {
  transition: left 0.24s var(--ease);
}
body.filter-panel-open .layout {
  left: var(--filter-rail-w);
}
/* Desktop: hide the backdrop entirely. The integrated rail handles
   focus visually; no dimming needed. */
@media (min-width: 769px) {
  .filter-panel-backdrop { display: none; }
}
.filter-panel-head {
  display: flex; align-items: center; justify-content: space-between;
  padding: 14px 18px 10px;
  border-bottom: 1px solid var(--separator-soft);
  flex-shrink: 0;
}
/* "Filters apply automatically" hint — sits just below the panel
   header. Answers the natural "do I need to press Apply?" question
   without adding chrome. Removed the prior sticky "Edit all
   assumptions" CTA since that action now lives in the controls bar
   next to Filters as a sibling pill. */
.filter-panel-autoapply {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 8px 18px;
  border-bottom: 1px solid var(--separator-soft);
  font-size: 11.5px;
  color: var(--text-tertiary);
  background: var(--bg);
  flex-shrink: 0;
}
.filter-panel-autoapply svg {
  color: var(--positive);
  flex-shrink: 0;
}
.filter-panel-title {
  font-family: var(--font-serif);
  font-size: 22px;
  font-weight: 500;
  letter-spacing: -0.012em;
  color: var(--text);
}
.filter-panel-close {
  width: 32px; height: 32px;
}
.filter-panel-body {
  flex: 1;
  overflow-y: auto;
  overflow-x: hidden;
  padding: 14px 18px 24px;
  -webkit-overflow-scrolling: touch;
}
/* Universal min-width: 0 on all descendants so number inputs / selects
   can't blow out the panel column with their intrinsic min-content size
   (the cause of the Max-price / Max-HOA overflow report). */
.filter-panel-body * { min-width: 0; }
.filter-panel-foot {
  display: flex; align-items: center; justify-content: space-between;
  padding: 12px 20px;
  border-top: 1px solid var(--separator-soft);
  background: var(--bg-surface);
  flex-shrink: 0;
}
.filter-panel-foot-summary {
  font-size: 13px;
  color: var(--text-secondary);
  letter-spacing: -0.005em;
}
.filter-panel-foot-summary strong {
  color: var(--text);
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}
.filter-panel-foot-reset {
  font-size: 13px;
  font-weight: 500;
  color: var(--accent);
}
/* Primary "Show N results" button at the panel bottom. Sized to its
   content so the copy stays naturally centered — the previous
   flex: 1 1 auto stretched it across the full row and the text drifted
   to the left of the resulting wide pill. */
.filter-panel-foot-done {
  padding: 10px 22px;
  font-size: 14px;
  font-weight: 600;
}

/* Filter groups (sections inside the panel) */
.filter-group {
  margin-bottom: 24px;
}
.filter-group:last-child { margin-bottom: 0; }
.filter-group-title {
  font-family: var(--font-sans);
  font-size: 11px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--text-tertiary);
  margin-bottom: 10px;
}
.filter-group-hint {
  font-size: 12.5px;
  color: var(--text-tertiary);
  line-height: 1.4;
  margin-bottom: 12px;
  margin-top: -6px;
}

/* (Removed: quick-preset chips. Were misleading in the WNY-only dataset
   where "Strong returns" returned 0 and "Multi-family" returned 1.
   Filters list is flat now — see structure below.) */

/* === Deelva Picks — verdict filter section ===
   Round 51 redesign per senior PD + PM sub-agent review:
     - Stripped the tinted container so this reads as a peer
       filter (matches Basics/Price/Cash flow above it). The
       previous "bolted-on widget" feel is gone.
     - Sparkle icon + chip colors carry the intelligence signal.
       Brand presence is in the title ("Deelva Picks"), no
       separate badge.
     - Subcopy leads with user value (skip the spreadsheet), per
       PM recommendation — not feature description.
     - Chips stay 2×2 (Pursue fits cleanly). Resting state shows
       the verdict color as a hint border so users see the
       spectrum before clicking; selected state fills with the
       verdict color so the active filter is unmistakable. */
.filter-group-picks {
  margin-bottom: 24px;
  padding: 14px 14px 16px;
  background: linear-gradient(180deg, rgba(46, 90, 56, 0.06), rgba(46, 90, 56, 0.025));
  border: 1px solid rgba(46, 90, 56, 0.14);
  border-radius: 14px;
}
.filter-picks-title {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  margin: 0 0 6px;
}
.filter-picks-icon {
  color: #2E5A38;
  flex-shrink: 0;
}
.help-hint-picks { margin-left: 2px; }
.filter-picks-sub {
  margin: 0 0 12px;
  font-size: 12.5px;
  line-height: 1.5;
  color: var(--text-secondary);
  letter-spacing: -0.003em;
}
/* Round 102: explanatory note under a filter field (e.g. the upfront-
   cash budget filter). Same voice as .filter-picks-sub but sits BELOW
   the input rather than under the section title. */
.filter-field-note {
  margin: 8px 0 0;
  font-size: 12px;
  line-height: 1.5;
  color: var(--text-secondary);
  letter-spacing: -0.003em;
}
/* Inline link to the Investment Assumptions panel from the Picks
   subcopy — clicking opens the panel via the existing
   [data-assumptions-toggle] delegated listener. */
.filter-picks-link {
  color: #2E5A38;
  border-bottom: 1px solid rgba(46, 90, 56, 0.35);
  text-decoration: none;
  transition: border-color 0.12s var(--ease), color 0.12s var(--ease);
}
.filter-picks-link:hover {
  color: #1F4326;
  border-bottom-color: #2E5A38;
}
.filter-picks-chips {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 8px;
}
.filter-verdict-chip {
  appearance: none;
  font: inherit;
  cursor: pointer;
  height: 32px;
  padding: 0 12px;
  border: 1.5px solid transparent;
  border-radius: 8px;
  background: transparent;
  color: var(--text);
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  text-align: center;
  transition: background 0.12s var(--ease), color 0.12s var(--ease),
              border-color 0.12s var(--ease), transform 0.08s var(--ease);
}
.filter-verdict-chip:hover {
  background: rgba(15, 31, 44, 0.03);
}
.filter-verdict-chip:active {
  transform: scale(0.97);
}
/* Active state uses a soft tinted fill + deep verdict-tone text + a
   full-strength colored border (the same natural, low-saturation look as
   the active filter pills) instead of a bold solid fill. This keeps
   "Review" from reading as a dark mustard while preserving the
   green/amber/orange/red quality gradient. Resting borders use the lighter
   hue at ~35-40% opacity; resting text keeps the deeper tones so labels
   stay legible on cream. */
.filter-verdict-chip.verdict-forest.is-active {
  background: rgba(63, 122, 78, 0.16); color: #2E5A38; border-color: #3F7A4E;
}
.filter-verdict-chip.verdict-amber.is-active {
  background: rgba(166, 122, 46, 0.18); color: #6b4e15; border-color: #A67A2E;
}
.filter-verdict-chip.verdict-orange.is-active {
  background: rgba(199, 106, 58, 0.16); color: #7a3611; border-color: #C76A3A;
}
.filter-verdict-chip.verdict-red.is-active {
  background: rgba(162, 58, 58, 0.14); color: #6e1c1c; border-color: #A23A3A;
}
.filter-verdict-chip.verdict-forest  { border-color: rgba(63, 122, 78, 0.35);  color: #2E5A38; }
.filter-verdict-chip.verdict-amber   { border-color: rgba(166, 122, 46, 0.40); color: #6b4e15; }
.filter-verdict-chip.verdict-orange  { border-color: rgba(199, 106, 58, 0.40); color: #7a3611; }
.filter-verdict-chip.verdict-red     { border-color: rgba(162, 58, 58, 0.40);  color: #6e1c1c; }

/* === Collapsible "More filters" section (Round 49) ===
   Uses native <details> for accessibility (keyboard + screen reader
   work for free). Summary is the clickable header; opens to reveal
   the hidden filter groups inside. */
.filter-more {
  margin-bottom: 16px;
  border-top: 1px solid var(--separator-soft);
}
.filter-more[open] { padding-bottom: 8px; }
.filter-more-summary {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 14px 0;
  cursor: pointer;
  list-style: none;
  user-select: none;
}
.filter-more-summary::-webkit-details-marker { display: none; }
.filter-more-summary::marker { content: ''; }
.filter-more-label {
  font-size: 13px;
  font-weight: 600;
  color: var(--text-secondary);
  letter-spacing: -0.005em;
}
.filter-more-chevron {
  color: var(--text-tertiary);
  transition: transform 0.18s var(--ease);
}
.filter-more[open] .filter-more-chevron { transform: rotate(180deg); }
.filter-more-body {
  padding-top: 4px;
}
.filter-more-body .filter-group:last-child { margin-bottom: 0; }

/* Filter fields (label + input pairs) */
.filter-field {
  display: flex;
  flex-direction: column;
  gap: 4px;
  margin-bottom: 12px;
}
.filter-field:last-child { margin-bottom: 0; }
.filter-field-label {
  font-size: 12px;
  font-weight: 500;
  color: var(--text-secondary);
  letter-spacing: -0.005em;
}
.filter-field input,
.filter-field select {
  height: 36px;
  padding: 0 10px;
  background: var(--bg);
  border: 1px solid var(--separator);
  border-radius: 8px;
  font-size: 13.5px;
  color: var(--text);
  font-family: var(--font-sans);
  transition: border-color 0.15s var(--ease), box-shadow 0.15s var(--ease);
  /* Fill the field/grid cell and allow shrinking: inputs carry a default
     intrinsic width (~170px) that, left unchecked, pushes the Max field in a
     1fr/1fr Min-Max row past the popover edge and forces sideways scrolling. */
  width: 100%;
  min-width: 0;
  box-sizing: border-box;
}
/* Strip native select styling and paint our own chevron so the field
   reads as a dropdown at a glance (the browser-default selects in the
   filter panel rendered without any visible affordance). */
.filter-field select {
  appearance: none;
  -webkit-appearance: none;
  padding-right: 32px;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='14' height='14' viewBox='0 0 24 24' fill='none' stroke='%2354627a' stroke-width='2.2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 10px center;
  background-size: 14px;
  cursor: pointer;
}
.filter-field input:focus,
.filter-field select:focus {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 3px rgba(47, 90, 63, 0.10);
}
.filter-field-row {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 8px;
}
.filter-field-row > .filter-field { margin-bottom: 0; min-width: 0; }

/* "More filters" collapsible group */
.filter-group-more {
  margin-bottom: 24px;
}
.filter-group-more-summary {
  display: flex;
  align-items: center;
  justify-content: space-between;
  cursor: pointer;
  padding: 10px 0;
  border-top: 1px solid var(--separator-soft);
  list-style: none;
}
.filter-group-more-summary::-webkit-details-marker { display: none; }
.filter-group-more-summary .filter-group-title { margin-bottom: 0; }
.filter-group-more-chevron {
  color: var(--text-tertiary);
  transition: transform 0.18s var(--ease);
}
.filter-group-more[open] .filter-group-more-chevron {
  transform: rotate(180deg);
}
.filter-group-more-body {
  padding-top: 12px;
  display: flex;
  flex-direction: column;
  gap: 12px;
}

/* Financing chips grid — 2 columns at 320px panel width, each chip
   stacks its label and sub-info vertically so the text never wraps
   awkwardly within the chip. */
.financing-chips-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 6px;
  margin: 6px 0 10px;
}
.financing-chips-grid .financing-chip {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  width: 100%;
  text-align: left;
  padding: 8px 10px;
  background: var(--bg-surface);
  border: 1px solid var(--separator-soft);
  border-radius: 8px;
  cursor: pointer;
  transition: background 0.15s var(--ease), border-color 0.15s var(--ease);
}
.financing-chips-grid .financing-chip:hover {
  background: var(--bg-hover);
  border-color: var(--separator);
}
.financing-chips-grid .financing-chip[aria-checked="true"] {
  background: var(--accent-bg);
  border-color: var(--accent);
}
.financing-chip-label {
  font-size: 12.5px;
  font-weight: 600;
  color: var(--text);
  letter-spacing: -0.005em;
}
.financing-chip-sub {
  font-size: 10.5px;
  color: var(--text-tertiary);
  margin-top: 1px;
}

/* Rate / Down / Term row beneath the financing chips. */
.filter-financing-fields-row {
  display: grid;
  grid-template-columns: 1fr 1fr 1fr;
  gap: 6px;
}
.filter-subfield {
  display: flex;
  flex-direction: column;
  gap: 3px;
  min-width: 0;
}
.filter-subfield span {
  font-size: 10.5px;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--text-tertiary);
}
.filter-subfield input,
.filter-subfield select {
  width: 100%;
  height: 32px;
  padding: 0 6px;
  background: var(--bg);
  border: 1px solid var(--separator);
  border-radius: 6px;
  font-size: 12.5px;
  color: var(--text);
}

/* Mobile: panel becomes a full-screen sheet sliding up from the bottom */
@media (max-width: 768px) {
  .filter-panel {
    top: 0;
    left: 0; right: 0;
    width: 100%;
    /* 100dvh = dynamic viewport height; reshapes when the iOS keyboard
       opens so the panel always covers the visible area. Without dvh,
       the panel was sized to the layout-viewport (full screen) and the
       portion under the keyboard left page chrome leaking through when
       the user tapped an input. */
    height: 100dvh;
    bottom: auto;
    border-right: 0;
    border-radius: 0;
    transform: translateY(100%);
  }
  .filter-panel.open {
    transform: translateY(0);
  }
  /* Don't shift the underlying layout on mobile — the panel is a
     bottom sheet covering everything, not an integrated rail. */
  body.filter-panel-open .layout { left: 0; }
  /* Lock the page so the body underneath doesn't scroll while the
     filter sheet is open — keyboard-focus on a filter input was
     leaving the page partially scrollable behind the sheet, which
     read as "is the filter even fullscreen?" */
  body.filter-panel-open {
    overflow: hidden;
    position: fixed;
    width: 100%;
    height: 100%;
  }
  /* Contain scroll chaining so a swipe inside the filter panel body
     never leaks out to the page underneath (Safari's default is to
     chain). */
  .filter-panel-body { overscroll-behavior: contain; }
  .filter-panel-head {
    padding-top: calc(16px + env(safe-area-inset-top, 0px));
  }
  .filter-panel-foot {
    padding-bottom: calc(12px + env(safe-area-inset-bottom, 0px));
  }
  .filter-presets {
    /* On mobile, presets scroll horizontally so they all fit on one
       row — matches the photo-thumb-strip pattern already in use. */
    flex-wrap: nowrap;
    overflow-x: auto;
    scrollbar-width: none;
    padding-bottom: 4px;
  }
  .filter-presets::-webkit-scrollbar { display: none; }
}

/* === Filter bar (DEPRECATED — old horizontal bar) ===
   Kept only for legacy class names referenced elsewhere. The bar
   itself is no longer rendered — see .controls-bar above. */
.filter-bar {
  position: fixed;
  top: var(--topbar-h);
  left: 0; right: 0;
  height: 52px;
  z-index: 9;
  display: flex; align-items: center;
  gap: 8px;
  padding: 0 20px;
  background: var(--bg);
  border-bottom: 1px solid var(--separator-soft);
}
.filter-bar-sep {
  width: 1px; height: 24px;
  background: var(--separator-soft);
  margin: 0 4px;
}
/* All filter-bar controls share a 34px height + matched border treatment
   so the view-switcher / selects / inputs / Advanced button / Reset
   visually align as one row. */
.filter-bar > .view-switcher,
.filter-bar > .view-switcher .view-btn,
.filter-bar-select,
.filter-advanced > summary {
  height: 34px;
  box-sizing: border-box;
}
.filter-bar-select {
  height: 34px;
  font-size: 13px;
  background-position: right 8px center;
  padding-right: 28px;
  border: 1px solid var(--separator-soft);
  background-color: var(--bg-surface);
}
.filter-bar-price {
  display: inline-flex; align-items: center; gap: 6px;
  font-size: 13px; color: var(--text-tertiary);
}
.filter-bar-price input {
  width: 92px; height: 34px;
  background: var(--bg-surface);
  border: 1px solid var(--separator-soft);
  border-radius: 8px;
  padding: 0 10px;
  font-size: 13px; color: var(--text);
  font-variant-numeric: tabular-nums;
  box-sizing: border-box;
}
.filter-bar-price input:focus {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 3px rgba(47, 90, 63, 0.12);
}
.filter-bar-reset {
  margin-left: auto;
  font-size: 12px;
  color: var(--text-tertiary);
}
/* Collapsible Advanced filters — `<details>` element. Default closed. */
.filter-advanced {
  position: relative;
}
.filter-advanced > summary {
  list-style: none;
  display: inline-flex; align-items: center; gap: 6px;
  height: 34px; padding: 0 12px;
  border: 1px solid var(--separator-soft);
  border-radius: 8px;
  font-size: 13px; color: var(--text-secondary);
  font-weight: 500;
  cursor: pointer;
  background: var(--bg-surface);
  -webkit-tap-highlight-color: transparent;
}
.filter-advanced > summary::-webkit-details-marker { display: none; }
.filter-advanced > summary:hover { color: var(--text); }
.filter-advanced[open] > summary {
  background: var(--accent); color: #fff; border-color: var(--accent);
}
.filter-advanced-body {
  position: absolute;
  top: calc(100% + 8px);
  /* Right-anchored so the popover never overflows the viewport on the
     right (the Advanced button sits near the right edge of the filter
     bar). Previously `left: 0` would push the 580px-wide popover past
     viewport at 1440px. */
  right: 0;
  display: flex; gap: 14px;
  flex-wrap: wrap;
  align-items: flex-start;
  padding: 14px;
  background: var(--bg);
  border: 1px solid var(--separator-soft);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-md);
  z-index: 10;
}
.filter-advanced-body .range-field { gap: 6px; }
.filter-advanced-body .range-field input {
  width: 90px; height: 34px;
  background: var(--bg-surface);
  border: 1px solid var(--separator-soft);
  border-radius: 8px; padding: 0 10px;
  font-size: 13px; font-variant-numeric: tabular-nums;
}
/* HOA filter group inside the Advanced popover — wider than the
   single-input ranges since it pairs Min/Max + an include-no-data
   checkbox. Sits at the end of the row, wrapping below on narrow
   viewports. */
.range-field-group {
  display: flex; flex-direction: column;
  gap: 6px;
  padding-left: 14px;
  border-left: 1px solid var(--separator-soft);
  min-width: 200px;
}
.range-field-group-label {
  font-size: 11px;
  font-weight: 500;
  color: var(--text-secondary);
  letter-spacing: 0.005em;
}
.range-field-pair {
  display: flex; align-items: center; gap: 6px;
}
.range-field-pair input {
  width: 78px; height: 34px;
  background: var(--bg-surface);
  border: 1px solid var(--separator-soft);
  border-radius: 8px; padding: 0 10px;
  font-size: 13px; font-variant-numeric: tabular-nums;
  box-sizing: border-box;
}
.range-field-pair span { color: var(--text-tertiary); font-size: 12px; }
.checkbox-field {
  display: inline-flex; align-items: center; gap: 6px;
  font-size: 11.5px;
  color: var(--text-secondary);
  cursor: pointer;
  line-height: 1.3;
}
.checkbox-field input[type="checkbox"] {
  width: 14px; height: 14px;
  margin: 0;
  accent-color: var(--accent);
  cursor: pointer;
}
/* In the More filters popover, give the "no HOA data" checkbox breathing room
   below the HOA Min/Max inputs so it doesn't crowd the fields. */
.fbar-pop .filter-field-row + .checkbox-field { margin-top: 14px; }

/* === Filter bar — Financing scenario expandable ===
   Same `<details>` pattern as Advanced. Summary shows the active
   scenario name. Body has a chip row for the five canonical presets
   plus three editable fields (rate, down, term) so investors can hand-
   tune off-preset values without leaving the filter bar. */
.filter-financing { position: relative; }
.filter-financing > summary {
  list-style: none;
  display: inline-flex; align-items: center; gap: 6px;
  height: 34px; padding: 0 12px;
  border: 1px solid var(--separator-soft);
  border-radius: 8px;
  font-size: 13px; color: var(--text-secondary);
  font-weight: 500;
  cursor: pointer;
  background: var(--bg-surface);
  -webkit-tap-highlight-color: transparent;
  box-sizing: border-box;
}
.filter-financing > summary::-webkit-details-marker { display: none; }
.filter-financing > summary:hover { color: var(--text); }
.filter-financing[open] > summary {
  background: var(--accent); color: #fff; border-color: var(--accent);
}
.filter-financing[open] > summary strong { color: #fff; }
.filter-financing-label strong { color: var(--text); font-weight: 600; }
.filter-financing-body {
  position: absolute;
  top: calc(100% + 8px);
  right: 0;
  width: 520px;
  max-width: calc(100vw - 32px);
  display: flex; flex-direction: column; gap: 12px;
  padding: 14px;
  background: var(--bg);
  border: 1px solid var(--separator-soft);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-md);
  z-index: 10;
}
.filter-financing-row {
  display: flex; flex-wrap: wrap; gap: 6px;
}
.filter-financing-row .financing-chip {
  flex: 0 0 auto;
  display: inline-flex; align-items: baseline; gap: 6px;
  padding: 5px 12px;
  font-size: 12.5px;
  font-weight: 600;
  background: var(--bg-surface);
  border: 1px solid var(--separator);
  border-radius: 980px;
  color: var(--text-secondary, var(--text));
  cursor: pointer;
  transition: border-color 0.12s var(--ease), background 0.12s var(--ease), color 0.12s var(--ease);
}
.filter-financing-row .financing-chip:hover {
  border-color: var(--accent);
  color: var(--text);
}
.filter-financing-row .financing-chip.active {
  background: var(--accent);
  border-color: var(--accent);
  color: #fff;
}
/* "Modified" state — the user picked this preset but tweaked one or more
   of its rate/down/term values. Keeps the active highlight (so the
   summary still reads "Conventional · Custom") but adds a dashed outer
   ring to signal divergence. */
.filter-financing-row .financing-chip.modified {
  outline: 2px dashed var(--accent);
  outline-offset: 2px;
}
.financing-chip-sub {
  font-size: 10.5px;
  font-weight: 400;
  opacity: 0.75;
}
.filter-financing-fields {
  display: grid;
  grid-template-columns: 1fr 1fr 1fr;
  gap: 10px;
  padding-top: 10px;
  border-top: 1px solid var(--separator-soft);
}
.filter-financing-fields .range-field { gap: 6px; }
.filter-financing-fields .range-field input,
.filter-financing-fields .range-field select {
  width: 100%; height: 34px;
  background: var(--bg-surface);
  border: 1px solid var(--separator-soft);
  border-radius: 8px; padding: 0 10px;
  font-size: 13px; font-variant-numeric: tabular-nums;
  box-sizing: border-box;
}
.filter-financing-all {
  align-self: flex-start;
  padding: 4px 10px;
  background: transparent;
  border: 0;
  font-size: 12px;
  font-weight: 500;
  color: var(--accent);
  cursor: pointer;
  border-radius: 6px;
}
.filter-financing-all:hover { background: rgba(47, 90, 63, 0.06); }
@media (max-width: 768px) {
  .filter-bar {
    height: auto;
    flex-wrap: wrap;
    padding: 8px 12px;
    gap: 6px;
  }
  .filter-bar-sep { display: none; }
  .view-switcher { width: 100%; justify-content: center; }
  .filter-bar-select { flex: 1 1 calc(50% - 4px); min-width: 0; }
  .filter-bar-price { flex: 1 1 100%; }
  .filter-bar-price input { flex: 1; min-width: 0; }
  .filter-advanced { flex: 1 1 auto; }
  .filter-advanced > summary { width: 100%; justify-content: center; }
  .filter-advanced[open] .filter-advanced-body {
    position: static;
    flex-wrap: wrap;
    width: 100%;
    margin-top: 8px;
  }
  .filter-bar-reset { margin-left: 0; flex: 0 0 auto; }
}

/* === Layout === */
.layout {
  /* Fills the viewport between the fixed topbar+filter-bar above and
     the fixed footer below. Uses CSS variables so changes to header or
     footer height stay consistent. The resize-handle is positioned
     against this fixed box (its `right: var(--panel-w)` resolves to
     the viewport right edge, placing it on the map/panel boundary). */
  position: fixed;
  inset: calc(var(--topbar-h) + var(--filter-bar-h)) 0 var(--footer-h) 0;
  display: grid;
  grid-template-columns: 1fr var(--panel-w, 440px);
}

@media (max-width: 1024px) {
  .layout { grid-template-columns: 1fr 380px; }
}
@media (max-width: 768px) {
  :root {
    /* New controls bar (Filters button + view switcher + counter) fits
       on a single row at mobile widths — no need for the 132px legacy
       height that the old multi-row filter bar required. */
    --filter-bar-h: 52px;
    --footer-h: 56px;
  }
}

/* === Map === */
.map-section { position: relative; background: var(--bg-surface); }
.map { position: absolute; inset: 0; }

/* === Resizable panel divider ===
   Sits on the boundary between .map-section and .panel. Drag to slide the
   panel wider (map gets smaller). Mobile layout stacks so the handle is
   hidden there. */
.resize-handle {
  position: absolute;
  top: 0; bottom: 0;
  right: var(--panel-w, 440px);
  width: 6px;
  margin-right: -3px;
  cursor: col-resize;
  z-index: 8;
  background: transparent;
  border: 0;
  padding: 0;
  transition: background 0.12s var(--ease);
}
.resize-handle:hover, .resize-handle.dragging {
  background: linear-gradient(to right, transparent, var(--accent), transparent);
}
.resize-handle:focus-visible { background: var(--accent); outline: none; }
.resize-handle::before {
  /* Visual grip dot in the middle of the handle. */
  content: '';
  position: absolute;
  top: 50%; left: 50%; transform: translate(-50%, -50%);
  width: 4px; height: 32px;
  border-radius: 2px;
  background: var(--separator);
  transition: background 0.12s var(--ease);
}
.resize-handle:hover::before, .resize-handle.dragging::before { background: var(--accent); }
body.resizing { cursor: col-resize; user-select: none; }
body.resizing iframe, body.resizing .map { pointer-events: none; }
@media (max-width: 768px) {
  .resize-handle { display: none; }
}

/* === Panel header (now slim — sort + reset only) === */
.panel-header-slim {
  padding: 12px 14px !important;
  display: flex !important;
  /* Explicit row — `.panel-header` (which this class also has) sets
     flex-direction: column, and without an override the label stacks
     above the select and the select collapses to ~46px. */
  flex-direction: row !important;
  align-items: center;
  gap: 8px;
}
/* "Sort" label outside the select, paired with the option list inside.
   Option labels no longer carry the "Sort:" prefix — the label is here. */
.panel-sort-label {
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-tertiary);
  flex: 0 0 auto;
}
/* Round 23: cap the sort dropdown width in grid view so it doesn't
   stretch across the whole panel. flex: 0 1 auto + max-width keeps
   it tight in both map and grid layouts. */
.panel-header-slim .select-wide { flex: 0 1 auto; max-width: 240px; }

.map-overlay-tl {
  position: absolute; top: 16px; left: 16px; z-index: 5;
}
.map-overlay-bl {
  position: absolute; bottom: 16px; left: 16px; z-index: 5;
  pointer-events: none;
}
.results-pill {
  background: rgba(255, 255, 255, 0.92);
  backdrop-filter: saturate(180%) blur(20px);
  -webkit-backdrop-filter: saturate(180%) blur(20px);
  border: 1px solid var(--separator-soft);
  border-radius: 980px;
  padding: 7px 14px; font-size: 13px; color: var(--text-secondary);
  font-weight: 500;
  box-shadow: var(--shadow-xs);
}
.results-pill span { color: var(--text); font-weight: 600; }

/* === Map markers === */
.au-marker {
  background: var(--bg);
  border: 0.5px solid rgba(0, 0, 0, 0.08);
  color: var(--text);
  font-family: var(--font-sans);
  font-size: 11px; font-weight: 600;
  padding: 3px 8px; border-radius: 980px;
  box-shadow: 0 2px 6px rgba(0, 0, 0, 0.12), 0 0 0 0.5px rgba(0, 0, 0, 0.04);
  cursor: pointer; white-space: nowrap;
  /* Round 113: do NOT transition `transform`. MapLibre writes this element's
     position into an inline `transform: translate(...)` on every animation
     frame during pan/zoom; transitioning transform makes each frame's update
     animate, so the markers lag the map and visibly jitter ("vibrate"). The
     hover/active `scale()` was already overridden by MapLibre's inline
     transform anyway, so only box-shadow/background/color need to animate. */
  transition: box-shadow 0.15s var(--ease), background 0.15s var(--ease), color 0.15s var(--ease);
  letter-spacing: -0.005em;
  font-variant-numeric: tabular-nums;
}
.au-marker:hover {
  transform: scale(1.06);
  box-shadow: 0 6px 16px rgba(0, 0, 0, 0.16);
  z-index: 5;
}
.au-marker.positive { color: var(--positive); }
.au-marker.negative { color: var(--negative); }
/* Round 73: 4-color verdict tinting for the pill labels. Overrides
   the legacy positive/negative colors so the map matches the
   Smart Picks spectrum (Pursue forest / Hold amber / Weak orange
   / Pass red) used everywhere else in the product. */
.au-marker.verdict-pursue { color: #2E5A38; }
.au-marker.verdict-hold   { color: #8a6420; }
.au-marker.verdict-weak   { color: #a14816; }
.au-marker.verdict-pass   { color: #8a2929; }
.au-marker.active {
  background: var(--accent); color: white !important;
  border-color: var(--accent);
  transform: scale(1.08);
  z-index: 6;
  box-shadow: 0 6px 16px rgba(47, 90, 63, 0.32);
}

.au-marker.compact {
  width: 12px; height: 12px; padding: 0; border-radius: 50%;
  font-size: 0; line-height: 0;
  background: var(--text); border: 2px solid white;
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.25);
}
.au-marker.compact.positive { background: var(--positive); }
.au-marker.compact.negative { background: var(--negative); }
/* Compact dots — slightly more saturated since they're tiny and
   need to read from a distance at low zoom. Same hue family as
   the verdict chips and markers above. */
.au-marker.compact.verdict-pursue { background: #3F7A4E; }
.au-marker.compact.verdict-hold   { background: #A67A2E; }
.au-marker.compact.verdict-weak   { background: #C76A3A; }
.au-marker.compact.verdict-pass   { background: #A23A3A; }
.au-marker.compact:hover {
  transform: scale(1.8);
  background: var(--accent);
}
.au-marker.compact.active {
  transform: scale(1.8);
  background: var(--accent);
  box-shadow: 0 0 0 3px rgba(47, 90, 63, 0.24);
}

/* === Deelva Smart Picks quick-filter card, overlaid on the map (top-left) ===
   Mirrors the Filters-panel Smart Picks section: a titled card with the four
   verdict chips (reusing .filter-verdict-chip so they read identically). Multi-
   select; shares filter state with the panel chips (synced by app.js). */
.map-overlay-tc {
  position: absolute; top: 14px; left: 50%; transform: translateX(-50%);
  z-index: 5; pointer-events: none;
  max-width: calc(100% - 120px);
}
.map-picks-bar {
  display: flex;
  align-items: center;
  gap: 4px;
  /* Frosted glassy capsule grouping the label + chips — 50% transparent so the
     map streets show through (a floating control, not a blocking card). */
  background: rgba(255, 255, 255, 0.5);
  backdrop-filter: saturate(150%) blur(16px);
  -webkit-backdrop-filter: saturate(150%) blur(16px);
  border: 1px solid rgba(255, 255, 255, 0.55);
  border-radius: 980px;
  padding: 5px 7px 5px 13px;
  box-shadow: 0 4px 18px rgba(15, 31, 44, 0.16), inset 0 1px 0 rgba(255, 255, 255, 0.5);
  pointer-events: auto;
}
.map-picks-label {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  margin-right: 4px;
  font-size: 12px;
  font-weight: 700;
  color: var(--text);
  letter-spacing: -0.01em;
  white-space: nowrap;
}
.map-picks-label .filter-picks-icon { color: #2E5A38; flex-shrink: 0; }
.map-verdict-filters {
  display: flex;
  gap: 3px;
  pointer-events: auto;
}
/* Dot · name · count chips inside the capsule. Inactive = bare; active =
   verdict-tinted soft fill. The dot always carries the verdict color. */
.map-verdict-chip {
  --vc: #555;
  --vc-soft: rgba(85, 85, 85, 0.12);
  --vc-border: rgba(85, 85, 85, 0.28);
  display: inline-flex;
  align-items: center;
  gap: 6px;
  height: 28px;
  padding: 0 11px;
  border: 1px solid transparent;
  border-radius: 980px;
  background: transparent;
  color: var(--text);
  font-family: var(--font-sans);
  font-size: 12px;
  font-weight: 600;
  letter-spacing: -0.005em;
  white-space: nowrap;
  cursor: pointer;
  transition: background 0.15s var(--ease), border-color 0.15s var(--ease);
}
.map-verdict-dot {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--vc);
  flex-shrink: 0;
}
.map-verdict-count {
  font-variant-numeric: tabular-nums;
  font-weight: 700;
  font-size: 11px;
  color: var(--text-tertiary);
}
.map-verdict-chip:hover { background: rgba(15, 31, 44, 0.06); }
.map-verdict-chip.is-active {
  background: var(--vc-soft);
  border-color: var(--vc-border);
}
.map-verdict-chip.is-active .map-verdict-name { color: var(--vc); }
.map-verdict-chip.is-active .map-verdict-count { color: var(--vc); }
.map-verdict-chip.verdict-forest { --vc: #2E5A38; --vc-soft: rgba(63, 122, 78, 0.16);  --vc-border: rgba(63, 122, 78, 0.32); }
.map-verdict-chip.verdict-amber  { --vc: #8a6420; --vc-soft: rgba(166, 122, 46, 0.17); --vc-border: rgba(166, 122, 46, 0.34); }
.map-verdict-chip.verdict-orange { --vc: #a14816; --vc-soft: rgba(199, 106, 58, 0.17); --vc-border: rgba(199, 106, 58, 0.34); }
.map-verdict-chip.verdict-red    { --vc: #8a2929; --vc-soft: rgba(162, 58, 58, 0.16);  --vc-border: rgba(162, 58, 58, 0.32); }
/* Mobile: the capsule would overflow the narrow map — drop the label and the
   counts so the four dot+name chips still fit. */
@media (max-width: 768px) {
  .map-overlay-tc { max-width: calc(100% - 84px); }
  .map-picks-label { display: none; }
  .map-verdict-count { display: none; }
  .map-picks-bar { padding: 5px 6px; gap: 2px; }
  .map-verdict-chip { padding: 0 10px; gap: 5px; }
}

/* === Investment assumptions promoted into the topbar (every viewport) ===
   Sits in .topbar-actions between the search and menu buttons. On desktop it's
   a labeled pill; on mobile the label is hidden (.topbar-actions .btn-label) so
   it reads as a compact icon button beside search + menu. This replaces the old
   second-row controls-bar copy, freeing that row so the map can grow. */
.assumptions-toggle-top {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  /* Neutral secondary control: a warm cream surface (not stark white) with navy
     text and a warm border reads naturally in the cream topbar, while the green
     "Add property" stays the standout primary action. */
  background: var(--bg-surface);
  color: var(--text);
  border: 1px solid var(--separator);
  font-weight: 500;
}
.assumptions-toggle-top:hover {
  background: var(--bg-hover);
  border-color: var(--text-tertiary);
}
.assumptions-toggle-top svg { color: var(--text-secondary); }
/* The controls-bar copy is retired — the topbar button now serves every width. */
.controls-bar .assumptions-toggle { display: none; }
/* Mobile: render the topbar assumptions button as a compact icon button (label
   hidden by .topbar-actions .btn-label at <=920px) that matches the search and
   menu icon buttons it sits between. */
@media (max-width: 768px) {
  .assumptions-toggle-top {
    width: 36px; height: 36px; padding: 0; gap: 0;
    justify-content: center;
    border-radius: 50%;
    border-color: transparent;
    background: var(--bg-surface);
  }
}

/* "Add a Zillow link" affordance built into the search bar (desktop). Replaces
   the topbar Add-property button: search an address on the left, add a link on
   the right. Opens the same Add-property modal via .add-listing-toggle. */
.search-add-cta {
  position: absolute;
  right: 5px; top: 50%; transform: translateY(-50%);
  display: inline-flex; align-items: center; gap: 5px;
  height: 30px; padding: 0 12px;
  background: rgba(63, 122, 78, 0.10);
  color: #2E5A38;
  border: none; border-radius: 7px;
  font-family: var(--font-sans);
  font-size: 12.5px; font-weight: 600; letter-spacing: -0.005em;
  white-space: nowrap; cursor: pointer;
  transition: background 0.15s var(--ease);
}
.search-add-cta::before {
  content: ""; position: absolute; left: -8px; top: 50%; transform: translateY(-50%);
  width: 1px; height: 20px; background: var(--separator);
}
.search-add-cta:hover { background: rgba(63, 122, 78, 0.18); }
.search-add-cta svg { flex-shrink: 0; }
@media (max-width: 980px) {
  .search-add-cta span { display: none; }
  .search-add-cta { padding: 0 9px; }
  .search input { padding-right: 44px; }
}
@media (max-width: 768px) {
  .search-add-cta { display: none; }
  .search input { padding-right: 16px; }
}

/* === Map popup card (Round 95) ===
   Zillow-style mini card that opens above a clicked marker. Mirrors
   the side-panel listing card visually (photo on top, verdict chip,
   price, address, two key metrics) but tighter to fit over the map. */
.au-map-popup .maplibregl-popup-content {
  padding: 0;
  border-radius: 14px;
  box-shadow: 0 12px 32px rgba(0, 0, 0, 0.18), 0 2px 6px rgba(0, 0, 0, 0.08);
  overflow: hidden;
  background: var(--bg);
}
.au-map-popup .maplibregl-popup-tip { border-top-color: var(--bg); }
.au-map-popup .maplibregl-popup-close-button {
  width: 26px; height: 26px;
  font-size: 18px; line-height: 1;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.92);
  color: var(--text);
  border: 1px solid rgba(0, 0, 0, 0.08);
  top: 8px; right: 8px;
  display: flex; align-items: center; justify-content: center;
  z-index: 3;
}
.au-map-popup .maplibregl-popup-close-button:hover { background: #fff; }

.map-popup-card {
  /* Round 106: cap at viewport width on phones so a marker near a screen
     edge doesn't push the 280px card off-screen (body has overflow-x:hidden,
     which would otherwise clip it). */
  width: min(280px, calc(100vw - 32px));
  cursor: pointer;
  font-family: inherit;
  position: relative;
}
.map-popup-photo-wrap {
  position: relative;
  width: 100%; height: 168px;
  background: var(--bg-surface);
  overflow: hidden;
}
.map-popup-photo-wrap .photo-carousel,
.map-popup-photo-wrap .photo-carousel-img {
  width: 100%; height: 100%;
}
.map-popup-photo-wrap .photo-carousel-img { object-fit: cover; display: block; }
.map-popup-photo-wrap .card-save-btn {
  position: absolute; top: 8px; left: 8px;
  z-index: 2;
}
/* Round 119: the verdict + No-HOA pills now sit in the body (first row),
   off the photo, so the full photo is visible and the pills read cleanly. */
.map-popup-card .verdict-chip-overlay {
  position: static;
  margin: 0 0 8px;
  z-index: auto;
}
.map-popup-card .verdict-overlay-group { flex-direction: row; gap: 6px; flex-wrap: wrap; }
.map-popup-card .verdict-chip-overlay .verdict-chip,
.map-popup-card .verdict-chip-overlay .no-hoa-pill { box-shadow: none; }
.map-popup-body {
  padding: 12px 14px 14px;
  display: flex; flex-direction: column; gap: 4px;
}
.map-popup-price-row {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
}
.map-popup-price {
  font-size: 19px; font-weight: 600;
  letter-spacing: -0.02em;
  color: var(--text);
  font-variant-numeric: tabular-nums;
}
.map-popup-specs {
  font-size: 12.5px;
  color: var(--text-secondary);
}
.map-popup-address {
  font-size: 12.5px;
  color: var(--text-tertiary);
  margin-bottom: 4px;
}
.map-popup-metrics {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 6px;
  margin-top: 6px;
  padding-top: 10px;
  border-top: 1px solid var(--separator-soft);
}
.map-popup-metrics .listing-metric { gap: 1px; min-width: 0; }
.map-popup-metrics .listing-metric .label { font-size: 10px; }
.map-popup-metrics .listing-metric .value { font-size: 13px; }

/* === Listings panel === */
.panel {
  background: var(--bg);
  border-left: 1px solid var(--separator);
  display: flex; flex-direction: column;
  overflow: hidden;
}

.panel-header {
  padding: 16px 20px 14px;
  border-bottom: 1px solid var(--separator-soft);
  display: flex; flex-direction: column; gap: 8px;
  background: var(--bg);
}
.filters { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; }
.filters-row-2 { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; }
.filters-row-3 { display: grid; grid-template-columns: 1fr auto; gap: 8px; align-items: center; }
.filters-row-type { display: grid; grid-template-columns: 1fr; gap: 8px; }
.filters-row-metrics { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 8px; }
/* Grid children default to min-width: auto, which lets intrinsic input widths
   blow out the track on narrow viewports and shove the panel past the viewport
   edge. Floor at 0 so the 1fr tracks actually compress. */
.filters > *, .filters-row-2 > *, .filters-row-3 > *, .filters-row-type > *, .filters-row-metrics > * { min-width: 0; }
.range-field {
  display: flex; align-items: center; gap: 6px;
  font-size: 12px; color: var(--text-tertiary);
  background: var(--bg-surface); border: 1px solid transparent;
  border-radius: var(--radius-sm); padding: 0 12px; height: 36px;
  font-weight: 500;
  transition: border-color 0.15s var(--ease), background 0.15s var(--ease);
}
.range-field:focus-within {
  background: var(--bg);
  border-color: var(--separator);
  box-shadow: 0 0 0 3px rgba(47, 90, 63, 0.12);
}
.range-field input {
  flex: 1; min-width: 0; background: transparent; border: 0;
  font-size: 14px; padding: 0; color: var(--text);
  font-variant-numeric: tabular-nums;
}
.select {
  height: 36px; background: var(--bg-surface); border: 1px solid transparent;
  color: var(--text); padding: 0 32px 0 12px; border-radius: var(--radius-sm);
  font-size: 13px; appearance: none; cursor: pointer;
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%236e6e73' stroke-width='1.75'><polyline points='6 9 12 15 18 9'/></svg>");
  background-repeat: no-repeat; background-position: right 12px center;
  text-overflow: ellipsis;
  font-weight: 500;
  transition: background 0.15s var(--ease);
}
.select:hover { background-color: var(--bg-hover); }
.select-wide { width: 100%; }

.listings {
  flex: 1; overflow-y: auto;
  scrollbar-width: thin;
  scrollbar-color: var(--separator) transparent;
}
.listings::-webkit-scrollbar { width: 8px; }
.listings::-webkit-scrollbar-thumb { background: var(--separator); border-radius: 4px; }

.listing-card {
  /* Tightened from 132×100 / 16-20 padding / 22px price to recover the
     ~25% vertical density that the html { zoom: 0.85 } hack used to give
     us. At 1440 the side panel now fits 3+ cards above the fold instead
     of 1.5. */
  display: grid; grid-template-columns: 108px 1fr; gap: 14px;
  padding: 12px 16px;
  cursor: pointer;
  border-bottom: 1px solid var(--separator-soft);
  transition: background 0.15s var(--ease);
  text-decoration: none; color: inherit;
  /* Round 49: positioned ancestor for the .verdict-chip-overlay.
     In list mode (this default) the chip lands at top-right of the
     body area (since photo is on the left). In grid mode the photo
     is on top, so top-right anchors to the photo overlay. */
  position: relative;
}
.listing-card:hover, .listing-card.active { background: var(--bg-surface); }
.listing-photo-wrap {
  width: 108px; height: 80px; border-radius: var(--radius-md);
  overflow: hidden; background: var(--bg-surface);
}
.listing-photo {
  width: 100%; height: 100%; object-fit: cover;
  display: block;
}

/* === Round 79: in-card photo carousel ====================================
   Lets users scan multiple property photos without clicking through.
   Arrows + dots overlay the photo; clicking either stops propagation
   so the parent card's open-listing handler doesn't fire. Single img
   element per carousel — src swaps on navigation, no eager-loading
   the full photo array. */
.photo-carousel {
  position: relative;
  width: 100%;
  height: 100%;
  overflow: hidden;
}
.photo-carousel-img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}
.photo-carousel-arrow {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  width: 28px;
  height: 28px;
  display: inline-grid;
  place-items: center;
  border: none;
  background: rgba(15, 31, 44, 0.55);
  color: #faf7f1;
  border-radius: 50%;
  cursor: pointer;
  opacity: 0;
  transition: opacity 0.18s var(--ease), background 0.15s var(--ease), transform 0.15s var(--ease);
  z-index: 2;
}
.photo-carousel-arrow:hover {
  background: rgba(15, 31, 44, 0.85);
}
.photo-carousel-prev { left: 8px; }
.photo-carousel-next { right: 8px; }
/* Arrows appear on hover/focus-within (desktop). On touch the arrows
   are persistently visible so tap targets are discoverable. */
.photo-carousel:hover .photo-carousel-arrow,
.photo-carousel:focus-within .photo-carousel-arrow {
  opacity: 1;
}
@media (hover: none) {
  .photo-carousel-arrow { opacity: 1; background: rgba(15, 31, 44, 0.50); }
}
.photo-carousel-dots {
  position: absolute;
  bottom: 6px;
  left: 50%;
  transform: translateX(-50%);
  display: inline-flex;
  gap: 4px;
  padding: 4px 8px;
  background: rgba(15, 31, 44, 0.42);
  border-radius: 980px;
  z-index: 2;
  pointer-events: auto;
  /* Round 80: hide dots by default to match the arrows. They appear
     together on hover so the photo reads as clean by default. */
  opacity: 0;
  transition: opacity 0.18s var(--ease);
}
.photo-carousel:hover .photo-carousel-dots,
.photo-carousel:focus-within .photo-carousel-dots {
  opacity: 1;
}
@media (hover: none) {
  .photo-carousel-dots { opacity: 1; }
}
.photo-carousel-dot {
  width: 5px;
  height: 5px;
  padding: 0;
  border: none;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.55);
  cursor: pointer;
  transition: background 0.15s var(--ease), transform 0.15s var(--ease);
}
.photo-carousel-dot.is-active {
  background: #ffffff;
  transform: scale(1.18);
}
/* When the carousel sits inside a small thumbnail (list-view), shrink
   arrow + dot UI so they don't dominate the 108x80 frame. */
.listing-photo-wrap .photo-carousel-arrow {
  width: 22px;
  height: 22px;
}
.listing-photo-wrap .photo-carousel-arrow svg { width: 12px; height: 12px; }
.listing-photo-wrap .photo-carousel-prev { left: 4px; }
.listing-photo-wrap .photo-carousel-next { right: 4px; }
.listing-photo-wrap .photo-carousel-dots {
  bottom: 4px;
  padding: 2px 6px;
  gap: 3px;
}
.listing-photo-wrap .photo-carousel-dot { width: 4px; height: 4px; }
.listing-body { display: flex; flex-direction: column; gap: 4px; min-width: 0; }
.listing-eyebrow {
  font-size: 10px;
  letter-spacing: -0.005em; color: var(--text-tertiary);
  font-weight: 500;
}
.listing-price {
  font-size: 18px; font-weight: 600;
  color: var(--text); line-height: 1.1;
  letter-spacing: -0.02em;
  font-variant-numeric: tabular-nums;
  margin-top: 2px;
}
.listing-address {
  font-size: 13px; color: var(--text-secondary);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  margin-top: 1px;
}
.listing-specs {
  font-size: 12px; color: var(--text-tertiary); display: flex; gap: 8px;
  margin-top: 2px;
}
.listing-specs span::before { content: '·'; margin-right: 8px; color: var(--separator); }
.listing-specs span:first-child::before { display: none; }
.listing-specs .ferry-near { color: var(--accent); font-weight: 600; }
.listing-metrics {
  display: flex; gap: 16px; margin-top: 8px;
}
.listing-metric { display: flex; flex-direction: column; }
.listing-metric .label {
  font-size: 10px; color: var(--text-tertiary);
  letter-spacing: 0;
  font-weight: 500;
}
.listing-metric .value {
  font-size: 13px; font-weight: 600; margin-top: 1px;
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.01em;
}
.value.positive { color: var(--positive); }
.value.negative { color: var(--negative); }
.value.neutral { color: var(--text); }

.empty {
  text-align: center; padding: 56px 24px; color: var(--text-secondary);
  display: flex; flex-direction: column; gap: 10px; align-items: center;
}
.empty-illustration {
  width: 64px; height: 64px;
  border-radius: 50%;
  background: var(--bg-surface);
  display: flex; align-items: center; justify-content: center;
  color: var(--text-tertiary);
  margin-bottom: 6px;
}
.empty-title { font-size: 16px; font-weight: 600; color: var(--text); margin: 0; letter-spacing: -0.01em; }
.empty-sub { font-size: 13px; color: var(--text-tertiary); margin: 0 0 8px; max-width: 36ch; line-height: 1.5; }
.empty .btn-primary { padding: 0 18px; height: 36px; }
.empty-actions {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
  margin-top: 4px;
}
.empty-actions .btn-text {
  font-size: 12.5px;
  color: var(--text-secondary);
  padding: 4px 8px;
}
.empty-actions .btn-text:hover { color: var(--accent); background: transparent; }

/* === Underwriting panel === */
/* Round 35: removed the saturate+blur backdrop-filter per user.
   The blur was making the rest of the page hard to read while the
   Investment Assumptions panel was open. Now just a faint dimming
   tint that signals "modal context" without obscuring content. */
.backdrop {
  position: fixed; inset: 0; z-index: 40;
  background: rgba(0, 0, 0, 0.18);
}
.assumptions-panel {
  position: fixed; top: 0; right: 0; bottom: 0; z-index: 41;
  width: 420px; max-width: 92vw;
  background: var(--bg);
  border-left: 1px solid var(--separator);
  box-shadow: var(--shadow-lg);
  display: flex; flex-direction: column;
  /* Round 11: padding moved to .ap-scroll so .ap-actions can sit flush
     at the bottom of the panel without empty padding underneath. The
     panel itself no longer scrolls — its inner .ap-scroll wrapper does,
     leaving .ap-actions as a non-scrolling flex child pinned via
     normal flow (flex column → last child sits at bottom). */
  padding: 0;
  overflow: hidden;
  box-sizing: border-box;
  transform: translateX(100%);
  transition: transform 0.28s var(--ease);
  /* Mobile: prevent double-tap-to-zoom on the assumptions sheet. */
  touch-action: manipulation;
}
.assumptions-panel .ap-scroll {
  flex: 1 1 auto;
  min-height: 0;          /* allow this to shrink so flex sibling fits */
  overflow-y: auto;
  overflow-x: hidden;
  padding: 24px 28px 16px;
}
.assumptions-panel * {
  /* Cascade touch-action so any tap target — buttons, inputs, the
     stepper +/- — also opts out of double-tap zoom. */
  touch-action: manipulation;
}
/* Every direct child must respect the panel's content box. min-width: 0
   on grid/flex children prevents intrinsic-min-width blow-up (e.g. a long
   select dropdown wanting 400px when the column only has 180px). */
.assumptions-panel * { min-width: 0; }
.assumptions-panel input,
.assumptions-panel select,
.assumptions-panel button { max-width: 100%; box-sizing: border-box; }
.assumptions-panel.open { transform: translateX(0); }
.ap-head { display: flex; justify-content: space-between; align-items: center; }
.ap-head h2 {
  font-size: 26px; font-weight: 600;
  letter-spacing: -0.025em;
  color: var(--text);
}
.ap-sub { color: var(--text-secondary); font-size: 14px; margin: 6px 0 24px; }
.ap-section { margin-bottom: 24px; }
/* Round 46: bumped section headings 12 → 14px and color from tertiary
   to secondary so the sections (Financing scenario / Financing / Risk
   profile / Operating / Growth) read as proper headers rather than
   small-caps labels. Heavier visual hierarchy makes the panel less
   wall-of-text on first open. */
.ap-section h3 {
  font-size: 14px; color: var(--text-secondary);
  font-weight: 600; letter-spacing: -0.008em;
  margin-bottom: 10px;
}
/* Round 38: small caveat note next to a section heading. Used on
   "Growth" to signal that those inputs only affect the 10-yr P&L
   (not the year-1 monthly breakdown or the map cash-flow markers),
   so the user knows it's not broken when they tweak appreciation
   / rent growth / etc. and don't see the year-1 numbers move. */
.ap-section-note {
  display: inline-block;
  margin-left: 8px;
  padding: 2px 8px;
  background: var(--bg-surface);
  color: var(--accent);
  border-radius: 980px;
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 0.02em;
  text-transform: none;
  font-variant-numeric: tabular-nums;
}
.ap-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 12px 14px; }
.field { display: flex; flex-direction: column; gap: 6px; }
.field-hint {
  font-size: 11px;
  color: var(--text-tertiary);
  line-height: 1.4;
  margin-top: 2px;
}
/* Round 52: full-width assumption-grid row for fields like Property
   mgmt where the hint should sit beside the input rather than under
   it. Spans both columns of the .ap-grid 2-up so input + hint can
   share a single horizontal line. */
.ap-grid-full { grid-column: 1 / -1; }
.field-with-aside .field-row-aside {
  display: grid;
  /* Round 65: was 1fr 2fr — that made the input narrower than the other
     operating-expense inputs. 1fr 1fr matches the standard ap-grid
     2-column field width so all operating inputs are the same size.
     Hint gets the other half. */
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  align-items: center;
  gap: 14px;
}
.field-hint-inline {
  margin-top: 0;
  line-height: 1.45;
}
@media (max-width: 520px) {
  /* Stack vertically on narrow viewports so the input doesn't squeeze. */
  .field-with-aside .field-row-aside {
    grid-template-columns: 1fr;
    gap: 6px;
  }
}
.field > span:first-child {
  font-size: 13px; color: var(--text); font-weight: 500;
  /* Round 66: REVERTED Round 65's inline-flex. Plain inline display
     matches the filter labels (which align cleanly), and lets the
     default .help-hint vertical-align: middle + translateY(-2px)
     work as intended. white-space: nowrap prevents the ? icon from
     wrapping to a new row in narrow cells. */
  white-space: nowrap;
}
.input-with-suffix {
  display: flex; align-items: center;
  background: var(--bg-surface); border: 1px solid transparent;
  border-radius: var(--radius-sm); height: 36px;
  /* Round 46: padding 0 10 0 12 → 0 8 0 10 so 4-character values like
     "0.50" or "2.5/yr" fit without the trailing character getting
     clipped by the suffix or the right stepper button. */
  padding: 0 8px 0 10px;
  transition: background 0.15s var(--ease), border-color 0.15s var(--ease), box-shadow 0.15s var(--ease);
}
.input-with-suffix:focus-within {
  background: var(--bg);
  border-color: var(--separator);
  box-shadow: 0 0 0 3px rgba(47, 90, 63, 0.12);
}
.input-with-suffix input {
  flex: 1; min-width: 0; background: transparent; border: 0;
  font-size: 14px; color: var(--text);
  font-variant-numeric: tabular-nums;
}
.input-with-suffix .suffix {
  color: var(--text-tertiary); font-size: 13px;
  /* Round 46: padding-left 8 → 4 to give the input more horizontal
     room. 4px is still a visible gap between the digits and the % */
  padding-left: 4px;
  flex-shrink: 0;
  white-space: nowrap;
}

/* === Numeric stepper buttons (−/+) on assumption inputs ===
   Injected by JS in assumptions-ui.js. Each click bumps the input by
   the input's own `step` attribute (eg. 0.125 for mortgage rate =
   standard 1/8 percentage point increments mortgage brokers quote in).
   Solves the "sliders are too sensitive / I can't land on 7.00 exactly"
   complaint, plus the keystroke-eating "step 0.001" bug. */
.input-with-stepper {
  display: flex;
  align-items: stretch;
  /* Round 46: gap 6 → 4 so the input itself gets more horizontal
     room — solves the truncation in narrow ap-grid cells. */
  gap: 4px;
}
.input-with-stepper .input-with-suffix {
  flex: 1;
  min-width: 0;
}
.stepper-btn {
  flex: 0 0 auto;
  /* Round 46: stepper width 32 → 28 so the central input has more
     room for 4-character values like "0.50" without truncation. */
  width: 28px;
  background: var(--bg-surface);
  border: 1px solid var(--separator-soft);
  border-radius: var(--radius-sm);
  font-size: 18px;
  font-weight: 500;
  line-height: 1;
  color: var(--text-secondary);
  cursor: pointer;
  user-select: none;
  transition: background 0.12s var(--ease), color 0.12s var(--ease), border-color 0.12s var(--ease);
}
.stepper-btn:hover {
  background: var(--bg-hover);
  color: var(--accent);
  border-color: var(--accent);
}
.stepper-btn:active {
  background: var(--accent-bg);
}
.stepper-btn:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
}
.field input[type="range"] {
  width: 100%; height: 4px; -webkit-appearance: none;
  background: var(--separator-soft);
  border-radius: 2px;
  margin-top: 2px;
}
.field input[type="range"]::-webkit-slider-thumb {
  -webkit-appearance: none; width: 16px; height: 16px; border-radius: 50%;
  background: white; cursor: pointer;
  border: 1px solid var(--separator);
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.15);
}
.ap-actions {
  margin-top: 16px; display: flex; justify-content: space-between; align-items: center;
  padding-top: 18px; border-top: 1px solid var(--separator-soft);
}
/* Round 11: .ap-actions is now a non-scrolling flex child of
   .assumptions-panel (the scroll context is its sibling .ap-scroll).
   Normal flow places it at the bottom of the panel automatically
   because flex column + .ap-scroll having flex: 1 absorbs all free
   space above. No more sticky positioning needed — the row is just
   the last child of the column. Hairline shadow above acts as the
   visual seam between scrollable content and the action bar. */
.assumptions-panel .ap-actions {
  flex: 0 0 auto;
  background: var(--bg);
  /* Reset margin-top from the base .ap-actions rule (16px) so the bar
     sits flush against the scroll content + its hairline border. */
  margin: 0;
  padding: 14px 28px calc(18px + env(safe-area-inset-bottom, 0px));
  border-top: 1px solid var(--separator-soft);
  /* Box shadow ABOVE the bar to reinforce the "anchored chrome" feel
     when content is scrolling underneath. */
  box-shadow: 0 -4px 12px -8px rgba(26, 33, 40, 0.12);
}

/* === Listing detail page === */
.detail-body { background: var(--bg); }
.detail {
  /* Bottom margin reduced from 96px to 32px — the "Add another property"
     CTA at the bottom of the detail page had ~96px of empty space below
     it before the footer, which read as the page having unfinished
     content. Tighter spacing makes the footer feel like the deliberate
     end of the page. */
  max-width: 1080px; margin: calc(var(--topbar-h) + 60px) auto 32px;
  padding: 0 32px;
  /* Round 27 — STICKY FIN-CARD ROOT CAUSE FIX. The previous rule used
     `overflow-x: hidden` to clip long Zillow URLs, but per CSS spec
     when one axis is hidden/auto/scroll and the other is `visible`,
     the visible axis silently promotes to `auto`. So `.detail` was
     computing as `overflow-x: hidden; overflow-y: auto` — creating
     an intermediate scrolling box between .fin-card (sticky) and
     body (the real scroll container). Sticky pins to the NEAREST
     scrolling ancestor; with .detail as that ancestor (height ≈
     content height, no actual scroll happening within it), the
     card moved with .detail instead of pinning to the viewport as
     the user scrolled body. Result: card scrolled with content,
     never stuck. Fix: `overflow-x: clip` does the same horizontal
     clamp without creating a scrolling container (clip is the
     non-scrolling overflow value, modern Chrome 90+/Firefox 81+/
     Safari 16+). */
  overflow-x: clip;
  /* Make sure long descriptions, addresses, or any inline URL wrap
     instead of pushing the column wider. */
  word-wrap: break-word;
  overflow-wrap: break-word;
}
.detail-loading { color: var(--text-tertiary); padding: 60px 0; text-align: center; }

/* Banner shown when the page loads with `?ov=` or `?a=` URL params (i.e.
   the user opened a shared link). Tells them the numbers reflect the
   sender's state and offers a one-click reset. */
.shared-state-banner {
  display: flex; align-items: center; justify-content: space-between;
  gap: 16px;
  background: var(--bg-surface);
  border: 1px solid var(--separator-soft);
  border-radius: var(--radius-md);
  padding: 10px 14px 10px 16px;
  margin-bottom: 14px;
  font-size: 13px;
  color: var(--text-secondary);
}
.shared-state-banner .ssb-text { line-height: 1.4; }
.shared-state-banner .ssb-reset {
  flex-shrink: 0;
  font-size: 13px;
  color: var(--accent);
  font-weight: 500;
  padding: 4px 8px;
}
@media (max-width: 480px) {
  .shared-state-banner { flex-direction: column; align-items: flex-start; gap: 6px; padding: 12px 14px; }
  .shared-state-banner .ssb-reset { padding: 2px 0; }
}

/* Detail page skeleton — shape-matched to the real layout so the page
   doesn't visibly reflow when content mounts. Shimmer animation runs at
   1.4s for a calm "loading" feel, paused for prefers-reduced-motion. */
.sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0;
}
.detail-skeleton {
  --sk-bg: var(--bg-surface);
  --sk-shimmer: rgba(255, 255, 255, 0.55);
}
.sk-hero, .sk-thumb, .sk-line, .sk-spec, .sk-metric, .sk-fin-card {
  background: var(--sk-bg);
  border-radius: var(--radius-md);
  position: relative;
  overflow: hidden;
}
.sk-hero::after, .sk-thumb::after, .sk-line::after, .sk-spec::after,
.sk-metric::after, .sk-fin-card::after {
  content: '';
  position: absolute; inset: 0;
  background: linear-gradient(90deg, transparent, var(--sk-shimmer), transparent);
  transform: translateX(-100%);
  animation: sk-shimmer 1.4s var(--ease) infinite;
}
@keyframes sk-shimmer { 100% { transform: translateX(100%); } }
@media (prefers-reduced-motion: reduce) {
  .sk-hero::after, .sk-thumb::after, .sk-line::after, .sk-spec::after,
  .sk-metric::after, .sk-fin-card::after { animation: none; }
}
.sk-hero { width: 100%; aspect-ratio: 5 / 3; margin-bottom: 16px; border-radius: var(--radius-xl); }
.sk-thumb-strip { display: flex; gap: 8px; margin-bottom: 24px; overflow: hidden; }
.sk-thumb { flex: 0 0 auto; width: 120px; height: 80px; }
.sk-grid {
  display: grid; grid-template-columns: 1.4fr 1fr; gap: 32px 56px;
}
.sk-col-main, .sk-col-aside { display: flex; flex-direction: column; gap: 12px; }
.sk-line { height: 16px; width: 92%; }
.sk-line-xs { height: 10px; width: 32%; }
.sk-line-sub { height: 14px; width: 64%; }
.sk-line-h { height: 40px; width: 78%; }
.sk-line-h2 { height: 24px; width: 38%; margin-top: 28px; }
.sk-line-price { height: 44px; width: 56%; }
.sk-line-short { width: 48%; }
.sk-specs { display: flex; gap: 24px; padding: 14px 0; margin-top: 10px; }
.sk-spec { width: 70px; height: 36px; }
.sk-fin-card { padding: 28px; height: 280px; }
.sk-fin-card .sk-line, .sk-fin-card .sk-line-xs, .sk-fin-card .sk-line-price,
.sk-fin-card .sk-line-sub { background: rgba(0, 0, 0, 0.06); }
.sk-metric-grid {
  display: grid; grid-template-columns: 1fr 1fr; gap: 12px;
  margin-top: 24px;
}
.sk-metric { height: 52px; background: rgba(0, 0, 0, 0.06); }
@media (max-width: 768px) {
  .sk-grid { grid-template-columns: 1fr; gap: 24px; }
  /* Match the real hero's mobile height (220px fixed, see line ~2872) so
     swapping the skeleton out doesn't reflow ~37–60px of layout. */
  .sk-hero { aspect-ratio: auto; height: 220px; margin-bottom: 16px; }
  .sk-thumb { width: 96px; height: 64px; }
  .sk-line-h { height: 32px; }
  .sk-line-price { height: 36px; }
}

.detail-hero {
  width: 100%;
  margin-bottom: 20px;
  border-radius: var(--radius-xl);
  overflow: hidden;
  background: var(--bg-surface);
}
/* Hero sizing — user feedback (2026-05-22): the 320/280px max-heights
   were leaving real listing photos looking cramped on desktop modals
   (lots of empty grey letterbox above/below). Bumped to give the
   primary photo more vertical real estate while still keeping the
   numbers card visible above the fold on a 13" laptop. The mobile
   override below tightens both back down for small viewports. */
.detail-hero-single { aspect-ratio: 4 / 3; max-height: 520px; }
/* When Google Maps key is set, the hero splits into two equally-weighted
   photos side by side: top-down satellite + real eye-level Street View. */
.detail-hero-split {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 8px;
  aspect-ratio: 16 / 9;
  max-height: 440px;
}
.detail-hero .main-photo {
  width: 100%; height: 100%;
  background-color: var(--bg-surface);
  background-size: cover; background-position: center;
  background-repeat: no-repeat;
}
.detail-hero .photo-placeholder {
  background-size: 240px auto !important;
  background-repeat: no-repeat;
  background-position: center;
}

/* Hero photo navigation — prev/next chevrons + position counter overlay
   on the main hero. Only render when galleryPhotos.length > 1. The
   buttons live INSIDE .main-photo[data-photo-idx="0"], so the main-photo
   needs position:relative to anchor them. */
.detail-hero .main-photo { position: relative; }
.hero-nav {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  width: 40px; height: 40px;
  border-radius: 50%;
  background: rgba(20, 35, 25, 0.62);
  color: #fff;
  border: 0;
  padding: 0;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: background 0.15s var(--ease), transform 0.15s var(--ease);
  -webkit-backdrop-filter: blur(6px);
  backdrop-filter: blur(6px);
  z-index: 2;
}
.hero-nav:hover { background: rgba(20, 35, 25, 0.85); transform: translateY(-50%) scale(1.05); }
.hero-nav:focus-visible { outline: 2px solid #fff; outline-offset: 2px; }
.hero-nav-prev { left: 14px; }
.hero-nav-next { right: 14px; }
.hero-counter {
  position: absolute;
  bottom: 12px; right: 12px;
  background: rgba(20, 35, 25, 0.62);
  color: #fff;
  font-size: 12px;
  font-weight: 500;
  padding: 4px 10px;
  border-radius: 999px;
  letter-spacing: 0.02em;
  font-variant-numeric: tabular-nums;
  -webkit-backdrop-filter: blur(6px);
  backdrop-filter: blur(6px);
  z-index: 2;
}
@media (max-width: 768px) {
  /* Round 24 (mobile audit HIGH): chevrons at vertical center on a
     portrait photo overlapped the focal point. Drop them to the
     bottom corners (out of the photo's primary subject area) and
     bump tap target to 44px to clear the touch floor. Also enable
     vertical-pan touch so users can scroll past the hero without
     getting their swipe captured by the photo. */
  .hero-nav {
    width: 44px; height: 44px;
    top: auto; bottom: 12px;
    transform: none;
  }
  .hero-nav:hover { transform: scale(1.05); }
  .hero-nav-prev { left: 10px; }
  .hero-nav-next { right: 10px; }
  /* Counter moves to top-right so it doesn't collide with the now
     bottom-positioned "next" chevron. */
  .hero-counter { top: 10px; bottom: auto; right: 10px; font-size: 11px; padding: 3px 8px; }
  .detail-hero .main-photo { touch-action: pan-y; }
}
@media (max-width: 768px) {
  .detail-hero-single { aspect-ratio: 4 / 3; }
  .detail-hero-split { grid-template-columns: 1fr; aspect-ratio: auto; }
  .detail-hero-split .main-photo { aspect-ratio: 4 / 3; }
  /* Stacking two 4:3 hero photos on mobile pushed the price + cash
     flow ~520px below the fold. Hide the second (Street View) photo —
     it's still reachable via the thumbnail strip below. */
  .detail-hero-split .main-photo[data-photo-idx="1"] { display: none; }
}

.detail-thumbs {
  display: grid; grid-template-columns: 1fr 1fr 1fr;
  gap: 12px; margin-bottom: 48px;
}
.detail-thumbs .photo {
  width: 100%; aspect-ratio: 16 / 10;
  background-color: var(--bg-surface);
  background-size: cover; background-position: center;
  border-radius: var(--radius-md);
}

/* Horizontal scrollable thumbnail strip below the hero — click a thumb to
   swap the main hero photo. The first thumb is highlighted when its URL
   matches the current hero. Satellite + Street view thumbs get a label
   overlay so they're distinguishable from interior photos. */
.detail-thumbs-strip {
  display: flex;
  gap: 8px;
  margin-top: -8px;
  margin-bottom: 24px;
  padding-bottom: 6px;
  overflow-x: auto;
  scrollbar-width: thin;
  scrollbar-color: var(--separator) transparent;
}
.detail-thumbs-strip::-webkit-scrollbar { height: 6px; }
.detail-thumbs-strip::-webkit-scrollbar-thumb { background: var(--separator); border-radius: 3px; }
.detail-thumb {
  position: relative;
  flex: 0 0 auto;
  width: 120px;
  height: 80px;
  padding: 0;
  border: 2px solid transparent;
  border-radius: var(--radius-md);
  overflow: hidden;
  cursor: pointer;
  background: var(--bg-surface);
  transition: border-color 0.15s var(--ease), transform 0.15s var(--ease);
}
.detail-thumb img {
  width: 100%; height: 100%;
  object-fit: cover;
  display: block;
}
.detail-thumb:hover { border-color: var(--separator); transform: translateY(-1px); }
.detail-thumb.active { border-color: var(--text); }
.detail-thumb-label {
  position: absolute;
  bottom: 4px; left: 4px;
  background: rgba(20, 35, 25, 0.78);
  color: #fff;
  font-size: 10px;
  font-weight: 500;
  padding: 2px 6px;
  border-radius: 4px;
  letter-spacing: 0.02em;
}
@media (max-width: 768px) {
  .detail-thumb { width: 96px; height: 64px; }
}

/* === In-page section nav (TOC) ===
   Horizontal chip strip injected just above .detail-grid. Sticky to the
   top of the nearest scrolling ancestor (modal body when opened from the
   map; document otherwise). Hairline border below provides a clear seam
   between the nav and the content scrolling under it. */
.detail-toc {
  position: sticky;
  top: 0;
  z-index: 8;
  margin: 16px 0 4px;
  background: rgba(250, 247, 241, 0.92);
  -webkit-backdrop-filter: saturate(180%) blur(20px);
  backdrop-filter: saturate(180%) blur(20px);
  border-bottom: 1px solid var(--separator-soft);
  /* Pull the strip flush with the .detail container's horizontal padding
     so it visually breaks out and forms a full-bleed bar across the
     content column. */
  margin-left: calc(-1 * var(--detail-padding-x, 32px));
  margin-right: calc(-1 * var(--detail-padding-x, 32px));
  padding: 8px var(--detail-padding-x, 32px);
}
.detail-toc-track {
  display: flex;
  gap: 4px;
  overflow-x: auto;
  scrollbar-width: none;
  -webkit-overflow-scrolling: touch;
  /* Center the pill row in the available width on both desktop and
     mobile. When pills overflow (long localized labels, narrow phones),
     the flex track still scrolls — `justify-content: center` doesn't
     break overflow-x; only the unscrolled axis is centered. */
  justify-content: center;
  /* Round 87: position: relative is the anchor for the absolute-
     positioned .detail-toc-back below. The back link sits at the left
     edge without disturbing the centered pill cluster. */
  position: relative;
}
.detail-toc-track::-webkit-scrollbar { display: none; }
.detail-toc-link {
  flex: 0 0 auto;
  padding: 7px 14px;
  border-radius: 980px;
  font-size: 13px;
  font-weight: 500;
  letter-spacing: -0.005em;
  color: var(--text-secondary);
  text-decoration: none;
  white-space: nowrap;
  transition: background 0.15s var(--ease), color 0.15s var(--ease);
}
.detail-toc-link:hover {
  background: var(--bg-surface);
  color: var(--text);
}
.detail-toc-link.active {
  background: var(--accent);
  color: var(--bg);
}
/* Round 82.1 / 85 / 87 / 91: Back-to-map link in the section-tabs
   strip. Absolute-positioned at the left of the (position: relative)
   track so the tabs stay centered. Round 91 update: shift the whole
   link right by 21px (chevron width 16px + 5px gap) so the
   BACK-ARROW lines up with the X where the text used to sit, which
   matches the brand-mark column above. Per user: arrow → logo,
   not text → logo. */
.detail-toc-back {
  position: absolute;
  left: 21px;
  top: 50%;
  transform: translateY(-50%);
  display: inline-flex;
  align-items: center;
  gap: 5px;
  color: var(--text);
}
.detail-toc-back:hover {
  background: var(--bg-surface);
  color: var(--text);
  /* Round 89: removed the translateX nudge on hover. It was
     overriding the base translateY(-50%) used to vertically center
     the absolute-positioned link, which made the whole link snap
     downward on hover. Fixed placement, no motion. */
}
.detail-toc-back svg { flex-shrink: 0; opacity: 0.7; }
.detail-toc-back:hover svg { opacity: 1; }
/* Premium chip — warm gold accent + tiny sparkle to signal it's the
   paid-tier section. Subtle in default state, becomes solid gold when
   active (currently scrolled-into-view). */
.detail-toc-link--premium {
  color: rgb(140, 100, 40);
  background: rgba(178, 138, 70, 0.08);
}
.detail-toc-link--premium:hover {
  background: rgba(178, 138, 70, 0.16);
  color: rgb(120, 85, 30);
}
.detail-toc-link--premium.active {
  background: rgb(178, 138, 70);
  color: #fff;
}
/* Standalone listing.html (not modal): the topbar is position: fixed, so
   sticky top: 0 would hide the TOC behind it. Offset by the topbar height. */
body.detail-body .detail-toc {
  top: var(--topbar-h, 68px);
}
@media (max-width: 768px) {
  /* Round 27: align the TOC's full-bleed offsets to the ACTUAL mobile
     .detail padding (16px — set at line ~6575). Previous values were
     -18px / 18px which assumed an 18px parent padding the mobile
     .detail rule never actually used. Net effect was a 4px-wide
     overflow band on each side that the body's overflow-x: hidden
     visually clipped but still reported as bodyScrollWidth > viewport.
     Mobile audit at 375px viewport confirmed this was the only
     remaining overflow source after Round 27's .detail clip fix. */
  .detail-toc {
    margin-left: -16px;
    margin-right: -16px;
    padding: 8px 16px;
  }
  .detail-toc-link { padding: 6px 12px; font-size: 12.5px; }
  /* Round 106 (P0): on mobile the centered pill row collided with the
     absolute "Back to map" link, and `justify-content: center` on an
     overflowing scroll track hides the FIRST pill ("About") past the
     left edge with no way to scroll back to it. Fix: left-align the
     track and reserve room for the back affordance, which becomes an
     icon-only chevron so it doesn't eat horizontal space. */
  /* Round 107: make the back link an IN-FLOW first item on mobile
     (position: static instead of absolute). The absolute-over-flex
     pattern repeatedly caused the back link to overlap the pills; an
     in-flow chevron simply takes its slot at the start of the row and
     the pills flow after it — overlap is structurally impossible. */
  .detail-toc-track { justify-content: flex-start; padding-left: 0; }
  .detail-toc-back {
    position: static;
    transform: none;
    left: auto; top: auto;
    flex: 0 0 auto;
    margin-right: 2px;
  }
  .detail-toc-back span { display: none; }
  /* Mobile topbar uses the same --topbar-h variable; rule above still works. */
}

.detail-grid {
  display: grid;
  grid-template-columns: 1.35fr 1fr;
  /* The left column (.dp-main = address + About) is a single grid cell, so the
     tall summary card (aside) beside it can never stretch or vertically center
     the address/About — they stay pinned to the top regardless of card height.
     Financials, Insights, and the Neighborhood group span full width. */
  grid-template-areas:
    "main         aside"
    "financials   financials"
    "premium      premium"
    "neighborhood neighborhood"
    "cta          cta";
  gap: 24px 40px;
  align-items: start;
}
/* Same pattern as the filter grids — without min-width: 0, the iframe inside
   .dp-location asserts its intrinsic width and blows the whole grid track to
   ~1200px on every viewport, pushing the fin-card thousands of px off-screen. */
.detail-grid > * { min-width: 0; }
.detail-grid > .dp-main { grid-area: main; }
.detail-grid > .dp-header        { grid-area: header; }
.detail-grid > .dp-specs         { grid-area: specs; }
/* Specs sit to the right of the address (2-col header row): a clean grid
   instead of the full-width divider strip. */
.detail-grid > .dp-specs {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 14px 20px;
  border: 0;
  padding: 0;
  align-self: start;
}
.detail-grid > .dp-specs .detail-spec { border-left: 0; padding-left: 0; padding-right: 0; flex: none; }
/* Specs strip + recommended offer now live inside the About section (left
   column), directly under the header and body respectively. */
.dp-about .dp-specs { margin-top: 10px; margin-bottom: 18px; }
.dp-about .rec-offer { margin-top: 18px; }
.detail-grid > .dp-aside         { grid-area: aside; }
.detail-grid > .dp-about         { grid-area: about; }
.detail-grid > .dp-recoffer      { grid-area: recoffer; }
.detail-grid > .dp-neighborhood  { grid-area: neighborhood; }
.detail-grid > .dp-financials    { grid-area: financials; }
.detail-grid > .dp-premium       { grid-area: premium; }
.detail-grid > .detail-add-cta   { grid-area: cta; }
/* Recommended-offer cell. Hidden until the post-render JS confirms it rendered
   content and adds .has-recoffer (so the base grid template — and its spacing —
   is untouched when there's no recommendation, and the cell never flashes in an
   unplaced position). When present, .has-recoffer inserts a "recoffer" row:
   desktop keeps it directly under About in the left column (aside spans both
   rows, sitting at the top via align-items:start — visually identical to when
   the card lived inside About); mobile orders it right after the summary card. */
.dp-recoffer { display: none; }
.detail-grid.has-recoffer {
  grid-template-areas:
    "main         aside"
    "recoffer     aside"
    "financials   financials"
    "premium      premium"
    "neighborhood neighborhood"
    "cta          cta";
}
.detail-grid.has-recoffer > .dp-recoffer {
  display: block;
  /* Old in-About spacing was margin-top:18px under the description; the grid
     row-gap is 24px, so pull up 6px to keep the desktop gap pixel-identical. */
  margin-top: -6px;
}
/* Financials + Insights groups want a touch more separation from the
   descriptive content above, but the full-width nature of these blocks
   already provides visual hierarchy — keep the margin modest. */
.detail-grid > .dp-financials,
.detail-grid > .dp-premium { margin-top: 8px; }

/* === Top-level group title + sub-text (Neighborhood / Financials) ===
   Larger H2 + a lead paragraph below sets up each group as a distinct
   editorial chapter. Sub-sections inside use H3 with a hairline separator
   above to read as logical chapter breaks. */
.dp-group-title {
  font-family: var(--font-serif);
  font-size: 32px;
  font-weight: 500;
  line-height: 1.05;
  letter-spacing: -0.018em;
  color: var(--text);
  margin-bottom: 8px;
}
/* Section-header icons (body headers only; the sticky TOC pills are untouched).
   Each section h2 becomes a flex row so the accent icon sits inline to the left
   of the title, vertically centered. */
.dp-about > h2,
.dp-group-title {
  display: flex;
  align-items: center;
  gap: 11px;
}
.dp-h-icon {
  flex: 0 0 auto;
  width: 22px;
  height: 22px;
  color: var(--accent);
}
.dp-group-sub {
  font-size: 15px;
  color: var(--text-secondary);
  line-height: 1.55;
  margin-bottom: 16px;
  /* No max-width: group leads are short single-line summaries; the
     readability constraint that 64ch enforced was producing a forced
     wrap on Financials' lead even when the container had plenty of
     horizontal room. Let the natural container width govern. */
}
.detail-subsection {
  margin-top: 20px;
  padding-top: 16px;
  border-top: 1px solid var(--separator-soft);
}
.detail-subsection:first-of-type {
  margin-top: 0;
  padding-top: 0;
  border-top: 0;
}
.detail-subsection h3 {
  font-family: var(--font-serif);
  font-size: 22px;
  font-weight: 500;
  line-height: 1.1;
  letter-spacing: -0.012em;
  color: var(--text);
  margin-bottom: 12px;
}

/* Collapsible neighborhood subsections (Transit, Getting around, Schools,
   Climate). Native <details>, collapsed by default; nested inside the existing
   .detail-subsection so all section spacing and hairline separators are kept. */
.dp-collapse > .dp-collapse-summary {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  cursor: pointer;
  list-style: none;
  user-select: none;
}
.dp-collapse > .dp-collapse-summary::-webkit-details-marker { display: none; }
.dp-collapse > .dp-collapse-summary::marker { content: ''; }
.dp-collapse > .dp-collapse-summary :is(h2, h3) { margin-bottom: 0; }
.dp-collapse > .dp-collapse-summary:hover :is(h2, h3) { color: var(--accent); }
.dp-collapse > .dp-collapse-summary:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
  border-radius: 6px;
}
.dp-collapse-chevron {
  flex-shrink: 0;
  color: var(--text-tertiary);
  transition: transform 0.2s var(--ease);
}
.dp-collapse[open] > .dp-collapse-summary .dp-collapse-chevron { transform: rotate(180deg); }
.dp-collapse-body { padding-top: 14px; }

/* === Premium "Insights" group: luxury treatment ===
   The section (.dp-premium) is the ONE warm container for this chapter. The
   former .dp-insights-box added a second nested box (gold gradient + hairline)
   inside it, reading as a container-in-a-container. It's now a transparent
   pass-through, so there is exactly one container and the verdict keeps its own
   subtle card like the section's other cards. Sets up cleanly for future
   paid-tier gating — hide this section and the rest of the page is unaffected. */
.dp-insights-box {
  padding: 0;
  border: 0;
  border-radius: 0;
  background: none;
}
.dp-insights .dp-premium-body { margin-top: 22px; }
.dp-insights .dp-premium-badge {
  display: inline-block;
  padding: 4px 12px;
  border-radius: 980px;
  background: rgba(178, 138, 70, 0.12);
  color: rgb(140, 100, 40);
  font-family: var(--font-sans);
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}
.dp-insights .dp-premium-title {
  margin-top: 12px;
  font-style: italic;
}
.dp-insights .dp-premium-sub {
  font-style: italic;
  color: var(--text-secondary);
}

/* Anchor scroll fix: when a TOC chip is tapped, scrollIntoView jumps to
   the section but the sticky TOC bar covers its heading. scroll-margin-top
   tells the browser to leave room above the target. ~80px was too tight —
   on the standalone listing page the topbar (76px) PLUS the sticky TOC
   bar (~52px) both cover content at the top, so the target heading was
   landing under both. Bumped to ~140px so the headline actually lands at
   the visible top of the content area. Also extended to .dp-insights
   (alias of .dp-premium) for any older render path. */
.dp-about,
.dp-neighborhood,
.dp-financials,
.dp-premium,
.dp-insights {
  scroll-margin-top: 140px;
}
/* Inside the in-page modal the TOC bar is sticky relative to the modal
   body (no fixed topbar above it), so 80px is enough. */
.detail-modal-body .dp-about,
.detail-modal-body .dp-neighborhood,
.detail-modal-body .dp-financials,
.detail-modal-body .dp-premium,
.detail-modal-body .dp-insights {
  scroll-margin-top: 80px;
}
/* Round 22 (mobile audit LOW): on phones the topbar shrinks to
   --topbar-h: 56px and the sticky TOC bar is ~52px, so total fixed
   chrome is ~108px. The 140px desktop margin leaves ~32px of dead
   space above each anchored heading on mobile. Override to 110px
   so headlines land flush.
   Round 24 (mobile audit HIGH): 110px left only ~2px buffer below
   the TOC, so headings appeared "kissed" by the TOC's blurred
   underline. Bumped to 124px so there's a ~16px breathing band
   between the TOC strip and the H2 it lands above. */
@media (max-width: 768px) {
  .dp-about,
  .dp-neighborhood,
  .dp-financials,
  .dp-premium,
  .dp-insights {
    scroll-margin-top: 124px;
  }
}

.detail-eyebrow {
  font-size: 12px; color: var(--text-secondary);
  font-weight: 500; margin-bottom: 6px;
  letter-spacing: -0.005em;
  display: inline-flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
}
/* === Round 78: listing-status badge =========================================
   Surfaces Active / Pending / Contingent / Recently Sold / Off-Market.
   Active stays quiet (neutral pill); the others get distinct colors that
   match the real-world weight (Pending + Contingent in amber, Sold gray). */
.status-badge {
  display: inline-flex;
  align-items: center;
  height: 20px;
  padding: 0 8px;
  border-radius: 980px;
  font-size: 10.5px;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  white-space: nowrap;
}
/* Round 80: Active is now a proper green pill so the status field
   actually signals "live and available", not "we have no info". The
   other states get their own color identity so they're scannable
   side-by-side. */
.status-badge.status-active {
  background: rgba(46, 90, 56, 0.13);
  color: #1f4128;
}
.status-badge.status-pending {
  background: rgba(166, 122, 46, 0.16);
  color: #6b4e15;
}
.status-badge.status-contingent {
  background: rgba(199, 106, 58, 0.16);
  color: #7a3611;
}
.status-badge.status-sold {
  background: rgba(89, 58, 162, 0.13);
  color: #4a2e85;
}
.status-badge.status-offmarket {
  background: rgba(15, 31, 44, 0.10);
  color: var(--text-secondary);
}
/* Round 90: "Edited values" badge surfaced in the detail header when
   the listing has per-listing overrides. Distinct color (amber-warning
   tint) so the user immediately notices that what they're seeing is
   their adjusted view, not the default underwriting. Inline Reset
   button clears every override on this listing in one click. */
.edited-badge {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  height: 22px;
  padding: 0 4px 0 9px;
  background: rgba(166, 122, 46, 0.16);
  color: #5e4513;
  border-radius: 980px;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.015em;
  vertical-align: 1px;
}
.edited-badge svg { flex-shrink: 0; opacity: 0.75; }
/* Round 123: "Edited" badge as its own left-aligned row at the top of the
   card body (moved out of the eyebrow). align-self keeps it content-width in
   the flex-column body instead of stretching full width. */
.edited-badge-row { align-self: flex-start; margin: 0 0 3px; }
/* Round 123: "Cap rate" breaks to two lines ("Cap" / "rate") in the mobile
   grid card so all three metric labels fill two rows and the values align
   with no gap. Hidden everywhere else (label stays "Cap rate" on one line). */
.cap-br { display: none; }
@media (max-width: 768px) {
  body.view-grid .cap-br { display: inline; }
}
.edited-badge-reset {
  display: inline-flex;
  align-items: center;
  height: 18px;
  padding: 0 8px;
  background: rgba(255, 255, 255, 0.7);
  border: 0;
  border-radius: 980px;
  font: inherit;
  font-size: 10.5px;
  font-weight: 700;
  letter-spacing: 0.02em;
  color: #5e4513;
  cursor: pointer;
  margin-left: 2px;
  transition: background 0.15s var(--ease);
}
.edited-badge-reset:hover { background: #ffffff; }
/* Compact variant on listing cards (eyebrow row, no inline Reset). */
.edited-badge.edited-badge-card {
  height: 17px;
  padding: 0 7px;
  font-size: 9.5px;
  letter-spacing: 0.02em;
  vertical-align: 1px;
  gap: 4px;
}
.edited-badge.edited-badge-card svg { width: 9px; height: 9px; }

/* Compact variant used in listing-card eyebrows + similar-unit cards.
   Slightly smaller so the eyebrow stays compact next to the neighborhood
   and property-type labels. */
.status-badge.status-badge-card {
  height: 17px;
  padding: 0 6px;
  font-size: 9.5px;
  letter-spacing: 0.05em;
  vertical-align: 1px;
  margin-left: 4px;
}

/* === Round 80: hero photo click → lightbox ============================ */
.main-photo-clickable { cursor: zoom-in; }
.main-photo-clickable:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

.photo-lightbox {
  position: fixed;
  inset: 0;
  z-index: 200;
  background: rgba(8, 16, 24, 0.94);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 32px;
  opacity: 0;
  transition: opacity 0.2s var(--ease);
}
.photo-lightbox.is-open { opacity: 1; }
.photo-lightbox-img {
  max-width: 100%;
  max-height: 100%;
  object-fit: contain;
  border-radius: 8px;
  box-shadow: 0 24px 64px rgba(0, 0, 0, 0.48);
}
.photo-lightbox-close {
  position: absolute;
  top: 20px;
  right: 20px;
  width: 44px;
  height: 44px;
  border: none;
  border-radius: 50%;
  background: rgba(250, 247, 241, 0.10);
  color: #faf7f1;
  display: inline-grid;
  place-items: center;
  cursor: pointer;
  transition: background 0.15s var(--ease);
}
.photo-lightbox-close:hover { background: rgba(250, 247, 241, 0.22); }
.photo-lightbox-arrow {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  width: 52px;
  height: 52px;
  border: none;
  border-radius: 50%;
  background: rgba(250, 247, 241, 0.10);
  color: #faf7f1;
  display: inline-grid;
  place-items: center;
  cursor: pointer;
  transition: background 0.15s var(--ease);
}
.photo-lightbox-arrow:hover { background: rgba(250, 247, 241, 0.22); }
.photo-lightbox-prev { left: 24px; }
.photo-lightbox-next { right: 24px; }
.photo-lightbox-counter {
  position: absolute;
  bottom: 24px;
  left: 50%;
  transform: translateX(-50%);
  padding: 6px 14px;
  border-radius: 980px;
  background: rgba(250, 247, 241, 0.12);
  color: #faf7f1;
  font-size: 13px;
  font-weight: 500;
  font-variant-numeric: tabular-nums;
}
@media (max-width: 768px) {
  .photo-lightbox { padding: 16px; }
  .photo-lightbox-arrow { width: 40px; height: 40px; }
  .photo-lightbox-prev { left: 10px; }
  .photo-lightbox-next { right: 10px; }
  .photo-lightbox-close { top: 12px; right: 12px; width: 38px; height: 38px; }
}

/* === Round 78: price history table ======================================== */
.dp-price-history { padding: 18px 22px; }
.ph-table-wrap {
  margin-top: 14px;
  border: 1px solid var(--separator-soft);
  border-radius: var(--radius-md);
  overflow: hidden;
}
.ph-table {
  width: 100%;
  border-collapse: collapse;
  font-variant-numeric: tabular-nums;
}
.ph-table thead th {
  text-align: left;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--text-tertiary);
  padding: 10px 16px;
  background: var(--bg-surface);
  border-bottom: 1px solid var(--separator-soft);
}
.ph-table tbody td {
  padding: 12px 16px;
  font-size: 13.5px;
  color: var(--text);
  border-bottom: 1px solid var(--separator-soft);
}
.ph-table tbody tr:last-child td { border-bottom: 0; }
.ph-table .ph-date { color: var(--text-secondary); white-space: nowrap; width: 110px; }
.ph-table .ph-event { color: var(--text); font-weight: 500; }
.ph-table .ph-price-amount { font-weight: 600; }
.ph-table .ph-price-psf {
  display: inline-block;
  margin-left: 8px;
  font-size: 11.5px;
  color: var(--text-tertiary);
}
.detail-header h1 {
  font-family: var(--font-serif);
  font-size: 34px; font-weight: 500;
  line-height: 1.05; letter-spacing: -0.018em;
  color: var(--text);
}
.detail-header .sub {
  color: var(--text-secondary); margin-top: 10px; font-size: 14px;
}
.detail-header-links {
  display: flex; flex-wrap: wrap; gap: 14px;
  margin-top: 4px;
}
/* Co-op caveat banner — appears under the address links on co-op
   properties. Investors who don't already know the co-op-vs-condo
   distinction need to see this BEFORE they spend time on the
   underwriting math, because sublet restrictions can disqualify the
   entire rental thesis regardless of how good the numbers look. */
.property-type-caveat {
  display: flex; align-items: flex-start; gap: 8px;
  margin-top: 10px;
  padding: 8px 12px;
  background: rgba(234, 88, 12, 0.06);
  border-left: 3px solid #d97706;
  border-radius: 0 4px 4px 0;
  font-size: 12.5px;
  color: var(--text-secondary, var(--text));
  line-height: 1.45;
  max-width: 62ch;
}
.property-type-caveat svg { flex: 0 0 auto; margin-top: 1px; color: #c2410c; }
.property-type-caveat strong { color: #c2410c; font-weight: 600; }

/* === Property type badge (saved tray + compare table) ===
   Small inline pill so investors see Condo / Co-op / Multi-Family at
   a glance. Co-op gets an amber accent — same color story as the
   detail-page caveat banner so the visual signal carries across the
   app: this is the one property type with materially different
   investability rules. */
.property-type-badge {
  display: inline-block;
  margin-left: 6px;
  padding: 1px 7px;
  font-size: 10.5px;
  font-weight: 500;
  letter-spacing: 0.02em;
  background: rgba(0, 0, 0, 0.06);
  color: var(--text-secondary, var(--text-tertiary));
  border-radius: 980px;
  vertical-align: 1px;
}
.property-type-badge-coop {
  background: rgba(217, 119, 6, 0.14);
  color: #92400e;
  font-weight: 600;
}
/* === Personal notes editor (sits under the address header) ===
   Two states: a minimal "+ Add a personal note" trigger when empty,
   or an expanded textarea when content exists or the user clicks
   open. Caveat line below sets expectations and is the spot for a
   future "Sign in to sync" CTA. */
.dp-notes { margin-top: 14px; }
.dp-notes-trigger {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 6px 12px;
  border: 1px dashed var(--separator);
  background: transparent;
  border-radius: 980px;
  font-size: 13px;
  color: var(--text-tertiary);
  cursor: pointer;
  transition: border-color 0.15s var(--ease), color 0.15s var(--ease);
}
.dp-notes-trigger:hover {
  border-color: var(--accent);
  color: var(--accent);
}
.dp-notes-editor {
  display: flex; flex-direction: column;
  gap: 6px;
}
.dp-notes-label {
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text-tertiary);
}
.dp-notes-input {
  width: 100%;
  padding: 10px 12px;
  border: 1px solid var(--separator);
  border-radius: var(--radius-md);
  background: var(--bg);
  font-family: inherit;
  font-size: 14px;
  line-height: 1.45;
  color: var(--text);
  resize: vertical;
  min-height: 64px;
  transition: border-color 0.15s var(--ease);
}
.dp-notes-input:focus {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 3px rgba(47, 90, 63, 0.08);
}
.dp-notes-input::placeholder { color: var(--text-quaternary, var(--text-tertiary)); opacity: 0.7; }
.dp-notes-foot {
  display: flex; justify-content: space-between; align-items: baseline;
  gap: 10px;
  font-size: 11px;
  color: var(--text-tertiary);
}
.dp-notes-caption { font-style: italic; }
.dp-notes-status { color: var(--positive); font-weight: 500; }
.detail-specs {
  /* Single row at desktop — 7 specs across with even spacing. `flex: 1`
     lets each cell claim equal width; nowrap keeps them on one line.
     The @media block below switches to wrap on narrower viewports. */
  display: flex; gap: 0; margin-top: 18px; padding: 12px 0;
  border-top: 1px solid var(--separator-soft);
  border-bottom: 1px solid var(--separator-soft);
  flex-wrap: nowrap;
}
.detail-spec {
  flex: 1 1 0;
  min-width: 0;
  padding-right: 12px;
}
.detail-spec + .detail-spec {
  border-left: 1px solid var(--separator-soft);
  padding-left: 14px;
}
.detail-spec .label {
  font-size: 10.5px; color: var(--text-tertiary);
  font-weight: 500;
  letter-spacing: 0.015em;
}
.detail-spec .value {
  /* Smaller so 7 specs fit one row without ellipsis. Was 17px which
     truncated "1,100" + "$ / sq ft" + "HOA / mo" on common viewports. */
  font-size: 15px; color: var(--text);
  margin-top: 2px; font-weight: 500;
  letter-spacing: -0.01em;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.detail-section { margin-top: 22px; }
.detail-section h2 {
  font-family: var(--font-serif);
  font-size: 26px; font-weight: 500;
  letter-spacing: -0.012em;
  line-height: 1.15;
  margin: 0 0 8px 0;
  color: var(--text);
}
.detail-description {
  color: var(--text-secondary); font-size: 15px; line-height: 1.55;
  max-width: 62ch;
  letter-spacing: -0.005em;
  /* sanitizeDescription() now collapses all whitespace (including
     hard-coded Zillow newlines) into single spaces, so the prose
     reads as one continuous paragraph. No pre-line treatment needed. */
}
/* Section descriptions that are SHORT UTILITY COPY (not prose) drop the
   62ch line-length cap so they fit naturally instead of wrapping
   mid-sentence when there's room to spare. The cap is right for the
   "About this residence" prose paragraph; wrong for one-sentence
   explanatory copy in Premium Insights / P&L sections. */
.dp-year1 .detail-description,
.dp-tenyr .detail-description,
.dp-premium .detail-description {
  max-width: none;
}

/* Financial summary card */
.fin-card {
  background: var(--bg-surface);
  border: 1px solid var(--separator);
  border-radius: var(--radius-xl); padding: 24px;
  align-self: start;
}
/* Summary card lives in the right column beside About (a rail, ~440px), so
   its content stacks vertically. */
/* Financing + risk-profile controls (under the recommended offer) */
.fin-controls {
  display: grid;
  grid-template-columns: 1fr;
  gap: 10px;
  margin-top: 20px;
}
.fin-control-row {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  padding: 12px 14px;
  border: 1px solid var(--separator);
  border-radius: var(--radius-md);
  background: var(--bg);
}
.fin-control-info { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
.fin-control-label { font-size: 10.5px; font-weight: 600; letter-spacing: 0.08em; text-transform: uppercase; color: var(--text-tertiary); }
.fin-control-value { font-size: 15px; font-weight: 600; color: var(--text); }
.fin-control-sub { font-size: 11.5px; color: var(--text-secondary); }
.fin-control-cta {
  flex-shrink: 0;
  border: 1px solid var(--accent); background: transparent;
  color: var(--accent); border-radius: 980px;
  padding: 7px 15px; font: inherit; font-size: 12.5px; font-weight: 600;
  cursor: pointer; transition: background 0.15s var(--ease), color 0.15s var(--ease);
}
.fin-control-cta:hover { background: var(--accent); color: #fff; }
/* Collapsible "Financing & return details" (metric grid + 10-yr kvs) */
.fin-details { margin-top: 12px; border-top: 1px solid var(--separator-soft); }
.fin-details > .dp-collapse-summary { padding: 12px 0 0; }
.fin-details-title { font-size: 13px; font-weight: 600; color: var(--text-secondary); letter-spacing: -0.005em; }
.fin-details .dp-collapse-body { padding-top: 14px; }
@media (max-width: 839px) {
  .fin-controls { grid-template-columns: 1fr; }
}
/* === Photo mosaic (Zillow-style: 1 large + up to 4) ================== */
.detail-mosaic {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 8px;
  position: relative;
  border-radius: var(--radius-lg);
  overflow: hidden;
  aspect-ratio: 2 / 1;
  margin-bottom: 22px;
  background: var(--bg-surface);
}
.mosaic-side {
  display: grid;
  grid-template-columns: 1fr 1fr;
  grid-template-rows: 1fr 1fr;
  gap: 8px;
  min-height: 0;
}
.mosaic-cell {
  border: 0; padding: 0; margin: 0; cursor: pointer;
  background-color: var(--bg-surface);
  background-size: cover; background-position: center; background-repeat: no-repeat;
  position: relative; min-height: 0; min-width: 0;
  transition: opacity 0.15s var(--ease);
}
.mosaic-cell:hover { opacity: 0.92; }
.mosaic-cell:focus-visible { outline: 3px solid var(--accent); outline-offset: -3px; }
.mosaic-label {
  position: absolute; bottom: 8px; left: 8px;
  background: rgba(15, 31, 44, 0.66); color: #fff;
  font-size: 11px; font-weight: 600; padding: 3px 8px; border-radius: 6px;
}
.mosaic-seeall {
  position: absolute; bottom: 16px; right: 16px;
  display: inline-flex; align-items: center; gap: 8px;
  background: #fff; color: var(--text);
  border: 1px solid var(--separator); border-radius: 10px;
  padding: 9px 15px; font: inherit; font-size: 13px; font-weight: 600;
  cursor: pointer; box-shadow: 0 2px 10px rgba(15, 31, 44, 0.18);
  transition: background 0.15s var(--ease), transform 0.15s var(--ease);
}
.mosaic-seeall:hover { background: var(--bg); transform: translateY(-1px); }
/* Save / Share / One-pager / Add note overlaid on the mosaic's top-right. */
.detail-mosaic .detail-actions-row {
  position: absolute; top: 14px; right: 16px; z-index: 3;
  margin: 0; gap: 8px;
}
.detail-mosaic .detail-actions-row .detail-save-btn,
.detail-mosaic .detail-actions-row .detail-share-btn {
  background: #fff;
  box-shadow: 0 2px 8px rgba(15, 31, 44, 0.18);
}
@media (max-width: 700px) {
  .detail-mosaic .detail-actions-row { top: 10px; right: 10px; gap: 6px; }
  .detail-mosaic .detail-actions-row .detail-save-btn span,
  .detail-mosaic .detail-actions-row .detail-share-btn span { display: none; }
}
/* Single photo: main spans full width, no side grid */
.detail-mosaic-1 { grid-template-columns: 1fr; }
@media (max-width: 700px) {
  .detail-mosaic { grid-template-columns: 1fr; aspect-ratio: 3 / 2; }
  .detail-mosaic .mosaic-side { display: none; }
}
/* Financing chip — sits just below the price block on the fin-card.
   Shows the active scenario (Conventional / DSCR / FHA / etc.) +
   sub-detail, with a Change CTA that opens the assumptions panel. */
.fin-chip {
  display: grid;
  grid-template-columns: 1fr auto;
  align-items: center;
  gap: 4px 12px;
  width: 100%;
  padding: 10px 14px;
  margin-top: 14px;
  background: var(--bg);
  border: 1px solid var(--separator);
  border-radius: var(--radius-md);
  text-align: left;
  cursor: pointer;
  transition: border-color 0.15s var(--ease), background 0.15s var(--ease);
}
.fin-chip:hover { border-color: var(--accent); background: var(--bg-elevated, var(--bg)); }
/* Modified-from-preset signal — same dashed-ring cue used in the
   filter-bar Financing popover and the assumptions panel so all three
   chip surfaces tell the same story. */
.fin-chip-modified { border-color: var(--accent); border-style: dashed; }
.fin-chip-modified .fin-chip-name { color: var(--accent); }
.fin-chip-label {
  grid-column: 1;
  font-size: 10.5px;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-tertiary);
}
.fin-chip-name {
  grid-column: 1;
  font-size: 15px;
  font-weight: 600;
  color: var(--text);
  letter-spacing: -0.005em;
}
.fin-chip-sub {
  grid-column: 1;
  font-size: 12px;
  color: var(--text-secondary, var(--text-tertiary));
}
.fin-chip-cta {
  grid-column: 2;
  grid-row: 1 / span 3;
  align-self: center;
  font-size: 12px;
  font-weight: 600;
  color: var(--accent);
}
.fin-card .price-block { position: relative; }
.fin-card .price {
  font-family: var(--font-serif);
  font-size: 30px; font-weight: 500;
  line-height: 1;
  letter-spacing: -0.015em;
  font-variant-numeric: tabular-nums;
  color: var(--text);
  display: inline-flex;
  align-items: center;
  gap: 10px;
}
.fin-card .price-amount { display: inline-block; }
.fin-card .price-edit-icon {
  color: var(--text-tertiary);
  flex-shrink: 0;
  transition: color 0.15s ease, transform 0.15s ease;
}
.fin-card .ed-cell-price {
  cursor: pointer;
  position: relative;
  border-radius: 6px;
  padding: 2px 6px;
  margin-left: -6px;
  transition: background 0.15s ease;
}
.fin-card .ed-cell-price:hover {
  background: rgba(47, 90, 63, 0.08);
}
.fin-card .ed-cell-price:hover .price-edit-icon {
  color: var(--accent);
}
.fin-card .ed-cell-price:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}
.fin-card .price-cta {
  margin-top: 8px;
  font-size: 12px;
  color: var(--text-tertiary);
  line-height: 1.4;
}
.fin-card .price-cta-reset {
  color: var(--accent);
  font-weight: 500;
  background: none;
  border: 0;
  padding: 0;
  cursor: pointer;
  font-size: inherit;
  line-height: inherit;
}
.fin-card .price-cta-reset:hover { text-decoration: underline; }

/* === Rent block — mirrors the price treatment one tier smaller. Rent is
   the other primary driver of cash flow, so it earns prominent placement. */
.fin-card .rent-block {
  margin-top: 18px;
  padding-top: 18px;
  border-top: 1px solid var(--separator-soft);
}
.fin-card .rent-label {
  font-size: 11px;
  font-weight: 600;
  color: var(--text-tertiary);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  margin-bottom: 4px;
}
.fin-card .rent-value {
  font-size: 28px;
  font-weight: 600;
  line-height: 1;
  letter-spacing: -0.02em;
  font-variant-numeric: tabular-nums;
  color: var(--text);
  display: inline-flex;
  align-items: baseline;
  gap: 4px;
}
.fin-card .ed-cell-rent {
  cursor: pointer;
  border-radius: 6px;
  padding: 2px 6px;
  margin-left: -6px;
  transition: background 0.15s ease;
}
.fin-card .ed-cell-rent:hover {
  background: rgba(47, 90, 63, 0.08);
}
.fin-card .ed-cell-rent:hover .rent-edit-icon {
  color: var(--accent);
}
.fin-card .ed-cell-rent:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}
.fin-card .rent-suffix {
  font-size: 14px;
  font-weight: 500;
  color: var(--text-tertiary);
  letter-spacing: 0;
}
.fin-card .rent-edit-icon {
  color: var(--text-tertiary);
  flex-shrink: 0;
  align-self: center;
  margin-left: 4px;
  transition: color 0.15s ease;
}
.fin-card .rent-range {
  font-size: 13px;
  color: var(--text-secondary);
  margin-top: 6px;
  font-variant-numeric: tabular-nums;
}
.fin-card .rent-range .rent-band {
  font-weight: 500;
  color: var(--text);
}
.fin-card .rent-cta {
  margin-top: 6px;
  font-size: 12px;
  color: var(--text-tertiary);
  line-height: 1.4;
}
.fin-card .price-sub {
  color: var(--text-tertiary); font-size: 13px;
  margin-top: 10px;
}
.fin-card-cta {
  margin-top: 20px;
  background: var(--bg);
  border-radius: var(--radius-md); padding: 18px 16px;
  display: flex; justify-content: space-between; align-items: center;
  gap: 16px;
}
.fin-card-cta .label {
  font-size: 12px; color: var(--text-tertiary);
  font-weight: 500;
}
.fin-card-cta .cf {
  /* Smaller than the price serif so the price stays the dominant focal
     point; the CF is supporting detail, not the headline. */
  font-size: 26px; font-weight: 600;
  margin-top: 4px;
  line-height: 1.05;
  letter-spacing: -0.025em;
  font-variant-numeric: tabular-nums;
  /* Don't let "-$1,234/mo" wrap mid-token — keeps the cash flow
     value as a single visual unit on narrow viewports. */
  white-space: nowrap;
}
.fin-card-cta .cf-sub {
  font-size: 12px; color: var(--text-tertiary);
  text-align: right; line-height: 1.55;
  font-variant-numeric: tabular-nums;
  display: flex; flex-direction: column; gap: 2px;
}
/* Each metric line keeps its number + abbr + help-hint icon on one row,
   aligned right. The abbr is underlined-dotted so users see a visual
   affordance that the term is explainable before they reach for the
   small `?` icon to the right of it. */
.fin-card-cta .cf-sub-row {
  display: flex; align-items: center; justify-content: flex-end;
  gap: 2px;
}
.cf-sub-term {
  border-bottom: 1px dotted currentColor;
  text-decoration: none;
  cursor: help;
  font-style: normal;
}

.fin-warn {
  margin-top: 12px;
  display: flex; gap: 10px;
  padding: 12px 14px;
  border-radius: var(--radius-md);
  border: 1px solid;
  font-size: 12.5px;
  line-height: 1.45;
}
.fin-warn-severe {
  background: rgba(220, 38, 38, 0.06);
  border-color: rgba(220, 38, 38, 0.22);
  color: #991b1b;
}
.fin-warn-warn {
  background: rgba(234, 88, 12, 0.06);
  border-color: rgba(234, 88, 12, 0.22);
  color: #9a3412;
}
.fin-warn-icon {
  flex: 0 0 auto;
  padding-top: 1px;
}
.fin-warn-head {
  font-weight: 600;
  font-size: 13px;
  margin-bottom: 2px;
}
.fin-warn-detail strong { font-weight: 600; }

.metric-grid {
  display: grid; grid-template-columns: 1fr 1fr; gap: 10px;
  margin-top: 20px;
}
.metric {
  background: var(--bg);
  border-radius: var(--radius-md); padding: 14px 16px;
}
.metric .label {
  font-size: 11px; color: var(--text-tertiary);
  font-weight: 500;
}
.metric .value {
  font-size: 18px; margin-top: 3px; font-weight: 600;
  letter-spacing: -0.015em;
  font-variant-numeric: tabular-nums;
}
.metric .sub { font-size: 11px; color: var(--text-tertiary); margin-top: 2px; }
/* Financing & cash strip, relocated from the summary card into the full-width
   Financials section. Reuses .metric; lays them out in a responsive row. */
.dp-financing-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: 16px 28px;
}
/* "Investment assumptions" inline link in the Financials sub-copy (merged from
   the old separate callout box into one line). */
.dp-assump-link {
  background: none; border: 0; padding: 0; font: inherit; cursor: pointer;
  color: var(--accent); font-weight: 600;
  text-decoration: underline; text-decoration-color: rgba(46, 90, 56, 0.4);
  text-underline-offset: 2px;
  transition: text-decoration-color 0.15s var(--ease);
}
.dp-assump-link:hover { text-decoration-color: var(--accent); }
/* Ten-year projection card: separated from the year-one table, with an always-
   visible Year-10 stat summary; the full table sits behind a toggle. */
/* Ten-year projection: consistent with the year-one breakdown — no colored card,
   just the subsection separator, the stat summary, and the toggle. */
/* The Year-one breakdown and the 10-year projection are both self-contained
   "results panel" cards: a matched pair within Financials, and the 10-year no
   longer bleeds into the Insights container below it. The card border replaces
   each subsection hairline. */
.detail-subsection.dp-year1,
.dp-tenyr-card {
  background: var(--bg-elevated);
  border: 1px solid var(--separator);
  border-radius: 16px;
  box-shadow: 0 1px 2px rgba(15, 31, 44, 0.04), 0 12px 28px -18px rgba(20, 35, 25, 0.12);
}
/* Year-one is itself the card (no inner wrapper) so it carries the padding and
   its full border replaces the subsection hairline. The 10-year has an inner
   .dp-tenyr-card that carries the border/padding, so its section just drops the
   hairline. Both headings keep the standard 22px subsection h3 size, so the
   pair matches with no extra title rule. */
.detail-subsection.dp-year1 { padding: 22px 24px; margin-top: 28px; }
.detail-subsection.dp-tenyr { border-top: 0; padding-top: 0; margin-top: 28px; margin-bottom: 8px; }
.dp-tenyr-card { padding: 22px 24px; }
.dp-tenyr-title { margin: 0 0 16px; font-family: var(--font-serif); font-size: 18px; font-weight: 500; color: var(--text); letter-spacing: -0.01em; }
.dp-tenyr-stats { display: grid; grid-template-columns: repeat(3, 1fr); gap: 16px; }
.dp-tenyr-stat { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.dp-tenyr-stat-v { font-size: 19px; font-weight: 600; color: var(--text); letter-spacing: -0.015em; line-height: 1.1; }
.dp-tenyr-stat-k { font-size: 11.5px; color: var(--text-tertiary); }
.dp-tenyr-collapse { margin-top: 16px; border-top: 1px solid var(--separator); padding-top: 14px; }
.dp-tenyr-toggle-label { font-size: 13px; font-weight: 600; color: var(--accent); }

/* === Premium Insights wrapper ========================================
   Groups recommendation / verdict / pencils / offer / watch-outs /
   similar units into a single full-width block below the 10-yr P&L.
   The wrapper carries the premium signal (badge + serif title + framed
   container with a subtle gradient and inner padding); inner sections
   use the existing styles with tighter top margins since they read as
   one continuous tier of premium analysis. */
.dp-premium {
  margin-top: 64px;
  padding: 40px 48px 44px 48px;
  border-radius: var(--radius-xl);
  border: 1px solid var(--separator);
  background:
    linear-gradient(180deg, rgba(47, 90, 63, 0.025) 0%, rgba(47, 90, 63, 0.008) 100%),
    var(--bg-surface);
  box-shadow: 0 1px 0 rgba(0, 0, 0, 0.02), 0 12px 32px -16px rgba(20, 35, 25, 0.08);
}
.dp-premium-header {
  margin-bottom: 24px;
  padding-bottom: 22px;
  border-bottom: 1px solid var(--separator-soft);
}
.dp-premium-badge {
  display: inline-block;
  padding: 3px 10px;
  font-size: 10.5px;
  font-weight: 600;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--accent, #2E5A38);
  background: rgba(47, 90, 63, 0.08);
  border-radius: 980px;
  margin-bottom: 10px;
}
.dp-premium-title {
  font-family: var(--font-serif);
  font-weight: 500;
  font-size: 30px;
  letter-spacing: -0.018em;
  color: var(--text);
  margin: 0 0 6px 0;
  line-height: 1.1;
}
.dp-premium-sub {
  font-size: 14px;
  color: var(--text-secondary, var(--text-tertiary));
  line-height: 1.5;
  /* No cap — single-sentence subhead, lets it flow to the wrapper
     width on wide viewports instead of wrapping at 62ch. */
  margin: 0;
}
/* Caveat blocks at the top of Premium Insights — set expectations
   about which inputs the verdict rests on, BEFORE the investor reads
   the recommendation. Two flavors:
     - data:        amber, only renders when there are real source
                    gaps (HOA/tax missing or estimated)
     - assumptions: neutral, always renders; lists the four
                    investor-tunable inputs and links to Edit
                    assumptions for what-if testing */
.dp-premium-caveat {
  margin-top: 14px;
  padding: 12px 14px;
  border-radius: var(--radius-md);
  font-size: 12.5px;
  line-height: 1.5;
}
.dp-premium-caveat-data {
  background: rgba(234, 88, 12, 0.06);
  border-left: 3px solid #d97706;
  color: var(--text-secondary, var(--text));
}
.dp-premium-caveat-data strong { color: #c2410c; }
.dp-premium-caveat-assumptions {
  background: rgba(47, 90, 63, 0.04);
  border-left: 3px solid var(--accent, #2E5A38);
  color: var(--text-secondary, var(--text));
}
.dp-premium-caveat-assumptions strong { color: var(--accent, #2E5A38); }
.dp-premium-caveat-head {
  display: flex; align-items: center; gap: 8px;
  flex-wrap: wrap;
}
.dp-premium-caveat-head svg { flex: 0 0 auto; }
.dp-premium-caveat-list {
  list-style: none;
  padding: 0;
  margin: 8px 0 0 0;
  font-size: 12.5px;
}
.dp-premium-caveat-list li {
  padding: 4px 0;
  border-top: 1px solid var(--separator-soft);
}
.dp-premium-caveat-list li:first-child { border-top: none; padding-top: 4px; }
.dp-premium-caveat-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
  gap: 8px 18px;
  margin: 10px 0 8px 0;
  padding: 0;
  list-style: none;
}
.dp-premium-caveat-grid li {
  display: flex; flex-direction: column;
  gap: 1px;
}
.dp-premium-caveat-k {
  font-size: 10.5px;
  letter-spacing: 0.03em;
  color: var(--text-tertiary);
  text-transform: uppercase;
}
.dp-premium-caveat-v {
  font-size: 13px;
  font-weight: 600;
  color: var(--text);
  font-variant-numeric: tabular-nums;
}
.dp-premium-caveat-foot {
  margin: 6px 0 0 0;
  font-size: 12px;
  color: var(--text-tertiary);
  font-style: italic;
}
.dp-premium-caveat-link {
  background: none;
  border: none;
  padding: 0;
  font: inherit;
  font-style: normal;
  font-weight: 600;
  color: var(--accent);
  cursor: pointer;
  text-decoration: underline;
  text-underline-offset: 2px;
}
.dp-premium-caveat-link:hover { text-decoration-thickness: 2px; }
.dp-premium .detail-section { margin-top: 28px; }
.dp-premium .detail-section:first-child { margin-top: 0; }
.dp-premium .detail-section h2 {
  font-size: 22px;
  margin-bottom: 6px;
}

@media (max-width: 768px) {
  .dp-premium { padding: 22px 18px; margin-top: 40px; border-radius: var(--radius-lg); }
  .dp-premium-title { font-size: 24px; }
  .dp-premium-sub { font-size: 13px; }
  .dp-premium .detail-section { margin-top: 22px; }
}

/* === Recommendation card =============================================
   First block inside Premium Insights. Tier-colored vertical bar + tier
   label, then headline + reason + next-step + verify-before-offering.
   Reads as the editorial summary of all the math below. */
.dp-recommendation { margin-top: 0 !important; }
.rec-card {
  display: grid;
  grid-template-columns: 168px 1fr;
  gap: 28px;
  align-items: start;
  padding: 24px 26px;
  border-radius: var(--radius-md);
  border: 1px solid var(--separator);
  background: var(--bg);
}
.rec-card.rec-pursue            { border-left: 4px solid #0a6e30; }
.rec-card.rec-pursue-conditions { border-left: 4px solid #2E5A38; }
.rec-card.rec-wait              { border-left: 4px solid #d97706; }
.rec-card.rec-pass              { border-left: 4px solid #c91b1b; }
.rec-tier {
  display: flex; align-items: center; gap: 10px;
  padding-top: 2px;
}
.rec-tier-dot {
  width: 10px; height: 10px; border-radius: 50%;
  flex: 0 0 auto;
}
.rec-pursue            .rec-tier-dot { background: #0a6e30; }
.rec-pursue-conditions .rec-tier-dot { background: #2E5A38; }
.rec-wait              .rec-tier-dot { background: #d97706; }
.rec-pass              .rec-tier-dot { background: #c91b1b; }
.rec-tier-label {
  font-family: var(--font-serif);
  font-size: 18px;
  font-weight: 600;
  letter-spacing: -0.005em;
  color: var(--text);
}
.rec-pursue            .rec-tier-label { color: #0a6e30; }
.rec-pursue-conditions .rec-tier-label { color: #2E5A38; }
.rec-wait              .rec-tier-label { color: #92400e; }
.rec-pass              .rec-tier-label { color: #991b1b; }
.rec-body { min-width: 0; }
.rec-headline {
  font-family: var(--font-serif);
  font-size: 22px;
  font-weight: 500;
  letter-spacing: -0.012em;
  line-height: 1.2;
  color: var(--text);
  margin: 0 0 6px 0;
}
.rec-reason {
  font-size: 14px;
  color: var(--text-secondary, var(--text-tertiary));
  line-height: 1.5;
  margin: 0 0 14px 0;
}
.rec-action {
  margin-bottom: 12px;
}
.rec-action p {
  margin: 4px 0 0 0;
  font-size: 14px;
  color: var(--text);
  line-height: 1.5;
}
.rec-action-label {
  display: inline-block;
  font-size: 10.5px;
  font-weight: 600;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--text-tertiary);
}
.rec-conditions-wrap { margin-top: 12px; }
.rec-conditions {
  list-style: none;
  margin: 4px 0 0 0;
  padding: 0;
}
.rec-conditions li {
  font-size: 13px;
  color: var(--text-secondary, var(--text-tertiary));
  line-height: 1.5;
  padding-left: 16px;
  position: relative;
  margin-bottom: 4px;
}
.rec-conditions li::before {
  content: '';
  position: absolute;
  left: 3px;
  top: 8px;
  width: 4px;
  height: 4px;
  border-radius: 50%;
  background: var(--text-tertiary);
}
@media (max-width: 768px) {
  .rec-card { grid-template-columns: 1fr; gap: 14px; padding: 18px 18px; }
  .rec-headline { font-size: 19px; }
}

/* === Pencils — 2-up primary + demoted context row ===================
   Lender-qualifying (DSCR 1.25) and 8% CoC target are the two anchors
   that matter. GRM + 1% rule + DSCR=1.0 floor are demoted to a small
   "context" row underneath — kept available for reference but no longer
   competing for attention. */
.pencils-grid-2up {
  grid-template-columns: repeat(2, 1fr) !important;
}
.pencils-context {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 14px;
  margin-top: 14px;
  padding: 12px 16px;
  border-radius: var(--radius-md);
  background: rgba(0, 0, 0, 0.025);
}
.pencils-context-item {
  display: flex;
  flex-direction: column;
  gap: 1px;
  font-variant-numeric: tabular-nums;
  min-width: 0;
}
.pencils-context-k {
  font-size: 10.5px;
  font-weight: 500;
  color: var(--text-tertiary);
  letter-spacing: 0.02em;
}
.pencils-context-v {
  font-family: var(--font-serif);
  font-size: 16px;
  font-weight: 500;
  letter-spacing: -0.01em;
  color: var(--text);
}
.pencils-context-note {
  font-size: 10.5px;
  color: var(--text-tertiary);
  line-height: 1.35;
}
@media (max-width: 768px) {
  .pencils-grid-2up { grid-template-columns: 1fr !important; }
  .pencils-context { grid-template-columns: 1fr; gap: 10px; }
}

/* === Carry-broken diagnostic block ===================================
   Replaces the pencils grid when no price clears DSCR at the listed
   rent. Shows the WHY (carry as % of rent) instead of fake numbers. */
.dp-pencils-broken h2 { color: #991b1b; }
/* "Strained" variant — used when the deal doesn't pencil at list, but
   we still show the editable CoC card below so the user can explore
   target prices. Headline stays neutral (deal isn't fundamentally
   broken, just expensive). */
.dp-pencils-strained > h2 { color: var(--text); }
.broken-diag-block {
  margin: 14px 0 8px;
}
.broken-diag-heading {
  font-size: 13px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: #991b1b;
  margin: 0 0 8px;
}
.broken-diag {
  margin: 14px 0;
  background: rgba(220, 38, 38, 0.04);
  border: 1px solid rgba(220, 38, 38, 0.2);
  border-radius: var(--radius-md);
  padding: 16px 18px;
}
.broken-diag-block .broken-diag {
  margin: 0;
}
.broken-diag-row {
  display: flex; justify-content: space-between; align-items: baseline;
  padding: 6px 0;
  border-top: 1px solid rgba(220, 38, 38, 0.12);
  font-size: 13.5px;
  font-variant-numeric: tabular-nums;
}
.broken-diag-row:first-child { border-top: none; }
.broken-diag-k { color: var(--text-secondary, var(--text-tertiary)); }
.broken-diag-v { font-weight: 600; color: var(--text); }
.broken-diag-v-red { color: #991b1b; }
.broken-diag-note {
  font-size: 13px;
  color: var(--text-secondary, var(--text-tertiary));
  line-height: 1.5;
  margin: 8px 0 0 0;
}

/* === "Things to verify" — premium-tone watch-list ==================== */
/* Replaces the previous severity-card layout that read as AI-generated
   (color-coded dots, count chips, hedging copy). This is a simple
   two-column list — label on the left, factual note on the right —
   the way a buyer's-broker memo would format it. */
.watch-list {
  list-style: none;
  padding: 0;
  margin: 4px 0 0 0;
  display: flex;
  flex-direction: column;
}
.watch-row {
  display: grid;
  grid-template-columns: minmax(170px, 0.36fr) 1fr;
  gap: 24px;
  padding: 14px 0;
  border-top: 1px solid var(--separator-soft);
  align-items: baseline;
}
.watch-row:first-child { border-top: none; padding-top: 6px; }
.watch-label {
  font-size: 14px;
  font-weight: 600;
  color: var(--text);
  letter-spacing: -0.005em;
}
.watch-high .watch-label { color: #991b1b; }
.watch-med  .watch-label { color: #92400e; }
.watch-note {
  font-size: 13.5px;
  color: var(--text-secondary, var(--text-tertiary));
  line-height: 1.5;
}
.watch-note strong { color: var(--text); font-weight: 600; }
@media (max-width: 768px) {
  .watch-row { grid-template-columns: 1fr; gap: 4px; padding: 12px 0; }
}

/* === Investment Deep Dive sections =================================== */
/* Headline verdict card — tier color-coded, score in a circular meter.
   STRONG: positive accent; FAIR: neutral; WEAK: warning amber;
   PASS: negative red. The currentColor on the SVG circle inherits the
   tier color so a single class controls the whole color story. */
.dp-verdict { margin-top: 22px; }
/* When multiple severity chips are shown side-by-side in a section heading,
   they need a thin breathing gap so they don't read as one run-on token. */
.risk-count + .risk-count { margin-left: 6px; }
.verdict-card {
  display: flex; align-items: flex-start; gap: 24px;
  padding: 24px 26px;
  border-radius: var(--radius-lg);
  border: 1px solid var(--separator);
  background: var(--bg);
}
.verdict-strong { color: #0a6e30; background: linear-gradient(0deg, rgba(10, 110, 48, 0.04), rgba(10, 110, 48, 0.04)), var(--bg); border-color: rgba(10, 110, 48, 0.18); }
/* Round 22 (color audit): bumped verdict-fair color from #5a4a1d → #7a5a14
   and tint from 0.04 → 0.07 so the FAIR tier visibly differentiates from
   STRONG (green) and PASS (red). At the previous color + tint, the FAIR
   verdict washed out on cream and read as ambient body text — losing the
   single most important pixel on a property page. */
.verdict-fair   { color: #7a5a14; background: linear-gradient(0deg, rgba(146, 109, 21, 0.07), rgba(146, 109, 21, 0.07)), var(--bg); border-color: rgba(146, 109, 21, 0.24); }
.verdict-weak   { color: #9a3412; background: linear-gradient(0deg, rgba(234, 88, 12, 0.04), rgba(234, 88, 12, 0.04)), var(--bg); border-color: rgba(234, 88, 12, 0.2); }
.verdict-pass   { color: #991b1b; background: linear-gradient(0deg, rgba(220, 38, 38, 0.04), rgba(220, 38, 38, 0.04)), var(--bg); border-color: rgba(220, 38, 38, 0.22); }
.verdict-badge {
  display: flex; flex-direction: column; align-items: center;
  gap: 6px;
  flex: 0 0 auto;
}
.verdict-tier {
  font-family: var(--font-serif);
  font-weight: 600;
  font-size: 13px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}
.verdict-score-ring svg { display: block; }
.verdict-body { flex: 1 1 auto; min-width: 0; }
.verdict-title {
  font-family: var(--font-serif);
  font-size: 18px;
  font-weight: 500;
  margin: 0 0 4px 0;
  color: var(--text);
  letter-spacing: -0.005em;
}
.verdict-narrative {
  font-size: 16px;
  font-weight: 500;
  color: var(--text);
  line-height: 1.4;
  margin-bottom: 10px;
}
.verdict-conditional {
  font-size: 12.5px;
  color: var(--text-tertiary);
  line-height: 1.45;
  margin-bottom: 10px;
  font-style: italic;
}
/* Round 107: HOA-missing callout inside the verdict card. Amber/warning
   tone, with an inline button that jumps to the editable HOA row. */
.verdict-hoa-flag {
  display: flex;
  align-items: flex-start;
  gap: 8px;
  font-size: 12.5px;
  line-height: 1.45;
  color: #6b4e15;
  background: #FBF3DF;
  border: 1px solid rgba(138, 100, 32, 0.3);
  border-radius: 10px;
  padding: 10px 12px;
  margin-top: 12px;
}
.verdict-hoa-flag svg { flex-shrink: 0; margin-top: 1px; color: #8A6420; }
.verdict-hoa-edit {
  background: transparent; border: 0; padding: 0;
  font: inherit; font-weight: 600;
  color: #8A6420; cursor: pointer;
  text-decoration: underline;
  text-decoration-color: rgba(138, 100, 32, 0.4);
  text-underline-offset: 2px;
}
.verdict-hoa-edit:hover { text-decoration-color: #8A6420; }
/* Merged verdict + recommendation card: the Smart Picks label sits under the
   score ring; the recommendation (headline, reason, next step) is a sub-block
   below the thesis, separated by a hairline. */
.verdict-pick {
  font-family: var(--font-serif);
  font-size: 17px;
  font-weight: 600;
  letter-spacing: -0.005em;
  color: currentColor;
  text-align: center;
}
/* Groups the tier label ("Shortlist") and the confidence pill into one block.
   Desktop: the badge is a column, so this stacks under the score ring exactly
   as before. Mobile: the badge is a row, so this keeps the label + pill paired
   and vertically centered beside the ring instead of strung out in a line. */
.verdict-badge-text {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
  min-width: 0;
}
.verdict-badge .verdict-confidence { margin-top: 0; }
.verdict-rec {
  margin-top: 18px;
  padding-top: 16px;
  border-top: 1px solid var(--separator-soft);
}
.verdict-rec .rec-headline { margin-top: 0; }
/* With the thesis removed, the recommendation often leads the card body — drop
   the hairline/spacing when it's the first element so it doesn't float at top. */
.verdict-body > .verdict-rec:first-child { margin-top: 0; padding-top: 0; border-top: 0; }
/* Round 110: primary "add the HOA fee" CTA on the recommendation card when
   a missing HOA has held the deal back from Pursue. Forest-green so it reads
   as the clear next action against the amber caution card. */
.rec-hoa-edit {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  margin-top: 12px;
  padding: 9px 16px;
  font: inherit;
  font-size: 13px;
  font-weight: 600;
  color: #fff;
  background: #2E5A38;
  border: 0;
  border-radius: 9px;
  cursor: pointer;
  transition: background 0.15s ease;
}
.rec-hoa-edit:hover { background: #24472c; }
.rec-hoa-edit:focus-visible { outline: 2px solid #2E5A38; outline-offset: 2px; }
.verdict-meta {
  font-size: 12px;
  color: var(--text-tertiary);
  display: flex; flex-wrap: wrap; gap: 6px; align-items: center;
}
.verdict-confidence {
  display: inline-block;
  padding: 2px 8px;
  font-size: 11px;
  font-weight: 500;
  letter-spacing: 0.02em;
  border-radius: 980px;
  /* Keep "Medium confidence" on one line — wrapping made the pill grow into a
     lumpy two-line shape next to the score ring on mobile. */
  white-space: nowrap;
}
.verdict-confidence-high   { background: rgba(10, 110, 48, 0.12);  color: #0a6e30; }
.verdict-confidence-medium { background: rgba(146, 109, 21, 0.14); color: #6b5118; }
.verdict-confidence-low    { background: rgba(220, 38, 38, 0.10);  color: #991b1b; }
.verdict-sep { color: var(--separator); }

/* Where the deal pencils — 2×2 (4-up at wide) of price targets. */
.pencils-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: 14px;
  margin-top: 4px;
}
.pencils-card {
  padding: 18px 18px;
  border-radius: var(--radius-md);
  background: var(--bg);
  border: 1px solid var(--separator);
}
.pencils-card-primary {
  border-color: var(--accent);
  background: linear-gradient(0deg, rgba(47, 90, 63, 0.03), rgba(47, 90, 63, 0.03)), var(--bg);
}
/* Round 73: when the offer-strategy section sits above the pencils
   reference (the new section order), the pencils cards no longer
   need to read as the primary CTA — the user has already seen the
   suggested opening / walk-away in the offer section. Tone down
   the primary cards to a quieter reference style: same content,
   smaller value, neutral background, no accent border. */
.pencils-card.pencils-card-primary {
  padding: 12px 14px;
  background: var(--bg-surface);
  border-color: var(--separator-soft);
}
.pencils-card.pencils-card-primary .pencils-card-value {
  font-size: 20px;
  margin-bottom: 4px;
}
.pencils-card.pencils-card-primary .pencils-card-label {
  font-size: 11.5px;
}
.pencils-card.pencils-card-primary .pencils-card-sub {
  font-size: 11.5px;
}
.pencils-card.pencils-card-primary .pencils-use-as-offer {
  margin-top: 8px;
  font-size: 11.5px;
}
.pencils-card-label {
  font-size: 12px;
  color: var(--text-tertiary);
  font-weight: 500;
  margin-bottom: 4px;
  display: flex; flex-wrap: wrap; align-items: baseline; gap: 6px;
}
.pencils-card-meta {
  font-size: 10.5px;
  font-weight: 400;
  color: var(--text-quaternary, var(--text-tertiary));
  opacity: 0.75;
}
/* Editable cash-on-cash target — investors type a different return goal
   (5%, 6%, 10%) and every downstream price + the recommendation tier
   recompute against the new target. */
.pencils-card-target-wrap {
  display: inline-flex; align-items: center;
  gap: 2px;
  font-size: 12px;
  font-weight: 600;
  color: var(--text);
}
.pencils-target-step {
  /* Bumped from 22×26 to 30×30 — clear 36-ish tap target without
     dominating the inline stack. Strong accent color so the user
     understands these are interactive. */
  width: 30px; height: 30px;
  background: var(--bg-surface);
  border: 1px solid var(--separator);
  border-radius: 6px;
  font-size: 18px;
  font-weight: 600;
  line-height: 1;
  color: var(--accent);
  cursor: pointer;
  user-select: none;
  display: inline-grid;
  place-items: center;
  transition: background 0.12s var(--ease), color 0.12s var(--ease), border-color 0.12s var(--ease), transform 0.08s var(--ease);
}
.pencils-target-step:hover {
  background: var(--accent-bg);
  border-color: var(--accent);
}
.pencils-target-step:active {
  transform: scale(0.94);
}
.pencils-target-pct {
  color: var(--text);
  font-weight: 600;
  margin-left: 2px;
}
/* (Removed: .pencils-target-negate ± button — replaced by the +/-
   stepper which already covers negative-value entry.) */
.pencils-card-target-input {
  width: 42px;
  padding: 2px 4px;
  font-size: 12px;
  font-weight: 600;
  font-family: inherit;
  font-variant-numeric: tabular-nums;
  background: var(--bg-surface);
  border: 1px solid var(--separator);
  border-radius: 4px;
  color: var(--text);
  text-align: right;
  -moz-appearance: textfield;
}
.pencils-card-target-input::-webkit-outer-spin-button,
.pencils-card-target-input::-webkit-inner-spin-button {
  -webkit-appearance: none; margin: 0;
}
.pencils-card-target-input:focus {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
  border-color: var(--accent);
}
.pencils-card-value {
  font-family: var(--font-serif);
  font-size: 26px;
  font-weight: 500;
  letter-spacing: -0.015em;
  font-variant-numeric: tabular-nums;
  color: var(--text);
  line-height: 1.05;
  margin-bottom: 6px;
}
.pencils-card-sub {
  font-size: 12px;
  color: var(--text-tertiary);
  line-height: 1.35;
}
/* Reference line above the pencils cards. Shows the current list
   price so the user has a constant anchor as they nudge the target
   CoC % and watch the calculated offer move. */
.pencils-reference {
  display: flex;
  align-items: baseline;
  gap: 10px;
  margin: 10px 0 14px;
  padding: 10px 14px;
  background: var(--bg-surface);
  border-radius: var(--radius-sm);
  border-left: 3px solid var(--separator);
  font-size: 13px;
  flex-wrap: wrap;
}
.pencils-reference-k {
  font-weight: 600;
  color: var(--text-secondary);
  letter-spacing: -0.005em;
}
.pencils-reference-v {
  font-family: var(--font-serif);
  font-weight: 500;
  font-size: 17px;
  color: var(--text);
  letter-spacing: -0.012em;
}
.pencils-reference-note {
  color: var(--text-tertiary);
  font-size: 12.5px;
  line-height: 1.4;
}

/* List / Your-offer compact card at the top of Financials. Inline-
   flex container that hugs its content (not stretched to section
   width). List on the left, hairline divider, your editable offer
   on the right, small delta + Reset link beneath the offer. */
.dp-financials-priceref {
  display: inline-flex;
  align-items: stretch;
  gap: 22px;
  margin: 8px 0 22px;
  padding: 14px 22px;
  background: var(--bg-surface);
  border-radius: var(--radius-md);
}
.dp-financials-priceref-cell {
  display: flex;
  flex-direction: column;
  gap: 4px;
  min-width: 0;
}
.dp-financials-priceref-divider {
  width: 1px;
  background: var(--separator);
  align-self: stretch;
}
.dp-financials-priceref-label {
  font-size: 10.5px;
  color: var(--text-tertiary);
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}
.dp-financials-priceref-value {
  font-family: var(--font-serif);
  font-weight: 500;
  font-size: 22px;
  color: var(--text);
  letter-spacing: -0.012em;
  font-variant-numeric: tabular-nums;
  line-height: 1.1;
}
.dp-financials-priceref-offer .dp-financials-priceref-value {
  color: var(--accent);
}
.dp-financials-priceref-value.ed-cell-price {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  cursor: pointer;
  padding: 2px 6px;
  margin: 0 -6px;
  border-radius: 6px;
  transition: background 0.15s var(--ease);
}
.dp-financials-priceref-value.ed-cell-price:hover {
  background: var(--accent-bg);
}
.dp-financials-priceref-value .price-edit-icon {
  color: var(--text-tertiary);
  flex-shrink: 0;
  opacity: 0.6;
}
.dp-financials-priceref-delta {
  font-size: 11px;
  color: var(--text-tertiary);
  letter-spacing: -0.005em;
  margin-top: 2px;
  display: inline-flex;
  align-items: baseline;
  gap: 6px;
  flex-wrap: wrap;
}
.dp-financials-priceref-reset {
  background: transparent;
  border: 0;
  padding: 0;
  font-size: 11px;
  font-weight: 600;
  color: var(--accent);
  cursor: pointer;
  text-decoration: underline;
  text-underline-offset: 2px;
}
.dp-financials-priceref-reset:hover { color: var(--accent-hover); }

@media (max-width: 640px) {
  .dp-financials-priceref { display: flex; width: 100%; padding: 14px 16px; gap: 14px; }
  .dp-financials-priceref-cell { flex: 1 1 0; }
  .dp-financials-priceref-value { font-size: 19px; }
}
/* "Use as my offer" CTA — sets the listing's price override to the
   calculated CoC target so the fin-card / year-1 / 10-yr all recompute
   at that price. Visually distinct from .pencils-card-sub (which is
   informational copy) to read as a clear action. */
.pencils-use-as-offer {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  margin-top: 10px;
  padding: 6px 12px;
  background: transparent;
  border: 1px solid var(--accent);
  border-radius: 980px;
  color: var(--accent);
  font-size: 12px;
  font-weight: 600;
  letter-spacing: -0.005em;
  cursor: pointer;
  transition: background 0.15s var(--ease), color 0.15s var(--ease);
}
.pencils-use-as-offer:hover {
  background: var(--accent);
  color: #fff;
}

/* Active-offer banner — shown above the offer-grid when the user
   has set a price override via "Use as my offer" or the fin-card
   price edit. Tells them where their current offer sits relative
   to the walk-away ceiling and list price. ok-variant = offer is
   inside ceiling (green); warn-variant = offer exceeds ceiling
   (amber). */
.offer-active-banner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 14px;
  margin: 12px 0 18px;
  padding: 12px 16px;
  border-radius: var(--radius-md);
  font-size: 13.5px;
  line-height: 1.45;
  flex-wrap: wrap;
}
.offer-active-banner-ok {
  background: var(--positive-bg);
  color: #0a4a2a;
  border-left: 3px solid var(--positive);
}
.offer-active-banner-warn {
  background: rgba(217, 119, 6, 0.08);
  color: #7c3a0b;
  border-left: 3px solid #d97706;
}
.offer-active-banner-text { flex: 1 1 auto; min-width: 0; }
.offer-active-banner-text strong { font-weight: 600; }
.offer-active-banner-reset {
  flex-shrink: 0;
  padding: 5px 12px;
  background: var(--bg);
  border: 1px solid currentColor;
  border-radius: 980px;
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  color: inherit;
  cursor: pointer;
  transition: background 0.12s var(--ease);
}
.offer-active-banner-reset:hover { background: var(--bg-surface); }

/* Round 50: Skip banner — shown at the top of Suggested offer
   strategy when the natural walk-away is more than 20% below list
   (Deelva caps the recommendation at the 20% floor and flags
   the deal as a skip). Distinct red-tinted block so the user
   doesn't miss the verdict. */
.offer-skip-banner {
  margin: 4px 0 16px;
  padding: 14px 16px;
  background: rgba(141, 36, 36, 0.06);
  border: 1px solid rgba(141, 36, 36, 0.22);
  border-radius: 10px;
  font-size: 13.5px;
  line-height: 1.5;
  color: var(--text);
}
.offer-skip-banner-head {
  font-size: 14px;
  margin-bottom: 4px;
  color: #6e1c1c;
}
/* Round 68: "OK" variant — used when the deal already pencils at
   list (verdict = Pursue/Hold). Forest-tinted instead of red so the
   banner reads as "good news" rather than a warning. */
.offer-skip-banner.offer-skip-banner-ok {
  background: rgba(46, 90, 56, 0.06);
  border-color: rgba(46, 90, 56, 0.22);
}
.offer-skip-banner.offer-skip-banner-ok .offer-skip-banner-head {
  color: #2E5A38;
}
.offer-skip-banner-head strong {
  font-weight: 700;
  margin-right: 4px;
}
.offer-skip-banner-body {
  color: var(--text-secondary);
  font-size: 12.5px;
}

/* Suggested offer strategy — open → ceiling, with the list price as
   reference. Arrow visually conveys the negotiation range. */
.offer-grid {
  display: flex;
  align-items: stretch;
  gap: 12px;
  flex-wrap: wrap;
  margin-top: 8px;
}
.offer-block {
  flex: 1 1 200px;
  padding: 16px 18px;
  border-radius: var(--radius-md);
  background: var(--bg);
  border: 1px solid var(--separator);
  min-width: 0;
}
.offer-block-arrow {
  flex: 0 0 24px;
  background: transparent;
  border: none;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 20px;
  color: var(--text-tertiary);
  padding: 0;
}
.offer-block-list {
  background: rgba(0, 0, 0, 0.015);
}
.offer-label {
  font-size: 12px;
  color: var(--text-tertiary);
  font-weight: 500;
  margin-bottom: 6px;
  letter-spacing: 0.005em;
}
.offer-value {
  font-family: var(--font-serif);
  font-size: 24px;
  font-weight: 500;
  letter-spacing: -0.015em;
  font-variant-numeric: tabular-nums;
  color: var(--text);
  line-height: 1.05;
  margin-bottom: 6px;
}
.offer-value-open { color: var(--accent, #2E5A38); }
.offer-value-ceiling { color: #0a6e30; }
.offer-sub {
  font-size: 12px;
  color: var(--text-tertiary);
  line-height: 1.35;
}
/* N5: monthly cash flow at each offer price point. Sits between the price and
   the sub-note; color is set inline (positive/negative). */
.offer-cf {
  font-size: 14px;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.005em;
  margin: 2px 0 6px;
}
.offer-cf-sub { color: var(--text-tertiary); font-weight: 400; font-size: 12px; }
.offer-cf-note {
  font-size: 12px;
  color: var(--text-tertiary);
  margin: 12px 0 0;
  text-align: center;
}
.offer-cta-hint {
  margin-top: 14px;
  font-size: 12px;
  color: var(--text-tertiary);
  font-style: italic;
}

/* Risk flags — color-coded list, severity in a left dot + border.
   high = red, med = amber, low = neutral. */
.risk-count {
  display: inline-block;
  margin-left: 8px;
  padding: 2px 9px;
  font-family: var(--font-sans, inherit);
  font-size: 12px;
  font-weight: 500;
  letter-spacing: 0;
  background: rgba(0, 0, 0, 0.06);
  border-radius: 980px;
  color: var(--text-tertiary);
  vertical-align: 3px;
}
.risk-count-high {
  background: rgba(220, 38, 38, 0.12);
  color: #991b1b;
}
.risk-count-med {
  background: rgba(217, 119, 6, 0.12);
  color: #92400e;
}
.risk-count-low {
  background: rgba(148, 163, 184, 0.18);
  color: #475569;
}
/* The walk-away ceiling label gets a small badge showing the % gap from
   list so investors see the negotiation magnitude without doing math. */
.offer-ceiling-pct {
  display: inline-block;
  margin-left: 6px;
  padding: 1px 7px;
  font-size: 10.5px;
  font-weight: 500;
  letter-spacing: 0.02em;
  background: rgba(0, 0, 0, 0.06);
  color: var(--text-tertiary);
  border-radius: 980px;
  vertical-align: 1px;
}
.risk-list {
  list-style: none;
  padding: 0;
  margin: 8px 0 0 0;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
  gap: 10px;
}
.risk-card {
  padding: 12px 14px;
  border-radius: var(--radius-md);
  border: 1px solid var(--separator);
  border-left-width: 3px;
  background: var(--bg);
}
.risk-card.risk-high { border-left-color: #c91b1b; background: rgba(220, 38, 38, 0.025); }
.risk-card.risk-med  { border-left-color: #d97706; background: rgba(217, 119, 6, 0.025); }
.risk-card.risk-low  { border-left-color: #94a3b8; }
.risk-card-head {
  display: flex; align-items: center; gap: 8px;
  margin-bottom: 4px;
}
.risk-dot {
  width: 8px; height: 8px; border-radius: 50%;
  flex: 0 0 auto;
}
.risk-high .risk-dot { background: #c91b1b; }
.risk-med  .risk-dot { background: #d97706; }
.risk-low  .risk-dot { background: #94a3b8; }
.risk-label {
  font-size: 13.5px;
  font-weight: 600;
  color: var(--text);
}
.risk-detail {
  font-size: 12.5px;
  color: var(--text-secondary, var(--text-tertiary));
  line-height: 1.45;
}
.risk-empty {
  font-size: 13px;
  color: var(--text-tertiary);
  font-style: italic;
  margin-top: 4px;
}

/* Comparable properties — card grid; click-through to detail page.
   Each group shows DEFAULT_VISIBLE (3) cards by default; extras have
   .comp-card-extra and are hidden until the parent gets
   data-expanded="true" via the "Show all N" toggle. */
.comps-group { margin-top: 18px; }
.comps-group:not([data-expanded="true"]) .comp-card-extra { display: none; }
.comps-expand {
  margin-top: 12px;
  padding: 8px 16px;
  background: transparent;
  border: 1px solid var(--separator);
  border-radius: 980px;
  color: var(--accent);
  font-size: 13px;
  font-weight: 500;
  letter-spacing: -0.005em;
  cursor: pointer;
  transition: background 0.15s var(--ease), border-color 0.15s var(--ease);
}
.comps-expand:hover {
  background: var(--accent-bg);
  border-color: var(--accent);
}
/* Sub-sub-heading inside each comps group. Needs higher specificity than
   .detail-subsection h3 (22px) so this smaller treatment wins, otherwise the
   subhead reads as big as the page's main section titles. Matches the
   .transit-subhead size band for a consistent subsection hierarchy. */
h3.comps-subhead,
.detail-subsection h3.comps-subhead {
  font-family: var(--font-serif);
  font-weight: 500;
  font-size: 16px;
  line-height: 1.2;
  letter-spacing: -0.005em;
  margin: 0 0 4px 0;
  color: var(--text);
}
/* Round 80: per-group description sits under each subheading instead of
   stacked at the top of the section. Quieter than the previous bullet
   list, easier to scan. */
.comps-subdesc {
  font-size: 13px;
  color: var(--text-tertiary);
  line-height: 1.5;
  margin: 0 0 12px 0;
  /* No max-width: the group is full-width (~1016px), so these one-sentence
     descriptions fit on a single row instead of wrapping into a narrow column. */
}
.comps-count {
  display: inline-block;
  margin-left: 8px;
  padding: 1px 8px;
  font-family: var(--font-sans, inherit);
  font-size: 12px;
  font-weight: 500;
  background: rgba(0, 0, 0, 0.06);
  border-radius: 980px;
  color: var(--text-tertiary);
  vertical-align: 2px;
}
.comps-blurb {
  font-size: 12px;
  color: var(--text-tertiary);
  margin: 0 0 12px 0;
}
/* Criteria block sits below the "Similar units" header and explains what
   each of the three groups below it actually means. Avoids the ambiguity
   the user flagged (similar price? similar size? similar cash flow?). */
.comps-criteria {
  display: flex;
  flex-direction: column;
  gap: 6px;
  font-size: 13px;
  color: var(--text-secondary, var(--text-tertiary));
  line-height: 1.5;
  margin: 6px 0 18px 0;
  padding: 14px 16px;
  border-left: 2px solid var(--separator);
  background: rgba(0, 0, 0, 0.015);
  border-radius: 0 6px 6px 0;
}
.comps-criteria strong { color: var(--text); font-weight: 600; }
/* Note above the "Best cash flow" grid when the city has no positives —
   surfaces the fallback explicitly so users don't assume the negative
   numbers are bugs. */
.comps-note {
  font-size: 12.5px;
  color: var(--text-tertiary);
  font-style: italic;
  margin: 0 0 10px 0;
  line-height: 1.45;
}
/* Overlap tag — "Same building" / "Nearby" — appears next to comp cards
   in the Best Cash Flow group when they also live in one of the other
   groups, so duplicates read as intentional cross-references. */
.comp-card-overlap {
  display: inline-block;
  margin-left: 6px;
  padding: 1px 6px;
  font-size: 10px;
  font-weight: 500;
  letter-spacing: 0.02em;
  background: rgba(47, 90, 63, 0.08);
  color: var(--accent);
  border-radius: 4px;
  vertical-align: 1px;
}
.comp-grid {
  display: grid;
  /* Cap each track at ~380px so a single card (e.g. only one "Other unit
     in this building") sits at the same width as one card in a 3-card row
     instead of stretching across the full content width. auto-fit still
     packs as many as fit on a given viewport. */
  grid-template-columns: repeat(auto-fit, minmax(240px, 380px));
  gap: 12px;
}
.comp-card {
  display: block;
  padding: 0;
  border-radius: var(--radius-md);
  background: var(--bg);
  border: 1px solid var(--separator);
  text-decoration: none;
  color: var(--text);
  overflow: hidden;
  transition: border-color 0.15s var(--ease), transform 0.15s var(--ease), box-shadow 0.15s var(--ease);
}
.comp-card:hover {
  border-color: var(--accent, var(--text));
  transform: translateY(-1px);
  box-shadow: 0 6px 14px rgba(15, 31, 44, 0.08);
}
/* Round 75/79: photo header on comp cards so similar units feel tangible.
   Round 79: now a wrapper for a .photo-carousel instead of a backgrounded
   div, so users can scan multiple photos in place. */
.comp-card-photo {
  position: relative;
  height: 130px;
  background-color: var(--bg-surface);
  border-bottom: 1px solid var(--separator-soft);
  overflow: hidden;
}
.comp-card-body { padding: 12px 14px 14px; }
.comp-card-addr {
  font-weight: 600;
  font-size: 14px;
  margin-bottom: 2px;
  letter-spacing: -0.005em;
}
.comp-card-meta {
  font-size: 12px;
  color: var(--text-tertiary);
  margin-bottom: 8px;
}
.comp-card-pricerow {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: 8px;
  margin-bottom: 6px;
}
.comp-card-price {
  font-family: var(--font-serif);
  font-size: 18px;
  font-weight: 500;
  letter-spacing: -0.01em;
  font-variant-numeric: tabular-nums;
}
.comp-card-dist {
  font-size: 11px;
  color: var(--text-tertiary);
  letter-spacing: 0.005em;
}
.comp-card-metrics {
  display: flex; gap: 12px;
  font-size: 12px;
  color: var(--text-tertiary);
  font-variant-numeric: tabular-nums;
}
.comp-card-metrics em {
  font-style: normal;
  font-size: 10.5px;
  opacity: 0.75;
  margin-left: 1px;
}
.comp-card-cf.pos { color: var(--positive); font-weight: 500; }
.comp-card-cf.neg { color: var(--negative); font-weight: 500; }

@media (max-width: 768px) {
  .verdict-card { flex-direction: column; align-items: flex-start; gap: 14px; padding: 18px 18px; }
  .verdict-badge { flex-direction: row; align-items: center; gap: 14px; }
  .pencils-card-value, .offer-value, .comp-card-price { font-size: 22px; }
  .offer-block-arrow { display: none; }
  /* Insights offer ladder (Opening offer / Walk-away ceiling / Asking price):
     on mobile each block is a full-width stacked card, so center its contents
     for a tidier, more balanced read. */
  .offer-block { text-align: center; }
  .offer-block .offer-label { display: flex; align-items: center; justify-content: center; flex-wrap: wrap; gap: 4px; }
  /* Financing & cash: pack the 5 metrics into 3 columns (2 rows) instead of
     2 columns (3 rows) so the strip is shorter on phones. */
  .dp-financing-grid {
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: 14px 8px;
  }
  .dp-financing-grid .metric { padding: 0; background: transparent; }
  .dp-financing-grid .metric .value { font-size: 15px; }
  .dp-financing-grid .metric .label { font-size: 10px; line-height: 1.25; }
  .dp-financing-grid .metric .sub { font-size: 9.5px; }
}

/* === Full-width section (used for grouped sections that span the grid) ===
   Inherits its top spacing from .detail-grid's row gap + the per-section
   margin-top above. Adding 56px on top of that double-stacked spacing. */
.detail-section-wide { margin-top: 0; }

/* P&L tables */
.pnl-table {
  width: 100%; border-collapse: collapse;
  font-size: 13px;
  table-layout: fixed;
  font-variant-numeric: tabular-nums;
}
/* Year-1 table fills its card width. The card now frames the table, so the old
   720px cap (added to stop the 4-column table sprawling when it sat loose in the
   section) would leave dead space on the card's right. Filling the card matches
   how the 10-year card's stats fill theirs. */
.pnl-wrap .pnl-table:not(.pnl-table-wide) {
  margin: 0;
}
/* The 10-year P&L has up to 11 columns, but defaults to a "key years" view
   (1, 2, 3, 5, 10) that fits without horizontal scroll. The full 10-year
   view restores the min-width so values never collide.
   Round 34: width: max-content so the table sizes to its VISIBLE cells
   only (5 cols + label in collapsed view, 10 cols + label expanded).
   Without this, the table stretched to fill its container and the
   section-header bg painted across empty space past the last visible
   column — user reported the dark bg "extends past content." Now the
   bg shrinks/expands automatically when toggling "Show all 10 years". */
.pnl-table-wide {
  width: max-content;
  max-width: 100%;
}
.pnl-table-wide.pnl-table-expanded {
  min-width: 1180px;
}
.pnl-table-wide th:not(:first-child),
.pnl-table-wide td:not(:first-child) {
  width: 92px;
}
/* Default collapsed view: hide years 4, 6, 7, 8, 9 so the table fits the
   ~1000px content column comfortably. `data-yr` attribute on each cell
   identifies which year it belongs to. */
.pnl-table-wide:not(.pnl-table-expanded) [data-yr="4"],
.pnl-table-wide:not(.pnl-table-expanded) [data-yr="6"],
.pnl-table-wide:not(.pnl-table-expanded) [data-yr="7"],
.pnl-table-wide:not(.pnl-table-expanded) [data-yr="8"],
.pnl-table-wide:not(.pnl-table-expanded) [data-yr="9"] {
  display: none;
}
.pnl-table th, .pnl-table td {
  padding: 10px 12px;
  /* Center-align numeric cells per design preference; first column (row label)
     stays left-aligned below. */
  text-align: center;
  border-bottom: 1px solid var(--separator-soft);
  white-space: nowrap;
  letter-spacing: -0.005em;
}
/* The 10-year toggle button — small, low-emphasis chip below the table. */
.pnl-yr-toggle {
  margin: 12px 0 0 0;
  padding: 6px 12px;
  background: transparent;
  border: 1px solid var(--separator);
  border-radius: 6px;
  color: var(--accent);
  font-size: 12px;
  font-weight: 500;
  cursor: pointer;
  transition: background 0.15s ease, border-color 0.15s ease;
}
.pnl-yr-toggle:hover { background: rgba(47, 90, 63, 0.05); border-color: var(--accent); }
.pnl-yr-toggle::before { content: '+ '; }
.pnl-table-wide.pnl-table-expanded ~ .pnl-yr-toggle::before { content: '− '; }
.pnl-table th {
  font-size: 11px;
  color: var(--text-tertiary); font-weight: 600;
  text-align: center; background: var(--bg);
  position: sticky; top: 0;
  border-bottom: 1px solid var(--separator);
}
.pnl-table th:first-child, .pnl-table td:first-child {
  text-align: left;
  color: var(--text-secondary);
  width: 180px;
  font-weight: 500;
}
/* Round 30: dropped the white/bg-elevated backgrounds on subtotal +
   total rows per user feedback ("kind of confusing... attracts the
   user's attention"). The font-weight 600 + border-top alone is
   enough to denote subtotal/total visually without painting an
   accent row across the table. */
.pnl-table tr.subtotal td {
  border-top: 1px solid var(--separator);
  font-weight: 600; color: var(--text);
}
.pnl-table tr.total td {
  border-top: 1px solid var(--text);
  border-bottom: 1px solid var(--text);
  font-weight: 600; color: var(--text);
}
.pnl-table tr.income td { color: var(--text); }
.pnl-table tr.expense td { color: var(--text-secondary); }

/* Section header row inside the 10-year P&L — labels the operating block vs.
   the equity/appreciation block so the user can scan the table top to bottom.
   Round 31: switched bg-elevated (near-white) to a darker cream tint so
   the header row reads as a subdued divider rather than an accent strip.
   The variable cascade: --bg-section is set on .pnl-table; falls back to
   a fixed darker cream if not defined. */
.pnl-table tr.section-header td {
  background: #e9e1cf;
  color: var(--accent);
  font-size: 11px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  text-align: left;
  padding: 14px 14px 10px;
  border-top: 2px solid var(--accent);
  border-bottom: 1px solid var(--separator);
}
.pnl-table tr.section-header:first-child td {
  border-top: 0;
}
/* Round 31: REVERTED the Round 30 28px spacer above the Equity section
   header. User feedback: the extra space made the two sections feel
   disconnected ("don't like it, let's go back to having the same-size
   rows"). With the new darker section-header background, the visual
   demarcation is clear without needing extra vertical space. */
/* Indent expense sub-lines so the operating-income structure reads like a
   real P&L. Subtotals/totals stay flush-left. */
.pnl-table tr.indent td.rowlabel {
  padding-left: 28px;
  color: var(--text-secondary);
  font-weight: 400;
}
/* Color is reserved for the headline rows at the bottom of each section
   (Cash flow, Total return). Subtotals and per-line items stay black with
   bold as the only weighting cue.
   Round 32: bumped to 700 weight + explicit !important so the Total
   return row reads with the same green visual weight as the Cash flow
   row above it (per user feedback — both are total-row "headline"
   numbers and should match visually). */
.pnl-table tr.total td.num.pos { color: var(--positive) !important; font-weight: 700; }
.pnl-table tr.total td.num.neg { color: var(--negative) !important; font-weight: 700; }

/* Round 34: mobile/desktop row visibility for the 10-yr Total return
   duplicate. On mobile, the Total return row appears right after
   Cash flow (so the two bottom-line numbers sit together); on
   desktop, it stays at the bottom of the Equity section. */
.pnl-row-mobile-only { display: none; }
.pnl-row-desktop-only { display: table-row; }
@media (max-width: 768px) {
  .pnl-row-mobile-only { display: table-row; }
  .pnl-row-desktop-only { display: none; }
}
.pnl-wrap {
  overflow-x: auto;
  border: 1px solid var(--separator-soft);
  border-radius: var(--radius-md);
  background: var(--bg);
}
/* The year-1 table is max-width 720px, but the wrap was stretching to its
   parent (often 900px+ on desktop) leaving a big empty bordered frame to
   the right of the table. Shrink-wrap the container to its table. */
.dp-year1 .pnl-wrap {
  width: fit-content;
  max-width: 100%;
}

/* Mobile-only card stack for the 10-year P&L. Each card is a <details>
   element so it expand/collapses without JS — Year 1 open by default,
   later years collapsed to summary (year + annual cash flow). Hidden on
   desktop where the wide table reads fine. */
.pnl-cards-stack { display: none; }
@media (max-width: 768px) {
  /* Hide only the 10-year wide P&L table on mobile — its 1180px min-width
     is unreadable. The Year-1 monthly breakdown (4 narrow columns) stays
     visible and gets its own horizontal-scroll wrap so editable cells
     still work. */
  .dp-tenyr .pnl-wrap { display: none; }
  .pnl-cards-stack { display: block; margin-top: 8px; }
  .dp-year1 .pnl-wrap {
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
    /* Slim scrollbar so the table breathes; iOS shows scrollbar only on
       scroll, so this is a no-op there but helps on desktop-narrow. */
    scrollbar-width: thin;
  }
  .dp-year1 .pnl-table {
    font-size: 11px;
    min-width: 0;
  }
  .dp-year1 .pnl-table th,
  .dp-year1 .pnl-table td {
    /* Bumped from 6px to 10px vertical so each editable cell hits the
       ~36px tap-target floor on touch devices. Was producing ~26px
       rows where adjacent cells were too easy to mis-tap. */
    padding: 10px 8px;
  }
  /* Annual column adds little value next to Monthly × 12 — hide on mobile
     to give the editable Monthly and % columns more room. */
  .dp-year1 .pnl-table th:nth-child(3),
  .dp-year1 .pnl-table td:nth-child(3) {
    display: none;
  }
}
.pnl-card {
  border: 1px solid var(--separator-soft);
  border-radius: var(--radius-md);
  background: var(--bg);
  margin-bottom: 8px;
  overflow: hidden;
}
.pnl-card summary {
  padding: 14px 16px;
  cursor: pointer;
  display: flex; justify-content: space-between; align-items: center;
  gap: 12px;
  list-style: none;
  font-weight: 500;
  -webkit-tap-highlight-color: transparent;
}
.pnl-card summary::-webkit-details-marker { display: none; }
.pnl-card summary::after {
  content: '';
  width: 10px; height: 10px;
  border-right: 1.5px solid var(--text-tertiary);
  border-bottom: 1.5px solid var(--text-tertiary);
  transform: rotate(45deg);
  transition: transform 0.18s var(--ease);
  margin-left: 6px;
  flex-shrink: 0;
}
.pnl-card[open] summary::after { transform: rotate(-135deg); }
.pnl-card-yr { font-size: 15px; color: var(--text); font-weight: 600; letter-spacing: -0.01em; }
.pnl-card-cf { font-size: 14px; font-variant-numeric: tabular-nums; font-weight: 600; }
.pnl-card-cf.pos { color: var(--positive); }
.pnl-card-cf.neg { color: var(--negative); }
.pnl-card-rows {
  display: grid; grid-template-columns: 1fr auto;
  gap: 6px 14px;
  padding: 0 16px 16px;
  margin: 0;
  font-size: 13px;
  font-variant-numeric: tabular-nums;
}
.pnl-card-rows dt { color: var(--text-secondary); }
.pnl-card-rows dd { margin: 0; color: var(--text); text-align: right; }
.pnl-card-rows .expense { color: var(--text-tertiary); }
.pnl-card-rows .subtotal {
  font-weight: 600;
  padding-top: 6px;
  border-top: 1px solid var(--separator-soft);
  margin-top: 2px;
}
.pnl-card-rows .total { font-weight: 600; padding-top: 6px; }
.pnl-card-rows .total.pos { color: var(--positive); }
.pnl-card-rows .total.neg { color: var(--negative); }
.pnl-card-section {
  grid-column: 1 / -1;
  font-size: 10px;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--text-tertiary);
  font-weight: 600;
  margin: 12px 0 2px;
}
.pnl-card-section-spacer { display: none; }
/* The detail-section-wide breaks out of the .detail-grid for the 10-yr
   table; on mobile we want it inside the normal 16px page padding so the
   cards align with everything else. */
@media (max-width: 768px) {
  .detail-section-wide { margin-top: 32px; }
}

.kvs {
  display: grid; grid-template-columns: 1fr 1fr; gap: 8px 28px;
  margin-top: 20px;
}
.kv {
  display: flex; justify-content: space-between;
  padding: 10px 0;
  border-bottom: 1px solid var(--separator-soft);
}
/* Round 27: prevent the breakeven-rent label + help-hint icon from
   wrapping to a second row when the .kv column is narrow. nowrap on
   the label keeps "Breakeven rent ?" intact; .v gets min-width: 0
   so it can shrink to whatever space remains rather than forcing
   the .k label to a second line. */
.kv .k {
  color: var(--text-secondary);
  font-size: 13px;
  white-space: nowrap;
}
.kv .v {
  font-size: 14px;
  font-variant-numeric: tabular-nums;
  font-weight: 500;
  min-width: 0;
  text-align: right;
}

/* === Demo data badge === */
.demo-badge {
  display: inline-flex; align-items: center; gap: 6px;
  background: var(--bg-surface);
  border-radius: 980px;
  padding: 4px 10px;
  font-size: 11px;
  color: var(--text-tertiary);
  font-weight: 500;
  letter-spacing: -0.005em;
}
.demo-badge::before {
  content: ''; width: 5px; height: 5px; border-radius: 50%;
  background: var(--text-tertiary);
}

/* === Sensitivity heatmap === */
.sensitivity-wrap {
  border: 1px solid var(--separator-soft);
  border-radius: var(--radius-md);
  overflow-x: auto;
  background: var(--bg);
}
.sensitivity-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 13px;
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.005em;
  table-layout: fixed;
  min-width: 720px;
}
.sensitivity-table th, .sensitivity-table td {
  padding: 14px 12px;
  text-align: center;
}
.sensitivity-table thead th {
  font-size: 11px;
  color: var(--text-tertiary);
  font-weight: 600;
  background: var(--bg-surface);
  border-bottom: 1px solid var(--separator);
}
.sensitivity-table thead th:first-child {
  background: var(--bg);
  color: var(--text-tertiary);
  font-weight: 500;
  text-align: left;
  padding-left: 18px;
}
.sensitivity-table tbody th {
  font-size: 11px;
  color: var(--text-tertiary);
  background: var(--bg-surface);
  border-right: 1px solid var(--separator-soft);
  font-weight: 600;
  text-align: right;
  padding-right: 14px;
  width: 110px;
}
/* Mobile: keep the row-header column visible while the rest scrolls
   horizontally — otherwise the 720px-min-width table loses context once
   the user pans past the first column. */
@media (max-width: 768px) {
  .sensitivity-table tbody th,
  .sensitivity-table thead th:first-child {
    position: sticky;
    left: 0;
    z-index: 1;
  }
  .sensitivity-table { font-size: 12px; }
  .sensitivity-table th, .sensitivity-table td { padding: 10px 8px; }
}
.sensitivity-table tbody td {
  border-right: 1px solid var(--separator-soft);
  border-bottom: 1px solid var(--separator-soft);
  text-align: center;
  font-weight: 600;
  font-size: 13.5px;
}
.sensitivity-table tbody tr:last-child td,
.sensitivity-table tbody tr:last-child th { border-bottom: 0; }
.sensitivity-table tbody td:last-child,
.sensitivity-table tbody tr th:last-child,
.sensitivity-table thead th:last-child { border-right: 0; }
.sens-baseline-row th,
.sens-baseline-row td {
  border-top: 1px solid var(--separator);
  border-bottom: 1px solid var(--separator);
}
.sens-baseline-col {
  border-left: 1px solid var(--separator) !important;
  border-right: 1px solid var(--separator) !important;
}

/* Round 63: "Rates as of [date], Freddie Mac" caption next to the
   Financing scenario header. Populated from rates.js when the FRED
   fetch resolves; empty when live rates are unavailable. */
.rates-asof {
  font-size: 10.5px;
  font-weight: 500;
  color: var(--text-tertiary);
  letter-spacing: 0;
  text-transform: none;
}
/* Standalone "Rates as of …" line at the top of the expanded Financing
   scenario body, just above the mortgage presets. */
.rates-asof-line { margin: 2px 0 14px; }
.rates-asof-line[hidden] { display: none; }

/* === Lender preset chips (inside Underwriting panel) ===
   Round 62: 7 presets = busy in the 2-column grid. New layout:
   3-column grid for the 6 "Mortgage" options, with "Cash" as a
   separate full-width chip below. Group labels above each row
   visually separate the two categories. */
.preset-group { display: flex; flex-direction: column; gap: 6px; }
.preset-group-label {
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 0.10em;
  text-transform: uppercase;
  color: var(--text-tertiary);
  margin: 4px 0 2px;
}
.preset-group-label-cash {
  margin-top: 8px;
  padding-top: 8px;
  border-top: 1px solid var(--separator-soft);
}
.preset-row {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 6px;
}
.preset-row-3 { grid-template-columns: 1fr 1fr 1fr; }
.preset-row-full { grid-template-columns: 1fr; }
@media (max-width: 480px) {
  /* Drop to 2 columns on narrow viewports so the 3-up doesn't crush
     the chip labels. */
  .preset-row-3 { grid-template-columns: 1fr 1fr; }
}
.preset {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  background: var(--bg-surface);
  border: 1px solid var(--separator-soft);
  border-radius: var(--radius-sm);
  /* Round 62: tightened padding so 3-column chips fit comfortably.
     Round 71: extra right padding reserves space for the absolute-
     positioned help-hint icon (top-right corner). */
  padding: 8px 22px 8px 10px;
  text-align: left;
  cursor: pointer;
  transition: background 0.15s var(--ease), border-color 0.15s var(--ease);
  min-width: 0;
}
.preset:hover { background: var(--bg-hover); }
.preset.active {
  background: var(--accent-bg);
  border-color: rgba(47, 90, 63, 0.4);
}
/* Round 64: dashed outline removed. Modified state now signaled by
   a small inline "Edited" badge inside the chip (see .chip-edited-
   badge below) which is more legible and less visually heavy. */
.preset-name {
  font-size: 12.5px;
  font-weight: 600;
  color: var(--text);
  letter-spacing: -0.005em;
  line-height: 1.2;
  /* Round 66: enforce no-wrap so the ? icon stays glued to the name
     on a single line. The chip is fixed-width in a 3-column grid;
     all preset names ("HELOC / 2nd", "Hard money", etc.) fit
     comfortably. */
  white-space: nowrap;
}
.preset.active .preset-name { color: var(--accent); }
.preset-sub {
  font-size: 10.5px;
  color: var(--text-tertiary);
  margin-top: 2px;
  line-height: 1.3;
}

/* === Risk-profile grouping container ===
   Round 44: wraps the Risk-profile chip row + Operating Expenses +
   Growth in one visually-tinted block so the user reads them as
   parent-child: "this preset controls every field in this group."
   Subtle tint differentiates from the Financing block above so the
   parent-child relationship is obvious without being heavy. */
.ap-group {
  background: rgba(46, 90, 56, 0.04);
  border: 1px solid rgba(46, 90, 56, 0.10);
  border-radius: 12px;
  padding: 18px 18px 14px;
  margin-bottom: 24px;
  display: flex;
  flex-direction: column;
  gap: 18px;
}
.ap-group > .ap-section {
  /* Sections inside the group lose their own bottom margin so the
     parent's gap controls spacing — and lose any background since
     the group provides the tint. */
  margin-bottom: 0;
  background: transparent;
}
.ap-group > .ap-section-risk h3 {
  /* Tighter top spacing on the Risk-profile header inside the group
     since the container padding already provides breathing room. */
  margin-top: 0;
}

/* === Round 101: collapsible Risk-profile group ===
   The group now opens with a toggle header (label + active-posture
   summary + chevron) and a collapsible body. Default collapsed so the
   assumptions panel reads cleanly on open. */
.ap-group-risk,
.ap-group-financing {
  /* When the JS-built collapsible group is used, the container manages
     its own internal spacing via the toggle + body, so reset the flex
     gap that the generic .ap-group applies. */
  gap: 0;
  padding: 0;
  overflow: hidden;
}
.ap-group-toggle {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  width: 100%;
  padding: 14px 16px;
  background: transparent;
  border: 0;
  cursor: pointer;
  text-align: left;
  font: inherit;
  color: var(--text);
}
.ap-group-toggle:hover { background: rgba(46, 90, 56, 0.05); }
.ap-group-toggle-label {
  font-size: 14px;
  font-weight: 600;
  letter-spacing: -0.01em;
  color: var(--text);
  display: inline-flex;
  align-items: center;
  gap: 4px;
}
.ap-group-toggle-right {
  display: inline-flex;
  align-items: center;
  gap: 10px;
}
.ap-group-summary {
  font-size: 12.5px;
  font-weight: 500;
  color: var(--accent);
  background: var(--accent-bg);
  border-radius: 980px;
  padding: 3px 10px;
  white-space: nowrap;
}
.ap-group-chevron {
  color: var(--text-tertiary);
  transition: transform 0.2s var(--ease);
  flex-shrink: 0;
}
.ap-group-risk:not(.is-collapsed) .ap-group-chevron,
.ap-group-financing:not(.is-collapsed) .ap-group-chevron { transform: rotate(180deg); }
.ap-group-body {
  display: flex;
  flex-direction: column;
  gap: 18px;
  padding: 4px 16px 16px;
}
.ap-group-risk.is-collapsed .ap-group-body,
.ap-group-financing.is-collapsed .ap-group-body { display: none; }
.ap-group-body > .ap-section { margin-bottom: 0; background: transparent; }

/* === Risk-profile chips (Conservative / Feasible / Aggressive)
   Sits above Operating Expenses inside the grouping container.
   Three-up grid on desktop, stays three-up on mobile too (chips
   are short enough). */
.posture-row {
  display: grid;
  grid-template-columns: 1fr 1fr 1fr;
  gap: 8px;
}
.posture-chip {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  background: var(--bg-surface);
  border: 1px solid var(--separator-soft);
  border-radius: var(--radius-sm);
  /* Round 71: extra right padding reserves space for the absolute-
     positioned help-hint icon anchored to the chip's top-right
     corner. Keeps the icon inside the chip border regardless of
     name length. */
  padding: 10px 24px 10px 12px;
  text-align: left;
  cursor: pointer;
  transition: background 0.15s var(--ease), border-color 0.15s var(--ease);
}
.posture-chip:hover { background: var(--bg-hover); }
.posture-chip.active {
  background: var(--accent-bg);
  border-color: rgba(47, 90, 63, 0.4);
}
/* Round 64: dashed outline removed (see .chip-edited-badge). */
.posture-chip-name {
  font-size: 13px;
  font-weight: 600;
  color: var(--text);
  letter-spacing: -0.005em;
  /* Round 66: no-wrap so the ? help-hint stays inline with the name. */
  white-space: nowrap;
}
.posture-chip.active .posture-chip-name { color: var(--accent); }
.posture-chip-sub {
  font-size: 11px;
  color: var(--text-tertiary);
  margin-top: 2px;
}
/* Round 64: .posture-blurb element removed entirely. Each chip's
   descriptive text now lives in its `?` tooltip. The min-height
   hack from Round 46 (to stop layout shift between Conservative /
   Feasible / Aggressive blurbs) is no longer needed. */

/* === In-chip help-hint icon (financing presets + risk profile) ===
   Round 71: anchored to the chip's top-right corner via absolute
   positioning. The previous inline placement was vulnerable to
   overflow when the chip's name + icon exceeded the column width
   in the 3-column preset / 3-column posture grids (icon poked
   outside the chip border). With the icon out of the text flow:
   - Chip width never depends on whether the icon is there.
   - Icon position is constant across all chips regardless of name.
   - Right padding on .preset / .posture-chip reserves the space
     so the name text never collides with the icon.
   Tooltip still fires on hover/focus/tap of the icon only. */
.help-hint.help-hint-chip {
  position: absolute;
  top: 7px;
  right: 7px;
  width: 12px; height: 12px;
  margin: 0;
  transform: none;
}

/* === "Edited" badge on a chip whose preset is selected but values
   have been hand-tuned away from the defaults. Subtle inline pill;
   replaces the prior dashed outline (which the user found
   confusing). Hidden by default; .modified flips it on. */
.chip-edited-badge {
  display: none;
  align-self: flex-start;
  margin-top: 4px;
  padding: 1px 6px;
  font-size: 9px;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: #2E5A38;
  background: rgba(46, 90, 56, 0.12);
  border-radius: 7px;
  line-height: 1.4;
}
.preset.modified .chip-edited-badge,
.posture-chip.modified .chip-edited-badge {
  display: inline-block;
}

/* === Inline "Adjust underwriting" CTA (inside fin-card) === */
.fin-card-action {
  display: flex;
  width: 100%;
  align-items: center;
  justify-content: center;
  gap: 8px;
  margin-top: 16px;
  margin-bottom: 4px;
  background: var(--accent);
  color: white;
  border: 0;
  border-radius: var(--radius-md);
  padding: 13px 18px;
  font-size: 15px;
  font-weight: 500;
  letter-spacing: -0.005em;
  cursor: pointer;
  transition: background 0.15s var(--ease), transform 0.08s var(--ease);
}
.fin-card-action:hover { background: var(--accent-hover); }
.fin-card-action:active { transform: scale(0.99); }
.fin-card-action svg { opacity: 0.95; flex-shrink: 0; }
.fin-card-action svg:last-child { margin-left: 2px; }

/* === Est. pill (marks defaulted/estimated values) === */
.est-pill {
  display: inline-flex;
  align-items: center;
  margin-left: 6px;
  padding: 1px 6px;
  background: var(--bg-elevated);
  border: 1px solid var(--separator);
  color: var(--text-tertiary);
  border-radius: 4px;
  font-size: 9.5px;
  font-weight: 600;
  letter-spacing: 0.02em;
  text-transform: lowercase;
  cursor: help;
  vertical-align: middle;
  line-height: 1.4;
}
.est-pill:hover, .est-pill:focus-visible { color: var(--text-secondary); border-color: var(--separator); }

/* === Rent confidence band (used inside the rent block on the detail page) === */
.rent-band { color: var(--text-secondary); font-weight: 500; font-variant-numeric: tabular-nums; }

/* === Sensitivity heatmap cells === */
.sensitivity-table .sens-cell {
  font-weight: 600;
  letter-spacing: -0.01em;
  font-variant-numeric: tabular-nums;
}
.sensitivity-table .sens-cell.pos { color: var(--positive); }
.sensitivity-table .sens-cell.neg { color: var(--negative); }
.sensitivity-table .sens-cell.sens-base {
  outline: 2px solid var(--accent);
  outline-offset: -2px;
  position: relative;
  z-index: 1;
}

/* === Floating action button for Underwriting (listing detail page) === */
.fab-underwriting {
  position: fixed;
  bottom: calc(28px + env(safe-area-inset-bottom, 0px));
  right: 28px;
  z-index: 30;
  display: inline-flex;
  align-items: center;
  gap: 10px;
  height: 52px;
  padding: 0 22px 0 18px;
  background: var(--accent);
  color: white;
  border-radius: 980px;
  font-size: 15px;
  font-weight: 500;
  letter-spacing: -0.01em;
  border: 0;
  cursor: pointer;
  box-shadow: 0 8px 24px rgba(47, 90, 63, 0.32), 0 0 0 0.5px rgba(0, 0, 0, 0.04);
  transition: bottom 0.26s var(--ease), transform 0.18s var(--ease), opacity 0.22s var(--ease), box-shadow 0.18s var(--ease), background 0.15s var(--ease);
}
.fab-underwriting:hover {
  transform: translateY(-2px);
  background: var(--accent-hover);
  box-shadow: 0 14px 32px rgba(47, 90, 63, 0.42), 0 0 0 0.5px rgba(0, 0, 0, 0.04);
}
.fab-underwriting:active { transform: translateY(0); }
.fab-underwriting svg { flex-shrink: 0; }
/* Round 111: tuck the FAB fully below the viewport whenever the page footer
   is in view, so the floating CTA can NEVER cover the footer links. JS
   (listing.js) toggles this class. Uses `bottom` (a layout property) rather
   than transform so the slide is reliable everywhere. Placed after :hover so
   it wins the (tied) specificity battle when both could apply. */
.fab-underwriting.fab-underwriting--tucked {
  bottom: -96px !important;
  opacity: 0 !important;
  pointer-events: none;
}
@media (max-width: 768px) {
  .fab-underwriting {
    bottom: calc(16px + env(safe-area-inset-bottom, 0px)); right: 16px;
    height: 48px; padding: 0 18px 0 14px;
    font-size: 14px;
  }
}

/* === View switcher (map | list segmented control in panel-header) === */
.view-switcher {
  display: inline-flex;
  background: var(--bg-surface);
  border: 1px solid var(--separator-soft);
  border-radius: 8px;
  padding: 2px;
  align-items: center;
  width: fit-content;
}
.view-btn {
  background: transparent;
  border: 0;
  padding: 0 12px;
  height: 28px;
  border-radius: 6px;
  font-size: 12px;
  font-weight: 500;
  letter-spacing: -0.005em;
  color: var(--text-secondary);
  cursor: pointer;
  display: inline-flex; align-items: center; gap: 5px;
  transition: background 0.15s var(--ease), color 0.15s var(--ease);
}
.view-btn:hover:not(.active) { color: var(--text); }
.view-btn.active {
  background: var(--bg);
  color: var(--text);
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.06);
}
.view-btn svg { opacity: 0.8; }

/* === Grid view — card-style, multiple per row, photo on top === */
body.view-grid .map-section { display: none; }
body.view-grid .layout { grid-template-columns: 1fr; }
body.view-grid .panel {
  border-left: 0;
  max-width: 1280px;
  margin: 0 auto;
  width: 100%;
  border-right: 1px solid var(--separator);
  border-left: 1px solid var(--separator);
}
body.view-grid .listings {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
  /* Without an explicit auto-row size, the inherited `flex: 1` from base
     `.listings` gives this grid a constrained height (panel's flex slot),
     and `grid-auto-rows: auto` then divides that height evenly across all
     rows — compressing each card to ~22px and clipping its content behind
     `overflow: hidden` on the card. max-content lets rows size to their
     full ~300px card height; overflow-y on the container provides scroll. */
  grid-auto-rows: max-content;
  gap: 20px;
  padding: 20px 24px;
  align-content: start;
}
/* Cards are restructured photo-on-top in grid view. The base card uses
   `display: grid` with two columns; here we override to a vertical layout
   and explicitly zero the inherited `gap: 16px` and `border-bottom` so the
   card reads as one self-contained tile inside a full border. */
body.view-grid .listing-card {
  display: flex;
  flex-direction: column;
  gap: 0;
  padding: 0;
  border: 1px solid var(--separator-soft);
  border-bottom: 1px solid var(--separator-soft);
  border-radius: var(--radius-lg);
  overflow: hidden;
  background: var(--bg);
  transition: box-shadow 0.18s var(--ease), transform 0.18s var(--ease);
  align-items: stretch;
}
body.view-grid .listing-card:hover,
body.view-grid .listing-card.active {
  background: var(--bg);
  box-shadow: 0 8px 24px rgba(20, 35, 25, 0.10);
  transform: translateY(-2px);
}
body.view-grid .listing-photo-wrap {
  width: auto;
  height: 180px;
  flex-shrink: 0;
  border-radius: 0;
  background: var(--bg-surface);
}
body.view-grid .listing-body {
  padding: 14px 16px 16px;
  gap: 4px;
  flex: 1;
  min-width: 0;
}
body.view-grid .listing-price { font-size: 22px; }
body.view-grid .listing-address { font-size: 13px; }
body.view-grid .listing-eyebrow { font-size: 11px; }
body.view-grid .listing-metrics { gap: 14px; margin-top: 8px; }
body.view-grid .listing-metric .value { font-size: 14px; }

/* === Detail modal (in-page property view) === */
.detail-modal-backdrop {
  position: fixed; inset: 0;
  z-index: 54;
  background: rgba(0, 0, 0, 0.32);
  -webkit-backdrop-filter: saturate(140%) blur(8px);
  backdrop-filter: saturate(140%) blur(8px);
}
.detail-modal {
  position: fixed; inset: 0;
  z-index: 55;
  display: flex; align-items: stretch; justify-content: center;
  padding: 24px;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.22s var(--ease);
}
.detail-modal.open {
  opacity: 1;
  pointer-events: auto;
}
.detail-modal-card {
  background: var(--bg);
  border-radius: var(--radius-xl);
  width: 100%;
  max-width: 1200px;
  display: flex; flex-direction: column;
  overflow: hidden;
  box-shadow: var(--shadow-lg);
  transform: translateY(20px);
  transition: transform 0.28s var(--ease);
}
.detail-modal.open .detail-modal-card { transform: translateY(0); }
.detail-modal-head {
  position: sticky; top: 0;
  z-index: 5;
  background: rgba(255, 255, 255, 0.92);
  -webkit-backdrop-filter: saturate(180%) blur(20px);
  backdrop-filter: saturate(180%) blur(20px);
  border-bottom: 1px solid var(--separator-soft);
  padding: 12px 20px;
  display: flex; justify-content: flex-end;
  flex-shrink: 0;
}
.detail-modal-close {
  background: var(--bg-surface);
  width: 36px; height: 36px;
}
.detail-modal-close:hover { background: var(--bg-hover); }
.detail-modal-body {
  flex: 1;
  overflow-y: auto;
  /* Lock horizontal axis — any wide child (sensitivity table, etc.) must
     provide its own scrolling within a constrained wrap. Without this lock
     a wide grandchild can push the whole modal body wider than the viewport
     and the user gets confusing page-level horizontal scroll. */
  overflow-x: hidden;
  padding: 0;
  -webkit-overflow-scrolling: touch;
}
.detail-modal-body .detail {
  margin: 24px auto 48px;
  max-width: 1080px;
  padding: 0 32px;
}
body.modal-open { overflow: hidden; }
@media (max-width: 768px) {
  .detail-modal { padding: 0; }
  .detail-modal-card { border-radius: 0; overflow-x: hidden; }
  .detail-modal-body .detail { padding: 0 18px; margin: 16px auto 32px; }
  /* Standalone listing.html page (no modal wrapper) — same lock. */
  body.detail-body { overflow-x: hidden; }
}

/* Make Calculator / Saved tray sit ABOVE the detail modal so users can
   tweak underwriting while the modal stays visible at the edges. */
#assumptions-panel, #saved-panel { z-index: 100; }
#assumptions-backdrop, #saved-backdrop { z-index: 99; }

/* === Flood-zone badge (FEMA NFHL data) === */
.flood-zone-high { color: var(--negative); }
.flood-zone-low { color: var(--positive); }
.flood-zone-unknown { color: var(--text-tertiary); }

/* === Detail-page actions row (Save + Share side by side) === */
.detail-actions-row {
  display: flex;
  gap: 10px;
  margin-top: 0;
  margin-bottom: 16px;
  flex-wrap: wrap;
  justify-content: flex-end;
}
.detail-actions-row .detail-save-btn,
.detail-actions-row .detail-share-btn { margin-top: 0; }

.detail-share-btn {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  /* Match .detail-save-btn — 10px vertical so the row of three action
     buttons (Save / Share / One-pager) all clear the mobile tap floor. */
  padding: 10px 16px;
  background: var(--bg-surface);
  color: var(--text-secondary);
  border: 0;
  border-radius: 980px;
  font-size: 14px;
  font-weight: 500;
  letter-spacing: -0.005em;
  cursor: pointer;
  transition: background 0.15s var(--ease), color 0.15s var(--ease);
}
.detail-share-btn:hover { background: var(--bg-hover); color: var(--text); }
.detail-share-btn svg { flex-shrink: 0; }

/* === Toast notification ===
   Two variants: neutral (default, dark) and .tally-toast-success
   (green, with icon) for confirmation moments. Wider padding and
   font weight scaled up on success so the confirmation reads
   clearly without feeling shouty. */
.tally-toast {
  position: fixed;
  bottom: calc(32px + env(safe-area-inset-bottom, 0px));
  left: 50%;
  transform: translateX(-50%) translateY(20px);
  background: var(--text);
  color: white;
  padding: 14px 26px;
  border-radius: 980px;
  font-size: 14px;
  font-weight: 500;
  max-width: min(440px, calc(100vw - 32px));
  display: inline-flex;
  align-items: center;
  gap: 10px;
  letter-spacing: -0.005em;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.2s var(--ease), transform 0.2s var(--ease);
  z-index: 200;
  box-shadow: var(--shadow-lg);
}
.tally-toast.show {
  opacity: 1;
  transform: translateX(-50%) translateY(0);
}
/* Success variant — confirmation moments like "offer set". Green
   palette for a positive feedback feel, plus a check-mark icon.
   Wider + larger padding than neutral toasts so the confirmation
   reads as a clear win without being a popover. */
.tally-toast-success {
  background: var(--positive);
  color: #fff;
  font-weight: 600;
  font-size: 15px;
  padding: 16px 28px;
  max-width: min(520px, calc(100vw - 24px));
  gap: 12px;
  box-shadow: 0 12px 32px -10px rgba(0, 135, 59, 0.6),
              0 4px 12px rgba(0, 0, 0, 0.18);
}
.tally-toast-icon {
  display: inline-grid;
  place-items: center;
  width: 26px; height: 26px;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.25);
  flex-shrink: 0;
}
.tally-toast-text { line-height: 1.35; }

/* Mobile: the success toast was rendering as a tall rounded bubble
   that took 3 lines for "Offer set to $XXX,XXX — underwriting
   updated". Compress to a single-row pill with shorter padding +
   wider min-width so it reads at a glance and clears the keyboard /
   thumb zone faster. */
@media (max-width: 768px) {
  .tally-toast {
    padding: 10px 18px;
    font-size: 13px;
    max-width: calc(100vw - 20px);
    width: calc(100vw - 20px);
    box-sizing: border-box;
    justify-content: center;
    bottom: calc(20px + env(safe-area-inset-bottom, 0px));
  }
  .tally-toast-success {
    padding: 12px 18px;
    font-size: 14px;
    gap: 10px;
  }
  .tally-toast-icon {
    width: 22px; height: 22px;
  }
  .tally-toast-text {
    line-height: 1.25;
  }
}

/* === Deprecated: inline hint for "Property mgmt — off, click to add" ===
   Superseded by the "off" est-badge pill, which fits inline next to the
   label like the HOA/Tax est. badges and doesn't wrap the row on narrow
   viewports. Class kept for compat in case any other listing variant
   still emits .mgmt-hint markup. */
.mgmt-hint {
  font-size: 11px;
  font-weight: 400;
  color: var(--text-tertiary);
  font-style: italic;
  letter-spacing: 0;
}

/* "est." badge — surfaces data-quality info inline. Currently used on
   Property tax (flat 2.4% NJ default from RentCast). Hover/title gives
   full context. */
.est-badge {
  display: inline-block;
  margin-left: 6px;
  padding: 1px 6px;
  font-size: 10px;
  font-weight: 500;
  letter-spacing: 0.03em;
  background: rgba(255, 168, 0, 0.12);
  color: #b8730a;
  border-radius: 4px;
  cursor: help;
  vertical-align: 1px;
  white-space: nowrap;
}
/* `no data` variant — stronger signal than `est.`. The source feed
   returned nothing for this field, so the value being used is a
   structural default (e.g. 2.4% NJ rate × price). Red rather than
   amber to read as "investigate" not "minor caveat". */
.est-badge-missing {
  background: rgba(220, 38, 38, 0.10);
  color: #991b1b;
}
/* On mobile, the inline hint crowds the $0 column and wraps awkwardly across
   the row. Hide it — the row's clickable affordance remains via the cell. */
@media (max-width: 768px) {
  .mgmt-hint { display: none; }
}

/* Info variant of est-badge — used for the Property mgmt "off" indicator.
   Same shape as .est-badge but in the navy accent palette to signal
   "informational" rather than "estimate" (amber) or "missing" (red). */
.est-badge-info {
  background: rgba(47, 90, 63, 0.08);
  color: var(--accent);
}

/* === Getting around (Walk / Transit / Bike scores) === */
.getting-around-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: 12px;
  margin-bottom: 14px;
}
.ga-score {
  display: flex;
  align-items: center;
  gap: 14px;
  background: var(--bg-surface);
  border-radius: var(--radius-md);
  padding: 14px 16px;
}
.ga-circle {
  width: 60px; height: 60px;
  border-radius: 50%;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  color: white;
  font-family: inherit;
  font-size: 22px;
  font-weight: 700;
  letter-spacing: -0.02em;
  font-variant-numeric: tabular-nums;
  flex-shrink: 0;
  line-height: 1;
  text-align: center;
}
.ga-of {
  display: block;
  font-size: 10px;
  font-weight: 600;
  opacity: 0.85;
  margin-top: 2px;
  letter-spacing: 0;
  line-height: 1;
}
.ga-high { background: var(--positive); }
.ga-mid { background: var(--accent); }
.ga-low { background: #ff9500; }
.ga-vlow { background: var(--negative); }

.ga-body { min-width: 0; }
.ga-label {
  font-size: 13px;
  font-weight: 600;
  color: var(--text);
  letter-spacing: -0.005em;
}
.ga-desc {
  font-size: 12px;
  color: var(--text-secondary);
  margin-top: 2px;
  line-height: 1.45;
}
.ga-note {
  font-size: 11px;
  color: var(--text-tertiary);
  font-style: italic;
  margin-top: 6px;
  line-height: 1.5;
}

/* === Nearby schools === */
.schools-list {
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.school-item {
  display: flex;
  align-items: center;
  gap: 14px;
  background: var(--bg-surface);
  border-radius: var(--radius-md);
  padding: 12px 16px;
}
.school-rating {
  width: 46px; height: 46px;
  border-radius: 50%;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  color: white;
  font-family: inherit;
  font-size: 17px;
  font-weight: 700;
  letter-spacing: -0.02em;
  font-variant-numeric: tabular-nums;
  flex-shrink: 0;
  line-height: 1;
  text-align: center;
}
.school-rating-of {
  display: block;
  font-size: 8px;
  font-weight: 600;
  opacity: 0.85;
  margin-top: 1px;
  letter-spacing: 0;
  line-height: 1;
}
.rating-good { background: var(--positive); }
.rating-ok { background: var(--accent); }
.rating-poor { background: #ff9500; }
.rating-low { background: var(--negative); }

.school-body { flex: 1; min-width: 0; }
.school-name {
  font-size: 14px;
  font-weight: 600;
  color: var(--text);
  letter-spacing: -0.005em;
}
.school-meta {
  font-size: 12px;
  color: var(--text-secondary);
  margin-top: 2px;
  font-variant-numeric: tabular-nums;
}
.schools-note {
  font-size: 11px;
  color: var(--text-tertiary);
  font-style: italic;
  margin-top: 12px;
  line-height: 1.5;
}

/* === Transit & commute === */
.transit-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
  gap: 12px;
  margin-bottom: 28px;
}
.transit-item {
  background: var(--bg-surface);
  border-radius: var(--radius-md);
  padding: 16px 18px;
}
.transit-type {
  font-size: 10px;
  text-transform: uppercase;
  letter-spacing: 0.12em;
  color: var(--accent);
  font-weight: 600;
  margin-bottom: 4px;
}
.transit-name {
  font-size: 15px;
  font-weight: 600;
  color: var(--text);
  letter-spacing: -0.01em;
}
.transit-desc {
  font-size: 13px;
  color: var(--text-secondary);
  margin: 4px 0 8px;
  line-height: 1.45;
}
.transit-distance {
  font-size: 13px;
  font-weight: 600;
  color: var(--text);
  font-variant-numeric: tabular-nums;
}
.transit-op {
  font-weight: 400;
  color: var(--text-tertiary);
}

/* Sub-sub-heading inside the Transit subsection. Smaller serif (not
   uppercase eyebrow) to stay consistent with the rest of the page's
   editorial typography — uppercase looked like an out-of-place label
   in an otherwise serif-led layout. Higher specificity than
   .detail-subsection h3 so this smaller treatment wins. */
h3.transit-subhead,
.detail-subsection h3.transit-subhead {
  font-family: var(--font-serif);
  font-size: 17px;
  font-weight: 500;
  letter-spacing: -0.005em;
  line-height: 1.2;
  color: var(--text);
  text-transform: none;
  margin: 20px 0 10px;
}
.commute-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
  gap: 10px;
}
.commute-item {
  display: flex;
  align-items: center;
  gap: 14px;
  background: var(--bg-surface);
  border-radius: var(--radius-md);
  padding: 14px 16px;
}
.commute-time {
  font-size: 22px;
  font-weight: 600;
  letter-spacing: -0.025em;
  color: var(--text);
  font-variant-numeric: tabular-nums;
  flex-shrink: 0;
}
.commute-unit {
  font-size: 13px;
  font-weight: 500;
  color: var(--text-tertiary);
  letter-spacing: 0;
}
.commute-body { min-width: 0; }
.commute-dest {
  font-size: 14px;
  font-weight: 600;
  color: var(--text);
}
.commute-via {
  font-size: 12px;
  color: var(--text-secondary);
  margin-top: 2px;
}
.commute-breakdown {
  font-size: 11.5px;
  color: var(--text-tertiary);
  margin-top: 4px;
  font-variant-numeric: tabular-nums;
}
.commute-note {
  font-size: 12.5px;
  color: var(--text-secondary);
  margin: -4px 0 10px;
}

/* === Climate & natural hazards === */
.risk-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: 12px;
}
.risk-item {
  background: var(--bg-surface);
  border-radius: var(--radius-md);
  padding: 16px 18px;
}
.risk-label {
  font-size: 11px;
  color: var(--text-tertiary);
  text-transform: uppercase;
  letter-spacing: 0.1em;
  font-weight: 600;
  margin-bottom: 6px;
}
.risk-value {
  font-size: 18px;
  font-weight: 600;
  letter-spacing: -0.01em;
}
.risk-note {
  font-size: 12px;
  color: var(--text-secondary);
  margin-top: 4px;
  line-height: 1.45;
}
.risk-low { color: var(--positive); }
.risk-medium { color: var(--warning); }
.risk-high { color: var(--negative); }

/* === Section header row (h2 + optional action button) === */
.section-head-row {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
  margin-bottom: 4px;
}
.section-head-row h2 { margin-bottom: 6px; }

/* === Reset overrides button (shown when ≥1 cell has been edited) ===
   Lives in .pnl-actions, a thin right-aligned bar sitting directly above
   the year-1 table. This keeps the action visually adjacent to the cells
   it resets — previous placement next to the H2 was too far away on
   mobile where the H2 wraps to two lines. */
.pnl-actions {
  display: flex;
  /* Left-aligned, sitting directly above the table — moved from the
     prior right-aligned position which floated the chip far from the
     cells it actually resets. The reduced bottom margin (negative
     pulls the chip closer to the wrap below) tightens the visual
     relationship. */
  justify-content: flex-start;
  margin-top: -6px;
  margin-bottom: 8px;
}
.reset-overrides-btn {
  background: var(--accent-bg);
  border: 1px solid var(--separator);
  color: var(--accent);
  font-size: 13px;
  font-weight: 500;
  letter-spacing: -0.005em;
  cursor: pointer;
  padding: 6px 12px;
  border-radius: 980px;
  transition: background 0.15s var(--ease), border-color 0.15s var(--ease);
}
.reset-overrides-btn:hover {
  background: rgba(47, 90, 63, 0.12);
  border-color: var(--accent);
}

/* === Editable cells in the year-one monthly breakdown ===
   Per designer audit: click-to-edit cells with subtle hairline on row hover,
   blue numerals as the override badge (no separate pill), per-row × reset
   appearing on row hover. */
.pnl-table .ed-cell {
  cursor: pointer;
  position: relative;
  user-select: none;
  transition: box-shadow 0.12s var(--ease);
  /* Persistent affordance: a subtle dotted underline so editable cells are
     spottable without hovering (the Year-1 copy points users to it). */
  text-decoration: underline dotted rgba(46, 90, 56, 0.45);
  text-underline-offset: 3px;
}
.pnl-table tr:hover .ed-cell {
  box-shadow: inset 0 0 0 1px var(--separator);
  border-radius: 4px;
}
.pnl-table .ed-cell:hover {
  box-shadow: inset 0 0 0 1px var(--accent);
}
.pnl-table .ed-cell:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: -2px;
  border-radius: 4px;
}
.pnl-table .ed-cell.ed-invalid {
  box-shadow: inset 0 0 0 2px var(--negative);
}
/* Round 116: missing HOA. When a condo/co-op has no HOA in the source data
   the HOA $ and %-of-rent cells read $0 — flag them red so the user is drawn
   to tap and enter the real fee. Clears automatically once an HOA override is
   set (the row is no longer "missing"). More specific than the touch-device
   accent treatment below, so it wins. */
.pnl-table .ed-cell.ed-cell-missing {
  color: var(--negative, #9b2c2c);
  font-weight: 700;
  background: rgba(155, 44, 44, 0.08);
  box-shadow: inset 0 0 0 1px rgba(155, 44, 44, 0.45);
  border-radius: 4px;
}
.pnl-table .ed-cell.ed-cell-missing:hover {
  box-shadow: inset 0 0 0 1.5px var(--negative, #9b2c2c);
}

/* === Editable cells: persistent affordance on touch devices ===
   Desktop users see the editability via :hover (inset hairline border).
   Touch devices have no hover state, so editable cells looked identical
   to read-only ones — users had no signal that vacancy/HOA/maintenance/
   capex could be tapped to override. Make the hover treatment persistent
   on touch devices: subtle blue tint + 1px accent inset reads as a form
   input. Override rows already signal themselves via blue numerals, so
   drop the input look there to avoid double-marking. */
@media (hover: none) {
  .pnl-table .ed-cell {
    box-shadow: inset 0 0 0 1px var(--accent);
    border-radius: 4px;
    background: rgba(47, 90, 63, 0.04);
  }
  .pnl-table tr.has-override .ed-cell {
    background: transparent;
  }
}

/* Override badge: blue numerals + 2px blue inset on the label cell. */
.pnl-table tr.has-override td:first-child {
  box-shadow: inset 2px 0 0 var(--accent);
  position: relative;
}
.pnl-table tr.has-override td:nth-child(2),
.pnl-table tr.has-override td:nth-child(4) {
  color: var(--accent);
  font-weight: 600;
}

/* Per-row × reset button — appears in the label cell on row hover. */
.pnl-table .row-reset {
  display: none;
  background: transparent;
  border: 0;
  color: var(--text-tertiary);
  cursor: pointer;
  font-size: 14px;
  line-height: 1;
  padding: 0 2px;
  margin-left: 6px;
  vertical-align: middle;
  border-radius: 4px;
  transition: color 0.12s var(--ease), background 0.12s var(--ease);
}
.pnl-table tr.has-override:hover .row-reset { display: inline-block; }
.pnl-table .row-reset:hover {
  color: var(--negative);
  background: rgba(201, 27, 27, 0.08);
}

/* Inline-edit input. type="text" + inputmode so iOS Safari behaves. */
.ed-input {
  width: 100%;
  background: var(--bg);
  border: 2px solid var(--accent);
  border-radius: 6px;
  padding: 4px 8px;
  font: inherit;
  font-variant-numeric: tabular-nums;
  color: var(--text);
  text-align: right;
  outline: none;
}

/* === Embedded Google Maps preview on the detail page === */
.map-embed {
  position: relative;
  border-radius: var(--radius-lg);
  overflow: hidden;
  background: var(--bg-surface);
  border: 1px solid var(--separator-soft);
  box-shadow: var(--shadow-xs);
}
.map-embed iframe {
  display: block;
  width: 100%;
  max-width: 100%;
}
@media (max-width: 768px) {
  .map-embed iframe { height: 280px; }
}
.map-zoom-controls {
  position: absolute;
  top: 12px;
  right: 12px;
  display: flex;
  flex-direction: column;
  gap: 6px;
  z-index: 2;
}
.map-zoom-btn {
  width: 36px;
  height: 36px;
  border-radius: 8px;
  border: 1px solid var(--separator-soft);
  background: rgba(255, 255, 255, 0.96);
  color: var(--text);
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.12);
  transition: background 0.15s ease, transform 0.05s ease;
}
.map-zoom-btn:hover {
  background: #fff;
}
.map-zoom-btn:active {
  transform: scale(0.96);
}
.map-zoom-btn:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

/* === External link (e.g. "View on Google Maps ↗") === */
.external-link {
  color: var(--accent);
  text-decoration: none;
  font-weight: 500;
  white-space: nowrap;
  transition: color 0.15s var(--ease);
}
.external-link:hover { color: var(--accent-hover); text-decoration: underline; }

/* === Saved-deals: topbar button + count === */
.saved-toggle {
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: 6px;
}
.saved-toggle .saved-count {
  font-size: 11px;
  font-weight: 600;
  color: var(--text-tertiary);
  min-width: 14px;
  text-align: center;
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.005em;
  /* Hidden by default. The .has-items class (set by saved-ui.js when
     count > 0) flips it back to visible with the accent badge styling.
     "Saved 0" before any user action read like a placeholder feature
     waiting for an account, per the first-time-visitor audit. */
  display: none;
}
.saved-toggle.has-items .saved-count {
  display: inline-block;
  background: var(--accent);
  color: white;
  padding: 1px 7px;
  border-radius: 980px;
  font-size: 10px;
}

/* === Recommended offer row (inside fin-card on listing page) ===
   Surfaces the price that hits the user's target CoC + the resulting
   monthly cash flow at that price, with a one-click "Use this" button.
   Sits between the cash-flow display and the Investment Assumptions
   button. Hidden when the user is already within $5k of the
   recommended price. */
.rec-offer {
  margin-top: 14px;
  padding: 12px 14px;
  background: var(--bg-surface);
  border: 1px solid var(--separator);
  border-radius: var(--radius-md);
}
/* Applied state: the user selected this offer. Subtle accent border + an
   "Applied" badge; the action flips to a neutral "Reset to list". */
.rec-offer-is-applied { border-color: rgba(46, 90, 56, 0.35); }
.rec-offer-badge {
  display: inline-flex;
  align-items: center;
  margin-left: 8px;
  padding: 2px 8px;
  border-radius: 980px;
  background: var(--accent);
  color: #fff;
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
}
.rec-offer-apply.rec-offer-reset {
  background: transparent;
  color: var(--text-secondary);
  border: 1px solid var(--separator);
}
.rec-offer-apply.rec-offer-reset:hover { background: var(--bg-hover); border-color: var(--text-tertiary); }
.rec-offer-head { margin-bottom: 4px; }
.rec-offer-eyebrow {
  font-size: 10.5px;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: #2E5A38;
}
.rec-offer-body {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
}
.rec-offer-numbers { min-width: 0; flex: 1; }
.rec-offer-price {
  font-size: 20px;
  font-weight: 600;
  color: var(--text);
  letter-spacing: -0.015em;
  line-height: 1.15;
}
.rec-offer-cf {
  font-size: 13px;
  font-weight: 500;
  margin-top: 2px;
  letter-spacing: -0.005em;
}
.rec-offer-cf-sub {
  color: var(--text-tertiary);
  font-weight: 400;
}
.rec-offer-sub {
  font-size: 11.5px;
  color: var(--text-tertiary);
  margin-top: 4px;
  line-height: 1.4;
}
.rec-offer-apply {
  flex-shrink: 0;
  background: #2E5A38;
  color: white;
  border: 0;
  border-radius: var(--radius-sm);
  padding: 8px 14px;
  font-size: 13px;
  font-weight: 500;
  letter-spacing: -0.005em;
  cursor: pointer;
  transition: background 0.15s var(--ease), transform 0.08s var(--ease);
}
.rec-offer-apply:hover { background: #1F4326; }
.rec-offer-apply:active { transform: scale(0.97); }
/* Advisory note that appears below the recommended-offer body when
   guardrails fire (stepped-down CoC, capped at 20% below list, etc.) */
.rec-offer-note {
  margin-top: 10px;
  padding: 8px 10px;
  font-size: 12px;
  line-height: 1.45;
  border-radius: 6px;
  letter-spacing: -0.003em;
}
.rec-offer-note strong { font-weight: 600; }
.rec-offer-note-info {
  background: rgba(15, 31, 44, 0.05);
  color: var(--text-secondary);
  border: 1px solid rgba(15, 31, 44, 0.08);
}
.rec-offer-note-warn {
  background: rgba(184, 133, 40, 0.08);
  color: #6b4e15;
  border: 1px solid rgba(184, 133, 40, 0.22);
}

/* === Compare-properties prompt (inside fin-card on listing page) ===
   Bridges the discoverability gap users hit: they save individual
   listings but never realize 2+ saved unlocks side-by-side compare.
   Three states driven by savedList().length:
     0 saved          → hint copy, no button
     1 saved          → hint to save one more
     2+ saved         → enabled button that opens the saved tray
   The button reuses [data-saved-toggle] so the existing saved-ui.js
   listener picks it up — no new wire-up needed. */
.compare-prompt {
  margin-top: 12px;
  padding: 10px 12px;
  border-radius: var(--radius-sm);
  font-size: 12.5px;
  line-height: 1.45;
  letter-spacing: -0.005em;
}
.compare-prompt-hint {
  background: rgba(46, 90, 56, 0.05);
  border: 1px dashed rgba(46, 90, 56, 0.25);
  color: var(--text-secondary);
  display: flex; align-items: flex-start; gap: 8px;
}
.compare-prompt-icon {
  color: #2E5A38;
  flex-shrink: 0;
  margin-top: 1px;
}
.compare-prompt-ready { padding: 0; background: transparent; border: 0; }
.compare-prompt-btn {
  display: flex; width: 100%;
  align-items: center; justify-content: center;
  gap: 8px;
  background: white;
  color: #2E5A38;
  border: 1px solid rgba(46, 90, 56, 0.35);
  border-radius: var(--radius-md);
  padding: 11px 16px;
  font-size: 14px;
  font-weight: 500;
  letter-spacing: -0.005em;
  cursor: pointer;
  transition: background 0.15s var(--ease), border-color 0.15s var(--ease);
}
.compare-prompt-btn:hover {
  background: rgba(46, 90, 56, 0.04);
  border-color: #2E5A38;
}

/* === Verdict chip (Pass / Weak / Hold / Go) + Co-op chip =========
   Driven by getVerdict() in finance.js using user-defined cash-flow
   thresholds. Round 40: moved out of the photo overlay into the
   card body (.listing-pills row) so it reads as a labeled signal
   rather than a sticker. Color tokens map to deal-quality so the
   chip is recognizable at a glance. */
.listing-pills {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin: 4px 0 6px;
}
.verdict-chip {
  display: inline-flex;
  align-items: center;
  height: 22px;
  padding: 0 9px;
  border-radius: 11px;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  color: white;
  user-select: none;
}
/* Round 52: lighter, desaturated palette per senior PD review.
   Hue gap preserved so the red→orange→amber→green ranking still
   reads instantly; saturation lifted toward Linear/Stripe softness.
   All fills hit AA contrast on white text. Round 106: amber + orange
   darkened (#A67A2E→#8A6420 ≈5.0:1, #C76A3A→#A8501F ≈5.2:1) — the prior
   values were 3.86:1 / 3.78:1, failing AA for the 11px chip text. */
.verdict-chip.verdict-forest  { background: #3F7A4E; }
.verdict-chip.verdict-amber   { background: #8A6420; }
.verdict-chip.verdict-orange  { background: #A8501F; }
.verdict-chip.verdict-red     { background: #A23A3A; }
.verdict-chip.verdict-neutral { background: #6a7480; }

/* Round 49: overlay variant. Positioned at top-right of the
   .listing-card (the positioned ancestor). In list mode this lands
   on the body area (right side, clear of the photo on the left).
   In grid mode the photo is on top of the body, so the same
   absolute position lands on top of the photo — preserves the
   previous grid-view look. */
.verdict-chip-overlay {
  position: absolute;
  top: 12px; right: 14px;
  z-index: 2;
  cursor: help;
}
/* Round 107: the overlay can now hold the verdict chip PLUS a "No HOA"
   pill, laid out as a right-aligned flex row. */
.verdict-overlay-group {
  display: flex;
  align-items: center;
  gap: 5px;
  flex-direction: row-reverse;  /* verdict chip stays rightmost; No-HOA sits left of it */
}
/* In grid view, the chip sits on the photo (no card surface behind
   it) — give it a subtle shadow so it lifts off the image. */
body.view-grid .verdict-chip-overlay {
  top: 8px; right: 8px;
}
body.view-grid .verdict-chip-overlay .verdict-chip,
body.view-grid .verdict-chip-overlay .no-hoa-pill {
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.25);
}
.listing-metric.has-tip { cursor: help; }

/* Round 107: "No HOA" data-gap pill shown beside the verdict on cards,
   the map popup, and the detail page for condo/co-op listings missing an
   HOA fee. Amber/warning tone (distinct from the verdict colors), small,
   so it reads as a caveat without competing with the verdict label. */
.no-hoa-pill {
  display: inline-flex;
  align-items: center;
  font-size: 9.5px;
  font-weight: 700;
  letter-spacing: 0.03em;
  text-transform: uppercase;
  /* Round 108: brand red (--negative #9b2c2c) as an OUTLINE pill so it
     reads as a caution flag yet stays distinct from the solid-red "Pass"
     verdict chip (filled #A23A3A, white text) it can sit beside. */
  color: #9b2c2c;
  background: rgba(155, 44, 44, 0.08);
  border: 1px solid rgba(155, 44, 44, 0.30);
  border-radius: 980px;
  padding: 3px 7px;
  white-space: nowrap;
  cursor: help;
}

/* Co-op chip — neutral slate so it reads as a property-type signal,
   not a deal-quality signal. Pairs alongside the verdict chip on
   list/grid cards and the property-detail summary card. */
.coop-chip {
  display: inline-flex;
  align-items: center;
  height: 22px;
  padding: 0 9px;
  border-radius: 11px;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  color: #324050;
  background: rgba(50, 64, 80, 0.10);
  border: 1px solid rgba(50, 64, 80, 0.18);
  user-select: none;
}

/* Small variants — used at the TOP of the property-detail fin-card
   (Round 41) so the user sees the at-a-glance verdict + property
   flag before reading the price/rent math below. Tighter geometry
   so they read as labels, not headlines. */
.fin-card-pills {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}
/* Round 42: pills sit INSIDE the .fin-card-cta cash-flow block,
   directly above the "Monthly cash flow" label. Small bottom margin
   so they don't crowd the label. */
.fin-card-pills-cf {
  margin: 0 0 6px;
}
.verdict-chip-sm,
.coop-chip-sm {
  height: 20px;
  padding: 0 8px;
  border-radius: 10px;
  font-size: 10.5px;
  letter-spacing: 0.05em;
}

/* === Card heart button (overlay on photo) === */
.listing-photo-wrap { position: relative; }
/* Round 47: heart moved top-right → top-LEFT to make room for the
   verdict chip in the top-right corner (.verdict-chip-overlay).
   The chip carries the primary signal ("is this a deal?"), so it
   gets the dominant top-right corner; heart is secondary. */
.card-save-btn {
  position: absolute;
  /* Round 108: the heart is now a direct child of .listing-card (was in
     the photo). On the desktop list card the photo sits at the card's
     padding origin (12px/16px), so 18/22 keeps the heart visually on the
     photo's top-left exactly as before. Grid + mobile override below. */
  top: 18px; left: 22px;
  width: 28px; height: 28px;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.92);
  color: var(--text-tertiary);
  border: 0;
  cursor: pointer;
  display: grid; place-items: center;
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.14);
  transition: transform 0.15s var(--ease), background 0.15s var(--ease), color 0.15s var(--ease);
  z-index: 3;
}
/* Grid card: photo is full-width on top with zero card padding, so the
   heart sits at the photo's top-left corner. */
body.view-grid .card-save-btn { top: 8px; left: 8px; }
.card-save-btn:hover {
  background: #fff;
  color: var(--negative);
  transform: scale(1.08);
}
.card-save-btn.saved {
  background: #fff;
  color: var(--negative);
}

/* === Detail-page save button (next to H1 in detail-header) === */
.detail-save-btn {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  margin-top: 14px;
  /* Bumped vertical padding so the resulting button height clears the
     ~36px mobile tap-target floor with margin to spare. */
  padding: 10px 16px;
  background: var(--bg-surface);
  color: var(--text-secondary);
  border: 0;
  border-radius: 980px;
  font-size: 14px;
  font-weight: 500;
  letter-spacing: -0.005em;
  cursor: pointer;
  transition: background 0.15s var(--ease), color 0.15s var(--ease);
}
.detail-save-btn:hover { background: var(--bg-hover); }
.detail-save-btn.saved {
  background: rgba(201, 27, 27, 0.08);
  color: var(--negative);
}
.detail-save-btn svg { flex-shrink: 0; }

/* === Saved tray (reuses .assumptions-panel base; adds saved-specific styles) === */
/* Flex column layout so the saved list scrolls within the panel and the
   action row (Clear / Compare) stays pinned at the bottom — visible
   regardless of how many saved deals the user has. */
.saved-panel { width: 460px; display: flex; flex-direction: column; }
/* Round 25: explicit horizontal padding on every direct child of
   the saved-panel. The .assumptions-panel base ships with padding: 0
   (it expects an inner .ap-scroll wrapper to carry the padding); the
   saved tray skipped that wrapper because the scroll context here is
   #saved-list, not a generic .ap-scroll. Result: every section read
   flush against the panel edges (the user's complaint: "everything
   is just filling the container panel"). Padding 28px matches the
   assumptions-panel's .ap-scroll inset so the two trays read at the
   same horizontal rhythm side-by-side. The header/sub get top padding;
   the actions row gets bottom padding inset for the safe-area. */
.saved-panel .ap-head {
  flex: 0 0 auto;
  padding: 24px 28px 0;
}
.saved-panel .ap-sub {
  flex: 0 0 auto;
  padding: 0 28px;
}
.saved-panel #saved-list {
  flex: 1 1 auto;
  overflow-y: auto;
  min-height: 0;
  padding: 0 28px;
}
.saved-panel #saved-empty {
  padding: 0 28px;
}
.saved-panel .ap-actions {
  flex: 0 0 auto;
  position: sticky;
  bottom: 0;
  background: var(--bg);
  z-index: 2;
  padding: 12px 28px calc(16px + env(safe-area-inset-bottom, 0px));
  border-top: 1px solid var(--separator-soft);
}
/* Mobile parity: shrink to the same 18px horizontal inset the
   assumptions-panel uses on mobile (line ~6573 block). */
@media (max-width: 768px) {
  .saved-panel .ap-head { padding: 20px 18px 0; }
  .saved-panel .ap-sub,
  .saved-panel #saved-list,
  .saved-panel #saved-empty { padding-left: 18px; padding-right: 18px; }
  .saved-panel .ap-actions {
    padding: 12px 18px calc(16px + env(safe-area-inset-bottom, 0px));
  }
}
.saved-list {
  display: flex;
  flex-direction: column;
  gap: 10px;
  margin-bottom: 16px;
}
/* Round 24 (saved panel polish): tighter padding + a hairline border
   for clearer card edges. The previous solid-fill card on a slightly
   different cream washed into the panel background and the cards
   read as a single textural blob; the border restores per-card
   silhouette without adding visual weight. */
.saved-item {
  display: flex;
  flex-direction: column;
  background: var(--bg-surface);
  border-radius: var(--radius-md);
  border: 1px solid var(--separator-soft);
  overflow: hidden;
  /* Round 82 fix: the parent .saved-list is a flex column with auto-
     overflow, but without flex-shrink: 0 the items get squashed to fit
     instead of growing to their natural content height. Result: the
     metrics row hung off the bottom of the visible card and got clipped
     by overflow: hidden. Flex-shrink 0 keeps each card full height and
     lets .saved-list scroll. */
  flex-shrink: 0;
}
/* Round 80: photo header on saved cards so users can scan multiple
   photos right in the tray. Uses the same .photo-carousel widget as
   the listing cards. */
.saved-item-photo {
  position: relative;
  height: 140px;
  background: var(--bg);
  border-bottom: 1px solid var(--separator-soft);
}
.saved-item-main {
  display: flex;
  gap: 12px;
  padding: 12px 14px;
}
/* Round 80: financing block is now read-only on saved cards. The
   preset-row buttons are removed; we just surface the financing
   chosen at save-time. */
.saved-item-financing-readonly {
  display: flex;
  align-items: baseline;
  gap: 6px;
  flex-wrap: wrap;
  font-size: 12px;
  padding: 6px 0;
  margin-top: 4px;
}
.saved-item-financing-readonly .saved-financing-label {
  font-size: 10.5px;
  font-weight: 600;
  color: var(--text-tertiary);
  letter-spacing: 0.04em;
  text-transform: uppercase;
}
.saved-item-financing-readonly .saved-financing-name {
  font-weight: 600;
  color: var(--text);
}
.saved-item-financing-readonly .saved-financing-sub {
  color: var(--text-tertiary);
  font-size: 11.5px;
}
.saved-checkbox {
  display: flex;
  align-items: flex-start;
  padding-top: 2px;
  cursor: pointer;
}
.saved-checkbox input {
  width: 16px; height: 16px;
  margin: 0;
  accent-color: var(--accent);
  cursor: pointer;
}
.saved-item-body {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  /* Round 24: bumped from 4 → 6 so address / meta / financing / metrics
     groups each get a clear vertical beat. Was too cramped, reading as
     one undifferentiated text wall. */
  gap: 6px;
}
.saved-item-head {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  gap: 8px;
}
.saved-item-head strong {
  font-size: 14px;
  font-weight: 600;
  color: var(--text);
  letter-spacing: -0.005em;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  flex: 1;
  min-width: 0;
}
/* Round 24 (mobile audit HIGH): keep visual proportion small (24×24
   inner) but pad the hit target to 36×36 on touch viewports — the
   delete-X was a sub-touch-floor target right next to the address
   link, making it easy to mis-tap on the densely packed Saved tray. */
.saved-item-head .btn-icon {
  width: 24px; height: 24px;
  display: inline-grid; place-items: center;
}
@media (max-width: 768px) {
  .saved-item-head .btn-icon {
    width: 36px; height: 36px;
  }
}
.saved-item-meta {
  font-size: 12px;
  color: var(--text-secondary);
  font-variant-numeric: tabular-nums;
}
.saved-item-metrics {
  display: flex;
  gap: 14px;
  margin-top: 2px;
  font-size: 13px;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}
.saved-item-snapshot {
  font-size: 11px;
  color: var(--text-tertiary);
  font-variant-numeric: tabular-nums;
}
/* Round 84: footer strip groups the "Saved [date]" timestamp and
   "View detail →" action onto a single row, split between the two
   edges. Previously stacked vertically along with the Add-note
   trigger, which looked busy. The note trigger keeps its own line
   above (since note state is variable-height). */
.saved-item-footer {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding-top: 6px;
  margin-top: 4px;
  border-top: 1px solid var(--separator-soft);
}
.saved-item-view {
  padding: 0;
  font-size: 13px;
  font-weight: 500;
  color: var(--accent);
}
.saved-item-view:hover { color: #244530; }
/* Financing block in the saved tray — shows the active scenario name +
   inline picker so investors can swap (Conventional / DSCR / FHA / etc.)
   without leaving the tray. */
/* Round 24 (saved panel polish): the financing chunk was the loudest
   block on each card — eyebrow label + scenario name + sub-detail
   + 5 chips + hint paragraph. Compacted: drop the eyebrow (the
   chip row already implies "Financing"), tuck the sub-detail inline
   with the chips, and shrink the hint copy. Visual hierarchy of
   the card now flows naturally address → metrics → scenario chips
   → note → date instead of competing with the address for attention. */
.saved-item-financing {
  margin-top: 2px;
  padding: 8px 10px;
  background: var(--bg);
  border-radius: var(--radius-sm);
  border: 1px solid var(--separator-soft);
}
.saved-financing-row {
  display: flex; flex-wrap: wrap; align-items: baseline; gap: 4px 8px;
  margin-bottom: 4px;
}
.saved-financing-label {
  font-size: 9.5px;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-tertiary);
}
.saved-financing-name {
  font-size: 12.5px;
  font-weight: 600;
  color: var(--text);
}
.saved-financing-sub {
  font-size: 11px;
  color: var(--text-tertiary);
  font-variant-numeric: tabular-nums;
}
.saved-preset-row {
  display: flex; flex-wrap: wrap; gap: 4px;
}
.saved-preset {
  flex: 0 0 auto;
  padding: 3px 9px;
  font-size: 11px;
  font-weight: 500;
  border-radius: 980px;
  border: 1px solid var(--separator);
  background: var(--bg-surface);
  color: var(--text-secondary, var(--text));
  cursor: pointer;
  transition: border-color 0.12s var(--ease), background 0.12s var(--ease), color 0.12s var(--ease);
}
.saved-preset:hover { border-color: var(--accent); color: var(--text); }
.saved-preset.active {
  background: var(--accent);
  border-color: var(--accent);
  color: #fff;
}
/* Round 27: removed the dashed-outline "modified" indicator per user
   feedback. The mixed visual states (some pills with dashed outline,
   some plain green) read as inconsistent across saved-deal cards.
   The modified state is still communicated via the live sub-detail
   below the financing row ("Conventional · 6.75% · 28% down · 30-yr"
   shows the actual tweaked values), so the dashed accent was redundant
   visual noise. Keep just the solid green for the active scenario. */
.saved-financing-hint {
  font-size: 10.5px;
  color: var(--text-tertiary);
  margin-top: 4px;
  line-height: 1.35;
}
.saved-financing-hint strong { color: var(--text-secondary, var(--text)); font-weight: 600; }

/* === Personal note on saved tray card === */
.saved-note-trigger {
  align-self: flex-start;
  display: inline-flex; align-items: center; gap: 4px;
  margin-top: 4px;
  padding: 4px 10px;
  font-size: 11px;
  color: var(--text-tertiary);
  background: transparent;
  border: 1px dashed var(--separator);
  border-radius: 980px;
  cursor: pointer;
}
.saved-note-trigger:hover { border-color: var(--accent); color: var(--accent); }
.saved-note {
  margin-top: 6px;
  padding: 8px 10px;
  background: rgba(255, 244, 200, 0.35);
  border-left: 3px solid #b8730a;
  border-radius: 0 4px 4px 0;
}
.saved-note-label {
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: #8a5a09;
  margin-bottom: 2px;
}
.saved-note-preview {
  font-size: 12.5px;
  color: var(--text);
  line-height: 1.4;
  white-space: pre-wrap;
  cursor: text;
}
.saved-note-editing { background: var(--bg); border-left-color: var(--accent); padding: 10px; }
.saved-note-input {
  width: 100%;
  padding: 8px 10px;
  border: 1px solid var(--separator);
  border-radius: var(--radius-md);
  font-family: inherit;
  font-size: 13px;
  line-height: 1.45;
  background: var(--bg);
  resize: vertical;
}
.saved-note-input:focus { outline: none; border-color: var(--accent); box-shadow: 0 0 0 3px rgba(47, 90, 63, 0.08); }
.saved-note-foot {
  display: flex; justify-content: space-between;
  font-size: 10.5px;
  color: var(--text-tertiary);
  margin-top: 4px;
}

/* === Compare table notes cell === */
.compare-note-cell { text-align: left !important; }
.compare-note-preview {
  font-size: 12.5px;
  white-space: normal;
  line-height: 1.4;
  display: inline-block;
  max-width: 32ch;
}
.compare-note-empty { color: var(--text-tertiary); font-style: italic; font-size: 11.5px; }
.compare-note-input {
  width: 100%;
  min-width: 180px;
  padding: 6px 8px;
  font-family: inherit;
  font-size: 12.5px;
  line-height: 1.4;
  border: 1px solid var(--accent);
  border-radius: 6px;
  resize: vertical;
  box-shadow: 0 0 0 3px rgba(47, 90, 63, 0.08);
  outline: none;
}
/* Empty state — centered with heart illustration + CTA. Replaces the
   former tiny single-line message at the bottom of the panel. The
   illustration uses currentColor so it inherits a muted forest tint;
   the CTA is a text link that closes the tray and returns the user
   to the map/list to start saving. */
.saved-empty {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  text-align: center;
  padding: 64px 24px;
  /* Push the empty state vertically into the middle of the panel so
     the heart + headline read as the destination, not a footer. */
  min-height: calc(100vh - 240px);
  gap: 0;
}
.saved-empty-illustration {
  width: 80px; height: 80px;
  display: grid; place-items: center;
  border-radius: 50%;
  background: rgba(46, 90, 56, 0.07);
  color: #2E5A38;
  margin-bottom: 20px;
}
.saved-empty-illustration svg { display: block; }
.saved-empty-title {
  /* Round 106: was 'Newsreader' (never imported → rendered as Georgia
     serif, off-brand in the Apple-light app). Use the brand stack. */
  font-family: var(--font-sans);
  font-size: 22px;
  font-weight: 600;
  letter-spacing: -0.01em;
  color: var(--text);
  margin: 0 0 8px;
}
.saved-empty-sub {
  font-size: 14px;
  line-height: 1.55;
  color: var(--text-secondary);
  max-width: 36ch;
  margin: 0 0 24px;
}
.saved-empty-cta {
  background: transparent;
  border: 0;
  padding: 0;
  font-size: 14px;
  font-weight: 500;
  color: #2E5A38;
  cursor: pointer;
  border-bottom: 1px solid rgba(46, 90, 56, 0.3);
  padding-bottom: 2px;
  transition: color 0.15s var(--ease), border-color 0.15s var(--ease);
}
.saved-empty-cta:hover {
  color: #1F4326;
  border-bottom-color: #2E5A38;
}

/* === Compare modal === */
.compare-backdrop { z-index: 48; }
.compare-modal {
  position: fixed; inset: 0; z-index: 50;
  display: grid; place-items: center;
  padding: 40px 24px;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.18s var(--ease);
}
.compare-modal.open { opacity: 1; pointer-events: auto; }
.compare-card {
  background: var(--bg);
  border-radius: var(--radius-xl);
  box-shadow: var(--shadow-lg);
  max-width: 100%;
  max-height: 100%;
  width: 1080px;
  display: flex;
  flex-direction: column;
  overflow: hidden;
}
.compare-head {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  gap: 16px;
  padding: 20px 24px;
  border-bottom: 1px solid var(--separator-soft);
}
.compare-head-left { min-width: 0; }
.compare-head h2 {
  font-size: 22px;
  font-weight: 600;
  letter-spacing: -0.022em;
  color: var(--text);
}
.compare-sub {
  margin-top: 4px;
  font-size: 12px;
  color: var(--text-tertiary);
  line-height: 1.4;
  /* No max-width — the 64ch cap was forcing the intro copy to wrap to
     two rows in the 1080px-wide modal even when the copy itself fit on
     one line at this width. */
}
.compare-head-actions {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-shrink: 0;
}
/* Round 57: lift the Save-as-PDF button from a near-invisible
   .btn-ghost into a clear secondary CTA. Forest-green outlined
   pill matching the accent so it reads as the affirmative action
   in the header, distinct from the icon-only close X. */
.compare-print-btn {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  height: 34px;
  padding: 0 14px;
  border-radius: 8px;
  border: 1px solid rgba(46, 90, 56, 0.35);
  background: white;
  color: #2E5A38;
  font-size: 13px;
  font-weight: 500;
  letter-spacing: -0.005em;
  cursor: pointer;
  transition: background 0.12s var(--ease), border-color 0.12s var(--ease), color 0.12s var(--ease);
}
.compare-print-btn:hover {
  background: rgba(46, 90, 56, 0.06);
  border-color: #2E5A38;
  color: #1F4326;
}
.compare-print-btn svg { flex-shrink: 0; }
.compare-body {
  /* Round 11: top padding removed so the sticky thead pins flush to
     the body's top edge (was 16px gap that let other rows visibly
     slide IN ABOVE the sticky header on scroll — confusing). Body
     now starts at the thead; side+bottom padding preserved. */
  padding: 0 24px 24px;
  overflow: auto;
  flex: 1;
}
/* Restore a subtle top spacer INSIDE the table on the first body row
   so it doesn't sit jammed against the dark thead bar. */
.compare-table tbody tr:first-child td { padding-top: 16px; }
.compare-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 13px;
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.005em;
}
.compare-table th,
.compare-table td {
  padding: 12px 14px;
  /* Center-align numeric cells; row-label column stays left below. */
  text-align: center;
  border-bottom: 1px solid var(--separator-soft);
  white-space: nowrap;
}
.compare-table thead th {
  /* Round 10: distinguish the property-address header row from the
     editable input cells below with a clearly darker background.
     Previously the header used --bg (cream) — identical to the table
     surface, so when sticky-scrolling it read as just another empty
     row sliding over the body. Now it's a dark slate that reads as a
     proper "this is the column key, not an input" hierarchy.
     The address links inside use a high-contrast cream-on-dark so
     they're clearly tappable. */
  background: var(--text);          /* dark charcoal — opposite of body bg */
  color: rgba(250, 247, 241, 0.7);  /* sub-label rows (none currently, but keeps contrast) */
  font-size: 11px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  position: sticky;
  top: 0;
  z-index: 3;                         /* above any future section dividers */
  /* Stronger shadow than --separator since the contrast border is
     less obvious against the dark fill. */
  box-shadow: 0 2px 6px rgba(26, 33, 40, 0.18);
  /* Bump padding so the address row reads as a bar, not a cell. */
  padding-top: 14px;
  padding-bottom: 14px;
}
/* Property-address links in the header — cream-on-charcoal, weighted
   so they stand out as the actual content of the row. */
.compare-table thead th a {
  color: #faf7f1;
  font-size: 13px;
  font-weight: 600;
  text-transform: none;          /* addresses keep proper case */
  letter-spacing: -0.005em;
  text-decoration: none;
}
.compare-table thead th a:hover {
  text-decoration: underline;
  text-decoration-color: var(--sage, #A5B5A3);
  text-underline-offset: 3px;
}
.compare-table .compare-label {
  text-align: left;
  color: var(--text-secondary);
  font-weight: 500;
  min-width: 170px;
}
.compare-table .compare-best {
  background: var(--positive-bg);
  color: var(--positive);
  font-weight: 600;
}
/* Editable per-listing cell — gets a subtle dashed accent border so the
   user can see at a glance which cells they can click to override. */
.compare-table .compare-editable {
  cursor: pointer;
  position: relative;
  border: 1px dashed rgba(47, 90, 63, 0.32);
  border-radius: 6px;
  transition: background 0.15s ease, border-color 0.15s ease;
}
.compare-table .compare-editable:hover,
.compare-table .compare-editable:focus-visible {
  background: var(--accent-bg);
  border-color: var(--accent);
  outline: none;
}
.compare-table .compare-editable .compare-edit-icon {
  margin-left: 6px;
  color: var(--accent);
  opacity: 0.55;
  vertical-align: -1px;
  transition: opacity 0.15s ease;
}
.compare-table .compare-editable:hover .compare-edit-icon { opacity: 1; }
.compare-table .compare-editable.is-overridden {
  background: var(--accent-bg);
  border-style: solid;
  border-color: var(--accent);
  font-weight: 600;
  color: var(--accent);
}
.compare-edit-input {
  width: 100%;
  text-align: center;
  background: var(--bg);
  border: 2px solid var(--accent);
  border-radius: 6px;
  padding: 4px 6px;
  font: inherit;
  color: var(--text);
  font-variant-numeric: tabular-nums;
  outline: none;
}
.compare-table .compare-divider td {
  background: var(--bg-surface);
  font-size: 11px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--text-tertiary);
  text-align: left;
  padding: 8px 14px;
  border-bottom: 1px solid var(--separator);
}
/* === Demo pill + disclaimer modal === */
.demo-pill {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 4px 10px;
  margin-left: 4px;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--text-tertiary);
  background: transparent;
  border: 1px solid var(--separator);
  border-radius: 999px;
  cursor: pointer;
  transition: color 0.15s ease, border-color 0.15s ease, background 0.15s ease;
}
.demo-pill:hover {
  color: var(--accent);
  border-color: var(--accent);
  background: var(--accent-bg);
}
.disclaimer-backdrop { z-index: 48; }
.disclaimer-modal {
  position: fixed; inset: 0; z-index: 50;
  display: grid; place-items: center;
  padding: 40px 24px;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.18s var(--ease);
}
.disclaimer-modal.open { opacity: 1; pointer-events: auto; }
.disclaimer-card {
  background: var(--bg);
  border-radius: var(--radius-xl);
  box-shadow: var(--shadow-lg);
  width: 560px; max-width: 100%; max-height: 100%;
  display: flex; flex-direction: column;
  overflow: hidden;
}
.disclaimer-head {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 20px 26px;
  border-bottom: 1px solid var(--separator-soft);
}
.disclaimer-head h2 {
  font-family: var(--font-serif);
  font-size: 24px; font-weight: 500;
  letter-spacing: -0.012em;
  color: var(--text);
}
.disclaimer-body {
  /* Round 11: top padding reduced from 20 → 8 so the brand lockup
     above flows into the body copy as one connected block, not two
     stacked panes with empty space between. */
  padding: 8px 26px 26px;
  overflow-y: auto;
  color: var(--text-secondary);
  font-size: 14px;
  line-height: 1.6;
}
.disclaimer-body p { margin-bottom: 12px; }
.disclaimer-body p:last-child { margin-bottom: 0; }
.disclaimer-body strong { color: var(--text); font-weight: 600; }

@media (max-width: 920px) {
  .demo-pill span { display: none; }
  .demo-pill { padding: 6px; }
}

/* === First-visit welcome / onboarding splash === */
.welcome-backdrop { z-index: 55; }
.welcome-modal {
  position: fixed; inset: 0; z-index: 56;
  display: grid; place-items: center;
  padding: 40px 24px;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.22s var(--ease);
}
.welcome-modal.open { opacity: 1; pointer-events: auto; }
.welcome-card {
  position: relative;
  background: var(--bg);
  border-radius: var(--radius-xl);
  box-shadow: var(--shadow-lg);
  width: 560px; max-width: 100%; max-height: calc(100vh - 80px);
  padding: 36px 32px 28px;
  display: flex; flex-direction: column;
  overflow-y: auto;
}
.welcome-close-icon {
  position: absolute;
  /* env(safe-area-inset-top) so the close button clears the iPhone
     Dynamic Island / notch when the welcome card hits the full
     viewport on small screens. Falls back to 14px when env() is
     unsupported. */
  top: calc(14px + env(safe-area-inset-top, 0px));
  right: 14px;
}
/* Round 10: Brand-lockup variants for the welcome + about modals.
   Replaces the old .welcome-icon (logo-only) with a full
   horizontal lockup: logo + deelva wordmark + "Signal over noise"
   tagline — mirrors the topbar so the modals read as brand surfaces.
   Round 11: tightened the margin-bottom so the lockup feels integrated
   with the body text below, not isolated as a separate hero strip. */
.welcome-brand,
.disclaimer-brand {
  display: flex;
  align-items: center;
  gap: 12px;
  margin-bottom: 8px;
}
.welcome-brand-logo,
.disclaimer-brand-logo {
  flex-shrink: 0;
}
/* The welcome lockup must read identically to the home header: 52px mark, the
   same -2px text nudge, and a 12px muted-gray tagline (see the
   .welcome-brand-tagline rule below). */
.welcome-brand-logo { width: 52px; height: 52px; }
.welcome-brand-text,
.disclaimer-brand-text {
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.welcome-brand-text { margin-top: -2px; }
.welcome-brand-name,
.disclaimer-brand-name {
  font-family: "Satoshi", var(--font-sans);
  font-weight: 700;
  font-size: 22px;
  line-height: 1;
  letter-spacing: 0.10em;
  text-transform: uppercase;
  color: var(--text);
  padding-right: 0.10em;
}
.welcome-brand-tagline,
.welcome-brand-name { line-height: 1; }
.welcome-brand-tagline {
  line-height: 1;
  font-family: "Satoshi", var(--font-sans);
  font-weight: 500;
  font-size: 12px;
  letter-spacing: 0.04em;
  color: rgba(26, 33, 40, 0.62);
}
.brand-tagline { line-height: 1; }
/* Round 23: pin tagline line-height to 1 so the wordmark-tagline gap
   reads identically across every surface. Sister rule added in
   welcome.css. */
.disclaimer-brand-tagline {
  line-height: 1;
  font-family: "Satoshi", var(--font-sans);
  font-weight: 500;
  font-size: 12px;
  letter-spacing: 0.04em;
  color: var(--text-secondary);
}
/* deelva handoff — hybrid lockup. The full tagline only reads at large sizes, so
   we drop it in the compact top bar + the disclaimer modal, and enlarge the
   welcome lockup so its tagline reads: uppercase, Satoshi 700, tracked, charcoal,
   right-aligned (align-self:stretch fills the column = wordmark width; the
   negative margin cancels trailing tracking so the last letter sits under "a"). */
.brand-tagline,
.disclaimer-brand-tagline { display: none; }
.welcome-brand-logo { width: 64px; height: 64px; }
.welcome-brand-name { font-size: 36px; padding-right: 0; }
.welcome-brand-tagline {
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.18em;
  font-size: 11px;
  color: #0F1F2C;
  align-self: stretch;
  text-align: right;
  margin-right: -0.18em;
}
.disclaimer-brand {
  /* Round 11: added top padding so the brand sits clear of the header
     border-bottom. Bottom kept tight via the shared rule above so the
     lockup integrates with the body copy. */
  padding: 16px 26px 0;
}
@media (max-width: 480px) {
  .welcome-brand-logo,
  .disclaimer-brand-logo { width: 44px; height: 44px; }
  .welcome-brand-name,
  .disclaimer-brand-name { font-size: 18px; }
  .welcome-brand-tagline { display: none; }
}
/* Old .welcome-icon retained below for any other call sites (safe
   no-op if no longer rendered). */
.welcome-icon {
  width: 56px; height: 56px;
  border-radius: 14px;
  display: flex; align-items: center; justify-content: center;
  background: var(--bg-surface);
  margin-bottom: 18px;
}
.welcome-modal h2 {
  font-size: 24px;
  line-height: 1.2;
  letter-spacing: -0.02em;
  margin: 0 0 10px;
  color: var(--text);
  font-weight: 600;
}
.welcome-sub {
  color: var(--text-secondary);
  font-size: 14px;
  line-height: 1.55;
  margin: 0 0 18px;
}
.welcome-steps {
  list-style: none;
  padding: 0;
  margin: 0 0 24px;
  display: flex; flex-direction: column; gap: 12px;
}
.welcome-steps li {
  font-size: 13px;
  line-height: 1.55;
  color: var(--text-secondary);
  padding-left: 22px;
  position: relative;
}
.welcome-steps li::before {
  content: '';
  position: absolute;
  left: 0; top: 6px;
  width: 8px; height: 8px;
  border-radius: 50%;
  background: var(--accent);
}
.welcome-steps strong { color: var(--text); font-weight: 600; }
/* Round 77: numbered-circle variant for the welcome-modal How It
   Works, matching the welcome page's .w-howitworks-steps treatment.
   Resets the dot pseudo-element and replaces it with a counter-
   numbered circle. Used by adding .welcome-steps-numbered alongside
   .welcome-steps on the <ol>. */
.welcome-steps.welcome-steps-numbered {
  list-style: none;
  padding: 0;
  margin: 0 0 24px;
  counter-reset: welcome-step;
  gap: 0;
}
.welcome-steps.welcome-steps-numbered li {
  position: relative;
  padding: 12px 0 12px 44px;
  font-size: 14px;
  line-height: 1.55;
  color: var(--text-secondary);
  border-bottom: 1px solid var(--separator-soft);
  counter-increment: welcome-step;
}
.welcome-steps.welcome-steps-numbered li:last-child { border-bottom: 0; padding-bottom: 4px; }
.welcome-steps.welcome-steps-numbered li::before {
  content: counter(welcome-step);
  position: absolute;
  left: 0;
  top: 10px;
  width: 28px; height: 28px;
  border-radius: 50%;
  background: rgba(47, 90, 63, 0.08);
  color: var(--accent);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-weight: 700;
  font-size: 13px;
}
.welcome-actions {
  display: flex; justify-content: flex-end; align-items: center; gap: 12px;
  margin-top: 4px;
}
.welcome-actions .btn-primary { padding: 0 22px; height: 38px; font-size: 14px; }
.welcome-actions .btn-text { padding: 0 8px; height: 38px; font-size: 13.5px; color: var(--text-secondary); }
.welcome-actions .btn-text:hover { color: var(--accent); background: transparent; }
@media (max-width: 480px) {
  .welcome-card { padding: 28px 20px 22px; }
  .welcome-modal h2 { font-size: 20px; }
  /* Stack the two action buttons full-width on iPhone SE so neither
     wraps awkwardly inside its own pill. Column-reverse keeps the
     primary "See a sample deal" CTA visually closer to the thumb. */
  .welcome-actions {
    flex-direction: column-reverse;
    align-items: stretch;
    gap: 8px;
  }
  .welcome-actions .btn-primary,
  .welcome-actions .btn-text { width: 100%; justify-content: center; }
}

/* === Welcome carousel — "How Deelva works" (Round 130) ===
   Replaces the text-heavy numbered list with a 4-slide, one-screenshot-
   per-step carousel: image plate + headline + single line. Progress dots,
   Back / Next (Next becomes the final CTA on the last slide), plus arrow-key
   and swipe nav. Built to read as the simplest possible first-run tour. */
.welcome-promise {
  font-size: 14.5px;
  margin: 0 0 16px;
}
.welcome-carousel {
  position: relative;
  overflow: hidden;
  border-radius: 14px;
}
.welcome-track {
  display: flex;
  transition: transform 0.36s var(--ease);
  will-change: transform;
}
.welcome-slide {
  flex: 0 0 100%;
  min-width: 0;
  margin: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
}
/* Cream mat behind the framed screenshot — the shot floats on it like a
   matted print, so the varying crop ratios all read as intentional. */
.welcome-shot {
  width: 100%;
  aspect-ratio: 3 / 2;
  overflow: hidden;
  border-radius: 14px;
  border: 1px solid rgba(15, 31, 44, 0.10);
  box-shadow: 0 16px 34px -18px rgba(15, 31, 44, 0.34);
  background: #eef1ee;
}
.welcome-shot img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}
.welcome-cap {
  margin: 16px 0 0;
  text-align: center;
  padding: 0 6px;
}
.welcome-slide-title {
  font-size: 18px;
  font-weight: 600;
  letter-spacing: -0.01em;
  color: var(--text);
  margin: 0 0 4px;
}
.welcome-slide-copy {
  font-size: 14px;
  line-height: 1.5;
  color: var(--text-secondary);
  margin: 0 auto;
  max-width: 38ch;
  text-wrap: balance;
}
.welcome-dots {
  display: flex;
  justify-content: center;
  gap: 8px;
  margin: 18px 0 16px;
}
.welcome-dot {
  width: 7px; height: 7px;
  padding: 0;
  border: 0;
  border-radius: 999px;
  background: rgba(15, 31, 44, 0.16);
  cursor: pointer;
  transition: background 0.2s var(--ease), width 0.2s var(--ease);
}
.welcome-dot:hover { background: rgba(15, 31, 44, 0.32); }
.welcome-dot.is-active {
  width: 22px;
  background: var(--accent);
}
.welcome-actions-carousel {
  display: flex;
  align-items: center;
  gap: 10px;
}
.welcome-actions-spacer { flex: 1 1 auto; }
.welcome-back {
  padding: 0 6px; height: 38px;
  font-size: 13.5px;
  color: var(--text-secondary);
}
.welcome-back:hover { color: var(--accent); background: transparent; }
@media (max-width: 480px) {
  /* Keep the carousel controls on one row — override the stacked
     column-reverse the legacy .welcome-actions rule applies on mobile. */
  .welcome-actions-carousel { flex-direction: row; align-items: center; gap: 8px; }
  .welcome-actions-carousel .btn-primary,
  .welcome-actions-carousel .btn-text { width: auto; }
  .welcome-slide-title { font-size: 16px; }
  .welcome-promise { font-size: 13.5px; }
}
@media (prefers-reduced-motion: reduce) {
  .welcome-track { transition: none; }
}

/* === Add-listing modal ===
   z-index sits ABOVE .detail-modal (54/55) so when the user triggers
   "Add property" from inside the in-page detail modal on the home
   page, the URL-fetch dialog opens on top, not behind it. */
.add-listing-backdrop { z-index: 56; }
.add-listing-modal {
  position: fixed; inset: 0; z-index: 57;
  display: grid; place-items: center;
  padding: 40px 24px;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.18s var(--ease);
}
.add-listing-modal.open { opacity: 1; pointer-events: auto; }
.add-listing-card {
  background: var(--bg);
  border-radius: var(--radius-xl);
  box-shadow: var(--shadow-lg);
  width: 720px;
  max-width: 100%;
  max-height: 100%;
  overflow: hidden;
  display: flex; flex-direction: column;
}
.add-listing-head {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  gap: 16px;
  padding: 22px 26px;
  border-bottom: 1px solid var(--separator-soft);
}
.add-listing-head h2 {
  font-family: var(--font-serif);
  font-size: 26px; font-weight: 500;
  letter-spacing: -0.012em;
  color: var(--text);
  line-height: 1.05;
}
.add-listing-sub {
  margin-top: 6px;
  font-size: 13px;
  color: var(--text-secondary);
  line-height: 1.5;
  max-width: 56ch;
}
.add-listing-form {
  padding: 22px 26px 26px;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  gap: 22px;
}
.al-section h3 {
  font-size: 12px; font-weight: 600;
  color: var(--text-tertiary);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  margin-bottom: 10px;
}
.al-grid {
  display: grid;
  gap: 12px 14px;
}
.al-grid-address {
  grid-template-columns: 2fr 1fr 1.5fr 0.7fr 0.9fr;
}
.al-grid-property {
  grid-template-columns: repeat(3, 1fr);
}
.al-full { grid-column: 1 / -1; }
.al-section .field input,
.al-section .field select {
  width: 100%;
  height: 38px;
  padding: 0 12px;
  background: var(--bg-elevated);
  border: 1px solid var(--separator);
  border-radius: var(--radius-sm);
  font: inherit;
  font-size: 14px;
  color: var(--text);
  outline: none;
  transition: border-color 0.15s ease, box-shadow 0.15s ease;
}
.al-section .field input:focus {
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-bg);
}
.al-section .field-hint[data-tone="ok"]   { color: var(--positive); }
.al-section .field-hint[data-tone="warn"] { color: #b85c00; }
.al-section .field-hint[data-tone="err"]  { color: var(--negative); }
.al-url-wrap { position: relative; }
.al-url-wrap input { padding-right: 36px; }
.al-url-clear {
  position: absolute;
  right: 6px;
  top: 50%;
  transform: translateY(-50%);
  width: 26px; height: 26px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: var(--bg-surface);
  border: 0;
  border-radius: 50%;
  color: var(--text-tertiary);
  cursor: pointer;
  transition: background 0.15s ease, color 0.15s ease;
}
.al-url-clear:hover { background: var(--bg-hover); color: var(--text); }
.al-url-clear[hidden] { display: none; }
.input-with-prefix {
  display: flex; align-items: center;
  background: var(--bg-elevated);
  border: 1px solid var(--separator);
  border-radius: var(--radius-sm);
  padding-left: 12px;
  transition: border-color 0.15s ease, box-shadow 0.15s ease;
}
.input-with-prefix:focus-within {
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-bg);
}
.input-with-prefix .prefix {
  color: var(--text-tertiary);
  font-size: 14px;
  margin-right: 6px;
}
.input-with-prefix input {
  flex: 1; min-width: 0;
  height: 36px;
  border: 0 !important;
  background: transparent !important;
  padding: 0 12px 0 0 !important;
  outline: none;
  box-shadow: none !important;
}
.input-with-prefix .suffix-hint {
  font-size: 11px;
  color: var(--text-tertiary);
  margin-right: 10px;
}
.al-status {
  font-size: 13px;
  color: var(--text-secondary);
  min-height: 18px;
}
.al-status[data-tone="ok"]  { color: var(--positive); }
.al-status[data-tone="err"] { color: var(--negative); }
.al-actions {
  display: flex;
  justify-content: flex-end;
  gap: 10px;
  padding-top: 4px;
  border-top: 1px solid var(--separator-soft);
  margin-top: 4px;
  padding-top: 16px;
}

/* Floating Action Button — sticky bottom-right "Add property" pill that
   appears on mobile where the topbar's text label gets hidden and the
   "Don't see what you're looking for?" CTA is buried at the bottom of
   the listings scroll. Always visible above the fold on small screens. */
.add-listing-fab {
  display: none;  /* hidden on desktop — topbar button is enough there */
}
/* Detail page already has the assumptions FAB plus a topbar "Add property"
   button; the floating Add-property FAB collided with the assumptions FAB
   in the same corner on mobile. Drop it on the detail page. */
body.detail-body .add-listing-fab { display: none !important; }
/* Round 32: on mobile, hide the wordmark text on the listing detail
   page so the back-button + Add/Saved/Assumptions icon row doesn't
   crowd into the brand block (user reported "Deelva" truncating
   to "MA…E" with the back chevron overlapping). The logo itself
   stays as the home affordance — tapping it goes back to /. */
@media (max-width: 560px) {
  body.detail-body .brand-name,
  body.detail-body .brand-tagline { display: none; }
}
@media (max-width: 768px) {
  .add-listing-fab {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    position: fixed;
    right: 16px;
    /* Round 107: the footer is now in-flow (not fixed), so the FAB sits
       at the normal bottom inset and floats over the scrolling content.
       The footer reserves bottom padding (72px) so its links clear the
       FAB when the user scrolls to the very end. */
    bottom: calc(16px + env(safe-area-inset-bottom, 0px));
    padding: 12px 18px 12px 14px;
    background: var(--accent);
    color: #fff;
    border: 0;
    border-radius: 999px;
    font-size: 14px;
    font-weight: 600;
    letter-spacing: -0.005em;
    cursor: pointer;
    z-index: 30;
    box-shadow: 0 8px 24px rgba(20, 35, 25, 0.32),
                0 2px 6px rgba(20, 35, 25, 0.16);
    transition: transform 0.15s ease, box-shadow 0.15s ease, background 0.15s ease;
  }
  .add-listing-fab:active {
    transform: translateY(1px);
    box-shadow: 0 4px 14px rgba(20, 35, 25, 0.32);
  }
  .add-listing-fab:hover { background: var(--accent-hover); }
}
.add-listing-fab-label { white-space: nowrap; }

/* "Don't see what you're looking for?" CTA — collapsed to a tiny
   single-row affordance at the bottom of the listings panel. The
   previous full card consumed ~140px of panel real estate on every
   view (map / list / grid); the topbar "Add property" button and the
   mobile FAB already cover the action, so this is now a low-weight
   reminder rather than a competing CTA. */
.add-listing-cta {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  margin: 8px 12px 4px;
  padding: 8px 12px;
  background: transparent;
  border: 0;
  border-top: 1px solid var(--separator-soft);
  border-radius: 0;
  text-align: center;
  flex-wrap: wrap;
}
.add-listing-cta-title {
  font-size: 12px;
  font-weight: 500;
  color: var(--text-tertiary);
}
.add-listing-cta-sub {
  display: none;
}
.add-listing-cta-btn {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  margin-top: 0;
  padding: 4px 10px;
  background: transparent;
  color: var(--accent);
  font-size: 12px;
  font-weight: 600;
  border-radius: 6px;
  border: 0;
}
.add-listing-cta-btn:hover { background: rgba(47, 90, 63, 0.06); text-decoration: none; }
.add-listing-cta-btn svg { width: 12px; height: 12px; }

@media (max-width: 768px) {
  .saved-panel { width: 100vw; max-width: 100vw; }
  .compare-modal { padding: 0; }
  .compare-card { border-radius: 0; height: 100%; width: 100%; }
  /* Round 11: ensure the compare table fits the mobile viewport.
     Tighter cell padding (was 12px) + smaller font + narrower label
     column so the dark address header row no longer overflows the
     viewport on a single-deal compare. The table itself can still
     scroll horizontally if the user pulls in 3+ deals — but at 1–2
     deals (the common case) it now fits comfortably. */
  .compare-table {
    font-size: 12px;
  }
  .compare-table th,
  .compare-table td {
    padding: 10px 8px;
    /* Allow address line to wrap to two rows if needed instead of
       overflowing horizontally. */
    white-space: normal;
  }
  .compare-table .compare-label {
    min-width: 110px;
    font-size: 11px;
  }
  .compare-table thead th a {
    font-size: 12px;
    /* Break long unbroken address strings rather than blowing out
       the column. */
    word-break: break-word;
  }
  .compare-body {
    padding: 0 12px 16px;
  }
  .add-listing-modal { padding: 0; }
  .add-listing-card { border-radius: 0; height: 100%; width: 100%; max-height: none; }
  .al-grid-address,
  .al-grid-property { grid-template-columns: 1fr 1fr; }
  .al-full { grid-column: 1 / -1; }
}

/* === Inline help-hint icon + instant tooltip ===
   Round 70: switched from text-rendered "?" / "i" glyphs to an SVG
   icon drawn via CSS mask. Previously the glyph was just the
   character "i" set at 9px / weight 700 inside a circle. That had
   two problems:
     1. The lowercase "i" — a small dot + tall stem — is optically
        bottom-heavy. Even when its bounding box is centered the
        glyph LOOKS low next to text.
     2. At 8–9px with bold weight, the dot of "i" can render so
        small that the glyph reads as a capital "I" in some places
        (filter title) and a lowercase "i" in others (assumption
        labels). Identical source, inconsistent paint.
   The mask approach renders the same vector glyph everywhere at
   every size, and the glyph is optically balanced (dot + stem
   placed for cap-height centering). Text inside the span is
   hidden via font-size: 0 + color: transparent so the icon is the
   only visible element; aria-label keeps the span accessible.

   Custom CSS tooltip replaces the native `title=` attribute that Chrome
   delays 1–2 seconds before showing. `[data-tip]` content appears
   immediately on hover/focus via a body-appended .hh-tooltip element. */
.help-hint {
  position: relative;
  display: inline-block;
  width: 14px; height: 14px;
  margin-left: 5px;
  border-radius: 50%;
  background: var(--bg-surface);
  color: var(--text-tertiary);
  cursor: help;
  /* Hide the literal text content ("i" / "?") so only the SVG-masked
     ::before glyph shows. Two layers of belt-and-suspenders so the
     character can't leak through in any context. */
  font-size: 0;
  color: transparent;
  text-indent: 0;
  overflow: hidden;
  /* vertical-align: middle centers the box on the parent text's
     x-height midpoint. The -1px nudge lifts the icon another pixel
     toward the cap-height center so it looks centered against
     capitalized / uppercase labels too. */
  vertical-align: middle;
  transform: translateY(-1px);
  transition: background 0.15s var(--ease);
}
/* SVG-masked "i" glyph rendered from currentColor. Set the icon
   color via background (which becomes the mask's fill once color
   is transparent on the parent). We instead use background-color
   on ::before so :hover can swap it via inheritance. */
.help-hint::before {
  content: "";
  position: absolute;
  inset: 0;
  background-color: var(--text-tertiary);
  -webkit-mask: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 14 14'><circle cx='7' cy='3.6' r='1.05'/><rect x='6' y='5.6' width='2' height='5.4' rx='0.4'/></svg>") center / 100% 100% no-repeat;
          mask: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 14 14'><circle cx='7' cy='3.6' r='1.05'/><rect x='6' y='5.6' width='2' height='5.4' rx='0.4'/></svg>") center / 100% 100% no-repeat;
  transition: background-color 0.15s var(--ease);
}
.help-hint:hover, .help-hint:focus, .help-hint.is-open {
  background: var(--bg-hover);
  outline: none;
}
.help-hint:hover::before, .help-hint:focus::before, .help-hint.is-open::before {
  background-color: var(--text-secondary);
}
/* Tiny variant — used inline next to small labels (e.g. hero card
   metric eyebrows on the welcome page) where the standard 14px
   circle would visually outweigh the 10–11px label text. */
.help-hint.help-hint-tiny {
  width: 11px; height: 11px;
  margin-left: 3px;
  transform: translateY(-1px);
}
/* Tooltip bubble — body-appended <div class="hh-tooltip"> managed
   entirely from tooltips.js. Position is set inline (top/left in px),
   so the only CSS responsibilities are: visual styling, default
   hidden state, and arrow placement via data-tip-place="above|below".

   Pseudo-element approach was abandoned (Round 41) because it was
   fragile across browsers — pointer-events on the host, ancestor
   overflow clipping, and CSS variable inheritance all conspired to
   break visibility on some pages. A real DOM element appended to
   body has no such issues. */
.hh-tooltip {
  position: fixed;
  top: 0; left: 0;
  max-width: min(280px, calc(100vw - 24px));
  padding: 8px 12px;
  background: #0F1F2C;
  color: white;
  font-family: var(--font-sans);
  font-size: 12px;
  font-weight: 400;
  line-height: 1.45;
  letter-spacing: -0.005em;
  border-radius: 6px;
  box-shadow: 0 8px 24px -8px rgba(0, 0, 0, 0.4);
  pointer-events: none;
  opacity: 0;
  visibility: hidden;
  transition: opacity 0.1s ease;
  z-index: 9999;
  /* Wrap natural words; force-break only when a single token won't
     fit (e.g. a long URL). Prevents the bubble from extending wider
     than max-width. */
  word-wrap: break-word;
  overflow-wrap: anywhere;
  white-space: normal;
}
.hh-tooltip.hh-tooltip-visible {
  opacity: 1;
  visibility: visible;
}
.hh-tooltip-body { display: block; }
.hh-tooltip-arrow {
  position: absolute;
  width: 0;
  height: 0;
  border: 5px solid transparent;
  /* arrow X is set inline by JS; we set Y based on placement */
  margin-left: -5px;
}
.hh-tooltip[data-tip-place="below"] .hh-tooltip-arrow {
  top: -10px;
  border-bottom-color: #0F1F2C;
}
.hh-tooltip[data-tip-place="above"] .hh-tooltip-arrow {
  bottom: -10px;
  border-top-color: #0F1F2C;
}

/* === Compliance footer (listing detail page) ===
   Round 32: trimmed top margin 80px → 24px so the "Underwrite another
   deal?" CTA above doesn't have an awkward gap before the footer. The
   verbose 3-paragraph About + Data + Fair Housing copy moved into the
   About modal — the footer is now a tight hyperlink + copyright row. */
.legal-footer {
  max-width: 1080px;
  margin: 24px auto 24px;
  padding: 20px 32px 0;
  border-top: 1px solid var(--separator-soft);
  font-size: 12px;
  color: var(--text-tertiary);
  line-height: 1.65;
  letter-spacing: -0.005em;
}
.legal-footer p { margin-bottom: 10px; }
.legal-footer p:last-child { margin-bottom: 0; }
.legal-footer strong { color: var(--text-secondary); font-weight: 600; }
/* Compact footer variant: single-row nav with the About link + copyright. */
.legal-footer-compact { padding-top: 16px; }
.legal-footer-nav {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  flex-wrap: wrap;
}
.legal-footer-link {
  background: transparent;
  border: 0;
  padding: 0;
  font: inherit;
  font-size: 13px;
  color: var(--accent);
  cursor: pointer;
  text-decoration: underline;
  text-decoration-color: rgba(47, 90, 63, 0.3);
  text-underline-offset: 3px;
  transition: text-decoration-color 0.15s var(--ease), color 0.15s var(--ease);
}
.legal-footer-link:hover {
  text-decoration-color: var(--accent);
}
.legal-footer-sep { color: var(--text-tertiary); opacity: 0.6; }
.legal-footer-copy { font-size: 13px; color: var(--text-tertiary); }
/* Round 105 (mobile audit): keep the listing-page footer on ONE row.
   It used flex-wrap:wrap, so on a phone the links + copyright wrapped
   to 2-3 ragged lines. Drop the copyright (and its dangling separator)
   and hold the nav links on a single line. */
@media (max-width: 768px) {
  .legal-footer-nav { flex-wrap: nowrap; gap: 8px; overflow-x: auto; scrollbar-width: none; }
  .legal-footer-nav::-webkit-scrollbar { display: none; }
  .legal-footer-link { font-size: 12px; white-space: nowrap; }
  .legal-footer-copy,
  .legal-footer-nav .legal-footer-sep:last-of-type { display: none; }
  /* Round 111: the fixed "Investment assumptions" FAB no longer needs a
     footer padding reserve — it now tucks itself off-screen the moment the
     footer enters view (IntersectionObserver in listing.js), so it can never
     overlap the footer links. (The R107 padding reserve here was overridden
     by the base .legal-footer shorthand and computed to 0 anyway.) */
}

/* Round 38: inline modals for the listing-page footer (How it works
   + About Deelva). Modeled on the welcome-page .w-demo-modal
   pattern but scoped to listing.html so the listing's own footer
   links open in context without navigating back to /. */
.lp-modal {
  position: fixed;
  inset: 0;
  z-index: 90;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.2s ease;
}
.lp-modal.open { opacity: 1; pointer-events: auto; }
.lp-modal-card {
  position: relative;
  background: var(--bg);
  border-radius: 18px;
  box-shadow: 0 30px 80px -20px rgba(15, 31, 44, 0.25),
              0 0 0 1px rgba(15, 31, 44, 0.06);
  padding: 36px 32px 32px;
  max-width: 560px;
  width: 100%;
  max-height: 86vh;
  overflow-y: auto;
}
.lp-modal-close {
  position: absolute;
  top: 14px;
  right: 14px;
  background: transparent;
  border: 0;
  padding: 6px;
  color: var(--text-tertiary);
  cursor: pointer;
  border-radius: 6px;
}
.lp-modal-close:hover { background: var(--bg-surface); color: var(--text); }
.lp-modal-h2 {
  font-family: var(--font-serif);
  font-size: 28px;
  font-weight: 500;
  line-height: 1.1;
  letter-spacing: -0.018em;
  color: var(--text);
  margin: 0 0 18px;
}
.lp-modal-body {
  font-size: 14.5px;
  line-height: 1.6;
  color: var(--text-secondary);
  margin: 0 0 14px;
}
.lp-modal-body:last-child { margin-bottom: 0; }
.lp-modal-body strong { color: var(--text); font-weight: 600; }
.lp-modal-steps {
  list-style: none;
  padding: 0;
  margin: 0;
  display: flex;
  flex-direction: column;
  gap: 14px;
  counter-reset: lp-step;
}
.lp-modal-steps li {
  position: relative;
  padding-left: 36px;
  font-size: 14.5px;
  line-height: 1.55;
  color: var(--text-secondary);
  counter-increment: lp-step;
}
.lp-modal-steps li::before {
  content: counter(lp-step);
  position: absolute;
  left: 0;
  top: 0;
  width: 24px;
  height: 24px;
  border-radius: 50%;
  background: var(--accent);
  color: #fff;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: 12px;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}
.lp-modal-steps strong { color: var(--text); font-weight: 600; }
/* Reuse .backdrop's z-index but bump for modal context. */
#lp-modal-backdrop { z-index: 89; opacity: 0; transition: opacity 0.2s ease; }
#lp-modal-backdrop:not([hidden]) { opacity: 1; }
#lp-modal-backdrop.open { opacity: 1; }
@media (max-width: 768px) {
  .legal-footer { padding: 16px 20px 0; margin-top: 20px; }
}

/* === Misc === */
.divider { height: 1px; background: var(--separator-soft); margin: 24px 0; }
[hidden] { display: none !important; }

/* === Responsive === */
@media (max-width: 768px) {
  /* On phones, drop the tagline — brand block stays as logo + name only.
     The vertical stack reads better with just the wordmark at small sizes. */
  .brand-tagline { display: none; }
}
@media (max-width: 920px) {
  .topbar { grid-template-columns: auto 1fr auto; gap: 14px; padding: 0 16px; }
  .topbar-actions .btn-label { display: none; }
  /* Round 25: REVERTED the Round-24 "keep Add label on mobile"
     decision. User feedback: the "Add property" pill was wide
     enough that combined with Saved + Assumptions it pushed the
     brand block partially off-screen (the "MA…E" truncation in
     the iPhone screenshot). Restore the universal icon-only mobile
     topbar — the + sign is the standard "add" affordance and
     pairs naturally with the saved-heart and assumptions-calc
     icons. Desktop ≥920px still shows full labels. */
  /* Strip the trailing chevron/divider SVG inside chip buttons (the ones
     with two SVGs: leading icon + trailing decoration). The :nth-child(2)
     selector matches the SECOND svg specifically, so single-SVG buttons
     like .search-toggle-mobile keep their (only) icon visible. */
  .topbar-actions .btn-ghost svg:nth-child(2) { display: none; }
}
@media (max-width: 768px) {
  body { overflow: auto; }
  /* Compact the giant desktop chrome down for phones — topbar shrinks from
     84px → 56px, brand from 52/34/19 → 36/22/(hidden tagline), fin-card
     price from 48px serif → 28px, hero height capped so the page doesn't
     read like a stretched desktop layout. */
  :root { --topbar-h: 56px; }
  .topbar { padding: 0 14px; gap: 12px; }
  .brand { gap: 10px; }
  .brand-link { gap: 10px; }
  .brand-logo { width: 40px !important; height: 40px !important; }
  /* Match the welcome page (index.html) lockup exactly: at ≤860px the welcome
     wordmark is 17px, and at ≤420px it drops to 16px (logo 40px in both). The
     app was running 20px here, making the same lockup read larger than the
     marketing page. Mirror it so the two surfaces feel like one product. */
  .brand-name { font-size: 17px; letter-spacing: 0.08em; padding-right: 0.08em; }
  .brand-tagline { display: none; }
  .btn-icon { width: 36px; height: 36px; }
  .topbar .btn-ghost { padding: 0 10px; height: 36px; }
  .topbar-actions { gap: 6px; }
  .demo-pill { padding: 3px 8px; font-size: 10px; }
  body { overflow: auto; }
  .layout {
    position: static;
    /* Clear BOTH the fixed topbar AND the fixed controls-bar (which
       can grow on mobile when filter chips wrap). Previously only
       reserved var(--topbar-h) → in grid view the first listing card's
       top edge slid under the controls bar. */
    margin-top: calc(var(--topbar-h) + var(--filter-bar-h));
    grid-template-columns: 1fr;
    grid-template-rows: 50vh auto;
    min-height: calc(100vh - var(--topbar-h) - var(--filter-bar-h));
    /* Round 107: footer is now in-flow on mobile (see .site-footer),
       so the layout no longer needs to reserve space for a fixed bar. */
  }
  .map-section { min-height: 50vh; }
  .panel { border-left: 0; border-top: 1px solid var(--separator); }
  .panel-header { padding: 12px 14px; }
  /* Round 108: roomier list cards on mobile — bigger photo + more
     breathing room per card (fewer fit per screen, by design). */
  .listing-card { padding: 16px; grid-template-columns: 132px 1fr; gap: 14px; }
  .listing-photo-wrap { width: 132px; height: 106px; }
  .listing-price { font-size: 20px; }
  /* Round 22 (mobile audit MEDIUM): floored card text at 11px on
     mobile. Was 10px on eyebrow + 9px on .label (sub-Apple-floor,
     hard to read in sunlight). */
  .listing-address { font-size: 13px; }
  .listing-eyebrow { font-size: 11px; }
  .listing-metrics { gap: 14px; margin-top: 8px; }
  .listing-metric .value { font-size: 13px; }
  .listing-metric .label { font-size: 11px; }
  /* Round 22 (mobile audit MEDIUM): body overflow-x guard for the
     app page (welcome + listing already had equivalents). Stops
     any rogue child (mapbox tooltip, wide table cell) from
     producing a horizontal scrollbar. */
  body { overflow-x: hidden; }
  /* Round 22 (mobile audit HIGH): view-btn touch target 28 → 36. */
  .view-btn { height: 36px; padding: 0 14px; }
  /* Round 22 (mobile audit HIGH): card-save-btn touch target. Keep
     visual 28×28 (per the audit) but bump to 36 on mobile so the
     heart is reachable by thumb on a small viewport. */
  .card-save-btn { width: 36px; height: 36px; }
  /* === Round 108 mobile LIST card (senior-PD layout) ===
     Clean photo (just arrows), pills as a body row, heart top-right,
     3 metrics on one row. Scoped to list view; grid + desktop untouched. */
  /* Heart → card top-right (the verdict left that corner; the photo, the
     left column, now reads completely clean). */
  /* Round 119: all the list-card overrides below are scoped to .listing-card
     so they no longer leak into the map popup card (which reuses
     .verdict-chip-overlay / .card-save-btn / .listing-metric). */
  body:not(.view-grid) .listing-card .card-save-btn { top: 14px; right: 14px; bottom: auto; left: auto; }
  /* Carousel dots hidden on the list photo. */
  body:not(.view-grid) .listing-card .photo-carousel-dots { display: none; }
  /* Verdict + No-HOA pills: flow as the FIRST row of the body (off the
     photo, above the eyebrow). Verdict leftmost, then No-HOA. */
  body:not(.view-grid) .listing-card .verdict-chip-overlay {
    position: static;
    top: auto; left: auto; right: auto;
    margin: 0 0 2px;
    z-index: auto;
  }
  body:not(.view-grid) .listing-card .verdict-overlay-group { flex-direction: row; gap: 6px; }
  body:not(.view-grid) .listing-card .verdict-chip-overlay .verdict-chip,
  body:not(.view-grid) .listing-card .verdict-chip-overlay .no-hoa-pill { box-shadow: none; }
  /* Three metrics on ONE row. Round 119: size each metric to its content and
     space them apart (not equal-width columns), with single-line nowrap
     labels — so every label stays one line (values line up) AND each value
     sits right under its label with no wasted vertical gap. */
  body:not(.view-grid) .listing-card .listing-metrics {
    flex-wrap: nowrap;
    gap: 8px;
    padding-right: 0;
    justify-content: space-between;
  }
  body:not(.view-grid) .listing-card .listing-metric { flex: 0 0 auto; min-width: 0; }
  body:not(.view-grid) .listing-card .listing-metric .label { font-size: 10.5px; letter-spacing: -0.01em; line-height: 1.25; white-space: nowrap; }
  body:not(.view-grid) .listing-card .listing-metric .value { font-size: 13px; white-space: nowrap; margin-top: 2px; }
  /* Bigger price headline for the roomier card. */
  body:not(.view-grid) .listing-price { font-size: 22px; }
  /* In LIST view on mobile, the map row still claims 50vh of the layout
     grid even with map-section display:none, which squeezes the panel into
     ~30% of the viewport and clips all listings except the first. Collapse
     to a single auto row, drop the panel's overflow clip, and let the page
     body handle scroll naturally through all 78 cards. */
  /* Grid view on mobile: force 2 columns for a gallery-style scan. */
  body.view-grid .layout { grid-template-rows: auto; min-height: auto; }
  body.view-grid .panel { overflow: visible; }
  body.view-grid .listings {
    overflow: visible; flex: none;
    grid-template-columns: repeat(2, 1fr);
    gap: 10px;
    padding: 12px;
  }
  body.view-grid .listing-photo-wrap { height: 110px; }
  body.view-grid .listing-body { padding: 10px 12px 12px; gap: 2px; }
  body.view-grid .listing-eyebrow { font-size: 10px; }
  body.view-grid .listing-price { font-size: 16px; }
  body.view-grid .listing-address { font-size: 11px; }
  body.view-grid .listing-specs { font-size: 11px; gap: 8px; flex-wrap: wrap; }
  /* Round 112: three metrics on ONE row in grid too (Cash-on-cash was
     wrapping to a 2nd row). flex:1 + min-width:0 lets them share the narrow
     2-col card; the 2-line label min-height keeps the values on one baseline. */
  body.view-grid .listing-metrics { flex-wrap: nowrap; gap: 7px; margin-top: 6px; }
  body.view-grid .listing-metric { flex: 1 1 0; min-width: 0; }
  body.view-grid .listing-metric .label { font-size: 9px; line-height: 1.2; min-height: 2.4em; letter-spacing: -0.01em; }
  body.view-grid .listing-metric .value { font-size: 11.5px; white-space: nowrap; }
  /* Round 112: hide carousel dots in grid (the left/right arrows already
     signal there are more photos). */
  body.view-grid .listing-photo-wrap .photo-carousel-dots { display: none; }
  /* Round 112: smaller verdict + No-HOA pills, stacked top-right so the group
     never reaches the top-left Save heart (it overlapped once "No HOA data"
     widened the row). */
  body.view-grid .verdict-overlay-group { flex-direction: column; align-items: flex-end; gap: 4px; }
  body.view-grid .verdict-chip { height: 19px; padding: 0 7px; font-size: 9.5px; }
  body.view-grid .no-hoa-pill { font-size: 8px; padding: 2px 5px; letter-spacing: 0.02em; }
  /* Mobile: the inline topbar search is hidden by default; tapping the
     magnifier icon in the topbar promotes .search to a full-screen sheet
     via body.search-open. Restores the search affordance that was a
     regression — a real-estate finder can't drop search on phones. */
  .search { display: none; }
  .btn.search-toggle-mobile { display: inline-flex; }
  body.search-open .search {
    display: block;
    position: fixed; inset: 0;
    z-index: 60;
    background: var(--bg);
    padding: 14px 14px 0;
    /* Round 117: the base .search width is clamp(220px, ...) which floors at
       220px on phones; without resetting it here the "full-screen" sheet
       stayed 220px wide and the topbar icons showed beside it. width:auto lets
       inset:0 stretch it edge to edge. */
    width: auto; max-width: none; margin: 0;
    animation: search-sheet-in 0.18s var(--ease);
  }
  body.search-open .search input {
    height: 52px;
    padding-right: 56px; /* room for the close button */
  }
  body.search-open .search-close-mobile { display: inline-flex; }
  body.search-open .search-results {
    position: relative; top: 8px; left: 0; right: 0;
    border: 0; box-shadow: none;
    max-height: none; padding: 0;
  }
  body.search-open .search-result { padding: 14px 12px; }
  @keyframes search-sheet-in { from { opacity: 0; transform: translateY(-4px); } to { opacity: 1; transform: translateY(0); } }
  .detail {
    padding: 0 16px;
    margin-top: calc(var(--topbar-h) + 12px);
    /* Leave room at the bottom for the .fab-underwriting FAB (48px
       tall + 16px from viewport bottom + safe-area inset). Without
       this padding the last sections of the detail page scroll under
       the FAB instead of clearing it. */
    padding-bottom: calc(80px + env(safe-area-inset-bottom, 0px));
  }
  .detail-hero { height: 220px; border-radius: var(--radius-lg); margin-bottom: 16px; }
  .detail-thumbs { grid-template-columns: 1fr 1fr; }
  .detail-header h1 { font-size: 24px; line-height: 1.1; }
  .detail-eyebrow { font-size: 11px; margin-bottom: 4px; }
  .detail-header .sub { font-size: 12px; margin-top: 8px; }
  /* Compact group titles + sub-text on mobile. */
  .dp-group-title { font-size: 26px; }
  .dp-group-sub { font-size: 14px; margin-bottom: 18px; }
  .detail-subsection { margin-top: 22px; padding-top: 18px; }
  .detail-subsection h3 { font-size: 19px; margin-bottom: 10px; }
  /* Premium block has its own tighter padding on mobile. */
  .dp-insights { padding: 20px; }
  /* Mobile: keep specs on a single horizontal scroll row rather than the
     one-row-fits-all desktop layout. Drop the dividers so the row breathes,
     bring back wrap as a fallback. */
  /* Mobile specs row: six specs (beds/baths/sqft/$psf/HOA/transit) fit on
     one line for most properties after dropping year-built. Centered for
     visual balance against the action buttons row above. Overflow-x: auto
     stays as a safety net for the rare property where transit + long HOA
     value pushes total width past the viewport — scrolling is still
     possible but the centered layout reads as the primary state. */
  .detail-specs {
    gap: 18px;
    padding: 14px 0;
    margin-top: 18px;
    flex-wrap: nowrap;
    justify-content: center;
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
    scrollbar-width: none;
    /* Subtle hairline above/below to set the row apart from the actions
       above and the About section below — gives the centered chips a
       clear visual band rather than floating in whitespace. */
    border-top: 1px solid var(--separator-soft);
    border-bottom: 1px solid var(--separator-soft);
  }
  .detail-specs::-webkit-scrollbar { display: none; }
  .detail-spec {
    flex: 0 0 auto;
    white-space: nowrap;
    text-align: center;
  }
  .detail-spec { flex: 0 0 auto; padding-right: 0; }
  .detail-spec + .detail-spec { border-left: none; padding-left: 0; }
  .detail-spec .label { font-size: 10px; }
  .detail-spec .value { font-size: 15px; }
  .detail-section { margin-top: 28px; }
  .detail-section h2 { font-size: 22px; margin-bottom: 10px; }
  .detail-description { font-size: 14px; line-height: 1.5; }
  .fin-card { padding: 18px !important; }
  .fin-card .price { font-size: 30px !important; }
  .fin-card .rent-value { font-size: 22px !important; }
  .fin-card .rent-suffix { font-size: 12px; }
  .fin-card-cta { padding: 14px 14px; }
  .fin-card-cta .cf { font-size: 26px; }
  .fin-card-cta .cf-sub { font-size: 11px; }
  .fin-card .metric-grid { gap: 8px; margin-top: 14px; }
  .fin-card .metric .value { font-size: 16px; }
  .fin-card .metric .label { font-size: 11px; }
  .fin-card .metric .sub { font-size: 10px; }
  .fin-card .kvs { gap: 6px 18px; margin-top: 14px; }
  .fin-card .kv .k, .fin-card .kv .v { font-size: 12px; }
  /* Round 24 (mobile audit LOW): 11px is below Apple's 12px legibility
     floor for static numeric data — readable in lab, hard in sunlight.
     12px keeps the dense table fitting on a 375px phone while clearing
     the floor. Inline-edit inputs continue to use the 16px floor rule
     above to dodge iOS auto-zoom. */
  .pnl-table { font-size: 12px; }
  .pnl-table th, .pnl-table td { padding: 8px 10px; }
  /* Round 105: dropped the bottom/right overrides here — they were a
     duplicate that (by source order) defeated the earlier rule meant to
     lift the FAB ABOVE the fixed footer, so the FAB landed on top of the
     footer links. The earlier rule (bottom: footer-h + 16px) now governs. */
  .add-listing-fab { padding: 10px 14px 10px 12px; font-size: 13px; }
  .add-listing-fab-label { font-size: 13px; }
  .detail-grid,
  .detail-modal-body .detail-grid {
    grid-template-columns: 1fr !important;
    /* Mobile order, stacked vertically: header, then About (which now holds
       the specs strip + body + recommended offer), the summary card,
       Financials, Insights, then the Neighborhood group (map / transit /
       schools / climate / comps) last, then CTA. */
    grid-template-areas:
      "main"
      "aside"
      "financials"
      "premium"
      "neighborhood"
      "cta" !important;
    gap: 24px !important;
  }
  /* With a recommended-offer card present, drop it in right after the summary
     card (aside): About copy -> summary -> recommended offer. */
  .detail-grid.has-recoffer,
  .detail-modal-body .detail-grid.has-recoffer {
    grid-template-areas:
      "main"
      "aside"
      "recoffer"
      "financials"
      "premium"
      "neighborhood"
      "cta" !important;
  }
  /* On mobile the row-gap already spaces the cell; cancel the desktop -6px pull. */
  .detail-grid.has-recoffer > .dp-recoffer { margin-top: 0; }
  /* Belt-and-suspenders: ensure every direct child of the grid stretches
     to fill its track and is never squeezed by intrinsic min-content. */
  .detail-grid > *,
  .detail-modal-body .detail-grid > * {
    min-width: 0 !important;
    width: 100% !important;
    max-width: 100% !important;
    justify-self: stretch !important;
  }
  .detail-grid > .dp-year1, .detail-grid > .dp-tenyr { margin-top: 0; }
  /* Removed dead overrides — earlier compact rules ALREADY set h1: 24px,
     .detail-specs gap: 18px, and .fin-card padding/price (!important).
     The dupes here were cascade-defeating the compact intent on the
     listing detail page. Only `position: static` on fin-card is unique
     and worth keeping (releases the desktop sticky behavior on mobile). */
  .fin-card { position: static; }
  .metric-grid { grid-template-columns: 1fr 1fr; }
  /* Round 12 fix: was `padding: 22px` which broke the flex-column
     scroll architecture from Round 11. After restructure, .assumptions-
     panel uses padding: 0 and lets its .ap-scroll child handle the
     scrolling area's padding instead. Force the same on mobile, and
     give the inner .ap-scroll a tighter 18px horizontal padding so
     the form fields still have room to breathe at narrow widths. */
  .assumptions-panel { width: 100vw; max-width: 100vw; padding: 0; }
  .assumptions-panel .ap-scroll { padding: 20px 18px 12px; }
  .assumptions-panel .ap-actions {
    padding: 12px 18px calc(16px + env(safe-area-inset-bottom, 0px));
  }
  .ap-grid { grid-template-columns: 1fr; }
  .kvs { grid-template-columns: 1fr; }
  /* Round 105: the Risk-profile posture chips were a fixed 3-up grid
     that clipped "Conservative" on the full-width mobile panel. Stack
     them so each label has room (matches how .ap-grid stacks). */
  .posture-row { grid-template-columns: 1fr; }
  /* Round 105: lock background scroll while the full-screen assumptions
     panel is open (parity with the filter bottom-sheet). */
  body.assumptions-open { overflow: hidden; }
}
@media (max-width: 480px) {
  .filters, .filters-row-2 { grid-template-columns: 1fr 1fr; }
  /* The three investor-grade metric inputs would crush to ~110px each at
     375px width and clip their labels. Stack them so each gets full width. */
  .filters-row-metrics { grid-template-columns: 1fr; }
  .listing-metrics { gap: 12px; flex-wrap: wrap; }
}
/* Small phones: the welcome page drops its wordmark to 16px at ≤420px, so the
   app matches it here for a pixel-identical lockup on the same device. */
@media (max-width: 420px) {
  .brand-name { font-size: 16px; }
}

/* === Site footer ===
   Fixed at viewport bottom so it always shows under the map+panel
   layout. Layout reserves --footer-h space so they don't overlap.
   Hidden in print. */
.site-footer {
  position: fixed;
  bottom: 0; left: 0; right: 0;
  height: var(--footer-h);
  /* Round 69: 3-zone grid (copyright | nav | attribution) so the
     elements have visual breathing room. Each zone holds together
     while the gaps between zones are generous. */
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  gap: 16px;
  padding: 0 24px;
  border-top: 1px solid var(--separator-soft);
  background: var(--bg);
  font-size: 12px; color: var(--text-tertiary);
  z-index: 6;
}
.site-footer-zone {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  min-width: 0;
}
.site-footer-zone-l { justify-self: start; }
.site-footer-zone-c { justify-self: center; }
.site-footer-zone-r { justify-self: end; }
.site-footer-brand { color: var(--text-secondary); font-weight: 600; }
.site-footer-copy { color: var(--text-tertiary); }
/* Round 67: map tile attribution moved out of the map and into
   the footer. Tiny, low-contrast, with the source links subtle
   but reachable for users who care. */
.site-footer-attrib {
  font-size: 10.5px;
  color: var(--text-tertiary);
  opacity: 0.85;
}
.site-footer-attrib a {
  color: inherit;
  text-decoration: underline;
  text-decoration-color: rgba(15, 31, 44, 0.2);
  text-underline-offset: 2px;
}
.site-footer-attrib a:hover {
  text-decoration-color: var(--text-tertiary);
}
.site-footer-link {
  background: transparent; border: 0; padding: 0;
  color: var(--accent); font: inherit; font-weight: 500;
  cursor: pointer; text-decoration: underline;
  text-decoration-color: rgba(47, 90, 63, 0.3);
  text-underline-offset: 3px;
}
.site-footer-link:hover { text-decoration-color: var(--accent); }
.site-footer-sep { opacity: 0.5; }
.site-footer-note { letter-spacing: -0.005em; }
.site-footer-loc { color: var(--text); font-weight: 500; }
.site-footer-sources {
  display: inline-flex;
  align-items: baseline;
  gap: 6px;
}
.site-footer-sources-label {
  font-size: 11px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--text-tertiary);
}
.site-footer-sources-list { letter-spacing: -0.005em; }
@media (max-width: 768px) {
  /* Round 105 (mobile audit): the base footer is a 3-column GRID
     (1fr auto 1fr). The old mobile override only set flex props, which
     are inert on a grid, so the copyright / nav / attribution zones
     stayed in their tracks and overlapped into illegible mush on a
     375px screen. Fix: switch to a real single flex row and drop the
     two side zones. The nav (How it works · Glossary · About · Contact)
     is the only thing a phone user needs here; the copyright and the
     map-tile attribution both live in the About modal already.
     Round 107: changed from position:fixed to STATIC on mobile. A fixed
     footer fought mobile Safari's dynamic toolbar — as the toolbar
     auto-hid on scroll, the footer stayed at the old viewport bottom and
     page content peeked through the revealed strip below it ("content
     under the footer"). In-flow at the end of the page eliminates that
     entirely (matches the listing + glossary footers, which never had
     the bug). The bottom padding keeps the links clear of the floating
     Add-property FAB when scrolled to the end. */
  .site-footer {
    position: static;
    height: auto;
    display: flex;
    grid-template-columns: none;
    flex-wrap: nowrap;
    align-items: center;
    justify-content: center;
    gap: 4px;
    padding: 14px 14px calc(72px + env(safe-area-inset-bottom, 0px));
    font-size: 12px;
    line-height: 1.4;
    overflow-x: auto;
    scrollbar-width: none;
    white-space: nowrap;
  }
  .site-footer::-webkit-scrollbar { display: none; }
  .site-footer-zone-l,
  .site-footer-zone-r { display: none; }
  .site-footer-zone-c { justify-self: auto; }
  /* Round 106: shorten "About Deelva" → "About" so the four links fit
     one row even at 360px (was tipping into horizontal scroll). */
  .site-footer-link-long { display: none; }
  .site-footer-sep { opacity: 0.4; }
  .site-footer-sources { display: none; }
  .site-footer-note { font-size: 10.5px; }
}

/* === Detail page "Add another property" CTA ===
   Sits below the 10-year P&L. Prominent card with a primary-button CTA
   so users on a detail page have a clearer next step than hunting for
   the small "Add property" link in the topbar. */
.detail-add-cta {
  margin: 56px 0 24px;
  padding: 28px 32px;
  background: var(--bg-surface);
  border: 1px solid var(--separator-soft);
  border-radius: var(--radius-xl);
  display: flex; align-items: center; justify-content: space-between;
  gap: 24px;
  grid-column: 1 / -1; /* break out of the detail-grid template-columns */
}
.detail-add-cta-text h2 {
  margin: 0 0 6px; font-size: 18px; font-weight: 600;
  color: var(--text); letter-spacing: -0.01em;
}
.detail-add-cta-text p {
  margin: 0; font-size: 14px; line-height: 1.55;
  color: var(--text-secondary);
  /* No cap — flex container already constrains width via the
     adjacent button; the 56ch cap was forcing premature wrap. */
}
.detail-add-cta-btn {
  flex-shrink: 0;
  padding: 0 20px; height: 40px; font-size: 14px;
}
@media (max-width: 768px) {
  .detail-add-cta { flex-direction: column; align-items: stretch; padding: 22px 18px; margin: 40px 0 24px; gap: 16px; }
  .detail-add-cta-btn { width: 100%; justify-content: center; }
}

/* === Print stylesheet — "Save as PDF" lender-ready tear sheet ===
   Hides chrome (nav, FABs, drawers, interactive controls), keeps the
   contextual sections (schools/transit/around/location) that a lender
   packet actually wants. Uses the 10-year P&L card stack instead of the
   wide table — narrow paper handles the cards correctly while the
   1180px-min-width table overflows. */
/* Round 67: MapLibre attribution control disabled entirely in app.js
   (attributionControl: false). Tile attribution now lives in the
   site footer (.site-footer-attrib). The Round 61 compact-mode
   CSS hack is gone since there's no in-map control to style. */

/* === Print summary tear sheet ===
   Lives hidden on screen; revealed by @media print as the ONLY content
   that prints. Curated 1-2 page lender packet — address, key metrics,
   year-1 P&L summary, 10-year highlights, assumptions, footer link.
   Built dynamically by buildPrintSummary() in listing.js on print click. */
#print-summary,
#compare-print-summary { display: none; }

@media print {
  /* Tight US-Letter margins. The default ~1in margin combined with the
     financing block + 2-up tables would overflow to a second page. */
  @page { size: letter; margin: 0.45in 0.5in; }
  html { zoom: 1 !important; }
  body { background: #fff !important; color: #000 !important; overflow: visible !important; font-family: var(--font-sans); }
  /* Hide everything that isn't the curated summary or compare tear sheet. */
  body > *:not(#print-summary):not(#compare-print-summary) { display: none !important; }
  #print-summary,
  #compare-print-summary {
    display: block !important;
    padding: 0;
    color: #000;
  }
  /* === Compare-modal "Save as PDF" tear sheet ====================== */
  #compare-print-summary .cp-head {
    display: flex;
    justify-content: space-between;
    align-items: center;
    padding-bottom: 10px;
    border-bottom: 1px solid #ddd;
    margin-bottom: 16px;
  }
  #compare-print-summary .cp-brand { display: flex; align-items: center; gap: 10px; }
  #compare-print-summary .cp-brand-name {
    font-size: 16px; font-weight: 700; letter-spacing: 0.10em; color: #0F1F2C;
  }
  #compare-print-summary .cp-brand-name .iq { color: #2E5A38; }
  #compare-print-summary .cp-brand-tagline {
    font-size: 10px; color: #5a6470; letter-spacing: 0.04em;
  }
  #compare-print-summary .cp-date { font-size: 10px; color: #5a6470; }
  #compare-print-summary .cp-title {
    font-size: 22px; font-weight: 600; margin: 0 0 4px; color: #0F1F2C;
    letter-spacing: -0.01em;
  }
  #compare-print-summary .cp-sub {
    font-size: 11px; color: #5a6470; margin: 0 0 18px;
  }
  /* Round 59: full compare table (was per-deal card layout). Mirrors
     the modal's table — deal addresses as column headers, every metric
     as a row, with section dividers (Property / Financing / Notes /
     Computed). */
  #compare-print-summary .cp-table {
    width: 100%;
    border-collapse: collapse;
    font-size: 10px;
    table-layout: fixed;
  }
  #compare-print-summary .cp-table thead th {
    text-align: left;
    padding: 8px 8px;
    border-bottom: 2px solid #0F1F2C;
    background: #faf7f1;
    vertical-align: bottom;
  }
  #compare-print-summary .cp-th-addr {
    font-size: 11px; font-weight: 600; color: #0F1F2C; line-height: 1.25;
  }
  #compare-print-summary .cp-th-sub {
    font-size: 9px; color: #5a6470; font-weight: 400; margin-top: 1px;
  }
  /* Section divider banner — small all-caps title spanning all columns */
  #compare-print-summary .cp-section th {
    background: #f3f1ec;
    color: #2E5A38;
    font-size: 9px;
    font-weight: 700;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    padding: 6px 8px;
    border-top: 1px solid #ddd;
  }
  /* Regular row — left label, right-aligned cells */
  #compare-print-summary .cp-table tbody tr:not(.cp-section) th.cp-row-label {
    text-align: left;
    font-weight: 500;
    color: #5a6470;
    padding: 4px 8px;
    border-bottom: 1px solid #eee;
    width: 22%;
  }
  #compare-print-summary .cp-table tbody tr:not(.cp-section) td {
    text-align: right;
    font-weight: 600;
    color: #0F1F2C;
    padding: 4px 8px;
    border-bottom: 1px solid #eee;
    font-variant-numeric: tabular-nums;
    vertical-align: top;
  }
  #compare-print-summary .cp-row-note {
    font-weight: 400;
    font-size: 9px;
    color: #6a7480;
    margin-left: 4px;
  }
  #compare-print-summary .cp-pos { color: #2E5A38; }
  #compare-print-summary .cp-neg { color: #8d2424; }
  /* Notes cell — left-aligned, prose, wraps */
  #compare-print-summary .cp-note-cell {
    text-align: left !important;
    font-weight: 400 !important;
    color: #2a323d !important;
    font-size: 9.5px;
    line-height: 1.45;
    white-space: normal;
    word-wrap: break-word;
  }
  #compare-print-summary .cp-note { display: block; }
  #compare-print-summary .cp-note-empty {
    color: #9aa3ad;
    font-style: italic;
  }
  /* Try to keep each section together on a page */
  #compare-print-summary .cp-section,
  #compare-print-summary .cp-section + tr {
    page-break-inside: avoid;
    break-inside: avoid;
  }
  /* When comparing 4 deals at once, switch the page to landscape so
     the 5-column table (label + 4 deals) gets enough horizontal room.
     The user can also override this in the browser's print dialog. */
  #compare-print-summary.cp-landscape {
    /* Trigger a landscape @page for the page(s) this element occupies.
       Letter landscape is 11"×8.5" so 5 columns at ~2" each fits. */
    page: cp-landscape;
  }
  @page cp-landscape { size: letter landscape; margin: 0.4in 0.5in; }
  #compare-print-summary .cp-foot {
    margin-top: 18px;
    padding-top: 10px;
    border-top: 1px solid #ddd;
    font-size: 9px;
    color: #6a7480;
  }
  .ps-head {
    display: flex; justify-content: space-between; align-items: center;
    padding-bottom: 10px;
    border-bottom: 2px solid #0F1F2C;
    margin-bottom: 12px;
  }
  /* Round 10: full Deelva brand lockup at the top of the PDF —
     logo + deelva wordmark + "Signal over noise" tagline. Matches the
     in-app topbar so the PDF reads as the canonical Deelva artifact.
     Inline SVG keeps the PDF self-contained. */
  .ps-brand-lockup {
    display: flex;
    align-items: center;
    gap: 10px;
  }
  .ps-brand-logo {
    flex-shrink: 0;
    /* SVG already has print-safe vector colors; no override needed. */
  }
  .ps-brand-text {
    display: flex;
    flex-direction: column;
    gap: 1px;
    line-height: 1;
  }
  .ps-brand-name {
    font-family: "Satoshi", var(--font-sans);
    font-size: 17px;
    font-weight: 700;
    letter-spacing: 0.10em;
    text-transform: uppercase;
    color: #0F1F2C;
    padding-right: 0.10em;
  }
  .ps-brand-tagline {
    font-family: "Satoshi", var(--font-sans);
    font-size: 9px;
    font-weight: 500;
    letter-spacing: 0.04em;
    color: #555;
  }
  /* Old .ps-brand kept as a no-op fallback in case any caller still
     emits the legacy markup. */
  .ps-brand {
    font-family: var(--font-serif);
    font-size: 20px; font-weight: 600;
    letter-spacing: -0.01em;
  }
  .ps-date { font-size: 10px; color: #555; letter-spacing: 0.04em; text-transform: uppercase; }
  .ps-title { font-size: 19px; font-weight: 600; margin: 0 0 2px; letter-spacing: -0.01em; line-height: 1.15; }
  .ps-sub { font-size: 11px; color: #555; margin-bottom: 10px; }
  /* Financing scenario block — top-of-page since the financing model is
     usually the headline question ("under what loan terms do these
     numbers work?"). Inline pill style keeps it to one line. */
  .ps-financing {
    display: flex; align-items: baseline; gap: 10px;
    padding: 6px 12px;
    background: #f6f5f1;
    border-left: 3px solid #2E5A38;
    border-radius: 0 4px 4px 0;
    margin-bottom: 12px;
    page-break-inside: avoid;
  }
  .ps-financing-label {
    font-size: 9px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.08em;
    color: #555;
  }
  .ps-financing-name { font-size: 13px; font-weight: 700; color: #000; }
  .ps-financing-detail { font-size: 11px; color: #333; font-variant-numeric: tabular-nums; }
  /* Round 12: callout line under the financing strip when the user has
     a price override active — surfaces that the metrics in the table
     below reflect their offer, not the list price. */
  .ps-offer-note {
    margin: 0 0 12px;
    padding: 6px 10px;
    background: #f2f7f4;
    border-left: 3px solid #2E5A38;
    border-radius: 0 4px 4px 0;
    font-size: 10.5px;
    color: #2E5A38;
    font-style: italic;
  }
  .ps-metrics {
    /* Bumped to 7 columns to fit "List price | Your offer | …4 metrics"
       on one row when the user has an offer override. With no override,
       the "Your offer" cell is omitted and we render only 6 cells (the
       grid auto-fits without an empty cell since template-columns is
       declared but cells just fill from left). */
    display: grid; grid-template-columns: repeat(7, 1fr); gap: 6px;
    margin-bottom: 12px;
    page-break-inside: avoid;
  }
  .ps-metric { border: 1px solid #ccc; padding: 6px 8px; border-radius: 4px; }
  .ps-metric-k { font-size: 8.5px; color: #666; text-transform: uppercase; letter-spacing: 0.04em; margin-bottom: 2px; }
  .ps-metric-v { font-size: 13px; font-weight: 700; letter-spacing: -0.01em; font-variant-numeric: tabular-nums; }
  .ps-metric-v.pos { color: #1d6f3d; }
  .ps-metric-v.neg { color: #a02929; }
  /* Two-column table grid so Year-1 P&L and 10-yr highlights live
     side-by-side. Lets the whole one-pager fit on one US Letter page
     without sacrificing either table's row detail. */
  .ps-tables {
    display: grid; grid-template-columns: 1fr 1fr; gap: 16px;
    margin-bottom: 8px;
    page-break-inside: avoid;
  }
  .ps-table-block { min-width: 0; }
  .ps-section {
    font-size: 10px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.06em;
    color: #333;
    margin: 0 0 4px;
    padding-bottom: 3px;
    border-bottom: 1px solid #ccc;
  }
  .ps-table { width: 100%; border-collapse: collapse; font-size: 10.5px; font-variant-numeric: tabular-nums; }
  .ps-table td, .ps-table th {
    padding: 3px 6px;
    border-bottom: 1px solid #eee;
    text-align: left;
  }
  .ps-table td:last-child, .ps-table th:last-child { text-align: right; }
  .ps-table th { font-weight: 600; font-size: 9.5px; color: #666; }
  .ps-table tr.ps-subtotal td { font-weight: 600; border-top: 1px solid #999; }
  .ps-table tr.ps-total td { font-weight: 700; font-size: 11.5px; border-top: 1px solid #000; border-bottom: 0; }
  .ps-table tr.ps-total.pos td { color: #1d6f3d; }
  .ps-table tr.ps-total.neg td { color: #a02929; }
  .ps-table-yr td, .ps-table-yr th { text-align: right; padding-left: 4px; padding-right: 4px; }
  .ps-table-yr td:first-child, .ps-table-yr th:first-child { text-align: left; padding-left: 6px; }
  .ps-assumptions {
    margin-top: 8px;
    padding: 6px 10px;
    background: #f6f5f1;
    border-radius: 4px;
    font-size: 9px; line-height: 1.45; color: #444;
    page-break-inside: avoid;
  }
  .ps-assumptions strong { color: #000; }
  .ps-foot {
    margin-top: 8px;
    padding-top: 6px;
    border-top: 1px solid #ccc;
    font-size: 8.5px; color: #666; text-align: center;
    line-height: 1.4;
  }
  @page { size: letter; margin: 0.5in; }
}

/* === Modal mobile fit — applied uniformly across all popovers ===
   Several modals were sized for desktop (480-720px width, 40px outer
   padding) and broke at 375px — content overflowed, close button
   floated off-screen. This block applies a shared "fits the viewport"
   shape: full-width, max-height: 100vh (with safe-area inset for iOS),
   scrollable, padding tightened. */
@media (max-width: 640px) {
  .welcome-modal, .disclaimer-modal, .add-listing-modal, .compare-modal {
    padding: 0;
    align-items: stretch;
  }
  .welcome-card, .disclaimer-card, .add-listing-card, .compare-card {
    width: 100% !important;
    max-width: 100% !important;
    max-height: calc(100vh - env(safe-area-inset-top, 0px) - env(safe-area-inset-bottom, 0px)) !important;
    border-radius: 0 !important;
    overflow-y: auto;
  }
  .welcome-card {
    padding: 24px 18px calc(20px + env(safe-area-inset-bottom, 0px)) !important;
  }
  .disclaimer-head, .add-listing-head {
    position: sticky; top: 0;
    background: var(--bg);
    z-index: 2;
  }
  .disclaimer-body, .add-listing-form {
    padding: 16px 18px !important;
  }
  /* Detail modal stacks on top of map on tap — same shape. */
  .detail-modal { padding: 0 !important; }
  .detail-modal-card {
    width: 100% !important;
    max-width: 100% !important;
    height: 100% !important;
    max-height: 100vh !important;
    border-radius: 0 !important;
    margin: 0 !important;
  }
  .detail-modal-head {
    padding: 12px 16px !important;
    padding-top: calc(12px + env(safe-area-inset-top, 0px)) !important;
  }
}

/* === R129: property-type multi-select chips (filter panel) === */
.filter-type-chips,
.filter-market-chips,
.filter-beds-chips { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 6px; }
.filter-type-chip,
.filter-market-chip,
.filter-beds-chip {
  appearance: none; font: inherit; cursor: pointer;
  height: 34px; padding: 0 14px;
  border: 1.5px solid rgba(15, 31, 44, 0.18);
  border-radius: 999px; background: transparent;
  color: var(--text);
  font-size: 13px; font-weight: 600;
  transition: background 0.15s ease, border-color 0.15s ease, color 0.15s ease;
}
.filter-type-chip:hover,
.filter-market-chip:hover,
.filter-beds-chip:hover { background: rgba(15, 31, 44, 0.04); }
.filter-type-chip:active,
.filter-market-chip:active,
.filter-beds-chip:active { transform: scale(0.97); }
.filter-type-chip.is-active,
.filter-type-chip[aria-pressed="true"],
.filter-market-chip.is-active,
.filter-market-chip[aria-pressed="true"],
.filter-beds-chip.is-active,
.filter-beds-chip[aria-pressed="true"] {
  background: var(--accent); border-color: var(--accent); color: #fff;
}

/* ===================================================================== */
/* Inline filter bar — pills + popovers. Replaces the Filters button, the */
/* left filter panel, and the active-chip strip. Each pill opens a popover */
/* (anchored dropdown on desktop, bottom-sheet on mobile).                 */
/* ===================================================================== */
.controls-bar.filter-bar { overflow: visible; gap: 10px; padding: 0 16px; }
/* "Filters" label so first-time visitors read the row as filters, not nav/tabs. */
.fbar-label { display: inline-flex; align-items: center; gap: 7px; flex: none; white-space: nowrap; font-family: var(--font-sans); font-weight: 700; font-size: 12.5px; letter-spacing: 0.01em; color: var(--text-secondary); padding-right: 12px; margin-right: 2px; border-right: 1px solid var(--separator); }
.fbar-label-icon { color: var(--text-tertiary); flex: none; }
.fbar-scroll { display: flex; align-items: center; gap: 8px; min-width: 0; overflow: visible; }
.fbar-item { position: relative; flex: none; }
.fbar-pill {
  display: inline-flex; align-items: center; gap: 7px;
  height: 36px; padding: 0 13px;
  background: var(--bg-surface); color: var(--text);
  border: 1px solid var(--separator); border-radius: 980px;
  font-size: 13px; font-weight: 500; letter-spacing: -0.005em;
  cursor: pointer; white-space: nowrap;
  transition: background 0.15s var(--ease), border-color 0.15s var(--ease), color 0.15s var(--ease), box-shadow 0.15s var(--ease);
}
.fbar-pill:hover { background: var(--bg-hover); border-color: var(--text-tertiary); }
.fbar-pill[aria-expanded="true"] { border-color: var(--text); box-shadow: inset 0 0 0 1px var(--text); }
/* Active (a filter is set): a soft warm-taupe fill with deep-taupe text. A
   natural, understated "on" state that reads clearly against the cream bar
   without the weight of navy, the brand green, or a yellow/honey cast. */
.fbar-pill.is-active { background: #e6ded0; border-color: #c3b39a; color: #61543f; }
.fbar-pill.is-active .fbar-pill-icon { color: #61543f; }
.fbar-pill.is-active .fbar-caret { opacity: 0.85; }
.fbar-pill.is-active:hover { background: #ded4c2; border-color: #b6a586; }
.fbar-pill-icon { color: var(--accent); flex: none; }
.fbar-pill-text { display: inline-flex; align-items: baseline; gap: 5px; min-width: 0; }
.fbar-pill-value { font-weight: 700; max-width: 150px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.fbar-pill-value::before { content: "·\00a0"; font-weight: 500; opacity: 0.5; }
.fbar-pill-value:empty { display: none; }
.fbar-caret { flex: none; opacity: 0.55; transition: transform 0.18s var(--ease); }
.fbar-pill[aria-expanded="true"] .fbar-caret { transform: rotate(180deg); }

.fbar-pop {
  position: absolute; top: calc(100% + 8px); left: 0; z-index: 60;
  width: max-content; min-width: 260px; max-width: 340px;
  background: var(--bg-elevated, #fff); border: 1px solid var(--separator);
  border-radius: 16px; padding: 16px;
  box-shadow: 0 18px 44px -14px rgba(15, 31, 44, 0.28), 0 0 0 1px rgba(15, 31, 44, 0.04);
}
.fbar-pop[hidden] { display: none; }
.fbar-pop-wide { min-width: 300px; }
.fbar-pop-title { font-family: var(--font-sans); font-weight: 600; font-size: 14px; color: var(--text); display: flex; align-items: center; gap: 6px; }
.fbar-pop-sub { font-size: 12px; color: var(--text-tertiary); line-height: 1.4; margin: 5px 0 12px; }
.fbar-pop-sub a { color: var(--accent); font-weight: 600; }
.fbar-pop-scroll { max-height: 56vh; overflow-y: auto; margin: 0 -4px; padding: 0 4px; }
.fbar-pop .filter-group { margin-bottom: 16px; padding: 0; border: 0; }
.fbar-pop .filter-group:last-child { margin-bottom: 0; }
.fbar-pop .filter-group-title { font-size: 12px; font-weight: 600; margin: 0 0 8px; color: var(--text-secondary); }
.fbar-pop-foot {
  display: flex; align-items: center; justify-content: space-between;
  margin-top: 14px; padding-top: 12px; border-top: 1px solid var(--separator-soft);
}
.fbar-pop-clear { appearance: none; background: none; border: 0; padding: 4px 2px; font: inherit; font-size: 13px; font-weight: 500; color: var(--text-tertiary); cursor: pointer; }
.fbar-pop-clear:hover { color: var(--text); }
.fbar-pop-done { appearance: none; border: 0; padding: 8px 20px; border-radius: 980px; background: var(--accent); color: #fff; font: inherit; font-size: 13px; font-weight: 600; cursor: pointer; }
.fbar-pop-done:hover { background: var(--accent-hover, #244530); }

.fbar-reset { appearance: none; background: none; border: 0; padding: 4px 6px; margin-left: 2px; font: inherit; font-size: 13px; font-weight: 500; color: var(--text-tertiary); cursor: pointer; text-decoration: underline; text-underline-offset: 2px; flex: none; white-space: nowrap; }
.fbar-reset:hover { color: var(--text); }
.fbar-reset[hidden] { display: none; }

/* Mobile bottom-sheet backdrop (element created by app.js). */
.fbar-backdrop { position: fixed; inset: 0; background: rgba(15, 31, 44, 0.32); z-index: 100; opacity: 0; pointer-events: none; transition: opacity 0.18s var(--ease); }
.fbar-backdrop.is-open { opacity: 1; pointer-events: auto; }

@media (max-width: 768px) {
  .controls-bar.filter-bar { overflow: hidden; padding: 0 12px; gap: 8px; }
  .fbar-scroll { flex: 1; overflow-x: auto; overflow-y: visible; scrollbar-width: none; -webkit-overflow-scrolling: touch; }
  .fbar-scroll::-webkit-scrollbar { display: none; }
  .fbar-pill { height: 34px; }
  .fbar-reset, .controls-bar-results { display: none; }
  /* Popovers become bottom-sheets, fixed to the viewport. */
  .fbar-pop {
    position: fixed; inset: auto 0 0 0; top: auto;
    width: auto; min-width: 0; max-width: none;
    border-radius: 18px 18px 0 0;
    padding: 18px 18px calc(18px + env(safe-area-inset-bottom, 0px));
    max-height: 82vh; z-index: 101;
    animation: fbar-sheet-in 0.22s var(--ease);
  }
  .fbar-pop-scroll { max-height: 62vh; }
  @keyframes fbar-sheet-in { from { transform: translateY(100%); } to { transform: translateY(0); } }
  /* While a sheet is open, lift the bar above the backdrop (so the fixed sheet,
     trapped in the bar's stacking context, renders above the dim) and hide the
     floating "Add property" FAB so it can't overlap the sheet's Done button. */
  body.fbar-sheet-open .controls-bar.filter-bar { z-index: 102; }
  body.fbar-sheet-open .add-listing-fab { display: none; }
}

/* === R129: cash-flow card "based on your assumptions" note (detail page) === */
.cf-assumptions-note {
  margin-top: 10px;
  font-size: 12px;
  line-height: 1.45;
  color: var(--text-tertiary);
}
.cf-assumptions-link {
  appearance: none; border: none; background: none; padding: 0;
  font: inherit; font-weight: 600; color: var(--accent);
  cursor: pointer; white-space: nowrap;
}
.cf-assumptions-link:hover { color: var(--accent-hover); text-decoration: underline; }
