:root {
  --brand: #c82021;
  --brand-dark: #a31718;
  --teal: #0d7377;
  --teal-light: #d6efef;
  --bg: #ffffff;
  --bg-soft: #f7f7f5;
  --surface: #ffffff;          /* card / modal / button background */
  --text: #1c1c1c;
  --muted: #6b6b6b;
  --border: #e3e3e3;
  --positive: #1d8a3b;
  --negative: #c82021;
  --grade-a: #1d8a3b;
  --grade-b: #5aa84b;
  --grade-c: #d49f00;
  --grade-d: #d76b00;
  --grade-f: #c82021;

  /* Loading-overlay tokens — each theme overrides this trio so the
     full-screen spinner adopts the active palette automatically (light
     today, dark at night, any future custom theme by setting these three).
     Light: dim slate veil over a bright canvas + brand-teal spinner with
     a subtle dark shadow that keeps the glyph crisp on busy bg imagery. */
  --overlay-backdrop:      rgba(15, 31, 38, .35);
  --overlay-spinner-color: var(--teal);
  --overlay-spinner-glow:  drop-shadow(0 2px 8px rgba(0, 0, 0, .25));
}

/* ─── Auto dark mode (applied by app.js between 19:00 and 06:59 local) ─── */
html.dark-mode {
  --bg:         #0f1416;
  --bg-soft:    #1a2127;
  --surface:    #1f262d;
  --text:       #e4e8ed;
  --muted:      #8a99a8;
  --border:     #2a3540;
  --teal:       #4cb8bc;
  --teal-light: rgba(76,184,188,.16);
  --brand:      #ef6b6b;
  --brand-dark: #c95252;
  --positive:   #4ea862;
  /* Dark-mode --negative was missing — the light-mode value (#c82021)
     gave only 2.65:1 contrast on --bg-soft (#1a2127), failing WCAG AA
     and making the Remove button's text invisible. #f87171 (lighter
     red) lands ~6.7:1, well above the 4.5:1 normal-text threshold. */
  --negative:   #f87171;
  --grade-a:    #4ea862;
  --grade-b:    #82c570;
  --grade-c:    #d8c14a;
  --grade-d:    #e0884a;
  --grade-f:    #ef6b6b;

  /* Dark-mode loading overlay: deeper veil over the already-dark canvas
     so the spinner reads as the focal point, and a soft teal halo
     instead of a black drop-shadow (a dark shadow on a dark bg
     disappears). --overlay-spinner-color still tracks --teal, which in
     dark mode is the lighter #4cb8bc — same accent as everywhere else. */
  --overlay-backdrop:      rgba(0, 0, 0, .55);
  --overlay-spinner-color: var(--teal);
  --overlay-spinner-glow:  drop-shadow(0 0 18px rgba(76, 184, 188, .45));
}
html.dark-mode body { background: var(--bg); color: var(--text); }

/* Re-paint hard-coded #fff surfaces using the surface variable in dark mode.
   In light mode --surface is also #fff so behavior is unchanged. */
html.dark-mode .topnav,
html.dark-mode .tab.active,
html.dark-mode .saved-pill,
html.dark-mode .search input,
html.dark-mode .suggest,
html.dark-mode .chip,
html.dark-mode .ms-trigger,
html.dark-mode .ms-menu,
html.dark-mode .filter-row select,
html.dark-mode .filter-row .settings,
html.dark-mode .filters-btn,
html.dark-mode .layout-trigger,
html.dark-mode .layout-menu,
html.dark-mode .pager button,
html.dark-mode .card-prop,
html.dark-mode .card,
html.dark-mode .map-tool,
html.dark-mode .map-badge,
html.dark-mode .badge-pop,
html.dark-mode .map-remove-outline,
html.dark-mode .modal-shell,
html.dark-mode .modal-tab,
html.dark-mode .modal-close,
html.dark-mode .fm-tile,
html.dark-mode .fm-range input,
html.dark-mode .fm-grid input,
html.dark-mode .fm-keyword input,
html.dark-mode .fm-select,
html.dark-mode .search-table {
  background: var(--surface); color: var(--text);
  border-color: var(--border);
}
html.dark-mode .feed-header .header-sort select { background: var(--surface); color: var(--text); }
html.dark-mode .map-status-corner { background: rgba(31,38,45,.92); }
html.dark-mode .stats-strip { background: var(--bg-soft); color: var(--text); }
html.dark-mode .card-prop .photo.no-photo {
  background: linear-gradient(135deg, #232b33 0%, #1a2229 60%, #131a1f 100%);
}
html.dark-mode .card-prop .photo.no-photo::after { opacity: .35; }
html.dark-mode .card-prop .photo.no-photo::before { color: #6c7a87; }
html.dark-mode .pin-dot { border-color: #1f262d; }
html.dark-mode .ms-menu input[type=checkbox] { accent-color: var(--teal); }
html.dark-mode .tab            { background: #1a2127; color: var(--text); }
html.dark-mode .tab[data-disabled] { color: var(--muted); }
html.dark-mode .saved-pill     { color: var(--text); }
html.dark-mode .search input::placeholder,
html.dark-mode .fm-range input::placeholder,
html.dark-mode .fm-grid input::placeholder,
html.dark-mode .fm-keyword input::placeholder { color: var(--muted); }
/* Soften pure-black shadows so cards remain visible against dark bg */
html.dark-mode .card-prop:hover { box-shadow: 0 4px 16px rgba(0,0,0,.55); }
html.dark-mode [data-tooltip]:hover::after { background: #2a3540; }
html.dark-mode [data-tooltip]:hover::before { border-top-color: #2a3540; }

* { box-sizing: border-box; }
html, body { margin: 0; padding: 0; }
body {
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  color: var(--text);
  background: var(--bg);
  line-height: 1.45;
}

a { color: var(--teal); text-decoration: none; }
a:hover { text-decoration: underline; }

/* ---- Top nav ---- */
.topnav {
  display: flex; align-items: center; gap: 24px;
  padding: 10px 24px; border-bottom: 1px solid var(--border);
  /* Must stay above the sticky feed-header (z:1003) so the search
     suggest dropdown — which is a child of .topnav and can't escape
     its parent's stacking context — paints over the feed header band
     as it drops down. */
  position: sticky; top: 0; background: #fff; z-index: 2000;
}
.brand { font-size: 22px; font-weight: 800; color: var(--brand); }
.brand span { color: var(--teal); }
.primary-tabs { display: flex; gap: 4px; }
.tab {
  padding: 8px 14px; border-radius: 4px 4px 0 0;
  background: #ececec; color: var(--text); font-weight: 600; font-size: 14px;
}
.tab.active { background: #fff; border: 1px solid var(--border); border-bottom: 2px solid #fff; }
.tab[data-disabled] { color: var(--muted); cursor: not-allowed; }
.search-wrap { flex: 1; position: relative; }
.search { display: flex; gap: 0; position: relative; }
/* Chip strip: holds the selected location chips inline with the
   typing input. Wraps gracefully when 4–5 chips don't fit one row. */
.search-chips {
  flex: 1; display: flex; flex-wrap: wrap; align-items: center; gap: 4px;
  padding: 4px 6px 4px 8px;
  border: 1px solid var(--border); border-right: none;
  border-radius: 6px 0 0 6px; background: var(--surface, #fff);
  min-height: 40px;
}
.search-chips:focus-within { border-color: var(--teal); }
.search input {
  /* Now lives inside .search-chips and stretches to fill the row. */
  flex: 1; min-width: 140px; padding: 6px 4px;
  border: none; outline: none;
  background: transparent; color: var(--text);
  font-size: 15px;
}
.search button[type="submit"] {
  /* Match #search-input / .search-chips so the box reads as one control */
  width: 44px; background: var(--surface, #fff); color: var(--teal);
  border: 1px solid var(--border); border-left: none;
  border-radius: 0 6px 6px 0; cursor: pointer; font-size: 16px;
}
.search button[type="submit"]:hover { background: var(--teal-light); }
/* "Clear all" (×) — a borderless middle segment between the chip strip
   and the 🔍 submit, so the three pieces still read as one rounded box.
   Hidden until there's at least one chip (toggled by app.js). */
.search-clear {
  flex: 0 0 auto; width: 36px; align-self: stretch;
  background: var(--surface, #fff); color: var(--muted);
  border: 1px solid var(--border); border-left: none; border-right: none;
  cursor: pointer; font-size: 20px; line-height: 1;
}
.search-clear:hover { color: var(--brand); background: var(--teal-light); }
.search-clear[hidden] { display: none; }
html.dark-mode .search-clear { background: var(--surface); }
/* "Copy search" (📋) — same skin as .search-clear (uses the class), with
   a slightly smaller icon and a green-tinted "copied" confirmation state. */
.search-copy { font-size: 16px; }
.search-copy.copied { color: #36b37e; }
.search-chip {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 3px 4px 3px 9px;
  background: var(--teal-light, #e0f2f1); color: var(--teal, #0d7377);
  border: 1px solid var(--teal, #0d7377);
  border-radius: 999px; font-size: 13px; font-weight: 600;
  max-width: 220px;
}
.search-chip .chip-kind {
  font-size: 10px; text-transform: uppercase; letter-spacing: .04em;
  padding: 1px 6px; background: var(--teal); color: #fff;
  border-radius: 999px;
}
.search-chip .chip-label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.search-chip .chip-x {
  border: none; background: transparent;
  color: var(--teal); font-size: 16px; line-height: 1;
  cursor: pointer; padding: 0 6px;
}
.search-chip .chip-x:hover { color: var(--brand); }
/* "Copied to clipboard" toast — floats just below the search bar after a
   successful copy (📋 click or Ctrl+C). Auto-hides via app.js. */
.search-copy-toast {
  position: absolute; top: calc(100% + 6px); right: 8px;
  background: #2b3a3a; color: #fff;
  padding: 6px 12px; border-radius: 6px;
  font-size: 12px; font-weight: 500;
  box-shadow: 0 4px 12px rgba(0,0,0,0.18);
  z-index: 1200; white-space: nowrap;
  opacity: 0; transform: translateY(-3px);
  transition: opacity .15s ease, transform .15s ease;
  pointer-events: none;
}
.search-copy-toast.show { opacity: 1; transform: translateY(0); }
.search-copy-toast[hidden] { display: none; }
.search-copy-toast::before {
  content: ""; position: absolute; right: 14px; top: -5px;
  border-left: 5px solid transparent; border-right: 5px solid transparent;
  border-bottom: 5px solid #2b3a3a;
}
/* Ctrl/Cmd+A "selected" state — fills the chip with the brand teal so the
   selection is obvious before the user hits Ctrl+C to copy. Cleared on
   any click, Escape, or input edit. */
.search-chip.selected {
  background: var(--teal); color: #fff;
  box-shadow: 0 0 0 2px rgba(35,184,180,0.22);
}
.search-chip.selected .chip-kind { background: #fff; color: var(--teal); }
.search-chip.selected .chip-x    { color: #fff; }
html.dark-mode .search-chips { background: var(--surface); }
html.dark-mode .search-chip { background: rgba(13,115,119,.18); }

.search-error {
  position: absolute; left: 0; right: 44px; top: 100%;
  margin-top: 4px; padding: 6px 12px;
  background: var(--brand); color: #fff;
  border-radius: 6px; font-size: 13px; font-weight: 600;
  z-index: 2100;        /* over the suggest dropdown */
  box-shadow: 0 2px 8px rgba(0,0,0,.18);
}
.search-error[hidden] { display: none; }
.suggest {
  position: absolute; top: 100%; left: 0; right: 44px;
  background: #fff; border: 1px solid var(--border); list-style: none;
  margin: 4px 0 0; padding: 4px 0; max-height: 320px; overflow-y: auto;
  box-shadow: 0 4px 12px rgba(0,0,0,.08); border-radius: 6px;
}
.suggest li { padding: 8px 14px; cursor: pointer; display: flex; justify-content: space-between; }
.suggest li:hover,
.suggest li.active { background: var(--bg-soft); }
/* Active marker (keyboard-selected) — slightly stronger so it's
   distinguishable from hover when both are present at the same time. */
.suggest li.active { box-shadow: inset 3px 0 0 var(--teal); }
.suggest .kind { color: var(--muted); font-size: 12px; text-transform: uppercase; }

/* ---- Hero ---- */
/* search-layout.js adds .search-mode-active to <body> when the user has
   any location filter set; we hide the hero strip and shrink the feed
   padding so the map+grid get the full viewport. */
body.search-mode-active .hero { display: none; }
.hero {
  /* Hidden site-wide — the brand-band aesthetic now lives on the sticky
     header chain (.feed-header → .filter-stack). The .hero markup is
     still rendered so saved-search pills / hero overlays inside other
     pages don't 404 on missing JS hooks, but the band itself doesn't
     paint and takes no vertical space. */
  display: none;
  height: 240px;
  /* Theme-matched: teal → teal-dark with a brand accent, plus a faint
     diagonal sheen. Dark overlay keeps the white hero text readable. */
  background:
    linear-gradient(rgba(0,0,0,.18), rgba(0,0,0,.42)),
    repeating-linear-gradient(135deg,
      rgba(255,255,255,.04) 0 28px, rgba(255,255,255,0) 28px 56px),
    linear-gradient(135deg,
      var(--teal) 0%, #0a5b5f 55%, var(--brand-dark) 135%);
  position: relative;
}
html.dark-mode .hero {
  background:
    linear-gradient(rgba(0,0,0,.30), rgba(0,0,0,.55)),
    repeating-linear-gradient(135deg,
      rgba(255,255,255,.03) 0 28px, rgba(255,255,255,0) 28px 56px),
    linear-gradient(135deg,
      #0a5b5f 0%, var(--bg-soft) 70%, var(--brand-dark) 140%);
}
.hero-overlay { padding: 28px 24px; color: #fff; }
.hero h1 { margin: 0 0 14px; font-size: 24px; }
.saved-strip {
  display: flex; gap: 12px; flex-wrap: wrap; align-items: center;
}
.saved-pill {
  display: inline-flex; flex-direction: column; padding: 10px 16px;
  border-radius: 6px; background: rgba(255,255,255,.92); color: var(--text);
  min-width: 200px;
}
.saved-pill .saved-icon { color: var(--teal); font-size: 14px; }
.saved-pill .saved-label { font-size: 11px; color: var(--teal); font-weight: 700; }
.saved-pill .saved-name { font-weight: 600; font-size: 15px; }
.saved-pill:hover { text-decoration: none; background: #fff; }
/* Outlined "ghost button" so the link stays legible on the dark hero in both
   day and night mode (the old var(--teal-light) washed out — and in dark mode
   it's nearly transparent). White text + white border reads on the dark hero
   regardless of theme; hover inverts to a solid white chip with teal text. */
.all-saved {
  display: inline-flex; align-items: center;
  color: #fff; font-weight: 700; margin-left: 8px;
  padding: 8px 14px; border-radius: 6px;
  border: 1.5px solid rgba(255,255,255,.65);
  background: rgba(255,255,255,.12);
  transition: background .15s ease, border-color .15s ease, color .15s ease;
}
.all-saved:hover {
  background: #fff; color: var(--teal);
  border-color: #fff; text-decoration: none;
}
/* "Recent" group divider in the hero strip (localStorage-backed pills). */
.saved-strip .recent-sep {
  align-self: center;
  color: #fff; opacity: .85;
  font-size: 11px; font-weight: 800; text-transform: uppercase;
  letter-spacing: .06em;
  padding-left: 6px; margin-left: 4px;
  border-left: 1px solid rgba(255,255,255,.35);
}
.saved-pill.recent-pill { min-width: 160px; background: rgba(255,255,255,.82); }
.saved-pill.recent-pill:hover { background: #fff; }
.saved-pill.recent-pill .saved-label { color: var(--muted); }
.empty { color: rgba(255,255,255,.85); }

/* ---- Feed (full-width, no centered max-width) ---- */
.content { padding: 12px 18px; max-width: none; margin: 0; }
/* Property detail page only: tighten the left/right gutter by ~10 %
   (18 px → 16 px each side) so the multi-column cards grid gets a touch
   more horizontal real estate without affecting any other page's layout.
   `:has(section.detail)` scopes to pages whose <main class="content">
   wraps the property-detail section — supported in every modern
   browser (Chrome 105+, Firefox 121+, Safari 15.4+). */
body:has(main.content > section.detail) main.content {
  padding-left: 16px;
  padding-right: 16px;
}
.feed h2 { font-size: 26px; margin: 8px 0 16px; }

/* Search-mode header row: title on the left, count + Sort on the right.
   The whole feed top section (.feed-header → .filter-row → #feed-stats)
   pins to the top of the viewport as the user scrolls down through
   results, so chips, dropdowns, and counts stay reachable. Each layer's
   `top` is the cumulative height of the layers above it. */
.feed-header {
  display: flex; align-items: center; justify-content: space-between;
  gap: 16px; flex-wrap: wrap;
  /* Freeze below the sticky topnav. --topnav-h is measured live by app.js
     (ResizeObserver) so the offset is exact at every width — the old
     hardcoded 57px assumed a one-row topnav and left a gap / overlap once the
     topnav wrapped on mobile. */
  position: sticky; top: var(--topnav-h, 57px);
  /* Hero theme: same teal → teal-dark → brand gradient + diagonal sheen +
     soft dark overlay as .hero, so the sticky title bar reads as a brand
     band rather than a flat surface. Layered identically to .hero so the
     two surfaces share the same depth/lighting. */
  background:
    linear-gradient(rgba(0,0,0,.18), rgba(0,0,0,.42)),
    repeating-linear-gradient(135deg,
      rgba(255,255,255,.04) 0 28px, rgba(255,255,255,0) 28px 56px),
    linear-gradient(135deg,
      var(--teal) 0%, #0a5b5f 55%, var(--brand-dark) 135%);
  color: #fff;
  /* Extend full-width of .content (negative side margins) AND flush with the
     topnav (negative top margin eats the .content padding-top:12px + the
     prior 4px gap = 16px of empty space). Padding-top is bumped by the
     same 16px so the bar's text contents stay vertically positioned the
     same as before — only the gradient bleeds upward to fill the gap. */
  margin: -12px -18px 12px;
  padding: 22px 18px 6px;
  z-index: 1003;
}
html.dark-mode .feed-header {
  background:
    linear-gradient(rgba(0,0,0,.30), rgba(0,0,0,.55)),
    repeating-linear-gradient(135deg,
      rgba(255,255,255,.03) 0 28px, rgba(255,255,255,0) 28px 56px),
    linear-gradient(135deg,
      #0a5b5f 0%, var(--bg-soft) 70%, var(--brand-dark) 140%);
}
.feed-header h2 { margin: 0; font-size: 22px; flex: 1; min-width: 0; color: #fff; }
/* Count was var(--muted) on the old white background; switch to a slightly
   translucent white so it still reads as a secondary tone against the
   hero gradient. */
.feed-header .feed-count { color: rgba(255,255,255,.85); font-size: 14px; font-weight: 600; }
/* Sort label sits inside .feed-header in some layouts — keep readable. */
.feed-header label,
.feed-header .header-sort,
.feed-header .header-sort label { color: #fff; }
/* In SEARCH mode the map covers the right side of the feed-header (we pulled
   it up to the topnav). Pack the count immediately after the title on the
   LEFT instead of letting `justify-content: space-between` push it under the
   map. H2 shrinks to its content so the count sits adjacent. */
body.search-mode-active .feed-header { justify-content: flex-start; }
body.search-mode-active .feed-header h2 { flex: 0 1 auto; }

/* Layout dropdown is relocated by search-layout.js into .feed-header
   (same line as #feed-h2). `margin-left: auto` pushes it to the right;
   the right-side offset depends on which layout mode is active:
   - GRID  → map hidden, Layout sits flush at viewport right edge
   - SPLIT/TABLE → map covers right ~44%, Layout stops just left of it
   - MAP   → cards hidden, Layout stays right (only the map shows below) */
@media (min-width: 981px) {
  body.search-mode-active .feed-header #layout-group { margin-left: auto; }
  /* SPLIT mode: map column = 44% → leave 44% + 12px breathing room. */
  body.search-mode-active:has(.search-layouts.mode-split) .feed-header #layout-group {
    margin-right: calc(44% + 12px);
  }
  /* TABLE mode: map column = 50/50 split (see .mode-table .search-layout-row) → 50% + 12px. */
  body.search-mode-active:has(.search-layouts.mode-table) .feed-header #layout-group {
    margin-right: calc(50% + 12px);
  }
  /* GRID / MAP modes: map either hidden or full-width below → push Layout to right edge. */
  body.search-mode-active:has(.search-layouts.mode-grid) .feed-header #layout-group,
  body.search-mode-active:has(.search-layouts.mode-map)  .feed-header #layout-group {
    margin-right: 0;
  }
}
.feed-header .header-sort { display: inline-flex; align-items: center; gap: 8px; }
.feed-header .header-sort select { height: 32px; padding: 0 10px; border: 1px solid var(--border); border-radius: 6px; background: var(--surface, #fff); font-size: 14px; }
.filter-row {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  position: sticky; top: 105px;           /* topnav + feed-header */
  background: var(--bg, #fff);
  margin: 0 -18px 16px;
  padding: 8px 18px 10px;
  border-bottom: 1px solid var(--border);
  /* Above Leaflet's pane stack (max ~700) so the ms-group dropdowns
     inside (property-type, neighborhood-tier, grade-filter) can paint
     over the map. The ms-menus are then layered within this stacking
     context via their own z-index (1300). */
  z-index: 1002;
}
/* Request #1: second controls row (filter dropdowns + switches + layout +
   save search) sits directly beneath the sticky row-1 (chips + Sort by)
   and above #feed-stats. Row-1 stays sticky; row-2 scrolls with content. */
.filter-row:has(+ .filter-row-2) { border-bottom: none; margin-bottom: 0; }
.filter-row-2 {
  position: static; top: auto;
  margin-top: 0;
  z-index: 1001;
}
/* #2/#3 Both rows freeze together in one sticky, fully-opaque bar that
   sits above the scrolling card grid — so row 2 stays pinned and card
   photos can't bleed up through it. The stack owns the sticky position,
   side-bleed margins, background and border; the inner rows become
   plain (non-sticky, transparent) flex rows. */
.filter-stack {
  /* Freeze directly beneath the (also-frozen) feed-header. Both heights are
     measured live by app.js so this stays gap-free as the topnav wraps or the
     header row reflows. */
  position: sticky;
  top: calc(var(--topnav-h, 57px) + var(--feed-header-h, 48px));
  z-index: 1002;
  background: var(--bg, #fff);
  margin: 0 -18px 6px;   /* tight bottom gap so the search map starts right below */
  border-bottom: 1px solid var(--border);
}
.filter-stack .filter-row,
.filter-stack .filter-row-2 {
  position: static; top: auto; z-index: auto;
  background: transparent;
  margin: 0; border-bottom: none;
}

/* ---- "More filters" toggle ----
   A slim bar BELOW the always-visible status-chips/sort row that shows/hides
   only the extra-filters row (.filter-row-2). Default-collapsed on phones. */
.filter-toggle {
  display: flex; align-items: center; gap: 10px;
  width: 100%; padding: 9px 18px; margin: 0;
  background: transparent; border: 0; border-top: 1px solid var(--border);
  cursor: pointer; font: inherit; color: var(--teal); font-weight: 700;
  font-size: 14px; text-align: left; -webkit-tap-highlight-color: transparent;
}
.filter-toggle:hover { color: var(--teal-dark, var(--teal)); }
.filter-toggle-main { display: inline-flex; align-items: center; gap: 8px; min-width: 0; }
.filter-toggle-icon { flex: none; }
.filter-toggle-text { flex: none; }
.filter-stack.collapsed .filter-row-2 { display: none; }

/* Desktop (≥641px) keeps the full filter stack permanently visible — the
   "More filters" toggle was only meant to save vertical space on phones.
   Hide the toggle entirely and force row 2 to render even if a stale
   localStorage value from a prior mobile session left `.collapsed` on the
   stack. The stack itself is already sticky (position:sticky above), so this
   pins the chips + dropdowns together at the top of the feed. */
@media (min-width: 641px) {
  #filter-toggle { display: none; }
  .filter-stack.collapsed .filter-row-2 { display: flex; }
}

/* ============================================================================
   MOBILE FEED (phones ≤ 640px) — proportionate to small screens, no
   horizontal overflow, ≥44px touch targets, ≥16px inputs (no iOS auto-zoom).
   ========================================================================== */
@media (max-width: 640px) {
  /* Topnav: wrap to compact rows instead of overflowing sideways. Disabled
     placeholder tabs (Mortgage/Sell/Rent) are hidden to save space. */
  .topnav { flex-wrap: wrap; gap: 8px 10px; padding: 8px 12px; }
  /* Row 1 = brand (left) + user-widget (right); the tabs and search each get
     their own full-width row below. Explicit flex order keeps the user-trigger
     pinned hard-right on row 1 no matter how many tabs there are (the old
     layout let the tabs crowd row 1 and bump the trigger off the right edge). */
  .topnav .brand { order: 1; font-size: 19px; }
  .topnav .user-widget { order: 2; margin-left: auto; }
  .primary-tabs { order: 3; flex-basis: 100%; flex-wrap: wrap; gap: 2px; }
  .primary-tabs .tab[data-disabled] { display: none; }
  .search-wrap { order: 5; flex-basis: 100%; }   /* search on its own full-width row */
  .search-wrap .search input { font-size: 16px; }  /* no iOS zoom-on-focus */

  /* Feed: tighter gutters. The header + filter stack STAY frozen (sticky) —
     their offsets come from the --topnav-h / --feed-header-h vars app.js
     measures, so they pin correctly under the (taller, wrapped) mobile topnav
     instead of scrolling away and leaving empty space. */
  .content { padding: 10px 12px; }
  .feed h2 { font-size: 20px; margin: 6px 0 12px; }
  .feed-header { margin: 2px -12px 8px; padding: 2px 12px; }
  .feed-header h2 { font-size: 19px; }
  .filter-stack { margin: 0 -12px 12px; }
  .filter-toggle { padding: 12px; }                /* ≥44px tap target */

  /* Hero + saved-search strip are hidden on phones — they ate scarce vertical
     space above the cards; mobile users go straight to the feed. (The styling
     below is retained for any future re-enable but is inert while display:none.) */
  .hero { display: none; }
  .hero-overlay { padding: 16px 12px; }
  .hero h1 { font-size: 18px; margin: 0 0 10px; }
  .saved-strip {
    flex-wrap: nowrap; gap: 8px; padding-bottom: 4px;
    overflow-x: auto; -webkit-overflow-scrolling: touch;
    scroll-snap-type: x proximity; scrollbar-width: none;
  }
  .saved-strip::-webkit-scrollbar { display: none; }
  .saved-pill, .saved-pill.recent-pill {
    flex: 0 0 auto; min-width: 0; max-width: 72vw;
    padding: 8px 12px; scroll-snap-align: start;
  }
  .saved-pill .saved-name { font-size: 14px; }
  .saved-strip .recent-sep { flex: 0 0 auto; }
  .all-saved { flex: 0 0 auto; margin-left: 4px; padding: 9px 13px; white-space: nowrap; }

  /* Row 1 (always-visible chips + sort): a single horizontal SWIPE row so the
     frozen header stays compact instead of the chips wrapping to 3 tall rows. */
  .filter-row:not(.filter-row-2) {
    flex-wrap: nowrap; gap: 8px; padding: 8px 12px;
    overflow-x: auto; -webkit-overflow-scrolling: touch; scrollbar-width: none;
  }
  .filter-row:not(.filter-row-2)::-webkit-scrollbar { display: none; }
  .filter-row:not(.filter-row-2) > * { flex: 0 0 auto; }   /* don't shrink chips/sort */
  .filter-spacer { display: none; }
  .chip { padding: 9px 13px; font-size: 14px; }

  /* Row 2 (collapsible extra filters): stack full-width for easy tapping. */
  .filter-row-2 { gap: 8px; padding: 10px 12px; }
  .filter-row-2 .ms-group,
  .filter-row-2 .layout-group { width: 100%; }
  .filter-row-2 .ms-trigger,
  .filter-row-2 .layout-trigger,
  .filter-row-2 .settings,
  .filter-row-2 .filters-btn { width: 100%; justify-content: space-between; }

  /* #1: open "More filters" dropdowns sit in the FOREGROUND on phones.
     The menus are position:absolute inside the sticky .filter-stack; on a
     narrow screen the default 220px popup can run off the right edge, sit
     under a sibling, or get clipped by an ancestor. So: span the full row
     width (left/right:0), lift above the frozen stack + scrolling cards
     (z-index well over the stack's 1002), keep every ancestor non-clipping
     (overflow:visible), and let a long checklist scroll itself instead of
     overflowing the viewport. .filter-row-2 .ms-menu (0,2,0) outranks the
     base .ms-menu (0,1,0), so these win regardless of source order. */
  .filter-stack,
  .filter-row-2,
  .filter-row-2 .ms-group { overflow: visible; }
  .filter-row-2 .ms-menu {
    left: 0; right: 0; min-width: 0;
    z-index: 4000;
    max-height: 60vh; overflow-y: auto; -webkit-overflow-scrolling: touch;
    box-shadow: 0 10px 30px rgba(0,0,0,.18);
  }

  /* ≥16px so iOS doesn't zoom on focus; ≥42px tall touch targets. */
  .filter-row select,
  .filter-row .settings,
  .filter-select,
  .ms-trigger,
  .layout-trigger,
  .filters-btn {
    font-size: 16px; min-height: 42px;
  }

  /* #2: the feed-stats strip is hidden on phones (rule lives just after the
     base .stats-strip definition, where it can win on source order) — it ate
     scarce vertical space above the cards on a small screen. */

  /* Cards: single column (set elsewhere) with comfortable spacing. */
  #feed-grid.grid { gap: 12px; }

  /* Search results on a phone: the split (grid + map) layout has no usable
     viewport, so collapse it to a full-width single-column grid and drop
     the broken "Split" choice from the Layout menu. The JS also defaults +
     coerces phones to Grid; these rules are the CSS-side safety net. */
  .search-layouts.mode-split .search-layout-row { grid-template-columns: 1fr; }
  .search-layouts.mode-split .search-right { display: none; }
  .layout-menu button[data-layout="split"] { display: none; }
  #feed-grid-search.grid { grid-template-columns: 1fr; }
  .search-left .grid { grid-template-columns: 1fr; }
}
.chip {
  padding: 7px 14px; border: 1px solid var(--border); border-radius: 999px;
  background: #fff; cursor: pointer; font-size: 14px; font-weight: 600; color: var(--text);
}
.chip:hover { border-color: var(--teal); }
/* Hover highlight matches the #feed-stats scheme: teal-light fill + teal text.
   Active chips keep the solid-green treatment below. */
.chip:hover:not(.active) { background: var(--teal-light); color: var(--teal); }
.chip.active { background: var(--teal); color: #fff; border-color: var(--teal); }
.chip .cnt {
  display: inline-block; min-width: 22px; padding: 1px 7px;
  margin-left: 6px; border-radius: 999px;
  background: rgba(0,0,0,.08); color: inherit; font-size: 12px;
}
.chip.active .cnt { background: rgba(255,255,255,.25); }
.filter-spacer { flex: 1; }
.filter-row select, .filter-row .settings {
  height: 34px; padding: 0 12px; border: 1px solid var(--border); border-radius: 6px;
  background: #fff; font-size: 14px;
}
/* Toggle switches (1% rule, 🔥 Hot) are pill chips. A fixed transparent border
   + padding baseline means turning one ON doesn't shift layout — it just fills
   in with the app's green "active" treatment, matching active chips + selected
   dropdowns so an enabled filter is unmistakable. */
.switch {
  display: inline-flex; align-items: center; gap: 6px; font-size: 14px;
  padding: 4px 11px; border: 1px solid transparent; border-radius: 999px;
  cursor: pointer; transition: background .12s ease, border-color .12s ease, color .12s ease;
}
.switch input[type=checkbox] { accent-color: var(--teal); cursor: pointer; }
.switch:hover:not(:has(input:checked)) { background: var(--teal-light); border-color: var(--teal); color: var(--teal); }  /* hover highlight (matches #feed-stats) */
.switch:has(input:checked) {
  color: var(--teal); font-weight: 700;
  background: var(--teal-light); border-color: var(--teal);
}
.settings { cursor: pointer; color: var(--teal); font-weight: 600; }
/* "Save search" is the primary CTA in search mode — give it the brand-color
   pill so it stands out against the muted teal-text .settings around it
   (Layout / Feed settings). #feed-settings is the Save-search button. */
#feed-settings {
  background: var(--brand); color: #fff;
  border: 1px solid var(--brand); border-radius: 999px;
  padding: 6px 14px; font-weight: 700;
}
#feed-settings:hover { background: var(--brand-dark); border-color: var(--brand-dark); }

/* Sort-by label */
.sort-group { display: inline-flex; align-items: center; gap: 8px; }
.sort-label { color: var(--muted); font-size: 13px; font-weight: 600; text-transform: uppercase; letter-spacing: .03em; }

/* Full-screen loading overlay — shown by app.js while feed load() is in
   flight. Sits above EVERYTHING (chat widget z=9000, compare drawer
   z=1500, Leaflet ~1000) so no control beneath stays interactive. The
   semi-transparent backdrop catches every pointer event; the spinner
   reuses @keyframes spin defined further down.

   Theme-driven: backdrop / spinner colour / glow all read from the
   --overlay-* tokens (light defaults defined in :root, dark overrides
   in html.dark-mode). Any future custom theme just sets the same trio
   and the overlay follows automatically. */
.loading-overlay {
  position: fixed; inset: 0; z-index: 99999;
  background: var(--overlay-backdrop);
  display: flex; align-items: center; justify-content: center;
  /* Belt-and-braces: even if a child happens to be pointer-events:none,
     the overlay itself catches the click. */
  cursor: progress;
  /* Subtle fade-in so a sub-200ms load doesn't strobe the screen. */
  animation: cfre-overlay-fade-in 120ms ease-out;
}
.loading-overlay[hidden] { display: none; }
.loading-spinner {
  font-size: 64px; line-height: 1;
  color: var(--overlay-spinner-color);
  display: inline-block;
  animation: spin 1s linear infinite;
  /* drop-shadow (not text-shadow) so the glow renders on the rotated
     glyph's bounding box, not behind it. */
  filter: var(--overlay-spinner-glow);
}
@keyframes cfre-overlay-fade-in { from { opacity: 0 } to { opacity: 1 } }

/* Mobile-only sort bar: a right-justified strip directly above the feed grid
   that holds the relocated .sort-group, so Sort is always reachable without
   horizontal-scrolling the filter row. JS (feed.html) moves the group here and
   toggles [hidden] by viewport; on desktop the group stays in the filter row. */
.mobile-sort-bar { display: flex; justify-content: flex-end; align-items: center; gap: 8px; padding: 8px 2px 10px; }
.mobile-sort-bar[hidden] { display: none; }

/* --- Multi-select dropdown (checkbox menu) ---
   #property-type / #neighborhood-tier / #grade-filter live inside the
   sticky .filter-row. Their popup menus must clear Leaflet's panes
   (~400-700) and controls (~1000) so the open dropdown isn't
   overlayed by the map in any layout mode. */
.ms-group { position: relative; display: inline-block; z-index: 1200; }
/* Label stacked ABOVE the value (each on its own line); caret pinned to
   the top-right corner so it doesn't get pushed onto a third line. */
.ms-trigger {
  position: relative;
  display: inline-flex; flex-direction: column; align-items: flex-start;
  gap: 1px; min-height: 34px; padding: 4px 24px 4px 12px;
  border: 1px solid var(--border); border-radius: 6px;
  background: #fff; color: var(--text); font-size: 14px;
  cursor: pointer; line-height: 1.25; text-align: left;
}
.ms-trigger:hover { border-color: var(--teal); background: var(--teal-light); color: var(--teal); }
.ms-trigger[aria-expanded="true"] { border-color: var(--teal); box-shadow: 0 0 0 2px rgba(13,115,119,.15); }
.ms-group.has-selection .ms-trigger { border-color: var(--teal); background: var(--teal-light); }
.ms-menu input[type=checkbox] { accent-color: var(--teal); }   /* green ticks (dark mode set separately) */
.ms-label { display: block; color: var(--muted); font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: .03em; }
.ms-value { display: block; font-weight: 600; }
.ms-caret { position: absolute; top: 6px; right: 9px; font-size: 10px; color: var(--muted); }
.ms-menu {
  position: absolute; top: calc(100% + 6px); left: 0; z-index: 1300;
  min-width: 220px; padding: 6px;
  background: #fff; border: 1px solid var(--border); border-radius: 8px;
  box-shadow: 0 6px 18px rgba(0,0,0,.10);
}
.ms-menu label {
  display: flex; align-items: center; gap: 8px;
  padding: 6px 8px; border-radius: 4px;
  font-size: 14px; cursor: pointer;
  color: var(--text);    /* themed: black in day mode, near-white in dark mode */
}
.ms-menu label:hover { background: var(--bg-soft); }
.ms-menu input[type=checkbox] {
  width: 16px; height: 16px; accent-color: var(--teal); margin: 0;
}
.ms-pill {
  display: inline-block; min-width: 26px; padding: 1px 6px;
  border-radius: 4px; font-weight: 800; font-size: 12px; text-align: center;
  background: rgba(0,0,0,.06); color: var(--muted);
}
.ms-pill.grade-A { background: #d6efdc; color: var(--grade-a); }
.ms-pill.grade-B { background: #e1f1d8; color: var(--grade-b); }
.ms-pill.grade-C { background: #fcf2cc; color: var(--grade-c); }

/* Color-coded grade dropdown options (visible when the menu is open in Chromium/Firefox) */
.grade-select option.grade-A { background: #d6efdc; color: var(--grade-a); font-weight: 700; }
.grade-select option.grade-B { background: #e1f1d8; color: var(--grade-b); font-weight: 700; }
.grade-select option.grade-C { background: #fcf2cc; color: var(--grade-c); font-weight: 700; }
.grade-select option.grade-D { background: #fde0c4; color: var(--grade-d); font-weight: 700; }
.grade-select option.grade-F { background: #f7d4d4; color: var(--grade-f); font-weight: 700; }

/* Reflect the chosen grade on the closed select control */
.grade-select[data-grade="A"] { border-color: var(--grade-a); color: var(--grade-a); font-weight: 700; background: #f3faf5; }
.grade-select[data-grade="B"] { border-color: var(--grade-b); color: var(--grade-b); font-weight: 700; background: #f6fbf2; }
.grade-select[data-grade="C"] { border-color: var(--grade-c); color: var(--grade-c); font-weight: 700; background: #fdf9ea; }
.grade-select[data-grade="D"] { border-color: var(--grade-d); color: var(--grade-d); font-weight: 700; background: #fef2e6; }
.grade-select[data-grade="F"] { border-color: var(--grade-f); color: var(--grade-f); font-weight: 700; background: #fce8e8; }

/* ---- Stats strip ---- */
/* Frozen below the filter row. Cards scroll beneath; the strip stays
   visible. Stretched edge-to-edge of .content (which has 18px lateral
   padding) so no scrolling card peeks out at the sides. The rounded-
   pill look is dropped in favour of a clean full-width bar — a sticky
   row that *partially* covered the viewport would let cards leak
   through the gutter when scrolling. */
#feed-stats.stats-strip,
.stats-strip {
  display: flex; gap: 0; flex-wrap: wrap; align-items: stretch;
  background: var(--bg-soft);
  font-size: 14px;
  /* Freeze directly beneath the (frozen) filter stack. All three heights are
     measured live by app.js, so this pins gap-free even as the topnav wraps or
     the filter stack collapses/expands — the old fixed 175px left it covered. */
  position: sticky;
  top: calc(var(--topnav-h, 57px) + var(--feed-header-h, 48px) + var(--filter-stack-h, 70px));
  z-index: 1001;
  margin: 0 -18px 16px;
  padding: 6px 12px;
  border-radius: 0;
  border-bottom: 1px solid var(--border);
}
.stats-strip b { color: var(--text); }

/* Sleek stat cells (value over uppercase label), thin dividers, subtle hover
   that hints the tooltip + tints the value the app's teal. */
.stats-strip .stat {
  display: flex; flex-direction: column; justify-content: center; gap: 1px;
  padding: 4px 18px; cursor: default; border-left: 1px solid var(--border);
  transition: background .12s ease; outline: none;
}
.stats-strip .stat:first-child { border-left: 0; }
.stats-strip .stat:hover,
.stats-strip .stat:focus-visible { background: var(--teal-light); }
.stats-strip .stat-val {
  font-size: 18px; font-weight: 800; line-height: 1.15; color: var(--text);
  font-variant-numeric: tabular-nums; transition: color .12s ease;
}
.stats-strip .stat:hover .stat-val,
.stats-strip .stat:focus-visible .stat-val { color: var(--teal); }
.stats-strip .stat-label {
  font-size: 11px; font-weight: 600; letter-spacing: .04em; text-transform: uppercase;
  color: var(--muted); white-space: nowrap;
}
/* The strip sits at the top of the feed, so flip its tooltips to appear BELOW
   the cell (the generic [data-tooltip] shows above). Right-anchor the last two
   so a wide tooltip can't overflow the viewport's right edge. */
.stats-strip .stat[data-tooltip]:hover::after,
.stats-strip .stat[data-tooltip]:focus-visible::after { top: calc(100% + 7px); bottom: auto; }
.stats-strip .stat[data-tooltip]:hover::before,
.stats-strip .stat[data-tooltip]:focus-visible::before {
  top: calc(100% + 2px); bottom: auto;
  border-top-color: transparent; border-bottom-color: #1c2530;
}
.stats-strip .stat:nth-last-child(-n+2)[data-tooltip]:hover::after,
.stats-strip .stat:nth-last-child(-n+2)[data-tooltip]:focus-visible::after { left: auto; right: 0; }
.stats-strip .stat:nth-last-child(-n+2)[data-tooltip]:hover::before,
.stats-strip .stat:nth-last-child(-n+2)[data-tooltip]:focus-visible::before { left: auto; right: 16px; }

/* #2: hide the stats strip entirely on phones — it consumed valuable vertical
   space on a small screen. Placed AFTER the base #feed-stats.stats-strip rule
   so at equal specificity (0,1,1) inside the breakpoint it wins on source
   order; the bare #feed-stats covers the strip before .stats-strip is applied. */
@media (max-width: 640px) {
  #feed-stats,
  #feed-stats.stats-strip { display: none; }
}

/* ---- Grid ---- */
.grid {
  display: grid; gap: 18px;
  grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
}
/* Default feed grid: exactly three properties per row. Scoped to
   #feed-grid so the search-mode grid, favorites & hidden pages keep
   their responsive auto-fill layout. Collapses on narrow viewports. */
#feed-grid.grid { grid-template-columns: repeat(4, minmax(0, 1fr)); }
@media (max-width: 1280px) { #feed-grid.grid { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
@media (max-width: 980px)  { #feed-grid.grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 640px)  { #feed-grid.grid { grid-template-columns: 1fr; } }
.card-prop {
  border: 1px solid var(--border); border-radius: 10px; overflow: hidden;
  background: #fff; display: flex; flex-direction: column;
  transition: box-shadow .15s ease;
}
.card-prop:hover { box-shadow: 0 4px 14px rgba(0,0,0,.08); }
/* Keyboard focus indicator — the card is an <a>, but had no visible focus
   ring, leaving keyboard users unable to see which card is selected. */
.card-prop:focus-visible {
  outline: 3px solid var(--teal, #0d7377);
  outline-offset: 2px;
  box-shadow: 0 4px 14px rgba(0,0,0,.12);
}
.card-prop .photo {
  position: relative; aspect-ratio: 4 / 3; background: #ddd center/cover no-repeat;
}
.card-prop .photo.no-photo {
  background:
    linear-gradient(135deg, #f1f4f7 0%, #dfe6ed 60%, #c8d3df 100%);
}
.card-prop .photo.no-photo::after {
  content: ""; position: absolute; inset: 0;
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 64 64' fill='none' stroke='%237a8a9a' stroke-width='2.4' stroke-linecap='round' stroke-linejoin='round'><path d='M8 28 L32 10 L56 28'/><path d='M14 26 V52 H50 V26'/><path d='M26 52 V36 H38 V52'/></svg>");
  background-repeat: no-repeat; background-position: center; background-size: 38%;
  opacity: .55;
}
.card-prop .photo.no-photo::before {
  content: "No photo"; position: absolute; bottom: 10px; left: 0; right: 0;
  text-align: center; font-size: 11px; letter-spacing: .04em;
  color: #6b7a8a; font-weight: 600; text-transform: uppercase;
  z-index: 1;
}
.card-prop .photo .photo-badges {
  position: absolute; top: 10px; left: 10px;
  display: flex; gap: 6px; flex-wrap: wrap;
  z-index: 2;
}
.card-prop .photo .badge {
  background: rgba(13,115,119,.95); color: #fff;
  padding: 4px 10px; border-radius: 4px; font-size: 12px; font-weight: 700;
  text-transform: uppercase;
}
.card-prop .photo .hot-badge {
  background: linear-gradient(135deg, #ff5a4a 0%, #c82021 100%);
  color: #fff;
  text-shadow: 0 1px 2px rgba(0,0,0,.25);
}
.card-prop .photo .frac-badge {
  background: linear-gradient(135deg, #8b5cf6 0%, #6d28d9 100%);
  color: #fff;
  text-shadow: 0 1px 2px rgba(0,0,0,.25);
}
/* Auction (nominal opening-bid list price) + likely-mislisted-rental flags. */
.card-prop .photo .auction-badge,
.badge.auction-badge {
  background: linear-gradient(135deg, #a855f7 0%, #7e22ce 100%);
  color: #fff; text-shadow: 0 1px 2px rgba(0,0,0,.25);
}
.card-prop .photo .rental-badge,
.badge.rental-badge {
  background: linear-gradient(135deg, #ea8a3a 0%, #b54708 100%);
  color: #fff; text-shadow: 0 1px 2px rgba(0,0,0,.25);
}
.card-prop .photo .newcon-badge,
.badge.newcon-badge {
  background: linear-gradient(135deg, #22a5c2 0%, #0e7490 100%);
  color: #fff; text-shadow: 0 1px 2px rgba(0,0,0,.25);
}
/* Co-op / cooperative — share price, not fee-simple; cashflow metrics N/A. */
.card-prop .photo .coop-badge,
.badge.coop-badge {
  background: linear-gradient(135deg, #8b6dd6 0%, #6941c6 100%);
  color: #fff; text-shadow: 0 1px 2px rgba(0,0,0,.25);
}

/* Tag row — rule-flag pills + RAG chips (PPSF / est-price / HOA / price-cut) on
   ONE wrapping line, sharing a single colour scheme: green = good, amber =
   at/neutral, red = warning/over. Green = below area comps (paying less per
   sqft / under estimate), amber = at, red = above. */
.card-prop .card-tags {
  display: flex; flex-wrap: wrap; align-items: center; gap: 5px; margin: 6px 0 0;
}
.card-prop .card-tags .flag { margin: 0; }   /* flex gap handles spacing */
.card-prop .ppsf-chip {
  display: inline-flex; align-items: center;
  font-size: 11px; font-weight: 600; line-height: 1.5;
  padding: 2px 8px; border-radius: 999px; border: 1px solid transparent;
}
/* RAG pills use the site's OWN semantic theme tokens so they match the rest of
   the UI and auto-adapt in dark mode (these vars flip there): good = --positive,
   at/neutral = --grade-c (the theme's amber/gold), warning/over = --negative.
   Soft tinted background via color-mix, echoing the theme's --teal-light pill
   idiom. The rule-flag pills (was teal) join the same scheme — positive green,
   ⚠ warnings red. A warning flag carries both classes; .flag-bad is declared
   last so it wins. */
.card-prop .flag, .card-prop .flag-bad { border: 1px solid transparent; }
.card-prop .ppsf-below, .card-prop .rag-good, .card-prop .flag {
  color: var(--positive);
  background: color-mix(in srgb, var(--positive) 12%, transparent);
  border-color: color-mix(in srgb, var(--positive) 30%, transparent);
}
.card-prop .ppsf-at, .card-prop .rag-warn {
  color: var(--grade-c);
  background: color-mix(in srgb, var(--grade-c) 15%, transparent);
  border-color: color-mix(in srgb, var(--grade-c) 34%, transparent);
}
.card-prop .ppsf-above, .card-prop .rag-bad, .card-prop .flag-bad {
  color: var(--negative);
  background: color-mix(in srgb, var(--negative) 12%, transparent);
  border-color: color-mix(in srgb, var(--negative) 30%, transparent);
}
.card-prop .rag-info {
  color: var(--muted); background: var(--surface); border-color: var(--border);
}
/* Listing market-status badges (from Redfin's normalized mlsStatus). A
   listing going Active → Contingent/Pending no longer vanishes from the
   scrape (FOR_SALE_STATUS bitmask), so the card photo flags it. Amber =
   under contract (still a backup-offer opportunity); red = pending (all but
   gone); slate = coming soon (not yet active). */
.card-prop .photo .contingent-badge,
.badge.contingent-badge {
  background: linear-gradient(135deg, #f59e0b 0%, #b45309 100%);
  color: #fff; text-shadow: 0 1px 2px rgba(0,0,0,.25);
}
.card-prop .photo .pending-badge,
.badge.pending-badge {
  background: linear-gradient(135deg, #ef4444 0%, #991b1b 100%);
  color: #fff; text-shadow: 0 1px 2px rgba(0,0,0,.25);
}
.card-prop .photo .coming-soon-badge,
.badge.coming-soon-badge {
  background: linear-gradient(135deg, #64748b 0%, #334155 100%);
  color: #fff; text-shadow: 0 1px 2px rgba(0,0,0,.25);
}

/* #2 Multi-family unit badge (Duplex/Triplex/Fourplex/N-Plex) */
.card-prop .photo .unit-badge,
.badge.unit-badge {
  background: linear-gradient(135deg, #0ea5a4 0%, #0f766e 100%);
  color: #fff;
  text-shadow: 0 1px 2px rgba(0,0,0,.25);
}
.detail-hero .hero-unit-badge {
  position: absolute; top: 12px; left: 12px; z-index: 3;
  padding: 5px 12px; border-radius: 4px;
  font-size: 13px; font-weight: 700; text-transform: uppercase;
}
/* #3 Per-unit cashflow breakdown */
.unit-breakdown { margin: 12px 0 4px; }
.unit-breakdown h4 { margin: 0 0 6px; font-size: 14px; }
.unit-table { width: 100%; border-collapse: collapse; font-size: 13px; }
.unit-table th, .unit-table td {
  text-align: right; padding: 4px 8px; border-bottom: 1px solid var(--border);
}
.unit-table th:first-child, .unit-table td:first-child { text-align: left; }
.unit-table thead th { color: var(--muted, #6b7280); font-weight: 600; }
.unit-table tfoot td { font-weight: 700; border-bottom: none; }

/* --- Photo carousel arrows (feed cards) --- */
.card-prop .photo-nav {
  position: absolute; top: 50%; transform: translateY(-50%);
  width: 32px; height: 32px; border-radius: 50%;
  border: none; background: rgba(0,0,0,.55); color: #fff;
  font-size: 20px; line-height: 1; cursor: pointer;
  opacity: 0; transition: opacity .15s ease, background .15s ease;
  z-index: 3;
}
.card-prop .photo-prev { left: 6px; }
.card-prop .photo-next { right: 6px; }
.card-prop:hover .photo-nav,
.card-prop .photo-nav:focus { opacity: 1; }
.card-prop .photo-nav:hover { background: rgba(0,0,0,.8); }
.card-prop .photo-counter {
  position: absolute; bottom: 8px; right: 8px;
  background: rgba(0,0,0,.6); color: #fff;
  padding: 2px 8px; border-radius: 999px;
  font-size: 11px; font-weight: 600;
  pointer-events: none; z-index: 3;
}
/* Hide arrows on cards that genuinely have no photo (button still
   functional via JS — it'll fetch and reveal photos if any exist). */
.card-prop .photo.no-photo .photo-counter { display: none; }

/* --- Hero photo MOSAIC (property detail) ---
   Two-column layout: large main image on the left, 2×2 thumbnail grid
   on the right. Matches the Redfin / Zillow "feature + grid" pattern
   so the page reads as a gallery from first paint rather than a single
   stretched image scaled past its native resolution. The previous
   one-img-stretched layout was rendering at ~1500×500 logical pixels —
   the source asset is ~1024 × 768, so it visibly upscaled. The mosaic
   shows each photo at a size much closer to its native dimensions
   while still presenting 5 photos at once. */
.detail-hero {
  position: relative;
  display: grid;
  grid-template-columns: 1.62fr 1fr;   /* close to phi — pleasant weight */
  gap: 8px;
  aspect-ratio: 16 / 7;                /* maintains a consistent banner ratio */
  /* Removed `max-height: 520px` — on wide viewports it clamped the
     height but the 16:7 ratio still drove width derivation, and the
     resulting visual was just enough narrower than .detail-head that
     the page looked inset. We let aspect-ratio drive width-to-height
     unconstrained; on 1240px-cap the hero renders ~542px tall, which
     reads as a banner not a wall, and matches detail-head's full
     width pixel-for-pixel. */
  width: 100%;
  /* Each photo is now an independently-rounded card with gaps between
     them, so the OUTER container drops border-radius + surface
     background — there's no continuous rounded panel anymore. The
     gaps show through to the page bg, same visual logic as a card
     grid. */
  overflow: visible;
  background: transparent;
}
.detail-hero .hero-main {
  position: relative; min-width: 0; min-height: 0;
  border-radius: 12px; overflow: hidden;
  background: var(--bg-soft, #1a2230);
}
.detail-hero .hero-main #hero-img {
  width: 100%; height: 100%; object-fit: cover; display: block;
}
.detail-hero #hero-img { cursor: zoom-in; }
.detail-hero .hero-tiles {
  display: grid;
  grid-template-columns: 1fr 1fr;
  grid-template-rows: 1fr 1fr;
  gap: 8px;
  min-width: 0;
}
.detail-hero .hero-tile {
  position: relative; padding: 0; border: 0; cursor: pointer;
  background: var(--bg-soft, #1a2230); overflow: hidden;
  min-width: 0; min-height: 0;
  /* All four corners rounded so each tile reads as its own card
     instead of a tile that only rounds where it meets the outer
     panel's edge. Matches the main photo's radius. */
  border-radius: 12px;
}
.detail-hero .hero-tile img {
  width: 100%; height: 100%; object-fit: cover; display: block;
  transition: transform .35s ease;
}
.detail-hero .hero-tile:hover img,
.detail-hero .hero-tile:focus-visible img { transform: scale(1.04); }
.detail-hero .hero-tile:focus-visible {
  outline: 2px solid var(--teal, #0d7377); outline-offset: -2px;
}
/* "See all N photos" pill anchored bottom-right of the last tile. */
.detail-hero .hero-tile-more {
  position: absolute; right: 12px; bottom: 12px;
  display: inline-flex; align-items: center; gap: 8px;
  background: #fff; color: #14233a;
  padding: 8px 14px; border-radius: 8px;
  font-size: 13px; font-weight: 700;
  box-shadow: 0 2px 6px rgba(0,0,0,.18), 0 6px 18px rgba(0,0,0,.14);
  white-space: nowrap;
}
.detail-hero .hero-tile-more[hidden] { display: none; }
.detail-hero .hero-tile-more-icon { font-size: 14px; line-height: 1; }
/* Hide the prev/next chevrons + counter — the 5-photo mosaic + lightbox
   replace them. (JS still updates the counter on lightbox open.) */
.detail-hero .hero-nav,
.detail-hero .hero-counter { display: none; }
/* Before /photos returns (or for single-photo listings), collapse to a
   single-column layout so the page doesn't briefly show 4 empty tiles.
   JS toggles .has-tiles on .detail-hero once paintTiles fires with at
   least one filled tile. */
.detail-hero:not(.has-tiles) {
  grid-template-columns: 1fr;
}
.detail-hero:not(.has-tiles) .hero-main { border-radius: 12px; }
.detail-hero:not(.has-tiles) .hero-tiles { display: none; }
/* Phone: collapse to single-column, hide tiles, let the main image fill. */
@media (max-width: 720px) {
  .detail-hero {
    grid-template-columns: 1fr;
    aspect-ratio: 4 / 3;
  }
  .detail-hero .hero-main { border-radius: 12px; }
  .detail-hero .hero-tiles { display: none; }
}
/* #4 Street View badge — bottom-left of the hero photo */
.detail-hero .sv-badge {
  position: absolute; left: 12px; bottom: 12px; z-index: 4;
  display: inline-flex; align-items: center; gap: 4px;
  background: rgba(0,0,0,.65); color: #fff; border: none;
  padding: 6px 12px; border-radius: 6px; cursor: pointer;
  font-size: 13px; font-weight: 700;
}
.detail-hero .sv-badge:hover { background: var(--teal); }
/* #4/#5 shared modal/lightbox overlay */
.lightbox {
  position: fixed; inset: 0; z-index: 4000;
  background: rgba(0,0,0,.92);
  display: flex; align-items: center; justify-content: center;
}
.lightbox[hidden] { display: none; }
#lightbox-img {
  max-width: 92vw; max-height: 88vh; object-fit: contain;
  box-shadow: 0 8px 40px rgba(0,0,0,.6);
}
.lightbox-close {
  position: absolute; top: 18px; right: 22px;
  background: rgba(255,255,255,.12); color: #fff; border: none;
  width: 40px; height: 40px; border-radius: 50%;
  font-size: 20px; cursor: pointer; z-index: 2;
}
.lightbox-close:hover { background: rgba(255,255,255,.28); }
.lightbox-nav {
  position: absolute; top: 50%; transform: translateY(-50%);
  background: rgba(255,255,255,.14); color: #fff; border: none;
  width: 52px; height: 72px; font-size: 32px; cursor: pointer;
}
.lightbox-nav:hover { background: rgba(255,255,255,.3); }
.lb-prev { left: 16px; } .lb-next { right: 16px; }
.lightbox-counter {
  position: absolute; bottom: 22px; left: 50%; transform: translateX(-50%);
  color: #fff; font-size: 14px; background: rgba(0,0,0,.5);
  padding: 4px 12px; border-radius: 999px;
}
.sv-modal .sv-frame-wrap {
  width: 88vw; height: 80vh; max-width: 1100px;
  background: #000; border-radius: 8px; overflow: hidden;
}
.sv-modal #sv-frame { width: 100%; height: 100%; border: 0; }

/* #2 Feed settings modal */
.fs-overlay {
  position: fixed; inset: 0; z-index: 4100;
  background: rgba(0,0,0,.45);
  display: flex; align-items: center; justify-content: center;
}
.fs-overlay[hidden] { display: none; }
.fs-card {
  position: relative; background: var(--surface, #fff); color: var(--text);
  width: 460px; max-width: 92vw; max-height: 88vh; overflow-y: auto;
  /* --surface sits ABOVE --bg in dark mode (and adds a visible --border edge) so
     the card reads as a distinct panel against the dimmed page; in light mode
     --surface is #fff so the look is unchanged. */
  border: 1px solid var(--border);
  border-radius: 14px; padding: 28px 30px;
  box-shadow: 0 20px 60px rgba(0,0,0,.45);
}
.fs-card h2 { margin: 0 0 18px; font-size: 22px; }
.fs-card h3 { margin: 22px 0 4px; font-size: 17px; }
.fs-card h4 { margin: 16px 0 2px; font-size: 14px; }
.fs-card h4.fs-mt { margin-top: 22px; }
.fs-sub { margin: 0 0 10px; font-size: 13px; color: var(--muted, #6b7280); }
.fs-x {
  position: absolute; top: 16px; right: 18px;
  background: none; border: none; font-size: 24px; line-height: 1;
  color: var(--muted, #6b7280); cursor: pointer;
}
.fs-input {
  width: 100%; box-sizing: border-box; padding: 12px 14px;
  border: 1px solid var(--border); border-radius: 8px;
  font-size: 15px; background: var(--bg); color: var(--text);
}
.fs-range { width: 100%; margin: 12px 0 4px; accent-color: var(--teal); }

/* Paywall modal — reuses .fs-overlay for the dim-out + center layout.
   The card is wider than the feed-settings modal so the 3-plan grid
   wraps comfortably; the embedded /billing/fragment renders the same
   .plan-grid + .plan-card the full /billing page uses. */
.pw-overlay { z-index: 4500; }   /* above .fs-overlay (4100) — a free user
                                    can hit the paywall from the feed
                                    settings or save-search modals too. */
html.pw-open, html.pw-open body { overflow: hidden; }
.pw-card {
  position: relative; background: var(--surface, #fff); color: var(--text);
  width: 640px; max-width: 94vw; max-height: 88vh; overflow-y: auto;
  border: 1px solid var(--border); border-radius: 14px; padding: 28px 30px;
  box-shadow: 0 20px 60px rgba(0,0,0,.45);
}
.pw-title { margin: 0 0 6px; font-size: 22px; }
.pw-sub   { margin: 0 0 16px; font-size: 14px; color: var(--muted, #6b7280); }
.pw-body  { min-height: 80px; }
.pw-body .billing > h2:first-child { display: none; }   /* fragment's own
   "Choose your plan" duplicates the modal's title — hide the inner one. */
.pw-loading {
  padding: 24px 0; text-align: center; color: var(--muted, #6b7280);
  font-size: 14px;
}

/* Two-thumb range on a single shared track (Waterfront "Water size"). Two
   <input type=range> overlap; only their thumbs take pointer events so both
   handles are draggable on one rail. JS keeps min<=max and paints the fill. */
.water-acres { padding: 6px 4px 2px; }
.dual-range { position: relative; height: 28px; margin-top: 2px; }
/* Price-distribution histogram (above the slider). 50 narrow bars, height
   scaled to the bucket with the most listings. Populated by initPriceRange
   on dropdown open. Aria-hidden — purely visual. */
.price-histogram {
  display: flex; align-items: flex-end; gap: 1px;
  height: 64px; margin: 4px 0 2px;
}
.price-histogram .bar {
  flex: 1; min-width: 2px; background: var(--teal);
  border-radius: 2px 2px 0 0; opacity: .92;
  transition: height 200ms ease, background 120ms ease;
}
.price-histogram .bar.empty { background: var(--border); opacity: .4; min-height: 2px; }
.price-histogram .bar.in-range { background: var(--teal); }
.price-histogram .bar.out-range { background: var(--border); opacity: .55; }
.dual-range__track, .dual-range__fill {
  position: absolute; top: 12px; height: 4px; border-radius: 3px;
}
.dual-range__track { left: 0; right: 0; background: var(--border); }
.dual-range__fill  { background: var(--teal); }
.dual-range input[type=range] {
  position: absolute; left: 0; top: 0; width: 100%; height: 28px; margin: 0;
  -webkit-appearance: none; appearance: none;
  background: none; pointer-events: none;   /* track ignores clicks */
}
.dual-range input#water-acres-min { z-index: 4; }
.dual-range input#water-acres-max { z-index: 3; }
.dual-range input#price-min { z-index: 4; }
.dual-range input#price-max { z-index: 3; }
.dual-range input[type=range]::-webkit-slider-runnable-track {
  height: 28px; background: none;
}
.dual-range input[type=range]::-webkit-slider-thumb {
  -webkit-appearance: none; appearance: none; pointer-events: auto;
  width: 16px; height: 16px; margin-top: 6px;   /* center on the 28px rail */
  border-radius: 50%; background: var(--teal); border: 2px solid var(--surface);
  box-shadow: 0 1px 3px rgba(0,0,0,.3); cursor: pointer;
}
.dual-range input[type=range]::-moz-range-track { height: 28px; background: none; }
.dual-range input[type=range]::-moz-range-thumb {
  pointer-events: auto;
  width: 16px; height: 16px;
  border-radius: 50%; background: var(--teal); border: 2px solid var(--surface);
  box-shadow: 0 1px 3px rgba(0,0,0,.3); cursor: pointer;
}
/* ── Redfin-style filter-row-2 dropdowns: status / price / beds-baths ────────
   In SEARCH mode the score-chip row (row 1) is hidden — chips like
   Feed/New/Sold aren't useful while looking at a city-scoped result set, and
   the empty row left a big black band between the title and filter-row-2.
   The sort dropdown that previously lived in row 1 is moved into row 2 by
   search-layout.js (activateSearchMode), so Sort stays reachable.
   filter-row-2 also carries class .filter-row → :not(.filter-row-2) scope. */
body.search-mode-active .filter-row:not(.filter-row-2) { display: none; }

/* SEARCH mode: let the right-side map extend flush against the viewport
   right edge. The .content padding (18px right) normally caps it; we cancel
   that with a negative right margin on the search-mode container, and zero
   the right-pad on the cards row so the map column starts and ends flush. */
body.search-mode-active #search-layouts { margin-right: -18px; }

.fr2-menu { min-width: 240px; padding: 12px; }
.fr2-section-title { font-weight: 700; font-size: 13px; margin: 0 0 8px; }
.fr2-check { display: flex; align-items: center; gap: 10px; padding: 7px 2px;
  font-size: 14px; cursor: pointer; }
.fr2-check input[type=checkbox] { accent-color: var(--teal); width: 16px; height: 16px; }

/* Price dropdown */
.price-menu { min-width: 300px; }
.price-range-labels { display: flex; justify-content: space-between;
  font-size: 12px; color: var(--muted); margin: 6px 0 12px; }
.price-inputs { display: flex; align-items: center; gap: 8px; }
.price-inputs input { flex: 1; min-width: 0; padding: 8px 10px; font-size: 13px;
  border: 1px solid var(--border); border-radius: 6px; background: var(--surface);
  color: var(--text); }
.price-inputs input:focus { outline: none; border-color: var(--teal); }
.price-dash { color: var(--muted); }

/* Beds / baths dropdown */
.bedbath-menu { min-width: 320px; }
.bb-block { margin-bottom: 14px; }
.bb-title { font-weight: 700; font-size: 13px; margin-bottom: 8px; }
.bb-hint { font-weight: 400; font-size: 12px; color: var(--muted); margin-left: 6px; }
.bb-seg { display: flex; border: 1px solid var(--border); border-radius: 8px; overflow: hidden; }
.bb-seg button { flex: 1; padding: 8px 4px; border: none; border-right: 1px solid var(--border);
  background: var(--surface); color: var(--text); font-size: 13px; font-weight: 600;
  cursor: pointer; white-space: nowrap; }
.bb-seg button:last-child { border-right: none; }
.bb-seg button:hover:not(.active) { background: var(--bg-soft); }
.bb-seg button.active { background: var(--teal); color: #fff; }
/* in-range (between the two selected beds endpoints) — lighter teal tint */
.bb-seg button.in-range { background: color-mix(in srgb, var(--teal) 18%, transparent); }

/* Shared Reset / Done footer (mirrors the Redfin templates) */
.fr2-actions { display: flex; justify-content: flex-end; align-items: center;
  gap: 14px; margin-top: 14px; }
.fr2-reset { background: none; border: none; color: var(--teal); font-weight: 600;
  font-size: 13px; cursor: pointer; }
.fr2-done { background: var(--teal); color: #fff; border: none; border-radius: 8px;
  padding: 8px 18px; font-weight: 700; font-size: 13px; cursor: pointer; }
.fr2-done:hover { filter: brightness(1.06); }

html.dark-mode .price-inputs input,
html.dark-mode .bb-seg button { background: var(--surface); color: var(--text); }
html.dark-mode .bb-seg button:hover:not(.active) { background: rgba(255,255,255,.06); }
html.dark-mode .bb-seg button.active { background: var(--teal); color: #fff; }

.fs-loc-list { margin: 6px 0 4px; }
.fs-row {
  display: flex; align-items: center; justify-content: space-between;
  padding: 12px 2px; border-bottom: 1px solid var(--border);
}
.fs-row:last-child { border-bottom: none; }
.fs-link {
  background: none; border: none; color: var(--teal);
  font-size: 14px; font-weight: 600; cursor: pointer; padding: 8px 0;
}
.fs-empty { padding: 8px 0; }
.fs-toggle { position: relative; display: inline-block; width: 46px; height: 26px; }
.fs-toggle input { opacity: 0; width: 0; height: 0; }
.fs-slider {
  position: absolute; inset: 0; cursor: pointer; border-radius: 999px;
  background: #c7ccd1; transition: background .15s;
}
.fs-slider::before {
  content: ""; position: absolute; height: 20px; width: 20px;
  left: 3px; top: 3px; background: #fff; border-radius: 50%;
  transition: transform .15s;
}
.fs-toggle input:checked + .fs-slider { background: var(--teal); }
.fs-toggle input:checked + .fs-slider::before { transform: translateX(20px); }
.fs-actions { display: flex; justify-content: flex-end; margin-top: 24px; }
.fs-done {
  background: #e23744; color: #fff; border: none;
  padding: 11px 30px; border-radius: 8px; font-size: 15px;
  font-weight: 700; cursor: pointer;
}
.fs-done:hover { background: #c92f3a; }

/* Save-search popup (reuses .fs-overlay backdrop) */
.ss-overlay { z-index: 4200; }
.ss-card {
  position: relative; background: var(--bg, #fff); color: var(--text);
  width: 380px; max-width: 92vw; max-height: 90vh; overflow-y: auto;
  border-radius: 14px; padding: 24px 26px;
  box-shadow: 0 20px 60px rgba(0,0,0,.35);
}
.ss-head { display: flex; align-items: center; gap: 10px; margin-bottom: 8px; }
.ss-check {
  display: inline-flex; align-items: center; justify-content: center;
  width: 28px; height: 28px; border-radius: 50%;
  background: var(--teal); color: #fff; font-weight: 800; font-size: 15px;
}
.ss-head h2 { margin: 0; font-size: 22px; }
.ss-sub { margin: 0 0 18px; font-size: 14px; color: var(--muted, #6b7280); }
.ss-field-label { display: block; font-size: 13px; font-weight: 700; margin: 0 0 6px; }
.ss-notify { margin-top: 20px; }
.ss-notify .ss-field-label { margin-bottom: 8px; }
.ss-radio { display: flex; align-items: center; gap: 10px; padding: 7px 0; font-size: 15px; cursor: pointer; }
.ss-radio input[type=radio] { width: 18px; height: 18px; accent-color: var(--teal); cursor: pointer; margin: 0; }
.ss-actions { display: flex; align-items: center; justify-content: flex-end; gap: 20px; margin-top: 22px; }
.ss-delete { background: none; border: none; color: var(--teal); font-size: 15px; font-weight: 700; cursor: pointer; padding: 8px 2px; }
.ss-delete:hover { text-decoration: underline; }
.ss-done {
  background: #e23744; color: #fff; border: none;
  padding: 11px 30px; border-radius: 8px; font-size: 15px; font-weight: 700; cursor: pointer;
}
.ss-done:hover { background: #c92f3a; }
.detail-hero .hero-nav {
  position: absolute; top: 50%; transform: translateY(-50%);
  width: 44px; height: 44px; border-radius: 50%;
  border: none; background: rgba(0,0,0,.55); color: #fff;
  font-size: 28px; line-height: 1; cursor: pointer;
  opacity: .35; transition: opacity .15s ease, background .15s ease;
  z-index: 3;
}
.detail-hero:hover .hero-nav,
.detail-hero .hero-nav:focus { opacity: 1; }
.detail-hero .hero-nav:hover { background: rgba(0,0,0,.8); }
.detail-hero .hero-prev { left: 12px; }
.detail-hero .hero-next { right: 12px; }
.detail-hero .hero-counter {
  position: absolute; bottom: 12px; right: 12px;
  background: rgba(0,0,0,.6); color: #fff;
  padding: 4px 12px; border-radius: 999px;
  font-size: 13px; font-weight: 600;
  pointer-events: none; z-index: 3;
}
.card-prop .photo .grade {
  position: absolute; top: 10px; right: 10px;
  background: #fff; padding: 4px 10px; border-radius: 4px;
  font-weight: 800; font-size: 14px; box-shadow: 0 1px 3px rgba(0,0,0,.2);
}
.grade-A { color: var(--grade-a); }
.grade-B { color: var(--grade-b); }
.grade-C { color: var(--grade-c); }
.grade-D { color: var(--grade-d); }
.grade-F { color: var(--grade-f); }
.card-prop .body { padding: 12px 14px; display: flex; flex-direction: column; gap: 6px; }
.card-prop .price { font-size: 20px; font-weight: 800; }
.card-prop .specs { color: var(--muted); font-size: 14px; }
.card-prop .addr { font-size: 13px; color: var(--muted); }
/* All six financial metrics (Cap, CoC, 1%, Cashflow, DSCR, Score) on
   ONE row so they align vertically with the six grade chips above
   (Amenities / Schools / Livability / Crime / Commute / Flood).  The
   .metrics-grades + .metrics pair below renders as a clean 6-column
   grid with each financial cell sitting directly under its grade
   counterpart. */
.card-prop .metrics {
  display: grid; grid-template-columns: repeat(6, 1fr); gap: 6px 8px;
  margin-top: 8px; padding-top: 8px; border-top: 1px solid var(--border);
  font-size: 11px;
}
.card-prop .metrics .metric .lbl    { font-size: 10px; line-height: 1.1; min-height: 2.1em; }
.card-prop .metrics .metric b       { font-size: 12px; }
/* Financial values that are long (Cashflow "$1,435/mo") must shrink to
   fit the narrower 6th-of-row cell instead of being clipped or
   pushing the layout. */
.card-prop .metrics .metric b       { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
/* #4 Grade metrics (Amenities, Schools, Livability, Crime, Commute, Flood)
   on one top row.  Now identical column count to the financial row so they
   align column-for-column. */
.card-prop .metrics-grades .metric.metric-neighborhood,
.card-prop .metrics-grades .metric.metric-schools { grid-column: auto; }

/* Stale-score indicator on the feed card's Cashflow cell. feed_scores
   .updated_at > 3 d → soft amber tag; > 7 d → strong amber. Helps users
   notice when the card value is materially older than what the property
   page's live recompute would produce. */
.stale-tag {
  display: inline-block;
  margin-left: 4px;
  padding: 0 4px;
  font-size: 9px;
  font-weight: 700;
  line-height: 1.6;
  letter-spacing: .02em;
  border-radius: 4px;
  vertical-align: middle;
  background: rgba(224, 161, 22, .18);
  color: #b8800e;
  border: 1px solid rgba(224, 161, 22, .35);
}
.stale-tag.stale-strong {
  background: rgba(224, 88, 77, .15);
  color: #b04039;
  border-color: rgba(224, 88, 77, .35);
}
/* Admin always-on variant — score is within the normal rotation window
   (≤ 3 d), so no user-facing warning is warranted. Rendered in a neutral
   teal so an operator scanning the feed can spot rotation health at a
   glance without it reading like a "warning" badge. Generated by
   stalenessHint() when window.__IS_ADMIN is true. */
.stale-tag.stale-fresh {
  background: rgba(38, 145, 138, .15);
  color: #1f706a;
  border-color: rgba(38, 145, 138, .35);
}
.card-prop .photo > .stale-tag.stale-fresh {
  background: rgba(255, 255, 255, .92);
  color: #1f706a;
  border-color: rgba(38, 145, 138, .55);
}
/* On the feed-card photo, the stale-tag overlays the bottom-right
   corner instead of trailing the cashflow metric.  Stronger background
   so it stays readable against any photo.  Stacked ABOVE the
   .photo-counter (carousel "N/M" pill, also bottom-right) so both
   badges stay visible — counter at bottom: 8px, stale-tag at
   bottom: ~34px (counter is ~22px tall including padding). */
.card-prop .photo > .stale-tag {
  position: absolute;
  right: 8px;
  bottom: 34px;
  margin: 0;
  padding: 2px 6px;
  font-size: 10px;
  background: rgba(255, 255, 255, .92);
  color: #b8800e;
  border-color: rgba(224, 161, 22, .55);
  box-shadow: 0 1px 2px rgba(0, 0, 0, .15);
  z-index: 3;
}
.card-prop .photo > .stale-tag.stale-strong {
  background: rgba(255, 255, 255, .92);
  color: #b04039;
  border-color: rgba(224, 88, 77, .55);
}
/* The bottom-right badge would push the generic tooltip past the photo's
   right edge (left:0 anchor, 200-280px wide → clipped by .card-prop's
   overflow:hidden).  Right-anchor the tooltip so it grows INWARD from
   the badge, and shift the arrow accordingly.  Same trick as the
   3rd/4th-column metric tooltips at L1510. */
.card-prop .photo > .stale-tag[data-tooltip]:hover::after,
.card-prop .photo > .stale-tag[data-tooltip]:focus-visible::after,
.card-prop .photo > .stale-tag[data-tooltip].tip-open::after {
  left: auto;
  right: 0;
  bottom: calc(100% + 8px);
  max-width: 260px;
}
.card-prop .photo > .stale-tag[data-tooltip]:hover::before,
.card-prop .photo > .stale-tag[data-tooltip]:focus-visible::before,
.card-prop .photo > .stale-tag[data-tooltip].tip-open::before {
  left: auto;
  right: 12px;
  bottom: calc(100% + 3px);
}
.metric.is-stale b { opacity: .85; }
.metric.is-stale-strong b { opacity: .70; text-decoration: underline dotted rgba(224, 88, 77, .5); }
.card-prop .metrics-grades + .metrics {
  border-top: none; margin-top: 4px; padding-top: 4px;
}
.card-prop .metric { display: flex; flex-direction: column; position: relative; }
.card-prop .metric b { font-size: 14px; color: var(--text); }
.card-prop .metric .lbl { color: var(--muted); cursor: help; text-decoration: underline dotted; text-underline-offset: 2px; text-decoration-color: rgba(0,0,0,.15); }
.card-prop .metric .sub { color: var(--muted); font-size: 11px; margin-top: 1px; }
/* metric-neighborhood / metric-schools used to span 2 cols in the old
   4-col financial-metrics grid.  After 2026-06-10 the grid is 6-col and
   every cell is 1 col so the two grades line up under their column in
   the grades row above. */
.card-prop .tier {
  display: inline-block; min-width: 22px; padding: 0 5px; margin-right: 4px;
  border-radius: 4px; background: var(--bg-soft);
  font-size: 11px; font-weight: 800; text-align: center;
}
.card-prop .tier.grade-A { color: var(--grade-a); background: #d6efdc; }
.card-prop .tier.grade-B { color: var(--grade-b); background: #e1f1d8; }
.card-prop .tier.grade-C { color: var(--grade-c); background: #fcf2cc; }
.card-prop .tier.grade-D { color: var(--grade-d); background: #fde0c4; }
.card-prop .tier.grade-F { color: var(--grade-f); background: #f7d4d4; }
.card-prop .metric .muted { color: var(--muted); font-weight: 600; }

/* --- RAG (Red/Amber/Green) status pill — e.g. rent-estimate confidence --- */
.rag {
  display: inline-flex; align-items: center; gap: 5px;
  padding: 1px 9px; border-radius: 999px;
  font-size: 11.5px; font-weight: 700; text-transform: capitalize;
  border: 1px solid; line-height: 1.6; vertical-align: middle;
}
.rag::before { content: ""; width: 7px; height: 7px; border-radius: 50%; background: currentColor; }
/* The RAG pill draws its status dot with ::before; the generic tooltip arrow
   (below) ALSO uses ::before and, being more specific, replaced the in-flow dot
   with a position:absolute zero-size triangle on hover — so the pill lost the
   7px dot + 5px gap and visibly SHRANK. Re-assert the dot here at higher
   specificity (.rag[data-tooltip]:hover::before) so the size stays fixed; the
   tooltip bubble (::after) still shows, just without the little arrow on this
   one pill. */
.rag[data-tooltip]:hover::before,
.rag[data-tooltip]:focus-visible::before,
.rag[data-tooltip].tip-open::before {
  content: ""; position: static; left: auto; bottom: auto; z-index: auto;
  width: 7px; height: 7px; border: 0; border-radius: 50%; background: currentColor;
}
.rag-high   { color: #1e7e34; border-color: rgba(30,126,52,.45);  background: rgba(30,126,52,.12); }
.rag-medium { color: #b26a00; border-color: rgba(178,106,0,.45);  background: rgba(178,106,0,.12); }
.rag-low    { color: #c1271b; border-color: rgba(193,39,27,.45);  background: rgba(193,39,27,.12); }
.rag-none   { color: var(--muted); border-color: var(--border);   background: rgba(0,0,0,.05); }

/* --- Tooltip on data-tooltip (works on metric labels + detail-page dt) --- */
[data-tooltip] { position: relative; }
[data-tooltip]:hover::after,
[data-tooltip]:focus-visible::after,
[data-tooltip].tip-open::after {          /* tap-to-open on touch (no hover) */
  content: attr(data-tooltip);
  position: absolute; left: 0; bottom: calc(100% + 6px);
  /* Top layer — above the sticky topnav (2000) and feed bars so the tooltip
     is never painted behind a neighbouring card or header. */
  z-index: 3000; min-width: 200px; max-width: 280px;
  padding: 8px 10px; border-radius: 6px;
  background: #1c2530; color: #fff; font-weight: 500; font-size: 12px;
  line-height: 1.35; white-space: normal; text-align: left;
  box-shadow: 0 4px 14px rgba(0,0,0,.18);
  pointer-events: none;
}
[data-tooltip]:hover::before,
[data-tooltip]:focus-visible::before,
[data-tooltip].tip-open::before {
  content: ""; position: absolute; left: 16px; bottom: calc(100% + 1px);
  z-index: 3000; width: 0; height: 0;
  border: 5px solid transparent; border-top-color: #1c2530;
  pointer-events: none;
}
/* Both metric rows are now SIX columns (repeat(6, 1fr)).  Tooltips on
   left-half cells (1-3) grow rightward; right-half cells (4-6) grow
   leftward so the rightmost tooltip can't extend past the card edge
   and get clipped by .card-prop { overflow: hidden }.  One unified
   rule covers .metrics AND .metrics-grades since both grids share
   the same shape. */
.card-prop .metric:nth-child(-n+3)[data-tooltip]:hover::after,
.card-prop .metric:nth-child(-n+3)[data-tooltip]:focus-visible::after,
.card-prop .metric:nth-child(-n+3)[data-tooltip].tip-open::after { left: 0; right: auto; }
.card-prop .metric:nth-child(-n+3)[data-tooltip]:hover::before,
.card-prop .metric:nth-child(-n+3)[data-tooltip]:focus-visible::before,
.card-prop .metric:nth-child(-n+3)[data-tooltip].tip-open::before { left: 16px; right: auto; }
.card-prop .metric:nth-child(n+4)[data-tooltip]:hover::after,
.card-prop .metric:nth-child(n+4)[data-tooltip]:focus-visible::after,
.card-prop .metric:nth-child(n+4)[data-tooltip].tip-open::after { left: auto; right: 0; }
.card-prop .metric:nth-child(n+4)[data-tooltip]:hover::before,
.card-prop .metric:nth-child(n+4)[data-tooltip]:focus-visible::before,
.card-prop .metric:nth-child(n+4)[data-tooltip].tip-open::before { left: auto; right: 16px; }

.metric .positive { color: var(--positive); }
.metric .negative { color: var(--negative); }
.flag {
  display: inline-block; background: var(--teal-light); color: var(--teal);
  font-size: 11px; padding: 2px 8px; border-radius: 999px;
  margin-right: 4px; margin-top: 4px; font-weight: 600;
}

/* ---- Layout dropdown (search mode only) ----
   Defensive [hidden] override: our `display: inline-block` rule has the
   same specificity as the user-agent `[hidden] { display: none }` rule
   and loads later, so [hidden] silently lost. Same bug class as the
   modal-overlay; same fix. */
/* Layout-group/trigger/menu must sit above Leaflet's panes (z-index ~400)
   and controls (~1000) — otherwise when the user opens Map mode the
   Leaflet canvas paints over the dropdown and the menu becomes
   inaccessible. Bump both the group and menu past Leaflet's stack. */
.layout-group { position: relative; display: inline-block; z-index: 1200; }
.layout-group[hidden] { display: none; }
.filters-btn[hidden]  { display: none; }
.layout-trigger {
  display: inline-flex; align-items: center; gap: 6px;
  height: 34px; padding: 0 12px;
  border: 1px solid var(--border); border-radius: 6px;
  background: #fff; color: var(--text); font-size: 14px; font-weight: 600;
  cursor: pointer;
}
.layout-trigger:hover { border-color: var(--teal); }
.layout-menu {
  position: absolute; top: calc(100% + 6px); right: 0; z-index: 1300;
  min-width: 180px; padding: 4px;
  background: #fff; border: 1px solid var(--border); border-radius: 8px;
  box-shadow: 0 6px 18px rgba(0,0,0,.10);
  display: flex; flex-direction: column; gap: 2px;
}
/* Defensive: `.layout-menu { display: flex }` overrides the user-agent
   `[hidden] { display: none }` rule (same specificity, our stylesheet
   loads later), so the menu was being painted on first render despite
   the [hidden] attribute. Explicit override fixes it. Same bug class
   as the modal-overlay and layout-group; centralised pattern. */
.layout-menu[hidden] { display: none; }
.header-sort[hidden] { display: none; }
.ms-menu[hidden]     { display: none; }
.badge-pop[hidden]   { display: none; }
.layout-menu button {
  display: flex; align-items: center; gap: 10px;
  padding: 8px 10px; border: none; background: transparent;
  color: var(--text);    /* themed: black in day mode, near-white in dark mode */
  font-size: 14px; text-align: left; cursor: pointer; border-radius: 4px;
}
.layout-menu button:hover { background: var(--bg-soft); }
.layout-menu .layout-icon { width: 18px; text-align: center; font-size: 14px; }

/* ---- Filters button (opens modal) ---- */
.filters-btn {
  height: 34px; padding: 0 14px;
  border: 1px solid var(--border); border-radius: 6px;
  background: #fff; color: var(--text); font-size: 14px; font-weight: 600;
  cursor: pointer; display: inline-flex; align-items: center; gap: 8px;
}
.filters-btn:hover { border-color: var(--teal); }
.filters-btn .active-count {
  background: var(--teal); color: #fff; padding: 1px 7px; border-radius: 999px;
  font-size: 11px; font-weight: 700;
}

/* ---- Map-first search layouts ---- */
.search-layouts { margin-top: 14px; }
.search-layout-row {
  display: grid; gap: 10px;
  /* Default: split mode (grid + map). */
  grid-template-columns: minmax(0, 1fr) minmax(0, 42%);
  /* The list/table (left) flows in the PAGE — ONE window scrollbar, no internal
     column scroll. The row is at least viewport-tall so the sticky map (right)
     fills the screen even with few results, and grows with the list otherwise.
     dvh accounts for mobile browser chrome. */
  min-height: calc(100dvh - var(--topnav-h, 57px) - var(--feed-header-h, 48px));
}
.search-layouts.mode-grid  .search-layout-row { grid-template-columns: 1fr; }
.search-layouts.mode-map   .search-layout-row { grid-template-columns: 1fr; }
/* Table mode: table on the left, map on the right (same split as the
   default mode). The selected-card-prop above the table previews the
   row the user clicked. */
.search-layouts.mode-table .search-layout-row {
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);   /* 50 / 50: table | map */
}
.search-layouts.mode-table .search-left { padding-right: 4px; }

.search-left {
  /* Flows in the page — the window scrollbar (right edge) scrolls the list /
     table; the map column is sticky. No internal column scrollbar. */
  overflow: visible; padding-right: 4px;
}
.search-left .grid {
  /* Within the side panel, cards stack one or two per row */
  grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
}
/* `.grid { display: grid }` would otherwise beat the user-agent
   `[hidden] { display: none }` rule, so the grid would still paint in
   table mode. Defensive override matching the modal-overlay / layout-
   menu pattern used elsewhere in this stylesheet. */
#feed-grid-search[hidden] { display: none !important; }
.search-right { position: relative; display: flex; flex-direction: column; }
.search-map { flex: 1; min-height: 360px; border-radius: 0; overflow: hidden; border: 1px solid var(--border); }
.search-layouts.mode-grid .search-right { display: none; }
.search-layouts.mode-map  .search-left  { display: none; }

/* ── Search-layout card density (desktop only; mobile stays 1/row above) ──────
   Split = grid (left) + map (right): exactly 2 cards/row so each is large; the
   map keeps its column and gains height from the freed vertical space (hidden
   #feed-stats + slimmer filter bar → taller --search-h). Grid = full width
   (map hidden): 4 cards/row filling the screen. */
@media (min-width: 981px) {
  .search-left .grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .search-layouts.mode-grid .search-left .grid { grid-template-columns: repeat(4, minmax(0, 1fr)); }
  /* Split: give the map a touch more room now that the bar is slimmer. */
  .search-layout-row { grid-template-columns: minmax(0, 1fr) minmax(0, 44%); }
  /* Split + Table: the map is STICKY so it stays in view while the page
     scrolls the list/table (one window scrollbar). Anchored just below the
     topnav and pulled up over the feed-header + filter-stack on its column
     via --map-pull (JS-measured: header height + stack height + 6). Height
     fills the rest of the viewport, less 16px so the corner badges
     (#search-map-status, #map-clear) clear browser chrome / a horizontal
     scrollbar. z-index above the sticky filter-stack (1002). Scrolling over
     the map zooms it (Leaflet scrollWheelZoom); scrolling over the list
     moves the page. */
  .search-layouts.mode-split .search-right,
  .search-layouts.mode-table .search-right {
    position: sticky;
    top: var(--topnav-h, 57px);
    align-self: start; z-index: 1003;
    margin-top: calc(-1 * var(--map-pull, 0px));
    /* Fill the entire viewport below the topnav — no bottom clearance. */
    height: calc(100dvh - var(--topnav-h, 57px));
  }
}
/* Keep the filter-row-2 dropdown menus above the pulled-up map. */
.fr2-dd .ms-menu { z-index: 1010; }

/* Task 4: hide the feed-stats strip entirely — it adds no user value and the
   reclaimed height (~50px) makes the search map + cards taller. */
#feed-stats,
#feed-stats.stats-strip { display: none; }

/* Task 5: the score/quality ms-group dropdowns + switches now live inside the
   "⚙ Filters" modal. Lay them out as a wrapped row; let their menus overlay. */
.fm-msgroups { display: flex; flex-wrap: wrap; gap: 8px; align-items: flex-start; }
.fm-msgroups .ms-group { position: relative; }
/* Each .ms-group is its own stacking context (.ms-group{z-index:1200}), so an
   OPEN dropdown's menu was painted BEHIND the next group's trigger (equal z →
   DOM order wins). Lift the group whose trigger is expanded above its siblings
   so its menu (property-type / neighborhood-tier / grade / school / crime) sits
   in the foreground. Restore the menu's own z-index too (the old 20 override
   undercut the base 1300). */
.fm-msgroups .ms-group:has(.ms-trigger[aria-expanded="true"]) { z-index: 2000; }
.fm-msgroups .ms-menu { z-index: 2001; }
.fm-msgroups .fm-switches { display: flex; gap: 14px; width: 100%; margin: 2px 0; }
/* Standalone .fm-switches (now used by the "Quick toggles" fieldset that
   replaced the .fm-msgroups wrapper). Same layout the parent-scoped rule
   above provides — kept as a separate selector so both contexts work. */
.fm-switches { display: flex; gap: 14px; flex-wrap: wrap; margin: 2px 0; }

/* Inline variant used by the 4 quality dropdowns inside #filter-modal
   (neighborhood-tier / grade-filter / school-grade / crime-grade). The
   ms-group DOM structure is preserved so app.js's initMultiSelect()
   continues to wire change events and rewrite state[<key>]; CSS just
   hides the trigger button and renders the menu as an always-visible
   inline checkbox row — matching the Home type / Status / Property
   details sections elsewhere in the modal. */
.fm-inline-msgroup {
  display: block;             /* full-width inside the fm-section */
  width: 100%;
  position: static;           /* drop the ms-group's stacking-context tweak */
  z-index: auto;
}
.fm-inline-msgroup > .ms-trigger {
  /* Trigger is now decorative state-only (initMultiSelect still toggles
     aria-expanded on it); hide visually while keeping it in the DOM. */
  display: none;
}
.fm-inline-msgroup > .ms-menu {
  /* Override popup chrome — render as a flat flex row aligned with the
     other modal sections. !important beats the [hidden] attribute that
     initMultiSelect sets when the trigger is "closed" (which it always
     is here, since the trigger is hidden + click events never fire). */
  display: flex !important;
  position: static;
  flex-wrap: wrap;
  gap: 6px 16px;
  min-width: 0; max-width: none; max-height: none;
  padding: 0; margin: 0;
  background: transparent;
  border: none;
  box-shadow: none;
  z-index: auto;
}
.fm-inline-msgroup > .ms-menu > label {
  /* Match .fm-checkgrid label rhythm (4 px vertical padding, no hover
     pill so the row reads as a single checkbox grid). */
  padding: 4px 6px;
  margin: 0;
  font-size: 14px;
  color: var(--text);
  background: transparent;
}
.fm-inline-msgroup > .ms-menu > label:hover {
  background: transparent;    /* the popup-style hover row is out of place inline */
}

/* ---- Table mode: side-by-side with the map, sticky map ---- */
/* Restructure the left column as a flex column so the table can grow
   to fill the row while #search-table-selected stays sized to its
   content. Scrolling is delegated to the table so the sticky map on
   the right stays in view as the user scans rows. */
.search-layouts.mode-table .search-left {
  /* Table flows in the page — the window scrollbar scrolls the rows; no
     internal table scrollbar. (Map positioning is the shared sticky rule above.) */
  display: block; overflow: visible;
}
.search-layouts.mode-table #search-table {
  display: table; width: 100%; overflow: visible;
}
/* Freeze the table header as the page scrolls the rows. Sticks at the top
   of the search-layout-row, just below the topnav + feed-header +
   filter-row-2 band. --map-pull already encodes the full distance from the
   topnav to the row top (set by sizeSearchLayout in search-layout.js), so
   `topnav + map-pull` lands the header right at the row top. Previously
   `+ feed-header` was double-counted on top of map-pull → the header stuck
   ~50 px BELOW the row top, sandwiched mid-table with rows visible both
   above and below it. Opaque background keeps rows from bleeding through. */
/* Table header in mode-table — flows in the page (no freeze) and uses the
   teal accent already in the color scheme so it reads as "header" without
   the shouting brand red. */
.search-layouts.mode-table #search-table thead th {
  background: var(--teal); color: #fff;
  font-weight: 700;
  border-bottom: 1px solid var(--teal-dark, var(--teal));
}
html.dark-mode .search-layouts.mode-table #search-table thead th { background: var(--teal); color: #fff; }

/* Map badges (vertical stack on the right edge)
   top: 96px keeps a clear ~22px gap below the Leaflet zoom control
   (top: 12px + ~58px tall), which would otherwise overlap #map-draw. */
.search-map { position: relative; }
.map-badges {
  position: absolute; top: 96px; right: 12px;
  display: flex; flex-direction: column; gap: 8px;
  z-index: 600;       /* above Leaflet panes & controls */
}
.map-badge {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  width: 56px; min-height: 56px; padding: 6px 4px;
  /* Match the rich popup-card surface so badges read as the same family */
  background: var(--surface, #fff); color: var(--text);
  border: 1px solid var(--border); border-radius: 8px;
  font-size: 11px; font-weight: 700;
  cursor: pointer; gap: 2px;
  box-shadow: 0 2px 6px rgba(0,0,0,.12);
  transition: background .12s, color .12s, transform .12s;
}
.map-badge:hover { background: var(--teal-light); border-color: var(--teal); }
.map-badge.active { background: var(--teal); color: #fff; border-color: var(--teal); }
.map-badge-icon { font-size: 18px; line-height: 1; }
.map-badge-label { line-height: 1; }

/* Leaflet zoom control sits above the badge column on the right edge —
   restyle so it reads as part of the same family (same surface, border,
   radius, shadow) instead of the default Leaflet light card. */
.search-map .leaflet-top.leaflet-right { top: 12px; right: 12px; }
.search-map .leaflet-bar {
  background: var(--surface, #fff);
  border: 1px solid var(--border); border-radius: 8px;
  box-shadow: 0 2px 6px rgba(0,0,0,.12);
  overflow: hidden;
}
.search-map .leaflet-bar a,
.search-map .leaflet-bar a:hover {
  background: var(--surface, #fff); color: var(--text);
  border-bottom: 1px solid var(--border);
  width: 56px; height: 28px; line-height: 28px;
  font-size: 18px; font-weight: 700;
}
.search-map .leaflet-bar a:last-child { border-bottom: none; }
.search-map .leaflet-bar a:hover { background: var(--teal-light); color: var(--teal); }
html.dark-mode .search-map .leaflet-bar,
html.dark-mode .search-map .leaflet-bar a,
html.dark-mode .search-map .leaflet-bar a:hover {
  background: var(--surface); color: var(--text); border-color: var(--border);
}

/* Popovers that flap out to the left of the badge stack */
.badge-pop {
  position: absolute; right: 80px; z-index: 700;
  background: var(--surface, #fff); border: 1px solid var(--border); border-radius: 8px;
  padding: 10px 12px; min-width: 180px;
  box-shadow: 0 8px 22px rgba(0,0,0,.16);
}
.badge-pop h4 {
  margin: 0 0 8px;
  font-size: 12px; text-transform: uppercase; letter-spacing: .03em;
  color: var(--muted);
}
.badge-pop label {
  display: flex; align-items: center; gap: 8px;
  padding: 5px 0; font-size: 13px; cursor: pointer;
  color: var(--text);
}
.badge-pop label.opt-disabled { color: var(--muted); cursor: not-allowed; }
#map-type-pop    { top: 96px; }     /* aligned with the Map badge row */
#map-options-pop { top: 158px; }    /* aligned with the Options badge row */

/* Bottom-LEFT "Remove outline" pill — anchored to the map's left edge so
   the bottom-right is free for the 💬 Help bubble. ~10% closer to the
   bottom edge than before (24 → 22 px). */
.map-remove-outline {
  position: absolute; bottom: 22px; left: 16px; z-index: 600;
  padding: 7px 14px;
  background: var(--surface, #fff); color: var(--text);
  border: 1px solid var(--border); border-radius: 999px;
  font-size: 13px; font-weight: 700; cursor: pointer;
  box-shadow: 0 2px 6px rgba(0,0,0,.12);
}
.map-remove-outline:hover { border-color: var(--brand); color: var(--brand); }

/* Top-left status counter (subtle, doesn't compete with attribution).
   The count itself is wrapped in `.cnt` and painted in the brand color so
   it pops against the muted label text. */
.map-status-corner {
  position: absolute; top: 14px; left: 14px; z-index: 600;
  background: rgba(255,255,255,.92); color: var(--muted);
  padding: 4px 10px; border-radius: 999px;
  font-size: 12px; font-weight: 600;
  pointer-events: none;
  box-shadow: 0 1px 3px rgba(0,0,0,.10);
}
.map-status-corner .cnt { color: var(--brand); font-weight: 800; }

/* Overlay POI dots (Schools / Transit / Amenities) */
.overlay-dot { background: transparent !important; border: none !important; }
.overlay-dot span {
  display: block; width: 10px; height: 10px; border-radius: 50%;
  border: 2px solid #fff; box-shadow: 0 1px 2px rgba(0,0,0,.4);
}

/* Rich property popup (Leaflet popup) ----------------------------------- */
.leaflet-popup.property-popup .leaflet-popup-content-wrapper {
  padding: 0; border-radius: 10px; overflow: hidden;
  background: var(--surface, #fff); color: var(--text);
  box-shadow: 0 6px 22px rgba(0,0,0,.28);
}
.leaflet-popup.property-popup .leaflet-popup-content { margin: 0; width: auto !important; }
.leaflet-popup.property-popup .leaflet-popup-tip { background: var(--surface, #fff); }
.popup-card { display: block; text-decoration: none; color: inherit; }
.popup-card:hover { text-decoration: none; }
.popup-photo {
  aspect-ratio: 16/9; background: #e3e8ed center/cover no-repeat;
}
.popup-photo-empty {
  display: grid; place-items: center;
  color: var(--muted); font-size: 12px; font-weight: 600; text-transform: uppercase;
  background: linear-gradient(135deg, #f1f4f7 0%, #c8d3df 100%);
}
html.dark-mode .popup-photo-empty {
  background: linear-gradient(135deg, #232b33 0%, #131a1f 100%);
}
.popup-body { padding: 10px 12px 12px; }
.popup-row { display: flex; align-items: center; justify-content: space-between; }
.popup-price { font-size: 20px; font-weight: 800; }
.popup-grade { font-weight: 800; font-size: 14px;
  padding: 2px 8px; border-radius: 4px; background: rgba(0,0,0,.06); }
.popup-specs { color: var(--muted); font-size: 13px; margin-top: 2px; }
.popup-hot { color: var(--brand); font-weight: 700; margin-left: 4px; }
.popup-addr { font-size: 12px; margin-top: 6px; line-height: 1.35; }
.popup-flags { display: flex; flex-wrap: wrap; gap: 4px; margin-top: 6px; }
.popup-metrics {
  display: grid; grid-template-columns: repeat(4, 1fr); gap: 4px 8px;
  margin-top: 8px; padding-top: 8px; border-top: 1px solid var(--border);
  font-size: 11px;
}
.popup-metrics .popup-m-lbl { color: var(--muted); display: block; }
.popup-metrics b { font-size: 13px; color: var(--text); }
.popup-metrics .positive { color: var(--positive); }
.popup-metrics .negative { color: var(--negative); }
.popup-cta {
  display: block; margin-top: 10px; padding-top: 8px;
  border-top: 1px dashed var(--border);
  color: var(--teal); font-weight: 700; font-size: 13px; text-align: right;
}

/* Cluster bubbles (replace Leaflet.markercluster's default beige rings) */
.pin-cluster {
  background: transparent !important; border: none !important;
  text-align: center;
}
.pin-cluster span {
  display: inline-flex; align-items: center; justify-content: center;
  border-radius: 50%; font-weight: 800; color: #fff;
  background: var(--teal);
  border: 3px solid rgba(255,255,255,.85);
  box-shadow: 0 2px 6px rgba(0,0,0,.35);
}
.pin-cluster-sm span { width: 32px; height: 32px; font-size: 13px; }
.pin-cluster-md span { width: 38px; height: 38px; font-size: 14px; }
.pin-cluster-lg span { width: 46px; height: 46px; font-size: 14px; }
.pin-cluster-xl span { width: 56px; height: 56px; font-size: 15px; }
/* Active cluster: when the user hovers a card whose pin is currently folded
   inside a cluster bubble, paint the bubble red so the location is obvious
   even though the individual marker isn't visible at this zoom. */
.pin-cluster.active span {
  background: var(--accent, #e63946);
  border-color: #fff;
  box-shadow: 0 0 0 3px rgba(230,57,70,.35), 0 2px 8px rgba(0,0,0,.4);
}
/* Hide the default leaflet.markercluster div (we wrap content in a span) */
.leaflet-marker-icon.marker-cluster div { display: none; }

/* ---- Property dot markers (dense-cluster friendly) ----
   Small enough not to overlap each other at typical zoom; hover scales
   them up; selected ones get a teal ring. Price appears in the tooltip. */
.pin-dot {
  width: 10px; height: 10px; border-radius: 50%;
  background: var(--teal);
  border: 2px solid #fff;
  box-shadow: 0 1px 3px rgba(0,0,0,.45);
  transition: transform .12s ease;
}
.pin-dot.hot { background: var(--brand); }
.pin-dot:hover {
  transform: scale(1.6);
  z-index: 1000 !important;
}
.pin-dot.selected {
  width: 14px; height: 14px;
  border: 3px solid var(--teal);
  background: #fff;
  box-shadow: 0 0 0 3px rgba(13,115,119,.25), 0 1px 3px rgba(0,0,0,.5);
  z-index: 1000 !important;
}

/* ---- Property house markers ---- red house glyph anchored at its base;
   hover/selected scale up, hot listings shade darker. */
.pin-house svg {
  width: 26px; height: 26px; display: block;
  fill: #e23b2e; stroke: #fff; stroke-width: 1.1; paint-order: stroke;
  filter: drop-shadow(0 1px 2px rgba(0,0,0,.5));
  transition: transform .12s ease;
  transform-origin: 50% 90%;
}
.pin-house.hot svg { fill: #b5160c; }
.pin-house:hover svg { transform: scale(1.4); }
.pin-house:hover { z-index: 1000 !important; }
.pin-house.selected svg {
  transform: scale(1.5);
  fill: #c52a1e;
  filter: drop-shadow(0 0 3px rgba(255,255,255,.95)) drop-shadow(0 1px 3px rgba(0,0,0,.6));
}
.pin-house.selected { z-index: 1000 !important; }

/* ---- Search table (Table mode) ---- */
.search-table {
  width: 100%;
  /* `border-collapse: collapse` would silently disable `position: sticky` on
     <th> in Chromium/WebKit (the freeze-thead behavior in mode-table). Use
     `separate` + `border-spacing: 0` to match the prior visual exactly while
     keeping the sticky header functional. */
  border-collapse: separate; border-spacing: 0;
  font-size: 13px; background: #fff;
}
.search-table th, .search-table td {
  padding: 8px 10px; text-align: left;
  border-bottom: 1px solid var(--border);
}
.search-table th { background: var(--bg-soft); font-weight: 700; color: var(--muted); font-size: 12px; }
.search-table tr { cursor: pointer; }
.search-table tr:hover { background: var(--teal-light); }
.search-table tr.highlighted { background: rgba(13,115,119,.15); }
.search-table tbody tr.selected {
  background: rgba(13,115,119,.18);
  box-shadow: inset 3px 0 0 var(--teal);
}
.search-table tbody tr.selected:hover { background: rgba(13,115,119,.22); }
.search-table a { color: var(--teal); font-weight: 600; }

/* Card-prop highlighted state (when matched from a map click) */
.card-prop.highlighted { box-shadow: 0 0 0 3px var(--teal); }

/* ---- Filter slide-in panel ----
   Anchored to the right edge of the *map column* (search-right) when
   search mode is active, so it overlays the map specifically — not the
   topnav or filter row. Falls back to viewport-anchored when, for any
   reason, search mode isn't active.
   Show/hide is class-based (.open), not the [hidden] attribute. */
.modal-overlay {
  position: fixed; right: 0;
  top: 170px;
  /* Clear the fixed Help chat bubble (.chat-widget: bottom 20px, ~44px tall,
     z-index 9000) — at bottom:14px the modal's "Apply filters" footer (bottom-
     right) was hidden behind it. Lift the panel so the footer sits above it. */
  bottom: 88px;
  /* Wider when in search mode so the form is unambiguously in the
     foreground — fills ~⅓ of the viewport over the map. Capped at
     820 px so the quality fm-tile-rows (8 tiles at neighborhood-tier)
     fit on a single line without wrapping; below that they used to
     wrap on the second-to-last tile which looked broken. */
  width: 38vw; min-width: 480px; max-width: 820px;
  /* Leaflet's controls and popupPane sit around z-index 1000. Push
     the modal above all of them so the form is the clear top layer. */
  z-index: 1100;
  display: flex;
  transform: translateX(100%);
  transition: transform .22s ease-out;
  pointer-events: none;
  /* Subtle backdrop blur fading the map so the form reads as the
     primary surface, without dimming the page. */
  filter: drop-shadow(-12px 0 18px rgba(0,0,0,.28));
}
body.search-mode-active .modal-overlay {
  right: 18px;
}
.modal-overlay.open {
  transform: translateX(0);
  pointer-events: auto;
}
.modal-overlay[hidden] { display: none; }
.modal-shell {
  background: var(--surface, #fff);
  width: 100%; height: 100%;
  display: flex; flex-direction: column;
  border-radius: 10px;
  box-shadow: -8px 0 24px rgba(0,0,0,.28);
  overflow: hidden;
}
.modal-head {
  display: flex; align-items: center; justify-content: space-between;
  padding: 12px 16px; border-bottom: 1px solid var(--border);
  flex-shrink: 0;
}
.modal-tabs { display: inline-flex; gap: 0; }
.modal-tab {
  padding: 7px 14px; border: 1px solid var(--border); background: #fff;
  font-size: 13px; font-weight: 600; cursor: pointer; border-right: none;
}
.modal-tab:first-child { border-radius: 6px 0 0 6px; }
.modal-tab:last-child { border-right: 1px solid var(--border); border-radius: 0 6px 6px 0; }
.modal-tab.active { background: var(--teal); color: #fff; border-color: var(--teal); }
.modal-tab:disabled { opacity: .35; cursor: not-allowed; }
.modal-close {
  width: 32px; height: 32px; border-radius: 50%;
  border: none; background: var(--bg-soft); cursor: pointer; font-size: 14px;
  display: flex; align-items: center; justify-content: center;
}
.modal-close:hover { background: var(--border); }
.modal-body { overflow-y: auto; padding: 12px 16px 8px; flex: 1; min-height: 0; }
.modal-foot {
  display: flex; justify-content: space-between; align-items: center; gap: 10px;
  padding: 12px 16px; border-top: 1px solid var(--border);
  flex-shrink: 0;
}
.fm-section { border: none; padding: 12px 0; margin: 0; }
.fm-section + .fm-section { border-top: 1px solid var(--border); }
.fm-section legend { font-weight: 700; font-size: 14px; padding: 0 0 6px; color: var(--text); }
/* The big interactive labels (checkboxes & status toggles) need full text
   contrast in dark mode; the small "Min/Max" caption labels stay muted
   (var(--muted) is already legible against the dark surface). */
.fm-types label, .fm-status label, .fm-keyword { color: var(--text); }
.fm-row { display: flex; gap: 18px; }
.fm-half { flex: 1; }

.fm-range { display: flex; align-items: end; gap: 10px; }
.fm-range label { display: flex; flex-direction: column; gap: 4px; font-size: 12px; color: var(--muted); }
.fm-range input {
  width: 130px; padding: 7px 10px;
  border: 1px solid var(--border); border-radius: 6px; font-size: 14px;
}
.fm-sep { padding-bottom: 8px; color: var(--muted); }

.fm-tile-row {
  display: flex; align-items: center; gap: 6px; flex-wrap: wrap;
  margin-top: 6px;
}
.fm-tile-row .fm-label { font-size: 12px; color: var(--muted); width: 80px; }
.fm-tile {
  padding: 6px 12px; border: 1px solid var(--border);
  background: #fff; border-radius: 6px; cursor: pointer;
  font-size: 13px; font-weight: 600;
}
.fm-tile:hover { border-color: var(--teal); }
/* Both selectors needed: `html.dark-mode .fm-tile` (spec 0,2,1) above
   would otherwise win over a bare `.fm-tile.active` (spec 0,2,0) and
   clobber the active background in dark mode → selection invisible. */
.fm-tile.active,
html.dark-mode .fm-tile.active { background: var(--teal); color: #fff; border-color: var(--teal); }

.fm-checkgrid {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
  gap: 6px 14px;
}
.fm-types label {
  display: flex; align-items: center; gap: 6px;
  border: 1px solid var(--border); border-radius: 6px;
  padding: 8px 10px; cursor: pointer; font-size: 13px;
}
.fm-types label:hover { border-color: var(--teal); background: var(--bg-soft); }
.fm-status label { display: flex; align-items: center; gap: 6px; padding: 6px 0; cursor: pointer; }

.fm-grid {
  display: grid; grid-template-columns: 1fr 1fr; gap: 8px 12px;
}
.fm-grid label { display: flex; flex-direction: column; gap: 4px; font-size: 12px; color: var(--muted); }
.fm-grid input, .fm-select {
  padding: 7px 10px; border: 1px solid var(--border); border-radius: 6px; font-size: 14px;
}
.fm-select { width: 100%; }

.fm-keyword { display: flex; flex-direction: column; gap: 6px; font-size: 13px; }
.fm-keyword input { padding: 8px 12px; border: 1px solid var(--border); border-radius: 6px; font-size: 14px; }
.fm-keyword .fm-hint { color: var(--muted); font-size: 11px; }

.fm-future { color: var(--muted); font-size: 12px; font-style: italic; padding: 6px 0 0; }

.fm-reset {
  background: transparent; border: none; color: var(--teal);
  font-weight: 700; cursor: pointer; font-size: 14px;
}
.fm-apply {
  background: var(--brand); color: #fff;
  border: none; border-radius: 6px; padding: 10px 24px;
  font-weight: 700; cursor: pointer; font-size: 14px;
}
.fm-apply:hover { background: var(--brand-dark); }

.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;
}

/* ---- Pager ---- */
.pager {
  display: flex; gap: 12px; justify-content: center; align-items: center;
  padding: 24px 0;
}
.pager button {
  padding: 8px 16px; border: 1px solid var(--border); background: #fff; border-radius: 4px;
  cursor: pointer;
}
.pager button:disabled { opacity: .4; cursor: not-allowed; }

/* ---- Footer ---- */
.footer {
  border-top: 1px solid var(--border);
  /* Bottom padding so the footer links clear the fixed Help button (bottom-right)
     when scrolled to the very bottom. reCAPTCHA now lives on the left edge. */
  padding: 16px 24px 72px;
  display: flex; justify-content: space-between; align-items: center;
  flex-wrap: wrap; gap: 8px 16px; color: var(--muted); font-size: 12px;
}
.footer-links { display: flex; flex-wrap: wrap; gap: 6px 14px; }
.footer-links a { color: var(--muted); text-decoration: none; }
.footer-links a:hover { color: var(--teal); }

/* reCAPTCHA v3 badge: LEFT edge, vertically centered. Collapsed to just the
   reCAPTCHA logo (~60px) to reclaim space, expanding to the full "protected by
   reCAPTCHA" panel on hover/focus (grows rightward into the page since the left
   edge is anchored).
   NOTE: overriding `right` disables Google's own slide-to-collapse, so we
   collapse via width + overflow:hidden instead — position-independent. The
   logo sits at the left of the badge, so the clamped width shows exactly it.
   Compliance preserved: logo stays visible + disclosure is in Privacy/Cookies. */
.grecaptcha-badge {
  left: 4px !important;
  right: auto !important;
  top: 75% !important;
  bottom: auto !important;
  transform: translateY(-50%) !important;     /* anchored at 3/4 page height */
  width: 60px !important;
  overflow: hidden !important;
  transition: width .3s cubic-bezier(.4, 0, .2, 1) !important;
  box-shadow: 0 0 8px rgba(0, 0, 0, .18) !important;
  border-radius: 4px !important;
}
.grecaptcha-badge:hover,
.grecaptcha-badge:focus-within {
  width: 256px !important;     /* reveal full badge text on intent */
}
@media (prefers-reduced-motion: reduce) {
  .grecaptcha-badge { transition: none !important; }
}

/* ---- Cookie consent banner ---- */
.cookie-consent {
  position: fixed; left: 12px; right: 12px; bottom: 12px; z-index: 9500;
  display: flex; align-items: center; justify-content: space-between;
  flex-wrap: wrap; gap: 10px 18px;
  max-width: 1100px; margin: 0 auto;
  background: var(--surface, #fff); color: var(--text);
  border: 1px solid var(--border); border-radius: 12px;
  padding: 14px 18px; box-shadow: 0 10px 34px rgba(0,0,0,.22);
}
.cookie-consent[hidden] { display: none; }
.cookie-consent .cc-text { margin: 0; font-size: 13.5px; line-height: 1.5; flex: 1 1 380px; }
.cookie-consent .cc-text a { color: var(--teal); }
.cookie-consent .cc-actions { display: flex; gap: 8px; flex: 0 0 auto; }
.cookie-consent .cc-btn {
  border: 1px solid var(--border); background: var(--bg, #fff); color: var(--text);
  border-radius: 8px; padding: 9px 16px; font-size: 13.5px; font-weight: 600; cursor: pointer;
}
.cookie-consent .cc-btn:hover { border-color: var(--teal); }
.cookie-consent .cc-btn.primary { background: var(--teal); color: #fff; border-color: var(--teal); }
@media (max-width: 640px) {
  .cookie-consent { flex-direction: column; align-items: stretch; }
  .cookie-consent .cc-actions { justify-content: stretch; }
  .cookie-consent .cc-btn { flex: 1; min-height: 42px; }
}

/* ---- Property detail ---- */
.detail { max-width: 1240px; margin: 0 auto; }
/* NOTE: the authoritative `.detail-hero` rule lives ~1200 lines above
   (the Redfin-style mosaic). The legacy single-image hero rule that
   used to sit here was clipping the per-tile border-radius via
   `overflow: hidden` + reinstating a `background: #eee` panel — kept
   only the descendant-image + no-image helpers, which both layouts
   still rely on. */
.detail-hero img { width: 100%; height: 100%; object-fit: cover; }
.detail-hero .no-image { display: grid; place-items: center; height: 100%; color: var(--muted); font-size: 18px; }
.detail-overlay { position: absolute; top: 12px; right: 12px; display: flex; gap: 8px; }
.detail-overlay .grade { background: #fff; padding: 6px 14px; border-radius: 6px; font-weight: 800; font-size: 16px; }
.detail-overlay .composite { background: var(--teal); color: #fff; padding: 6px 12px; border-radius: 6px; font-weight: 700; font-size: 14px; }
.detail-head {
  padding: 14px 16px;
  border: 1px solid var(--border);
  border-radius: 10px;
  margin-top: 12px;
}
.detail-head h1 { margin: 0; font-size: 32px; }
.detail-head .addr { color: var(--muted); margin-top: 4px; }
.detail-head .specs { margin-top: 8px; font-size: 15px; }
/* Divider between the facts block (price/addr/specs) and the row of
   action pill-buttons (Compare / Deal sheet / Offer / DD / Notes).
   The compare-toggle pills sit immediately below this line, so a thin
   rule + a few px of breathing room clearly separates "what is this
   property" from "what can I do with it". */
.detail-head-divider {
  border: 0; border-top: 1px solid var(--border);
  margin: 14px 0 10px;
  height: 0;
}

/* Listing remarks + key facts + tags */
.listing-extra {
  margin-top: 14px;
  padding: 16px;
  border: 1px solid var(--border);
  border-radius: 10px;
  background: var(--bg-soft);
}

/* Plain-English investor summary panel (between listing-extra and .cards). */
.investor-summary {
  margin-top: 14px;
  padding: 16px 18px;
  border: 1px solid var(--teal);
  border-left: 4px solid var(--teal);
  border-radius: 10px;
  background: var(--surface);
}
.investor-summary h2 {
  margin: 0 0 10px; font-size: 17px; font-weight: 700;
  display: flex; align-items: center; gap: 8px;
}
.investor-summary .is-lines {
  margin: 0; padding-left: 18px;
  list-style: disc; color: var(--text);
  font-size: 14px; line-height: 1.55;
}
.investor-summary .is-lines li + li { margin-top: 4px; }
.investor-summary .is-lines b { color: var(--brand); font-weight: 700; }
.investor-summary .is-callout {
  margin-top: 12px; padding: 10px 14px;
  background: var(--teal-light); color: var(--text);
  border-radius: 8px;
  display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap;
}
.investor-summary .is-callout-label {
  font-size: 12px; font-weight: 700; text-transform: uppercase;
  letter-spacing: .04em; color: var(--teal);
}
.investor-summary .is-callout-value {
  font-size: 20px; font-weight: 800; color: var(--brand);
}
.investor-summary .is-callout-delta {
  font-size: 13px; color: var(--muted); font-weight: 600;
}
.investor-summary .is-questions {
  margin-top: 16px; padding-top: 14px;
  border-top: 1px dashed var(--border);
}
.investor-summary .is-questions h3 {
  margin: 0 0 8px; font-size: 14px; font-weight: 700;
  text-transform: uppercase; letter-spacing: .04em; color: var(--teal);
}
.investor-summary .is-questions ol {
  margin: 0; padding-left: 22px;
  font-size: 13.5px; line-height: 1.55; color: var(--text);
}
.investor-summary .is-questions li { margin-top: 4px; }
.investor-summary .is-questions b { color: var(--brand); font-weight: 700; }

/* ── Sectioned investor summary (2026-06-16) ──────────────────────────
   Groups the 18-line wall into 5 scannable buckets with subtle header
   chips + leading sentiment icons. Color is intentionally restrained —
   the section TITLE picks up the section's accent only for the left
   border + chip, never the body text — to avoid the accessibility tax
   of color-coded prose AND the print/PDF degradation when a user
   exports the summary.
   ──────────────────────────────────────────────────────────────────── */
.investor-summary .is-section + .is-section { margin-top: 14px; }
.investor-summary .is-section-title {
  margin: 0 0 6px; font-size: 12px; font-weight: 700;
  text-transform: uppercase; letter-spacing: .06em;
  color: var(--muted);
  display: inline-block; padding: 2px 8px;
  border-radius: 4px;
  background: var(--surface-2, rgba(0,0,0,.04));
}
/* Per-section accent — left border on the bullet list. Subtle hue,
   no color on the actual text. Keeps the contrast for body content. */
.investor-summary .is-section .is-lines {
  list-style: none; padding-left: 0;
  border-left: 2px solid var(--border);
  padding: 2px 0 2px 12px;
}
.investor-summary .is-section-deal_economics   .is-lines { border-left-color: var(--brand); }
.investor-summary .is-section-location_tenants .is-lines { border-left-color: #5b8db3; }
.investor-summary .is-section-forward_outlook  .is-lines { border-left-color: #6aa17a; }
.investor-summary .is-section-negotiation      .is-lines { border-left-color: #c0905a; }
.investor-summary .is-section-risks            .is-lines { border-left-color: #b06262; }
.investor-summary .is-line {
  position: relative; padding-left: 24px;
  display: block;
}
.investor-summary .is-line + .is-line { margin-top: 5px; }
.investor-summary .is-icon {
  position: absolute; left: 0; top: 1px;
  width: 18px; text-align: center;
  font-size: 13px; line-height: 1.4;
  opacity: .9;
}
.listing-extra .lx-block + .lx-block { margin-top: 18px; }
.listing-extra h2 {
  margin: 0 0 8px;
  font-size: 15px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .03em;
  color: var(--muted);
}
.listing-extra .lx-remarks {
  margin: 0;
  line-height: 1.55;
  white-space: pre-line;
}
.listing-extra .lx-facts {
  margin: 0;
  padding-left: 20px;
  columns: 2;
  column-gap: 28px;
}
.listing-extra .lx-facts li { margin: 2px 0; break-inside: avoid; }
.listing-extra .lx-fact-label { color: var(--muted); }
.listing-extra .lx-fact-value { font-weight: 600; }
.listing-extra .lx-tags { display: flex; flex-wrap: wrap; gap: 6px; }
.listing-extra .lx-tag {
  background: var(--teal);
  color: #fff;
  padding: 3px 10px;
  border-radius: 999px;
  font-size: 12px;
  font-weight: 600;
}
@media (max-width: 640px) {
  .listing-extra .lx-facts { columns: 1; }
}
/* Legacy `.flags` rule kept as a no-op so any stray usage stays cosmetic;
   the .flags <div> at the top of property.html was deleted 2026-06-01 —
   rule pills are now rendered inside .invest-kpi .kpi-flags. */
.flags { padding: 4px; }

/* Card grid — bumped min cell width 320 → 440 px so every card is wide
   enough that #loan-product-check-card's 3 inner columns (Conventional /
   DSCR / Hard money) all fit on a single line, AND every card in the
   grid shares the same width for visual consistency.  At the 1240 px
   max-width page wrapper this resolves to 2 cards per row × ~600 px
   each; on very wide screens (>1500 px) it relaxes to 3 cards per row. */
.cards {
  display: grid; gap: 16px; margin-top: 14px;
  grid-template-columns: repeat(auto-fill, minmax(440px, 1fr));
}

/* invest-kpi is intentionally always visible.  The strip sits between
   #property-hero and .detail-head and renders the deal-level scoreboard
   (Composite · 1% rule · Cap · CoC · Cashflow · DSCR) regardless of
   which card-tab is active — investors want this summary present no
   matter which tab they're browsing.  Tab-scoped visibility was tried
   on 2026-06-01 and removed 2026-06-03 per user request. */

/* ─── Investment-tab headline KPI strip ─────────────────────────────────
   Light card-style palette — matches .card (white background, slate
   border, dark text) so the scoreboard reads as part of the same
   surface vocabulary as the cards below it. Color comes from the
   saturated band accents on each tile's big number, not from the
   strip background. */
.invest-kpi {
  grid-column: 1 / -1;
  display: grid;
  /* 6 columns: headline (Composite) + 1% rule + Cap + CoC + Cashflow + DSCR.
     Was 5 (headline + 4) before the 1% rule tile was added 2026-06-03. */
  grid-template-columns: minmax(220px, 1.4fr) repeat(5, minmax(0, 1fr));
  gap: 12px;
  padding: 14px 16px;
  border-radius: 10px;
  background: #fff;
  color: var(--text, #14233a);
  border: 1px solid var(--border, #e3e8f0);
  /* Sit ~12 px below #property-hero — matches .detail-head's
     margin-top so the hero → invest-kpi → detail-head sequence reads
     as a consistent 12 px rhythm. Was tight-stacked against the
     hero before 2026-06-03. */
  margin-top: 12px;
}
.invest-kpi .kpi-tile {
  display: flex; flex-direction: column; justify-content: center;
  padding: 10px 14px; border-radius: 8px;
  background: var(--bg-soft, #f6f8fb);
  border: 1px solid var(--border, #e3e8f0);
  min-width: 0;
}
.invest-kpi .kpi-headline {
  background: #fff;
  border-color: var(--border, #e3e8f0);
  padding: 12px 14px 10px;
}
.invest-kpi .kpi-grade-wrap { display: flex; align-items: center; gap: 12px; }
.invest-kpi .kpi-grade {
  font-family: var(--mono, ui-monospace, monospace);
  font-size: 28px; font-weight: 700;
  padding: 4px 12px; border-radius: 8px;
  line-height: 1; letter-spacing: -.02em;
}
.invest-kpi .kpi-grade-meta { display: flex; flex-direction: column; gap: 2px; }
.invest-kpi .kpi-label {
  font-size: 10.5px; letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--muted, #6b7a93);
  font-weight: 700;
}
.invest-kpi .kpi-value {
  font-size: 24px; font-weight: 700; line-height: 1.05;
  font-variant-numeric: tabular-nums;
  color: var(--text, #14233a);
  margin-top: 4px;
}
.invest-kpi .kpi-value small {
  font-size: 12px; font-weight: 500;
  color: var(--muted, #6b7a93);
  margin-left: 3px;
}
.invest-kpi .kpi-value.kpi-composite { font-size: 28px; }
/* Band-color overrides — punchy enough to read against the white tile
   background. Slightly darker hues than the prior dark-bg palette so
   the contrast ratio with #f6f8fb stays AA. */
.invest-kpi .kpi-value.positive { color: #1ea25f; }   /* emerald */
.invest-kpi .kpi-value.negative { color: #d63a2f; }   /* clean red */
.invest-kpi .kpi-value.neutral  { color: #c08200; }   /* amber */
.invest-kpi .kpi-value.positive small,
.invest-kpi .kpi-value.negative small,
.invest-kpi .kpi-value.neutral  small {
  color: inherit; opacity: .7;
}
.invest-kpi .kpi-score-bar {
  margin-top: 10px; height: 7px; border-radius: 4px;
  background: var(--bg-soft, #eef1f7);
  border: 1px solid var(--border, #e3e8f0);
  overflow: hidden;
}
.invest-kpi .kpi-score-fill {
  height: 100%;
  background: linear-gradient(90deg, #d63a2f 0%, #c08200 50%, #1ea25f 100%);
  border-radius: 3px;
  transition: width .25s ease;
}
.invest-kpi .kpi-flags {
  grid-column: 1 / -1;
  display: flex; flex-wrap: wrap; gap: 6px;
  margin-top: 4px;
}
.invest-kpi .kpi-flag {
  background: var(--bg-soft, #f6f8fb);
  border: 1px solid var(--border, #e3e8f0);
  color: var(--text, #14233a);
  padding: 4px 10px; font-size: 11.5px; font-weight: 600;
  border-radius: 999px; letter-spacing: .01em;
}
/* Dark-mode override — when the rest of the page uses the dark theme,
   the KPI strip flips to the same dark surface variables so it doesn't
   become an awkward bright slab. */
html.dark-mode .invest-kpi,
html.dark-mode .invest-kpi .kpi-headline {
  background: var(--surface, #131820);
  border-color: var(--border, #283142);
  color: var(--text, #e7eef7);
}
html.dark-mode .invest-kpi .kpi-tile {
  background: var(--surface2, #1a2230);
  border-color: var(--border, #283142);
}
html.dark-mode .invest-kpi .kpi-value { color: var(--text, #e7eef7); }
html.dark-mode .invest-kpi .kpi-flag {
  background: var(--surface2, #1a2230);
  border-color: var(--border, #283142);
  color: var(--text, #e7eef7);
}
@media (max-width: 720px) {
  .invest-kpi { grid-template-columns: 1fr 1fr; }
  .invest-kpi .kpi-headline { grid-column: 1 / -1; }
}

/* ─── Investment-tab card accent stripes ────────────────────────────────
   Subtle left-edge color tag per card category so the eye groups them
   without reading the heading. Only fires on data-active-tab="investment";
   other tabs render unchanged. */
.cards[data-active-tab="investment"] .card-pair > .card:nth-of-type(1) { box-shadow: inset 3px 0 0 #36b37e; }   /* Investment metrics — scoring */
.cards[data-active-tab="investment"] .card-pair > .card:nth-of-type(2) { box-shadow: inset 3px 0 0 #c9a866; }   /* CMA / ARV — valuation */
.cards[data-active-tab="investment"] .card-pair > .projection-card     { box-shadow: inset 3px 0 0 #8e44ad; }   /* Projected returns — forward */
.cards[data-active-tab="investment"] #cashflow-card                    { box-shadow: inset 3px 0 0 #1d6fe0; }   /* Cashflow — primary */
.cards[data-active-tab="investment"] #financing-card                   { box-shadow: inset 3px 0 0 #5b6cce; }   /* Financing — indigo */
.cards[data-active-tab="investment"] #loan-product-check-card          { box-shadow: inset 3px 0 0 #2f4a83; }   /* Loan-product check — navy */

/* ─── Other tabs: extend the Investment accent-stripe vocabulary ────────
   Same `inset 3px 0 0 <hue>` device — one accent color per tab so the
   user reads tab identity from the card-edge color, while every card
   keeps the light .card palette (white bg, slate border, dark text)
   it shares with the Investment tab.
   Hue palette by tab:
     market    → teal      #0d7377  (location / external signal)
     property  → clay      #a05a2c  (physical asset / structural)
     taxation  → olive     #6b7d00  (revenue / IRS)
     rent      → coral     #e0875a  (cashflow inbound)
     climate   → sky blue  #3a7cdf  (environmental risk)               */
.cards[data-active-tab="market"]    .card { box-shadow: inset 3px 0 0 #0d7377; }
.cards[data-active-tab="property"]  .card { box-shadow: inset 3px 0 0 #a05a2c; }
.cards[data-active-tab="taxation"]  .card { box-shadow: inset 3px 0 0 #6b7d00; }
.cards[data-active-tab="rent"]      .card { box-shadow: inset 3px 0 0 #e0875a; }
.cards[data-active-tab="climate"]   .card { box-shadow: inset 3px 0 0 #3a7cdf; }

/* Each tab also gets a matching accent on the active-tab button so the
   header chip echoes the card-edge color. Keeps the visual tie obvious
   when the user is mid-tab-switch. */
.card-tabs .card-tab.active[data-tab="investment"] { border-bottom-color: #36b37e; }
.card-tabs .card-tab.active[data-tab="market"]     { border-bottom-color: #0d7377; }
.card-tabs .card-tab.active[data-tab="property"]   { border-bottom-color: #a05a2c; }
.card-tabs .card-tab.active[data-tab="taxation"]   { border-bottom-color: #6b7d00; }
.card-tabs .card-tab.active[data-tab="rent"]       { border-bottom-color: #e0875a; }
.card-tabs .card-tab.active[data-tab="climate"]    { border-bottom-color: #3a7cdf; }

.card { border: 1px solid var(--border); border-radius: 10px; padding: 16px; background: #fff; }
.card h3 { margin: 0 0 10px; font-size: 16px; }
.card dl { display: grid; grid-template-columns: 1fr auto; gap: 4px 12px; margin: 0; font-size: 14px; }
.card dt { color: var(--muted); }
.card dd { margin: 0; font-weight: 600; }
.card dd.total, .card dt.total { font-size: 16px; padding-top: 6px; border-top: 1px solid var(--border); margin-top: 6px; }
.card .positive { color: var(--positive); }
.card .negative { color: var(--negative); }
.card .pass { color: var(--positive); font-weight: 700; }
.card .fail { color: var(--negative); font-weight: 700; }
/* Taxation card: the tax-effect / after-tax block sits below the P&L, and the
   passive-investor notes read as a small justified footnote. */
.taxation-card .taxation-effect { margin-top: 10px; padding-top: 8px; border-top: 1px solid var(--border); }
.taxation-card .taxation-effect dd { font-size: 15px; }
.taxation-card .tax-notes { margin-top: 12px; line-height: 1.5; }
.taxation-card .tax-notes b { color: var(--text); }
.taxation-card .tax-rate-ctl { margin-top: 10px; display: flex; flex-wrap: wrap; align-items: baseline; gap: 4px 8px; font-size: 13px; color: var(--muted); }
.taxation-card .tax-rate-ctl label { color: var(--text); font-weight: 600; }
.taxation-card .tax-rate-ctl input { width: 52px; margin: 0 2px; padding: 3px 6px; font-size: 13px; font-weight: 700;
  border: 1px solid var(--border); border-radius: 6px; background: var(--bg, #fff); color: var(--text); text-align: right; }
.muted { color: var(--muted); }
.small { font-size: 12px; }
.grade-grid {
  display: grid; grid-template-columns: repeat(2, 1fr); gap: 6px 12px;
  font-size: 13px; margin-top: 8px;
}
.grade-grid > span {
  display: flex; align-items: center; justify-content: space-between;
  padding: 4px 6px; border-radius: 4px; background: var(--bg-soft);
}
.grade-grid .cat-name { color: var(--text); }
.grade-grid .cat-grade {
  display: inline-block; min-width: 26px; padding: 1px 6px;
  border-radius: 4px; font-weight: 800; text-align: center;
  background: rgba(0,0,0,.06); color: var(--muted);
}
.grade-grid .cat-grade.cat-A { background: #d6efdc; color: var(--grade-a); }
.grade-grid .cat-grade.cat-B { background: #e1f1d8; color: var(--grade-b); }
.grade-grid .cat-grade.cat-C { background: #fcf2cc; color: var(--grade-c); }
.grade-grid .cat-grade.cat-D { background: #fde0c4; color: var(--grade-d); }
.grade-grid .cat-grade.cat-F { background: #f7d4d4; color: var(--grade-f); }

.sub-h { font-size: 13px; margin: 14px 0 6px; color: var(--muted); text-transform: uppercase; letter-spacing: .04em; }
.mono  { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; }
.src-badge {
  display: inline-block; margin-left: 6px; padding: 1px 6px;
  border-radius: 999px; background: var(--teal-light); color: var(--teal);
  font-size: 10px; font-weight: 800; text-transform: uppercase; letter-spacing: .04em;
  cursor: help;
}

/* Industry mix on Market trends card — table with click-to-expand rows. */
.industry-table {
  width: 100%; border-collapse: collapse;
  font-size: 13px; margin: 6px 0 0;
}
.industry-table thead th {
  text-align: left; font-weight: 600;
  font-size: 10px; color: var(--muted);
  text-transform: uppercase; letter-spacing: 0.05em;
  padding: 4px 8px 4px 0;
  border-bottom: 1px solid var(--border);
  white-space: nowrap;
}
.industry-table thead .ind-th-count,
.industry-table thead .ind-th-rev { text-align: right; }

.industry-table .ind-row {
  cursor: pointer; transition: background .1s ease;
  border-bottom: 1px solid var(--border);
}
.industry-table .ind-row:hover,
.industry-table .ind-row:focus-visible { background: var(--bg-soft); outline: none; }
.industry-table .ind-row[aria-expanded="true"] { background: var(--bg-soft); }

.industry-table .ind-row td { padding: 7px 8px 7px 0; vertical-align: baseline; }
.industry-table .ind-name  { font-weight: 600; }
.industry-table .ind-count { color: var(--teal); font-weight: 700; text-align: right; font-variant-numeric: tabular-nums; }
.industry-table .ind-rev   { font-variant-numeric: tabular-nums; text-align: right; color: var(--muted); }

.industry-table .ind-caret {
  display: inline-block; width: 12px; margin-right: 6px;
  color: var(--muted); font-size: 10px;
  transition: transform .12s ease;
}
.industry-table .ind-row[aria-expanded="true"] .ind-caret { transform: rotate(90deg); }

.industry-table .ind-expand-row td {
  padding: 4px 8px 12px 24px;
  background: var(--bg-soft);
  border-bottom: 1px solid var(--border);
}
.ind-companies {
  list-style: none; padding: 0; margin: 0;
  display: grid; gap: 2px;
}
.ind-companies li {
  display: grid;
  grid-template-columns: 44px minmax(0,1.4fr) minmax(0,1fr) auto;
  gap: 12px; align-items: baseline;
  padding: 3px 0; font-size: 12px;
  border-bottom: 1px dashed var(--border);
}
.ind-companies li:last-child { border-bottom: 0; }
.ind-companies .co-rank {
  color: var(--muted); font-variant-numeric: tabular-nums;
  font-size: 11px; text-align: right;
}
.ind-companies .co-name { font-weight: 500; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.ind-companies .co-city { font-size: 11px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.ind-companies .co-rev  { font-size: 11px; font-variant-numeric: tabular-nums; text-align: right; }

/* Rent-vs-own bar in the Census card */
.bar-chart {
  display: inline-flex; width: 160px; height: 10px; vertical-align: middle;
  border-radius: 999px; overflow: hidden; background: var(--bg-soft);
  border: 1px solid var(--border);
}
.bar-renter { background: var(--teal); }
.bar-owner  { background: #c9d6e3; }
.bar-legend { margin-left: 8px; font-size: 11px; color: var(--muted); }

/* Honest "not ingested" stripe on the Census card */
.not-ingested dt, .not-ingested dd { color: var(--muted); font-style: italic; }
.not-ingested dt::after { content: " ⓘ"; opacity: .5; }

/* --- YoY delta arrows --- */
.delta {
  display: inline-flex; align-items: center; gap: 3px;
  padding: 1px 8px; border-radius: 999px;
  font-size: 12px; font-weight: 700;
}
.delta-up   { background: #dff4e3; color: var(--positive); }
.delta-down { background: #fbe1e1; color: var(--negative); }
.delta-flat { background: var(--bg-soft); color: var(--muted); }
.delta-na   { color: var(--muted); }

/* --- CMA verdict pill colors --- */
.verdict {
  display: inline-block; padding: 2px 10px; border-radius: 999px;
  font-weight: 800; font-size: 12px; letter-spacing: .02em;
}
.verdict-underpriced { background: #dff4e3; color: var(--positive); }
.verdict-fair        { background: #fcf2cc; color: var(--grade-c); }
.verdict-overpriced  { background: #fbe1e1; color: var(--negative); }

/* "Why is this my comp?" transparency drawer */
.comps-drawer { margin-top: 10px; }
.comps-drawer > summary {
  display: flex; align-items: baseline; gap: 8px;
  cursor: pointer; padding: 6px 0;
  font-size: 12px; font-weight: 600;
  border-top: 1px solid var(--border);
  list-style: none;
}
.comps-drawer > summary::-webkit-details-marker { display: none; }
.comps-drawer > summary::before {
  content: "▸"; color: var(--muted); font-size: 10px;
  display: inline-block; width: 12px;
  transition: transform .12s ease;
}
.comps-drawer[open] > summary::before { transform: rotate(90deg); }

.comps-table {
  width: 100%; border-collapse: collapse;
  font-size: 11px; margin-top: 4px;
  font-variant-numeric: tabular-nums;
}
.comps-table thead th {
  text-align: left; font-weight: 600;
  font-size: 10px; color: var(--muted);
  text-transform: uppercase; letter-spacing: .04em;
  padding: 4px 6px 4px 0; border-bottom: 1px solid var(--border);
}
.comps-table thead .comps-th-num { text-align: right; }
.comps-table tbody td { padding: 5px 6px 5px 0; border-bottom: 1px dashed var(--border); }
.comps-table tbody tr:last-child td { border-bottom: 0; }
.comps-table .comp-addr { max-width: 220px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.comps-table .num { text-align: right; }

/* Match-score chip (mirrors verdict pill tone) */
.match-chip {
  display: inline-block; min-width: 32px; padding: 1px 7px;
  border-radius: 999px; font-weight: 700; font-size: 11px;
  font-variant-numeric: tabular-nums;
}
.match-chip.match-hi  { background: #dff4e3; color: var(--positive); }
.match-chip.match-mid { background: #fcf2cc; color: var(--grade-c); }
.match-chip.match-lo  { background: #fbe1e1; color: var(--negative); }

.comps-foot { margin-top: 6px; font-size: 10px; }

/* "Fractional" pill shown in the Investment-metrics / CMA card headings. */
.frac-tag {
  display: inline-block; margin-left: 8px; vertical-align: middle;
  padding: 2px 9px; border-radius: 999px;
  background: linear-gradient(135deg, #8b5cf6 0%, #6d28d9 100%);
  color: #fff; font-size: 11px; font-weight: 700;
  letter-spacing: .02em; text-transform: uppercase;
}

/* --- Property map card --- */
.map-card { margin: 14px 0; }
.map-header {
  display: flex; align-items: center; gap: 16px; flex-wrap: wrap;
  margin-bottom: 8px;
}
.map-header h3 { margin: 0; font-size: 16px; }

/* POI / Rentals toggle */
.map-toggle {
  display: inline-flex; border: 1px solid var(--border); border-radius: 6px;
  overflow: hidden; background: var(--surface, #fff);
}
.map-toggle-btn {
  padding: 5px 12px; border: none; background: transparent;
  font-size: 13px; font-weight: 600; color: var(--text); cursor: pointer;
  border-right: 1px solid var(--border);
}
.map-toggle-btn:last-child { border-right: none; }
.map-toggle-btn.active { background: var(--teal); color: #fff; }
.map-toggle-btn:hover:not(.active) { background: var(--bg-soft); }
/* Dark-mode override — the default white background is unreadable on dark,
   and .map-toggle-btn color: var(--text) needs an explicit hover surface
   that contrasts with the panel underneath. */
html.dark-mode .map-toggle { background: var(--surface); border-color: var(--border); }
html.dark-mode .map-toggle-btn { color: var(--text); border-right-color: var(--border); }
html.dark-mode .map-toggle-btn:hover:not(.active) { background: rgba(255,255,255,.06); }
html.dark-mode .map-toggle-btn.active { background: var(--teal); color: #fff; }

/* Sold-only view: time-window selector that appears beside the toggle */
.sold-window {
  display: inline-flex; align-items: center; gap: 8px;
  font-size: 13px; color: var(--muted);
}
.sold-window[hidden] { display: none; }
.sold-window-label { font-weight: 600; }
/* Match the .map-toggle scheme: transparent buttons on a surface container,
   teal active, bg-soft hover that never overrides the active (clicked) state. */
.sold-window-btns { display: inline-flex; border: 1px solid var(--border); border-radius: 6px; overflow: hidden; background: var(--surface, #fff); }
.sold-win-btn {
  padding: 5px 10px; border: none; background: transparent;
  color: var(--text); font-size: 12px; font-weight: 600; cursor: pointer;
  border-right: 1px solid var(--border);
}
.sold-win-btn:last-child { border-right: none; }
.sold-win-btn.active { background: var(--teal); color: #fff; }
.sold-win-btn:hover:not(.active) { background: var(--bg-soft); }
html.dark-mode .sold-window-btns { background: var(--surface); border-color: var(--border); }
html.dark-mode .sold-win-btn { background: transparent; color: var(--text); border-right-color: var(--border); }
html.dark-mode .sold-win-btn:hover:not(.active) { background: rgba(255,255,255,.06); }
html.dark-mode .sold-win-btn.active { background: var(--teal); color: #fff; }
.map-legend {
  display: inline-flex; align-items: center; gap: 12px;
  font-size: 12px; color: var(--text); flex-wrap: wrap;
}
.map-legend .dot {
  display: inline-block; width: 10px; height: 10px; border-radius: 50%;
  margin-right: 4px; vertical-align: middle;
}
.map-legend .dot-house   { background: var(--brand); }
.map-legend .dot-rental  { background: var(--teal); border: 2px solid #fff; box-shadow: 0 0 0 1px var(--teal); }
.map-legend .dot-retail  { background: var(--teal); }
.map-legend .dot-transit { background: #5b6cff; }
.map-legend .dot-school  { background: #7a3fbf; }
.map-legend .dot-stadium { background: #d97a00; }
.map-legend .dot-fortune { background: var(--brand); }

/* Two-column layout: map on the left, breakdown panel on the right */
.map-layout {
  display: grid; grid-template-columns: 1fr 280px; gap: 16px;
  align-items: stretch;
}
@media (max-width: 900px) {
  .map-layout { grid-template-columns: 1fr; }
}
.leaflet-mount {
  width: 100%; aspect-ratio: 16 / 7; max-height: 580px; min-height: 420px;
  border-radius: 10px; overflow: hidden; border: 1px solid var(--border);
  background: #e8edf0;
}
.svg-marker { background: transparent; border: none; }
.map-status { padding: 6px 4px 0; }
.map-status .warn { color: #b04200; font-weight: 600; }
.map-status a { color: var(--teal); font-weight: 600; }

/* Breakdown side-panel: a clean, modern card stack — each lg-section is
   its own bordered tile so categories feel distinct at a glance. */
.map-breakdown {
  border: 1px solid var(--border); border-radius: 10px; padding: 10px;
  background: var(--surface, #fff); overflow-y: auto; max-height: 580px;
  font-size: 13px; color: var(--text);
  display: flex; flex-direction: column; gap: 8px;
}
.map-breakdown .lg-section {
  background: var(--bg-soft, #f6f8fa);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 10px 12px;
  color: var(--text);
}
/* Drop the old dashed separator now that each section is its own card */
.map-breakdown .lg-section + .lg-section { margin-top: 0; padding-top: 10px; border-top: 1px solid var(--border); }
.map-breakdown h4 {
  margin: 0 0 8px;
  font-size: 11px; text-transform: uppercase;
  letter-spacing: .06em; font-weight: 700;
  color: var(--muted);
  display: flex; align-items: baseline; justify-content: space-between;
  border-bottom: 1px solid var(--border); padding-bottom: 6px;
}
.map-breakdown h4 .muted { font-size: 12px; font-weight: 700; color: var(--text); }
.map-breakdown .brand-list {
  list-style: none; padding: 0; margin: 0 0 8px 0;
}
.map-breakdown .brand-list li {
  display: flex; justify-content: space-between; gap: 6px;
  padding: 3px 6px; color: var(--text);
  border-bottom: 1px dotted transparent;
  border-radius: 4px;
}
.map-breakdown .brand-list li.brand-row {
  cursor: pointer;
  transition: background .12s, color .12s;
}
.map-breakdown .brand-list li.brand-row:hover {
  background: var(--teal-light); color: var(--teal);
}
.map-breakdown .brand-list li.brand-row.selected {
  background: var(--teal); color: #fff;
}
.map-breakdown .brand-list li.brand-row.selected .brand,
.map-breakdown .brand-list li.brand-row.selected .cnt { color: #fff; }
html.dark-mode .map-breakdown .brand-list li.brand-row:hover {
  background: rgba(13,115,119,.18); color: var(--teal);
}
/* Override the global .brand (red, used by the topnav) and the
   anchor-tag color — POI brand labels should read as normal text. */
.map-breakdown .brand-list .brand {
  flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  color: var(--text); font-size: 13px; font-weight: 500;
}
.map-breakdown .brand-list .cnt {
  color: var(--muted); font-variant-numeric: tabular-nums;
  font-size: 12px; font-weight: 600;
}
.map-breakdown .nearest {
  color: var(--text); padding-top: 4px; font-size: 12px;
  display: block;
}
.map-breakdown .nearest b { color: var(--teal); font-weight: 700; }
.map-breakdown .nearest .muted.small { font-size: 11px; }

/* Dark-mode polish for the breakdown panel */
html.dark-mode .map-breakdown {
  background: var(--surface); border-color: var(--border); color: var(--text);
}
html.dark-mode .map-breakdown .lg-section {
  background: rgba(255,255,255,.02);
  border-color: var(--border);
}
html.dark-mode .map-breakdown h4 { color: var(--muted); border-bottom-color: var(--border); }
html.dark-mode .map-breakdown h4 .muted,
html.dark-mode .map-breakdown .brand-list .brand,
html.dark-mode .map-breakdown .nearest { color: var(--text); }
html.dark-mode .map-breakdown .brand-list .cnt { color: var(--muted); }

/* ---- Cashflow card — live editable + scenarios + stack ----
   The old bottom .recompute form was replaced by inline controls inside
   the cashflow card itself; these styles cover the new arrangement. */
.cf-scenarios { display: flex; gap: 6px; align-items: center; margin: 8px 0 10px; }
.cf-scen {
  padding: 4px 12px; font-size: 12px; font-weight: 600; cursor: pointer;
  background: var(--surface, #fff); color: var(--muted);
  border: 1px solid var(--border); border-radius: 999px;
}
.cf-scen:hover { color: var(--teal); border-color: var(--teal); }
.cf-scen.active { background: var(--teal); color: #fff; border-color: var(--teal); }
.cf-spinner { color: var(--teal); font-size: 14px; margin-left: 4px;
              animation: spin 1s linear infinite; display: inline-block; }
@keyframes spin { to { transform: rotate(360deg); } }

.cf-inline {
  display: flex; flex-wrap: wrap; gap: 8px 12px; align-items: end;
  padding: 10px 12px; margin: 0 0 12px;
  background: var(--bg-soft); border: 1px solid var(--border); border-radius: 8px;
}
.cf-inline label {
  display: flex; flex-direction: column; gap: 3px;
  font-size: 11px; color: var(--muted); text-transform: uppercase; letter-spacing: .03em;
}
.cf-inline input {
  width: 70px; padding: 5px 7px; font-size: 13px;
  background: var(--surface, #fff); color: var(--text, var(--ink));
  border: 1px solid var(--border); border-radius: 5px;
  font-variant-numeric: tabular-nums;
}
/* Price input needs more room: 7+ digits and the spinner controls. */
.cf-inline .cf-price input { width: 110px; font-weight: 700; }
.cf-inline .cf-price { color: var(--teal); }
.cf-save-default { font-size: 11px; color: var(--muted); text-decoration: none;
                   margin-left: auto; align-self: center; }
.cf-save-default:hover { color: var(--teal); }

/* Range sliders paired with the key underwriting inputs (price/down/rate/vac).
   Sized so the slider widens its flex-column label to a usable track width. */
.cf-range {
  -webkit-appearance: none; appearance: none;
  width: 96px; height: 4px; margin-top: 6px;
  background: var(--border); border-radius: 3px; cursor: pointer;
}
.cf-inline .cf-price .cf-range { width: 110px; }
.cf-range::-webkit-slider-thumb {
  -webkit-appearance: none; appearance: none;
  width: 13px; height: 13px; border-radius: 50%;
  background: var(--teal); border: 2px solid #fff; cursor: pointer;
  box-shadow: 0 0 0 1px var(--border);
}
.cf-range::-moz-range-thumb {
  width: 13px; height: 13px; border-radius: 50%;
  background: var(--teal); border: 2px solid #fff; cursor: pointer;
}
.cf-range:focus-visible { outline: 2px solid var(--teal); outline-offset: 2px; }

/* Break-even thresholds block (inside the cashflow card). */
.cf-breakeven { margin: 14px 0 4px; padding-top: 12px; border-top: 1px dashed var(--border); }
.cf-breakeven .sub-h { margin: 0 0 8px; font-size: 13px; }
.be-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; }
.be-cell {
  background: var(--bg-soft); border: 1px solid var(--border); border-radius: 8px;
  padding: 8px 10px; display: flex; flex-direction: column; gap: 2px;
}
.be-label { font-size: 10px; text-transform: uppercase; letter-spacing: .04em; color: var(--muted); }
.be-val { font-size: 16px; font-weight: 700; font-variant-numeric: tabular-nums; color: var(--text, var(--ink)); }
.be-sub { font-size: 11px; font-weight: 600; min-height: 14px; }
.be-sub.positive { color: var(--good, #1a9e54); }
.be-sub.negative { color: var(--bad, #d33); }
@media (max-width: 560px) { .be-grid { grid-template-columns: 1fr; } }

/* P/I/T/I/Other stacked bar — composition of one month of operating cost. */
.cf-stack-wrap { margin: 14px 0 4px; }
.cf-stack {
  display: flex; height: 14px; width: 100%;
  border-radius: 6px; overflow: hidden;
  background: var(--bg-soft); border: 1px solid var(--border);
}
.cf-seg { display: inline-block; height: 100%; transition: width .25s ease; }
.cf-stack-legend {
  display: flex; flex-wrap: wrap; gap: 6px 12px;
  font-size: 11px; color: var(--muted); margin-top: 6px;
}
.cf-leg-chip { display: inline-flex; align-items: center; gap: 4px; }
.cf-leg-chip i { display: inline-block; width: 10px; height: 10px; border-radius: 2px; }

/* Loan-product DSCR check — 3-column compare card. Inside #financing-card
   the columns were OVERFLOWING the parent .card border (visible in the
   user's screenshot: the "Hard money" column ran past the card's right
   edge). Fix: grid + flex children default to `min-width: auto` ==
   content-min-width, which prevents them from shrinking below their
   intrinsic content. Force `min-width: 0` on .lp-col so the 1fr columns
   actually obey their fractional width. */
.lp-grid {
  display: grid; grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 10px; margin-top: 6px;
  width: 100%; min-width: 0;
}
.lp-col {
  padding: 10px 12px; background: var(--bg-soft); border: 1px solid var(--border);
  border-radius: 8px;
  min-width: 0;            /* allow shrink below intrinsic content width */
  overflow: hidden;        /* belt-and-suspenders if a long string still tries */
}
.lp-col h4 { margin: 0 0 2px; font-size: 14px; color: var(--text, var(--ink)); }
.lp-col dl { display: grid; grid-template-columns: 1fr auto; gap: 4px 8px;
             margin: 8px 0 6px; font-size: 12px; min-width: 0; }
/* dt labels (Monthly cashflow, Down + closing, etc.) — allow wrap to a
   second line in narrow columns instead of ellipsis-truncating. The
   .financing-pair stays single-column (one .cards-grid cell), so each
   .lp-col is ~100 px wide; "Monthly cashflow" needs ~2 lines to fit
   there. Operators preferred 2-line labels over "Monthly…" ellipsis. */
.lp-col dt { color: var(--muted); min-width: 0;
             overflow-wrap: anywhere; line-height: 1.2; }
/* dd values stay nowrap so the formatted numbers ($1,234) don't break
   mid-amount; if a column gets too narrow the dd will simply push the
   dt to wrap further, which is the desired hierarchy. */
.lp-col dd { margin: 0; font-variant-numeric: tabular-nums; text-align: right;
             color: var(--text, var(--ink)); white-space: nowrap; }
.lp-col dd.positive { color: #36b37e; font-weight: 600; }
.lp-col dd.negative { color: #e0584d; font-weight: 600; }
@media (max-width: 720px) {
  .lp-grid { grid-template-columns: 1fr; }
}

/* Loan-product check card now hosts the lp-grid directly (was nested
   inside #financing-card .fin-half before the 2026-06-01 split). */
#loan-product-check-card { overflow: hidden; }

/* Financing pair — wraps #financing-card on top + #loan-product-check-card
   below as ONE grid cell of the .cards grid, so the pair stretches to
   match the height of #cashflow-card sitting in the adjacent cell. The
   top card sizes to its (short) content; the bottom card eats the
   remaining vertical space — net effect: top aligns with cashflow-card's
   top, bottom aligns with cashflow-card's bottom. */
.financing-pair {
  display: grid;
  grid-template-rows: auto 1fr;     /* top: natural height · bottom: fills */
  gap: 16px;                         /* mirrors the parent .cards gap */
  align-self: stretch;               /* ensure the wrapper stretches its row */
  min-height: 0;                     /* allow children to shrink as needed */
}
.financing-pair > .card { margin: 0; }
.financing-pair > #loan-product-check-card {
  min-height: 0;                     /* allow it to shrink below intrinsic */
  display: flex; flex-direction: column;
}
.financing-pair > #loan-product-check-card .lp-grid {
  /* When the parent cell stretches, let the inner 3-column grid expand
     vertically so the dl rows breathe rather than clipping. */
  flex: 1 1 auto; min-height: 0;
}
@media (max-width: 720px) {
  /* On narrow viewports the .cards grid collapses to 1 column; the pair
     becomes a regular vertical stack of two cards. Drop the 2-col span
     so it doesn't try to span past the available column. */
  .financing-pair {
    grid-template-rows: auto auto;
    align-self: auto;
  }
  .financing-pair > #loan-product-check-card .lp-grid {
    flex: initial;
  }
}

/* ---- Wave 2 cards ----------------------------------------------- */
/* ZIP macro — compact 2-col definition list with sentiment colors. */
#zip-macro-card dl.zipmacro {
  display: grid; grid-template-columns: 1fr auto;
  gap: 6px 14px; margin: 4px 0 0;
}
#zip-macro-card dt { color: var(--muted); font-size: 13px; }
#zip-macro-card dd { margin: 0; font-variant-numeric: tabular-nums; font-weight: 600;
                     text-align: right; }
#zip-macro-card dd.positive { color: #36b37e; }
#zip-macro-card dd.negative { color: #e0584d; }

/* Landlord ↔ Tenant lean — three layers (State / County / City) stacked
   beneath an Overall headline. Score color band matches lean.linuxhomeserver.org's
   palette: green = landlord-favorable, red = tenant. */
#landlord-tenant-lean-card dl.lean-grid {
  display: grid; grid-template-columns: 1fr auto;
  gap: 8px 14px; margin: 4px 0 8px; align-items: baseline;
}
#landlord-tenant-lean-card dt {
  color: var(--muted); font-size: 13px; line-height: 1.3;
}
#landlord-tenant-lean-card dd {
  margin: 0; text-align: right; font-variant-numeric: tabular-nums;
}
#landlord-tenant-lean-card dt:nth-of-type(1) {
  font-weight: 700; color: var(--text); font-size: 14px;
}
#landlord-tenant-lean-card .lean-score {
  font-size: 26px; font-weight: 700; font-variant-numeric: tabular-nums;
  vertical-align: middle; margin-right: 6px;
}
#landlord-tenant-lean-card .lean-score-sm {
  font-size: 16px; font-weight: 600; font-variant-numeric: tabular-nums;
  vertical-align: middle; margin-right: 6px;
}
#landlord-tenant-lean-card .lean-label {
  font-size: 12px; color: var(--muted); display: block;
  margin-top: 2px;
}
#landlord-tenant-lean-card .lean-pvi {
  font-family: var(--mono, monospace); font-size: 11px; color: var(--muted);
}
#landlord-tenant-lean-card .lean-notes {
  margin-top: 8px; padding-top: 8px; border-top: 1px solid var(--border);
  font-size: 12px; color: var(--muted); line-height: 1.4; font-style: italic;
}
#landlord-tenant-lean-card .card-foot { margin-top: 8px; }
#landlord-tenant-lean-card .card-foot a { color: var(--teal); }
/* Score color bands — match lean-app's flipped polarity exactly:
   green = landlord (go), red = tenant (stop). */
.lean-band-land-strong   { color: #2da55a; }
.lean-band-land          { color: #36b37e; }
.lean-band-balanced      { color: #c9a866; }
.lean-band-tenant        { color: #e08b5e; }
.lean-band-tenant-strong { color: #d63a2f; }

/* Rent comps table — dense, scannable.
   Wrapped in .rcomps-scroll to keep the card compact: ~10 rows visible
   at a time, the rest accessible via the inner vertical scroll. The
   thead is sticky so the column labels stay in view while the user
   scrolls the body. */

/* Span the full width of the .cards grid so the comp table aligns with
   .investor-summary .is-questions above (the questions block is full
   content width, while the default .cards grid cell is 320px). With
   8 columns visible (Address / Beds / Baths / Sqft / Rent / $/sqft /
   DOM / Units / Dist) the table needs the room to stay scannable. */
#rent-comps-card { grid-column: 1 / -1; }

.rcomps-scroll {
  max-height: 350px;        /* ≈ 10 rows × 30px + 30px header */
  overflow-y: auto;
  border: 1px solid var(--border); border-radius: 8px;
  margin-top: 6px;
}
table.rcomps { width: 100%; border-collapse: collapse; font-size: 12.5px;
                margin: 0; }
table.rcomps th, table.rcomps td {
  padding: 6px 8px; border-bottom: 1px solid var(--border); text-align: right;
  white-space: nowrap; font-variant-numeric: tabular-nums;
}
table.rcomps th { background: var(--bg-soft); color: var(--muted);
                   font-size: 11px; text-transform: uppercase; letter-spacing: .03em;
                   position: sticky; top: 0; z-index: 1; }
table.rcomps th.txt, table.rcomps td.txt { text-align: left; white-space: normal; }
table.rcomps tbody tr:hover { background: rgba(35,184,180,.06); }
/* DOM > 30d — soft amber to flag stale comps without screaming. */
table.rcomps td.rcomp-stale { color: #b4661c; font-weight: 700; }
/* Multi-unit complex flag — one address with N units rented at the same
   price is one data point dressed up as N, so investors should weight it
   accordingly. Italic + muted brand colour. */
table.rcomps td.rcomp-multi { color: var(--brand); font-weight: 700; font-style: italic; }
/* Heuristic comp (mislisted-sale) — softer row so the eye knows it's
   down-weighted in the estimate, plus a tiny pill in the address cell. */
table.rcomps tr.rcomp-mislisted td { background: rgba(180,102,28,.04); }
table.rcomps tr.rcomp-mislisted td.txt strong { color: var(--muted); font-weight: 600; }
.rcomp-tag {
  display: inline-block; margin-left: 6px; padding: 1px 5px;
  font-size: 10px; font-weight: 700; letter-spacing: .03em;
  text-transform: uppercase;
  color: #b4661c; background: rgba(180,102,28,.10);
  border: 1px solid rgba(180,102,28,.25); border-radius: 4px;
  vertical-align: 1px;
}
/* Anchor styling in the address cell — keep the link visible but not
   visually heavy; underline only on hover. */
table.rcomps td.txt a { color: var(--text); text-decoration: none; }
table.rcomps td.txt a:hover { color: var(--brand); text-decoration: underline; }

/* HOA detail — chip list for what dues cover + snippet styling. */
.hoa-chip {
  display: inline-block; padding: 2px 9px; margin: 2px 4px 2px 0;
  background: var(--teal-light, #e0f2f1); color: var(--teal, #0d7377);
  border-radius: 999px; font-size: 11px; font-weight: 600;
}
.hoa-snip {
  background: rgba(224,88,77,.08); border-left: 3px solid #e0584d;
  padding: 6px 10px; margin: 4px 0; font-size: 12px; font-style: italic;
  border-radius: 4px;
}

/* Tax reassessment — total row uses the existing positive/negative classes. */
#tax-reassessment-card dl { display: grid; grid-template-columns: 1fr auto;
  gap: 6px 14px; margin: 4px 0 8px; }
#tax-reassessment-card dt { color: var(--muted); font-size: 13px; }
#tax-reassessment-card dd { margin: 0; text-align: right; font-variant-numeric: tabular-nums; }

/* Comps map + amenities map — leaflet containers + toggle chips. */
.comps-map { margin-top: 6px; border: 1px solid var(--border); }
.amenities-toggles { display: flex; flex-wrap: wrap; gap: 6px 10px; margin: 6px 0 8px; }
.amen-toggle {
  display: inline-flex; align-items: center; gap: 5px;
  font-size: 12px; color: var(--text, var(--ink)); cursor: pointer;
  padding: 2px 8px; border: 1px solid var(--border); border-radius: 999px;
  background: var(--surface, #fff); user-select: none;
}
.amen-toggle span {
  display: inline-block; width: 10px; height: 10px; border-radius: 50%;
}
.amen-toggle input { margin: 0 4px 0 0; }
.amen-toggle input:disabled + span { opacity: .35; }
.amen-toggle b { color: var(--muted); font-weight: 600; margin-left: 2px; }

/* ---- Wave 3 cards ---------------------------------------------- */
/* Climate panel — one row per risk.
   Real CSS-grid layout with five fixed columns so factor / severity /
   detail line up across rows even when some risks omit them. Each cell
   is always emitted in the template (empty placeholders when a field is
   missing) so auto-placement can't collapse a column on one row and
   shift the rest right. */
/* ── Listing history timeline ────────────────────────────────────
   Vertical change-log for the property: every observed price /
   status / DOM / remarks change since the trigger went live, plus
   the pre-trigger backfill from property_details.listing_events.
   Reads through --surface/--border/--text/--muted theme tokens. */
.lh-timeline { list-style: none; padding: 0; margin: 8px 0 12px;
               max-height: 460px; overflow-y: auto; }
.lh-row { display: grid; grid-template-columns: 92px 1fr; gap: 12px;
          padding: 8px 0; border-bottom: 1px dashed var(--border);
          font-size: 13px; color: var(--text); }
.lh-row:last-child { border-bottom: none; }
.lh-date { color: var(--muted); font-variant-numeric: tabular-nums;
           font-size: 12px; padding-top: 2px; }
.lh-body { min-width: 0; }
.lh-headline { display: flex; flex-wrap: wrap; gap: 6px 10px;
               align-items: baseline; }
.lh-tag { display: inline-block; font-size: 10px; font-weight: 700;
          text-transform: uppercase; letter-spacing: .04em;
          padding: 1px 6px; border-radius: 4px;
          background: var(--bg-soft); color: var(--muted);
          border: 1px solid var(--border); }
.lh-tag-listed   { background: rgba(35,184,180,.12); color: var(--teal);
                   border-color: rgba(35,184,180,.35); }
.lh-tag-price    { background: rgba(224,161,22,.14); color: #b8800e;
                   border-color: rgba(224,161,22,.32); }
.lh-tag-status   { background: rgba(91,115,199,.14); color: #4a5fbf;
                   border-color: rgba(91,115,199,.32); }
.lh-tag-sold     { background: rgba(31,157,87,.14); color: #1f9d57;
                   border-color: rgba(31,157,87,.32); }
.lh-tag-remarks  { background: rgba(142,68,173,.12); color: #7a3f99;
                   border-color: rgba(142,68,173,.32); }
.lh-tag-listing_id, .lh-tag-historical, .lh-tag-days_on_market {
                   background: var(--bg-soft); color: var(--muted); }
.lh-price-val { font-weight: 700; font-variant-numeric: tabular-nums;
                color: var(--text); }
.lh-status { color: var(--text); font-weight: 600; }
.lh-remark { margin-top: 4px; }
.lh-remark summary { cursor: pointer; padding: 2px 0; }
.lh-remark-text { margin: 6px 0 0; padding: 8px 10px;
                  background: var(--bg-soft); border-radius: 6px;
                  font-size: 12.5px; line-height: 1.5; color: var(--text); }
/* Faint left rail accent so sold / price / listed rows are scannable
   without reading the tag chips. */
.lh-row.lh-sold   { border-left: 2px solid rgba(31,157,87,.45);  padding-left: 8px; }
.lh-row.lh-price  { border-left: 2px solid rgba(224,161,22,.45); padding-left: 8px; }
.lh-row.lh-listed { border-left: 2px solid rgba(35,184,180,.45); padding-left: 8px; }

.climate-list { list-style: none; padding: 0; margin: 6px 0 0; }
.climate-list li {
  display: grid;
  grid-template-columns: 22px 100px 52px 86px 1fr;
  align-items: center;
  gap: 6px 10px; padding: 7px 0;
  border-bottom: 1px dashed var(--border);
  font-size: 13px;
}
.climate-list li:last-child { border-bottom: none; }
.climate-icon   { font-size: 16px; text-align: center; }
.climate-kind   { font-weight: 600; }
.climate-factor { color: var(--muted); font-variant-numeric: tabular-nums; text-align: right; }
.climate-sev {
  /* justify-self lets the pill keep its natural width while staying in
     the same grid column on every row. */
  justify-self: start;
  display: inline-block; padding: 1px 8px; border-radius: 999px;
  font-size: 11px; font-weight: 700;
}
.climate-good { background: rgba(31,157,87,.15); color: #1f9d57; }
.climate-ok   { background: rgba(35,184,180,.15); color: var(--teal); }
.climate-warn { background: rgba(224,161,22,.15); color: #e0a116; }
.climate-bad  { background: rgba(224,88,77,.15);  color: #e0584d; }
/* Empty severity / factor cells keep their grid track (so rows stay
   aligned) but render no visible chrome. */
.climate-sev:empty { padding: 0; background: transparent; }
.climate-detail { color: var(--muted); }
/* Mobile: drop the detail column under the rest so the row stays
   readable below ~520 px. */
@media (max-width: 520px) {
  .climate-list li {
    grid-template-columns: 22px 1fr 52px 86px;
  }
  .climate-detail { grid-column: 1 / -1; padding-left: 28px; }
}

/* Renovation cost table. */
table.reno-tbl { width: 100%; border-collapse: collapse; font-size: 13px; margin-top: 4px; }
table.reno-tbl th, table.reno-tbl td {
  padding: 7px 8px; border-bottom: 1px solid var(--border);
  text-align: right; font-variant-numeric: tabular-nums;
}
table.reno-tbl th { background: var(--bg-soft); color: var(--muted);
                     font-size: 11px; text-transform: uppercase; letter-spacing: .03em; }
table.reno-tbl th.txt, table.reno-tbl td.txt { text-align: left; }
.reno-total { background: rgba(35,184,180,.06); }
.reno-roi { list-style: none; padding: 0; margin: 6px 0; }
.reno-roi li {
  display: flex; align-items: center; gap: 10px;
  padding: 5px 0; border-bottom: 1px dashed var(--border);
  font-size: 13px;
}
.roi-impact {
  display: inline-block; padding: 1px 8px; border-radius: 999px;
  font-size: 10px; font-weight: 700; text-transform: uppercase; letter-spacing: .03em;
  min-width: 60px; text-align: center;
}
.roi-impact-resale { background: rgba(35,184,180,.15); color: var(--teal); }
.roi-impact-rental { background: rgba(31,157,87,.15);  color: #1f9d57; }
.roi-impact-both   { background: rgba(91,115,199,.15); color: #5b73c7; }
.roi-item   { flex: 1; }
.roi-arrow  { color: #1f9d57; font-weight: 700; font-size: 16px; }

/* Due-diligence list — label + why on one row each. */
ul.dd-list { list-style: none; padding: 0; margin: 6px 0 0; }
ul.dd-list li {
  display: grid; grid-template-columns: 220px 1fr; gap: 8px 14px;
  padding: 7px 0; border-bottom: 1px dashed var(--border);
  font-size: 13px;
}
ul.dd-list li:last-child { border-bottom: none; }
.dd-label { font-weight: 600; }
@media (max-width: 720px) {
  ul.dd-list li { grid-template-columns: 1fr; gap: 2px; }
}

/* Private notes — edit-in-place card. */
#notes-card .notes-textarea {
  width: 100%; box-sizing: border-box;
  min-height: 90px; max-height: 260px; resize: vertical;
  padding: 8px 10px; font: inherit; font-size: 13px;
  background: var(--bg-soft); color: var(--text, var(--ink));
  border: 1px solid var(--border); border-radius: 6px;
  font-family: ui-monospace, SF Mono, Menlo, monospace;
}
.notes-foot {
  display: flex; justify-content: space-between; align-items: center;
  margin-top: 6px; gap: 8px;
}
.notes-actions { display: flex; gap: 6px; }
.notes-actions button {
  padding: 5px 12px; font-size: 12px; font-weight: 600;
  border: 1px solid var(--border); border-radius: 6px;
  background: var(--surface, #fff); color: var(--text, var(--ink));
  cursor: pointer;
}
.notes-actions .ssm-save { background: var(--teal); color: #fff; border-color: var(--teal); }
.notes-actions .ssm-save[disabled] { opacity: .55; cursor: not-allowed; }
.notes-actions button:hover:not([disabled]) { border-color: var(--teal); }
.notes-status { margin-left: 10px; }
.notes-status.saved { color: #36b37e; }
.notes-status.saving { color: var(--muted); }
.notes-status.err { color: #e0584d; }

/* Per-card freshness badge — set as a data-fresh attribute by the JS
   pass at the bottom of property.html ("Updated 3h ago"). Sits in the
   top-right corner of every card. Replaces the former global
   .data-freshness footer. */
.cards .card { position: relative; }
.cards .card[data-fresh]::after {
  content: attr(data-fresh);
  position: absolute;
  top: 10px; right: 14px;
  font-size: 10.5px; font-weight: 500;
  color: var(--muted);
  letter-spacing: .01em;
  pointer-events: none;            /* don't intercept clicks on the card */
}
/* Subtle on touch / small screens — keep it from crowding short card titles. */
@media (max-width: 540px) {
  .cards .card[data-fresh]::after { font-size: 10px; top: 8px; right: 8px; }
}

/* ---- Wave 4: compare drawer (floating, site-wide) --------------
   `bottom` is calc()'d above the .chat-widget toggle (.chat-toggle is
   ~44 px tall at bottom: 20 px) so the drawer never sits on top of the
   chat bubble. Mobile override at the bottom of this file matches the
   compacted toggle. */
#compare-drawer {
  position: fixed; right: 18px; bottom: 84px; z-index: 1500;
  background: var(--surface, #fff); color: var(--text, var(--ink));
  border: 1px solid var(--border); border-radius: 12px;
  box-shadow: 0 8px 24px rgba(0,0,0,.18);
  padding: 10px 12px; min-width: 240px; max-width: 320px;
  font-size: 13px;
}
#compare-drawer[hidden] { display: none; }
#compare-drawer .cd-head {
  display: flex; align-items: center; justify-content: space-between;
  margin-bottom: 6px;
}
#compare-drawer .cd-title { font-weight: 700; }
#compare-drawer #cd-clear {
  background: none; border: none; color: var(--muted); cursor: pointer;
  font-size: 16px; line-height: 1; padding: 0 4px;
}
#compare-drawer #cd-clear:hover { color: #e0584d; }
#compare-drawer ul { list-style: none; padding: 0; margin: 0 0 8px;
                      max-height: 180px; overflow-y: auto; }
#compare-drawer li {
  display: flex; align-items: center; justify-content: space-between;
  padding: 4px 0; border-bottom: 1px dashed var(--border);
}
#compare-drawer li a { color: var(--text); text-decoration: none;
                        font-family: ui-monospace, monospace; font-size: 12px; }
#compare-drawer li a:hover { color: var(--teal); }
#compare-drawer li button {
  background: none; border: none; color: var(--muted); cursor: pointer;
  padding: 0 4px;
}
#compare-drawer li button:hover { color: #e0584d; }
#compare-drawer .cd-go {
  display: block; text-align: center; padding: 7px 12px;
  background: var(--teal); color: #fff; text-decoration: none;
  border-radius: 6px; font-weight: 600; font-size: 12px;
}
#compare-drawer .cd-go.disabled { background: var(--border); color: var(--muted);
                                    cursor: not-allowed; }
#compare-drawer .cd-go:hover:not(.disabled) { background: #0f8f5e; }

/* Compare toggle button (used on property detail page + feed cards) */
.compare-toggle {
  display: inline-flex; align-items: center; gap: 5px;
  padding: 4px 10px; font-size: 11px; font-weight: 600;
  background: var(--surface, #fff); color: var(--muted);
  border: 1px solid var(--border); border-radius: 999px; cursor: pointer;
  text-transform: uppercase; letter-spacing: .03em;
}
.compare-toggle:hover { color: var(--teal); border-color: var(--teal); }
.compare-toggle.active { background: var(--teal); color: #fff; border-color: var(--teal); }
.compare-toggle.active::before { content: "✓ "; }

/* ---- Card pair (Investment-tab left column: Investment metrics + CMA/ARV
   + Projected returns stacked) -----------------------------------------
   The wrapper is ONE grid cell of the .cards grid; align-items: stretch
   (the grid default) makes it as tall as #cashflow-card in the adjacent
   cell. Inside, three rows split via grid-template-rows: auto auto 1fr:
   Investment metrics + CMA/ARV size to their own content (typically
   short — 5-6 rows each), and Projected returns claims the remaining
   vertical space so its bottom edge sits flush with cashflow-card's
   bottom. The pair always contains exactly 3 cards on the Investment
   tab (data-tab gating); other tabs hide all of them together. */
.card-pair {
  display: grid;
  grid-template-rows: auto auto 1fr;
  gap: 8px;
  align-self: stretch;             /* fill the row's stretched cell */
  min-height: 0;                   /* allow children to shrink as needed */
  margin-bottom: 14px;             /* match the other .card vertical rhythm */
}
.card-pair > .card {
  margin: 0;                       /* let the grid gap do the spacing */
  min-height: 0;                   /* allow inner cards to shrink */
}
.card-pair > .projection-card {
  display: flex; flex-direction: column;
}
.card-pair > .projection-card .proj-chart-wrap {
  flex: 1 1 auto; min-height: 0;   /* equity chart absorbs the stretch */
}
@media (max-width: 720px) {
  /* On narrow viewports the .cards grid collapses to 1 column; the pair
     becomes a regular vertical stack and each card sizes naturally. */
  .card-pair {
    grid-template-rows: auto auto auto;
    align-self: auto;
  }
  .card-pair > .projection-card { display: block; }
  .card-pair > .projection-card .proj-chart-wrap { flex: initial; }
}

/* ---- Property-page tabs (Investment / Property / Market) ---------- */
/* Strip is sticky just below the global nav so the active tab stays in
   view while the user scrolls a long cards section. */
.card-tabs {
  position: sticky;
  top: 0; z-index: 60;
  display: flex; gap: 4px;
  padding: 8px 16px 0;
  margin: 8px 0 0;
  background: var(--bg, #f4f8fa);
  border-bottom: 1px solid var(--border);
}
.card-tab {
  flex: 0 0 auto; padding: 8px 18px;
  background: transparent; color: var(--muted);
  border: 1px solid transparent; border-bottom: none;
  border-radius: 8px 8px 0 0;
  font-size: 13px; font-weight: 600; cursor: pointer;
  display: inline-flex; align-items: center; gap: 6px;
  transition: color .12s, background .12s, border-color .12s;
}
.card-tab:hover { color: var(--teal); background: var(--bg-soft); }
.card-tab.active {
  color: var(--text, var(--ink));
  background: var(--surface, #fff);
  border-color: var(--border);
  border-bottom-color: var(--surface, #fff);
  position: relative; top: 1px;            /* covers the strip's bottom border */
}
.card-tab .ct-emoji { font-size: 14px; }

/* When a tab is active, hide every card that doesn't belong to it.
   Six tabs total — Investment / Taxation / Property / Rent comps /
   Climate / Market. */
.cards[data-active-tab="investment"] [data-tab]:not([data-tab="investment"]) { display: none; }
.cards[data-active-tab="taxation"]   [data-tab]:not([data-tab="taxation"])   { display: none; }
.cards[data-active-tab="property"]   [data-tab]:not([data-tab="property"])   { display: none; }
.cards[data-active-tab="rent"]       [data-tab]:not([data-tab="rent"])       { display: none; }
.cards[data-active-tab="climate"]    [data-tab]:not([data-tab="climate"])    { display: none; }
.cards[data-active-tab="market"]     [data-tab]:not([data-tab="market"])     { display: none; }

/* Mobile: tabs stretch full width, smaller padding. */
@media (max-width: 720px) {
  .card-tabs { padding: 6px 8px 0; }
  .card-tab { flex: 1; padding: 8px 4px; font-size: 12px; }
  .card-tab .ct-emoji { font-size: 13px; }
}

/* ---- Financing card (Wave 7 merge of cash-to-close + DSCR check) ----
   Original two-column layout (1fr / 2fr) cramped the cash-to-close labels
   into multi-line wraps ("Down" / "payment") and shoved the loan-product
   columns to a narrow right rail. Re-stack VERTICALLY: cash-to-close at
   the top using its natural width, the 3-product loan check below at full
   card width. Both regions read cleanly and the LP-grid columns are
   visibly aligned with each other and the card edges. */
/* Cash-to-close dl — one label/value per row so the eye doesn't jump
   between two pairs per line. (Was wrapped in .fin-half inside .fin-grid
   when the financing card also carried the loan-product check; both
   wrappers were stripped 2026-06-01 when Loan-product check was split
   into its own card.) */
#financing-card h4.fin-sub {
  margin: 8px 0 8px; font-size: 12px; color: var(--muted);
  text-transform: uppercase; letter-spacing: .04em; font-weight: 700;
}
#financing-card dl#cash-to-close-card {
  display: grid; grid-template-columns: 1fr auto;
  gap: 6px 16px; margin: 0; font-size: 13px; align-items: baseline;
}
#financing-card dl#cash-to-close-card dt { color: var(--muted); white-space: nowrap; }
#financing-card dl#cash-to-close-card dd {
  margin: 0; text-align: right; font-variant-numeric: tabular-nums;
  color: var(--text, var(--ink)); white-space: nowrap;
}
#financing-card dl#cash-to-close-card dt.total,
#financing-card dl#cash-to-close-card dd.total {
  border-top: 1px solid var(--border); padding-top: 6px; margin-top: 4px;
  font-weight: 700;
}
/* Reserves input — keep it inline with the label */
#financing-card #ctc-reserves {
  width: 3em; padding: 1px 4px; font-size: 12px;
}

/* ---- Notes modal (replaces the inline notes-card) ----------------- */
.notes-modal-overlay {
  position: fixed; inset: 0; z-index: 8500;
  background: rgba(0,0,0,0.45);
  display: flex; align-items: center; justify-content: center;
  padding: 24px;
  opacity: 0; transition: opacity .15s ease;
}
.notes-modal-overlay.show { opacity: 1; }
.notes-modal-overlay[hidden] { display: none; }
.notes-modal-shell {
  background: var(--surface, #fff); color: var(--text, var(--ink));
  border: 1px solid var(--border); border-radius: 14px;
  width: 100%; max-width: 640px; max-height: 80vh; overflow-y: auto;
  padding: 16px 18px;
  box-shadow: 0 14px 40px rgba(0,0,0,0.4);
}
.notes-modal-head {
  display: flex; align-items: center; justify-content: space-between;
  margin-bottom: 8px;
}
.notes-modal-head h3 { margin: 0; font-size: 15px; }
.notes-modal-close {
  background: none; border: none; color: var(--muted);
  cursor: pointer; font-size: 18px; padding: 4px 8px; border-radius: 6px;
}
.notes-modal-close:hover { background: var(--bg-soft); color: var(--text); }

/* Collab-filter rail on the property detail page */
ul.collab-grid {
  list-style: none; padding: 0; margin: 6px 0 0;
  display: grid; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); gap: 10px;
}
.collab-card {
  display: flex; flex-direction: column; gap: 3px;
  padding: 10px 12px; background: var(--bg-soft);
  border: 1px solid var(--border); border-radius: 8px;
  text-decoration: none; color: var(--text);
}
.collab-card:hover { border-color: var(--teal); }
.collab-head { display: flex; align-items: baseline; justify-content: space-between; gap: 6px; }
.collab-price { font-weight: 700; font-variant-numeric: tabular-nums; }
.collab-grade {
  display: inline-block; padding: 1px 6px; border-radius: 999px;
  background: var(--teal); color: #fff; font-size: 10px; font-weight: 700;
}
.collab-addr { font-size: 13px; font-weight: 600; line-height: 1.3; }
.collab-foot .pos { color: #36b37e; font-weight: 600; }
.collab-foot .neg { color: #e0584d; font-weight: 600; }

/* Wave 5.3 — facet-count badge inside filter modal chip labels. */
.facet-count { color: var(--muted); font-size: 11px; font-weight: 500; }

/* ---- Wave 6.2 — keyboard-shortcut help overlay -------------------- */
#kbd-overlay {
  position: fixed; inset: 0; z-index: 9500;
  background: rgba(0,0,0,0.55);
  display: flex; align-items: center; justify-content: center;
  padding: 24px;
  opacity: 0; transition: opacity .15s ease;
}
#kbd-overlay.show { opacity: 1; }
#kbd-overlay[hidden] { display: none; }
#kbd-overlay .kbd-shell {
  background: var(--surface, #fff); color: var(--text, var(--ink));
  border: 1px solid var(--border); border-radius: 14px;
  width: 100%; max-width: 720px; max-height: 80vh; overflow-y: auto;
  box-shadow: 0 14px 40px rgba(0,0,0,0.4);
}
#kbd-overlay .kbd-head {
  display: flex; align-items: center; justify-content: space-between;
  padding: 14px 18px; border-bottom: 1px solid var(--border);
}
#kbd-overlay .kbd-head h3 { margin: 0; font-size: 16px; }
#kbd-overlay .kbd-close {
  background: none; border: none; color: var(--muted);
  cursor: pointer; font-size: 18px; padding: 4px 8px; border-radius: 6px;
}
#kbd-overlay .kbd-close:hover { background: var(--bg-soft); color: var(--text); }
#kbd-overlay .kbd-body { padding: 14px 20px; display: grid;
                          grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); gap: 18px; }
#kbd-overlay h4 { margin: 0 0 6px; font-size: 12px; text-transform: uppercase;
                   letter-spacing: .04em; color: var(--muted); }
#kbd-overlay dl { display: grid; grid-template-columns: minmax(90px, auto) 1fr;
                    gap: 5px 12px; margin: 0; font-size: 13px; }
#kbd-overlay dt { text-align: right; color: var(--text); }
#kbd-overlay dd { margin: 0; color: var(--muted); }
#kbd-overlay kbd {
  display: inline-block; padding: 1px 6px; border-radius: 4px;
  background: var(--bg-soft); border: 1px solid var(--border);
  font-family: ui-monospace, monospace; font-size: 11px; color: var(--text);
  box-shadow: 0 1px 0 var(--border);
  min-width: 16px; text-align: center;
}
#kbd-overlay .kbd-foot {
  padding: 10px 18px; border-top: 1px solid var(--border);
  font-size: 12px; color: var(--muted); text-align: center;
}

/* ---- Saved-searches page ---- */
.saved-grid { list-style: none; padding: 0; display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: 14px; }
.saved-card { border: 1px solid var(--border); border-radius: 10px; padding: 16px; background: #fff; }
.saved-card pre { background: var(--bg-soft); padding: 8px; border-radius: 4px; font-size: 12px; overflow-x: auto; }
.saved-card .row { display: flex; gap: 8px; margin-top: 10px; }
.btn { padding: 6px 12px; border-radius: 6px; border: 1px solid var(--border); background: #fff; cursor: pointer; font-weight: 600; }
.btn.danger { color: var(--brand); border-color: var(--brand); }
.btn.primary { background: var(--teal); color: #fff; border-color: var(--teal); }
.btn.primary:hover { background: #0a5b5f; border-color: #0a5b5f; }

/* ---- Registration wall (anonymous visitors) ---- */
/* The teaser is blurred immediately but stays scrollable for a short grace
   period; auth-gate.js then "arms" the gate, which shows the overlay and
   freezes scroll (body.locked). */
body.gated .hero,
body.gated main.content { filter: blur(5px); -webkit-filter: blur(5px); }
body.gated.locked { overflow: hidden; }       /* scroll freeze once armed */
.auth-gate {
  position: fixed; inset: 0; z-index: 9999;
  display: none; align-items: center; justify-content: center; padding: 24px;
  background: rgba(15, 20, 22, .45);
  -webkit-backdrop-filter: blur(7px); backdrop-filter: blur(7px);
}
.auth-gate.armed { display: flex; }
/* Non-blocking countdown shown during the interactive grace period. */
.gate-countdown {
  position: fixed; left: 50%; bottom: 24px; transform: translateX(-50%);
  z-index: 9998; pointer-events: none;
  background: rgba(15, 20, 22, .88); color: #fff;
  padding: 10px 16px; border-radius: 999px;
  font-size: 13px; font-weight: 600; box-shadow: 0 6px 20px rgba(0, 0, 0, .35);
}
.auth-gate-card {
  background: var(--surface); color: var(--text);
  width: 100%; max-width: 420px; padding: 30px 28px;
  border-radius: 12px; text-align: center;
  box-shadow: 0 16px 48px rgba(0, 0, 0, .4);
}
.auth-gate-card h2 { margin: 0 0 10px; font-size: 22px; }
.auth-gate-card p { margin: 0 0 22px; color: var(--muted); font-size: 14px; line-height: 1.55; }
.auth-gate-actions { display: flex; gap: 12px; justify-content: center; }
.auth-gate-actions .btn { padding: 11px 20px; font-size: 15px; text-decoration: none; }

/* ---- Sign in / Sign up modal (sits ABOVE the gate, z 9999) ---- */
.auth-modal {
  position: fixed; inset: 0; z-index: 10001;
  display: flex; align-items: center; justify-content: center; padding: 16px;
}
.auth-modal[hidden] { display: none; }
.auth-modal-backdrop {
  position: absolute; inset: 0; background: rgba(10, 20, 25, .55);
  -webkit-backdrop-filter: blur(2px); backdrop-filter: blur(2px);
}
.auth-modal-shell {
  position: relative; z-index: 1; width: 100%; max-width: 420px;
  max-height: 92vh; overflow-y: auto;
  background: var(--surface, #fff); color: var(--text);
  border: 1px solid var(--border); border-radius: 14px;
  padding: 28px 26px 22px; box-shadow: 0 18px 50px rgba(0, 0, 0, .38);
}
.auth-modal-x {
  position: absolute; top: 10px; right: 12px; width: 34px; height: 34px;
  border: none; background: transparent; color: var(--muted);
  font-size: 20px; line-height: 1; cursor: pointer; border-radius: 8px;
}
.auth-modal-x:hover { background: var(--bg-soft, #f0f3f4); color: var(--text); }
.auth-modal-pane h2 { margin: 0 0 16px; font-size: 22px; }
.auth-modal .auth-form { display: flex; flex-direction: column; gap: 12px; }
.auth-modal .auth-form label {
  display: flex; flex-direction: column; gap: 4px; font-size: 13px; color: var(--muted);
}
.auth-modal .auth-form input {
  padding: 9px 11px; border: 1px solid var(--border); border-radius: 8px;
  font-size: 16px; background: var(--surface, #fff); color: var(--text);
}
.auth-modal .auth-form .btn { margin-top: 4px; }
.auth-modal .auth-alt { margin: 8px 0 0; font-size: 13px; color: var(--muted); text-align: center; }

/* ---- Billing / pricing page ---- */
.billing { max-width: 920px; margin: 0 auto; padding: 8px 0 32px; }
.billing h2 { font-size: 26px; margin: 8px 0 14px; }
.billing-status { font-size: 15px; color: var(--text); margin: 0 0 16px; }
.billing-status.active { color: var(--positive); font-weight: 600; }
.billing-status.admin  { color: var(--teal); font-weight: 600; }
.billing .btn { text-decoration: none; }
.plan-grid {
  display: grid; gap: 16px; margin: 20px 0 12px;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
}
.plan-card {
  position: relative; background: var(--surface); color: var(--text);
  border: 1px solid var(--border); border-radius: 12px; padding: 24px 20px;
  text-align: center; display: flex; flex-direction: column; gap: 8px;
}
.plan-card.featured { border-color: var(--teal); box-shadow: 0 6px 24px rgba(13, 115, 119, .18); }
.plan-tag {
  position: absolute; top: -11px; left: 50%; transform: translateX(-50%);
  background: var(--teal); color: #fff; font-size: 11px; font-weight: 700;
  padding: 3px 10px; border-radius: 999px; text-transform: uppercase; letter-spacing: .04em;
}
.plan-card h3 { margin: 4px 0 0; font-size: 17px; }
.plan-price { font-size: 34px; font-weight: 800; color: var(--text); }
.plan-meta { color: var(--muted); font-size: 13px; margin-bottom: 8px; }
.plan-card .btn { margin-top: auto; padding: 10px 14px; font-size: 15px; }
.billing-note { color: var(--muted); margin-top: 10px; }

/* ---- Help chatbot widget ---- */
/* Bottom-right of the viewport (= bottom-right of the search-map since the
   map extends flush to the viewport edges in search mode). Remove-outline
   was moved to the LEFT corner so the two no longer overlap. */
.chat-widget { position: fixed; right: 20px; bottom: 20px; z-index: 9000; }
.chat-toggle {
  border: none; cursor: pointer; border-radius: 999px;
  background: var(--teal); color: #fff; font-weight: 700; font-size: 14px;
  padding: 12px 18px; box-shadow: 0 6px 20px rgba(0,0,0,.25);
}
.chat-toggle:hover { background: #0a5b5f; }
.chat-panel {
  position: absolute; right: 0; bottom: 56px; width: 360px; max-width: 92vw;
  height: 520px; max-height: 78vh; display: flex; flex-direction: column;
  background: var(--surface); color: var(--text);
  border: 1px solid var(--border); border-radius: 12px; overflow: hidden;
  box-shadow: 0 16px 48px rgba(0,0,0,.35);
}
/* The class sets display:flex, which would otherwise override the [hidden]
   attribute (same specificity, later rule wins) and keep the panel open — so
   the toggle/✕ appear to do nothing. Make hidden win so it collapses/expands. */
.chat-panel[hidden] { display: none; }
.chat-head {
  display: flex; align-items: center; justify-content: space-between;
  padding: 12px 14px; background: var(--teal); color: #fff; font-weight: 700;
}
.chat-close { background: none; border: none; color: #fff; font-size: 16px; cursor: pointer; }
.chat-body { flex: 1; overflow-y: auto; padding: 12px; display: flex; flex-direction: column; gap: 8px; }
.chat-msg { max-width: 85%; padding: 9px 12px; border-radius: 12px; font-size: 13.5px; line-height: 1.5; white-space: pre-wrap; word-wrap: break-word; }
.chat-msg-bot  { align-self: flex-start; background: var(--bg-soft); color: var(--text); border-bottom-left-radius: 4px; }
.chat-msg-user { align-self: flex-end;   background: var(--teal); color: #fff; border-bottom-right-radius: 4px; }
.chat-suggest { display: flex; flex-wrap: wrap; gap: 6px; padding: 0 12px 8px; }
.chat-chip {
  border: 1px solid var(--border); background: var(--bg); color: var(--text);
  border-radius: 999px; padding: 5px 10px; font-size: 12px; cursor: pointer;
}
.chat-chip:hover { border-color: var(--teal); color: var(--teal); }
.chat-report-banner {
  display: flex; align-items: center; justify-content: space-between; gap: 8px;
  padding: 8px 12px; font-size: 12px; color: var(--muted); background: var(--bg-soft);
}
.chat-cancel { background: none; border: none; color: var(--brand); cursor: pointer; font-size: 12px; text-decoration: underline; }
.chat-form { display: flex; gap: 6px; padding: 10px; border-top: 1px solid var(--border); }
.chat-input { flex: 1; padding: 9px 11px; border: 1px solid var(--border); border-radius: 8px; background: var(--bg); color: var(--text); font-size: 13.5px; }
.chat-input:focus { outline: 2px solid var(--teal); outline-offset: -1px; }
.chat-send { border: none; background: var(--teal); color: #fff; border-radius: 8px; width: 40px; cursor: pointer; font-size: 15px; }
.chat-actions { display: flex; gap: 6px; padding: 0 10px 10px; }
.chat-report-btn {
  flex: 1; border: 1px solid var(--border); background: var(--bg); color: var(--text);
  border-radius: 8px; padding: 7px 4px; font-size: 11.5px; font-weight: 600; cursor: pointer;
}
.chat-report-btn:hover { border-color: var(--teal); color: var(--teal); }
.chat-explainer {
  align-self: flex-start; max-width: 90%; margin-top: -2px;
  border: 1px solid var(--teal); background: var(--teal-light); color: var(--teal);
  border-radius: 10px; padding: 8px 12px; font-size: 12.5px; font-weight: 600;
  cursor: pointer; text-align: left; line-height: 1.4;
}
.chat-explainer:hover { background: var(--teal); color: #fff; }

/* Property-search result cards in the chat */
.chat-result {
  display: flex; gap: 10px; align-self: stretch; text-decoration: none;
  background: var(--bg-soft, #f5f7f8); border: 1px solid var(--border);
  border-radius: 10px; overflow: hidden; color: var(--text);
  transition: border-color .15s ease, box-shadow .15s ease;
}
.chat-result:hover { border-color: var(--teal); box-shadow: 0 2px 10px rgba(0,0,0,.08); }
.chat-result-photo {
  flex: 0 0 88px; align-self: stretch; min-height: 74px;
  background: #dde3e8 center/cover no-repeat;
}
.chat-result-info { flex: 1; min-width: 0; padding: 8px 10px 8px 0; display: flex; flex-direction: column; gap: 2px; }
.chat-result-price { font-weight: 800; font-size: 14px; color: var(--text); }
.chat-result-specs { font-size: 12px; color: var(--muted); }
.chat-result-addr {
  font-size: 12px; color: var(--muted);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.chat-result-metrics { font-size: 12px; color: var(--teal); font-weight: 700; margin-top: 1px; }

/* Phones: the 360×520px popover is awkward on a small screen, so the Help bot
   becomes a near-full-width bottom sheet — pinned to the viewport edges with
   small margins, sitting above the (slightly compacted) toggle. Inputs go to
   16px so iOS doesn't zoom on focus. */
@media (max-width: 640px) {
  .chat-widget { right: 12px; bottom: 12px; }
  .chat-toggle { padding: 11px 16px; font-size: 13px; }
  /* Match the compacted chat-toggle (≈40 px tall at bottom: 12 px) so
     the floating compare drawer continues to clear it on mobile. */
  #compare-drawer { right: 12px; bottom: 68px; }
  .chat-panel {
    position: fixed;
    top: 16px; bottom: 72px; left: 8px; right: 8px;   /* full box from edges, toggle stays visible */
    width: auto; max-width: none; height: auto; max-height: none;
  }
  .chat-input { font-size: 16px; }     /* ≥16px → no iOS zoom-on-focus */
  .chat-msg { max-width: 88%; }
}

/* ---- Public education section (/learn) ---- */
.learn { max-width: 980px; margin: 0 auto; padding: 8px 18px 48px; }
.learn-hero { padding: 18px 0 8px; }
.learn h1 { font-size: 32px; margin: 6px 0 14px; }
.learn-lede { font-size: 17px; line-height: 1.6; color: var(--text); max-width: 760px; }
.learn .learn-cta { display: inline-block; margin-top: 18px; text-decoration: none; padding: 11px 18px; }
.learn h2 { font-size: 22px; margin: 34px 0 14px; }
.learn-grid {
  display: grid; gap: 14px;
  grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
}
.learn-card {
  display: block; text-decoration: none; color: var(--text);
  background: var(--surface); border: 1px solid var(--border); border-radius: 12px;
  padding: 18px 18px 14px; transition: border-color .15s, box-shadow .15s;
}
.learn-card:hover { border-color: var(--teal); box-shadow: 0 4px 16px rgba(13,115,119,.14); text-decoration: none; }
.learn-card h3 { margin: 0 0 8px; font-size: 17px; color: var(--teal); }
.learn-card p { margin: 0 0 10px; font-size: 13.5px; line-height: 1.5; color: var(--muted); }
.learn-card-more { font-size: 13px; font-weight: 700; color: var(--teal); }
.learn-faq details {
  border: 1px solid var(--border); border-radius: 10px; padding: 12px 14px; margin-bottom: 10px;
  background: var(--surface);
}
.learn-faq summary { font-weight: 700; cursor: pointer; font-size: 15px; }
.learn-faq p { margin: 10px 0 0; line-height: 1.6; color: var(--text); }
.learn-footcta { margin-top: 32px; font-size: 16px; }
/* term page */
.learn-term { max-width: 760px; margin: 0 auto; padding: 8px 18px 48px; }
.learn-term .crumbs { font-size: 13px; color: var(--muted); margin: 6px 0 12px; }
.learn-term h1 { font-size: 30px; margin: 4px 0 16px; }
.learn-def { font-size: 18px; line-height: 1.65; }
.learn-video { display: inline-block; margin: 14px 0; text-decoration: none; }
.learn-howused { margin: 26px 0; padding: 16px 18px; background: var(--bg-soft); border-radius: 12px; }
.learn-howused h2 { margin: 0 0 8px; font-size: 18px; }
.learn-howused p { margin: 0; line-height: 1.6; }
.learn-related { display: flex; flex-wrap: wrap; gap: 8px; list-style: none; padding: 0; }
.learn-related li a {
  display: inline-block; border: 1px solid var(--border); border-radius: 999px;
  padding: 6px 12px; font-size: 13px; font-weight: 600; text-decoration: none; color: var(--teal);
}
.learn-related li a:hover { border-color: var(--teal); }

/* ---- User widget (top-right of the topnav) ---- */
/* Transient status toast (replaces window.alert/prompt for save-search). */
#toast-host {
  position: fixed; left: 50%; bottom: 32px; transform: translateX(-50%);
  z-index: 2000; display: flex; flex-direction: column; gap: 8px;
  pointer-events: none;
}
.toast {
  background: var(--teal); color: #fff;
  padding: 10px 18px; border-radius: 999px;
  font-size: 14px; font-weight: 700; letter-spacing: .01em;
  box-shadow: 0 8px 24px rgba(0,0,0,.22);
  opacity: 0; transform: translateY(8px);
  transition: opacity .2s ease, transform .2s ease;
}
.toast.show { opacity: 1; transform: translateY(0); }

/* Markets dropdown — top-20 US rental metros.  Mirrors the user-widget
   trigger/menu pattern.  Sits between the search bar and the user widget
   in the topnav. */
.markets-widget { position: relative; display: inline-flex; align-items: center; margin-left: 8px; }
.markets-trigger {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 6px 12px;
  border: 1px solid var(--border); border-radius: 999px;
  background: var(--surface, #fff); color: var(--text);
  font-size: 14px; font-weight: 600; cursor: pointer;
}
.markets-trigger:hover { border-color: var(--teal); }
.markets-trigger[aria-expanded="true"] { border-color: var(--teal); background: var(--bg-soft); }
.markets-caret { color: var(--muted); font-size: 11px; }
.markets-menu {
  position: absolute; top: calc(100% + 8px); right: 0;
  min-width: 220px; padding: 6px;
  background: var(--surface, #fff); border: 1px solid var(--border);
  border-radius: 8px; box-shadow: 0 8px 24px rgba(0,0,0,.16);
  display: grid; grid-template-columns: 1fr 1fr; gap: 2px;
  z-index: 1400;          /* same stack as user-menu — top of every layer */
}
.markets-menu[hidden] { display: none; }
/* Hover bridge — same trick as .user-menu::before so a 1px gap between
   the trigger and the menu doesn't fire mouseleave mid-pick. */
.markets-menu::before {
  content: ""; position: absolute; left: 0; right: 0;
  top: -12px; height: 12px;
}
.markets-item {
  display: block; padding: 8px 10px; border-radius: 4px;
  color: var(--text); font-size: 13px; font-weight: 500;
  white-space: nowrap;
}
.markets-item:hover { background: var(--bg-soft); text-decoration: none; color: var(--teal); }

.user-widget { position: relative; display: inline-flex; align-items: center; gap: 8px; margin-left: 8px; }
.user-link {
  padding: 6px 12px; border-radius: 6px; font-size: 14px; font-weight: 600;
  color: var(--text); border: 1px solid transparent;
}
.user-link:hover { text-decoration: none; background: var(--bg-soft); }
.user-link.primary { background: var(--teal); color: #fff; border-color: var(--teal); }
.user-link.primary:hover { background: #0a5b5f; border-color: #0a5b5f; }
.user-trigger {
  display: inline-flex; align-items: center; gap: 8px;
  padding: 4px 10px 4px 4px;
  border: 1px solid var(--border); border-radius: 999px;
  background: var(--surface, #fff); color: var(--text);
  font-size: 14px; font-weight: 600; cursor: pointer;
}
.user-trigger:hover { border-color: var(--teal); }
.user-avatar {
  display: inline-flex; align-items: center; justify-content: center;
  width: 26px; height: 26px; border-radius: 50%;
  background: var(--teal); color: #fff;
  font-size: 13px; font-weight: 800;
}
.user-avatar-img { object-fit: cover; background: var(--bg-soft); }
.user-name { max-width: 140px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.user-caret { color: var(--muted); font-size: 11px; }
.user-menu {
  position: absolute; top: calc(100% + 8px); right: 0;
  min-width: 200px; padding: 6px;
  background: var(--surface, #fff); border: 1px solid var(--border);
  border-radius: 8px; box-shadow: 0 8px 24px rgba(0,0,0,.16);
  display: flex; flex-direction: column; gap: 2px;
  z-index: 1400;       /* above filter row, map, modal — top of every stack */
}
.user-menu[hidden] { display: none; }
/* Invisible bridge spanning the 8px gap between the trigger and the menu
   so moving the cursor down onto a menu item never leaves #user-widget
   (which would fire mouseleave and close the menu mid-selection). */
.user-menu::before {
  content: "";
  position: absolute;
  left: 0; right: 0;
  top: -12px; height: 12px;
}
.user-menu a {
  display: flex; align-items: center; gap: 10px;
  padding: 8px 10px; border-radius: 4px;
  color: var(--text); font-size: 14px; font-weight: 500;
}
.user-menu a:hover { background: var(--bg-soft); text-decoration: none; }
.user-menu .menu-icon { width: 18px; text-align: center; color: var(--teal); }
.user-menu .user-menu-divider { border-top: 1px solid var(--border); margin-top: 4px; padding-top: 12px; }
/* Theme switcher (Auto / Day / Night) at the top of the user menu. */
.theme-toggle {
  display: flex; align-items: center; gap: 6px;
  padding: 6px 10px 12px; margin-bottom: 4px;
  border-bottom: 1px solid var(--border);
}
.theme-toggle button {
  flex: 1; padding: 5px 0; border: 1px solid var(--border);
  border-radius: 5px; background: var(--surface, #fff); color: var(--text);
  font-size: 12px; font-weight: 600; cursor: pointer;
}
.theme-toggle button:hover { border-color: var(--teal); }
.theme-toggle button.active {
  background: var(--teal); color: #fff; border-color: var(--teal);
}
html.dark-mode .theme-toggle button { background: var(--surface); color: var(--text); }
html.dark-mode .theme-toggle button.active { background: var(--teal); color: #fff; }

/* Compact variant for the anon header — no menu wrapper, no bottom border,
   smaller buttons that read as a single icon trio. */
.theme-toggle.theme-toggle-compact {
  padding: 0; margin: 0 8px 0 0; border-bottom: none;
  gap: 2px;
}
.theme-toggle.theme-toggle-compact button {
  flex: 0 0 auto; width: 26px; padding: 4px 0;
  font-size: 13px; line-height: 1;
}

html.dark-mode .user-trigger,
html.dark-mode .user-menu { background: var(--surface); color: var(--text); border-color: var(--border); }
html.dark-mode .user-menu a:hover { background: rgba(255,255,255,.05); }

/* ---- Flash messages ---- */
.flash-list { list-style: none; padding: 0; margin: 0 0 12px; }
.flash {
  padding: 10px 14px; border-radius: 8px; font-size: 14px; font-weight: 500;
  margin-bottom: 6px;
}
.flash-info  { background: rgba(13,115,119,.12); color: var(--teal); }
.flash-error { background: rgba(200,32,33,.12); color: var(--brand); }

/* ---- Heart / favorite button ---- */
/* Default state: outlined white heart with subtle border, so the icon
   reads as "not favorited" at a glance. Favorited state: solid red
   filled heart, matching the global --brand colour the rest of the app
   already uses for emphasis (badges, accents). */
.fav-btn {
  display: inline-flex; align-items: center; justify-content: center;
  width: 32px; height: 32px; padding: 0;
  border: 1px solid var(--border); border-radius: 50%;
  background: var(--surface, #fff);
  color: var(--muted);                /* outline-heart colour when unfavorited */
  font-size: 18px; line-height: 1; cursor: pointer;
  transition: transform .12s ease, color .12s, background .12s, border-color .12s;
  flex: 0 0 auto;
}
.fav-btn:hover { transform: scale(1.08); border-color: var(--brand); color: var(--brand); }
.fav-btn.favorited {
  color: var(--brand);                /* the red heart */
  border-color: var(--brand);
  background: rgba(200,32,33,.08);
}
.fav-btn.favorited:hover { background: rgba(200,32,33,.16); }
html.dark-mode .fav-btn {
  background: var(--surface); color: var(--muted); border-color: var(--border);
}
html.dark-mode .fav-btn.favorited {
  background: rgba(200,32,33,.18); color: var(--brand); border-color: var(--brand);
}

/* Heart sits to the right of the price within the card body. */
.card-prop .price-row {
  display: flex; align-items: center; justify-content: space-between; gap: 8px;
}
/* Smaller heart cell inside the search-table */
.search-table .td-fav, .search-table .th-fav { width: 36px; text-align: center; padding: 4px 0; }
.search-table .td-fav .fav-btn { width: 26px; height: 26px; font-size: 14px; }

/* Favorite + hide + share buttons share the right side of the price row.
   De-scoped from .card-prop so the same trio renders on the property
   detail page (.detail-head-info .card-actions). */
.card-actions { display: flex; align-items: center; gap: 6px; flex: 0 0 auto; }

/* Hide button: house + diagonal slash. Default (not hidden) = white glyph
   on a dark chip so it's visible on the white card. Hidden = grey glyph on
   a light chip (matches the user's "white → grey" spec). */
.hide-btn {
  display: inline-flex; align-items: center; justify-content: center;
  width: 32px; height: 32px; padding: 0;
  border: 1px solid var(--border); border-radius: 50%;
  background: rgba(20,28,33,.55);
  color: #fff;
  cursor: pointer; flex: 0 0 auto;
  transition: transform .12s ease, color .12s, background .12s, border-color .12s;
}
.hide-btn .hide-ico { display: block; }
.hide-btn:hover { transform: scale(1.08); background: rgba(20,28,33,.75); }
.hide-btn.hidden-on {
  background: var(--surface, #fff);
  color: #9aa0a6;                       /* grey house when hidden */
  border-color: var(--border);
}
.hide-btn.hidden-on:hover { transform: scale(1.08); color: #7c8186; }
html.dark-mode .hide-btn { background: rgba(0,0,0,.55); color: #fff; }
html.dark-mode .hide-btn.hidden-on { background: var(--surface); color: #8a99a8; }
.search-table .td-fav .hide-btn { width: 26px; height: 26px; }

/* Public-records-note variant of .unit-confirmation — shown when the
   parcel's county classification (e.g. Multi-Family 2-4 Unit) differs
   from the operator-effective listing type (e.g. SingleFamily).
   Reads as informational, not a warning: muted left rule, soft tint. */
.unit-confirmation.classify-note {
  border-left: 3px solid var(--border);
  padding: 10px 12px 8px;
  background: var(--bg-soft);
  border-radius: 8px;
}
.unit-confirmation.classify-note h2 {
  font-size: 16px; color: var(--muted); margin: 0 0 4px;
}
.unit-confirmation.classify-note p { margin: 4px 0; }

/* Admin-only inline reclassify control on .detail-head-info .specs.
   Tiny select + Apply button + status badge.  Sits AFTER the specs +
   public-records badge to read as "operational tool" rather than as
   first-class listing info.  Only rendered when is_admin in the
   property template, so this rule is dead for non-admins. */
.ptype-admin {
  display: inline-flex; align-items: center; gap: 6px;
  margin-left: 12px;
  padding: 2px 8px;
  border: 1px dashed var(--border);
  border-radius: 6px;
  font-size: 12px;
  color: var(--muted);
}
.ptype-override-sel {
  padding: 2px 6px; border: 1px solid var(--border); border-radius: 4px;
  background: var(--surface, #fff); color: var(--text);
  font-size: 12px;
}
.ptype-override-apply {
  padding: 3px 10px; border: 1px solid var(--border); border-radius: 4px;
  background: var(--bg-soft); color: var(--text);
  font-size: 12px; font-weight: 600; cursor: pointer;
}
.ptype-override-apply:hover { border-color: var(--teal); color: var(--teal); }
.ptype-override-apply:disabled { opacity: .6; cursor: wait; }
.ptype-override-status {
  font-size: 11px; color: var(--teal); font-weight: 600;
}

/* Share button — same chip dimensions as .hide-btn / .fav-btn so they
   sit on one row with the other card actions.  Always-white glyph on
   the dark chip; no "active" state since one click opens the modal. */
.share-btn {
  display: inline-flex; align-items: center; justify-content: center;
  width: 32px; height: 32px; padding: 0;
  border: 1px solid var(--border); border-radius: 50%;
  background: rgba(20,28,33,.55);
  color: #fff;
  cursor: pointer; flex: 0 0 auto;
  transition: transform .12s ease, background .12s, border-color .12s;
}
.share-btn .share-ico { display: block; }
.share-btn:hover { transform: scale(1.08); background: rgba(20,28,33,.75); }
html.dark-mode .share-btn { background: rgba(0,0,0,.55); }
.search-table .td-fav .share-btn { width: 26px; height: 26px; }

/* "Share listing" modal — same .fs-overlay dim pattern as the others.
   Mirrors the design in the spec PNG: stacked email input row, note
   textarea + char counter, red Send CTA, social-icon strip below. */
.sh-overlay { z-index: 4600; }
html.sh-open, html.sh-open body { overflow: hidden; }
.sh-card {
  position: relative; background: var(--surface, #fff); color: var(--text);
  width: 480px; max-width: 94vw; max-height: 88vh; overflow-y: auto;
  border: 1px solid var(--border); border-radius: 14px; padding: 24px 28px;
  box-shadow: 0 20px 60px rgba(0,0,0,.45);
}
.sh-title { margin: 0 0 18px; font-size: 22px; font-weight: 700; }
.sh-emails { display: flex; flex-direction: column; gap: 8px; margin-bottom: 12px; }
.sh-email-row { display: flex; align-items: center; gap: 8px; }
.sh-email {
  flex: 1; padding: 12px 14px;
  border: 1px solid var(--border); border-radius: 8px;
  font-size: 15px; background: var(--bg); color: var(--text);
}
/* Set by JS when /api/share returns invalid:[{email,reason}] — the
   hover title shows the reason (Null MX / NXDOMAIN / etc.) so the
   user knows WHY their typo failed, not just THAT it did. */
.sh-email.sh-bad {
  border-color: #dc2626;
  background: rgba(220, 38, 38, .06);
}
.sh-plus, .sh-minus {
  flex: 0 0 auto; width: 36px; height: 36px;
  border: 1px solid transparent; border-radius: 8px;
  background: transparent; color: var(--muted, #6b7280);
  font-size: 22px; line-height: 1; cursor: pointer;
}
.sh-plus:hover, .sh-minus:hover { color: var(--text); background: var(--bg-soft); }
.sh-note {
  width: 100%; box-sizing: border-box; min-height: 110px; padding: 12px 14px;
  border: 1px solid var(--border); border-radius: 8px;
  font-size: 14px; font-family: inherit; resize: vertical;
  background: var(--bg); color: var(--text);
}
.sh-counter {
  margin: 6px 2px 16px; font-size: 12px; color: var(--muted, #6b7280);
}
.sh-send {
  display: block; width: 100%; padding: 14px;
  background: #dc2626; color: #fff; border: none; border-radius: 999px;
  font-size: 15px; font-weight: 700; cursor: pointer;
  transition: background .15s ease;
}
.sh-send:hover  { background: #b91c1c; }
.sh-send:disabled { background: #f3a8a8; cursor: not-allowed; }
.sh-icons {
  display: flex; justify-content: space-around; align-items: center;
  gap: 8px; margin-top: 18px; padding-top: 18px;
  border-top: 1px solid var(--border);
}
.sh-ico {
  display: inline-flex; align-items: center; justify-content: center;
  width: 40px; height: 40px; padding: 0;
  border: none; border-radius: 50%;
  background: transparent; color: var(--muted, #6b7280);
  cursor: pointer; transition: background .15s ease, color .15s ease;
}
.sh-ico:hover { background: var(--bg-soft); color: var(--text); }
.sh-ico svg { display: block; }
html.dark-mode .sh-ico { color: #c8d0d8; }

/* "Per N/N share" note above the card metrics for fractional listings. */
.card-prop .metrics-frac {
  margin: 6px 0 2px;
  font-size: 11px; font-weight: 700; letter-spacing: .02em;
  color: var(--teal);
  text-transform: uppercase;
}

/* ---- Search-table selected card preview ---- */
.search-table-selected {
  padding: 8px; margin-bottom: 8px;
  background: var(--surface, #fff); border: 1px solid var(--border);
  border-radius: 10px; box-shadow: 0 2px 8px rgba(0,0,0,.06);
}
.search-table-selected[hidden] { display: none; }
.search-table-selected .card-prop {
  display: grid; grid-template-columns: 200px 1fr; gap: 12px;
  border: none; box-shadow: none; padding: 0;
}
.search-table-selected .card-prop .photo { aspect-ratio: 16/10; min-height: 0; }
.search-table-selected .card-prop .body { padding: 6px 4px; }

/* ---- Auth pages ---- */
.auth-card {
  max-width: 420px; margin: 40px auto;
  padding: 28px 28px 24px;
  background: var(--surface, #fff);
  border: 1px solid var(--border); border-radius: 12px;
  box-shadow: 0 6px 22px rgba(0,0,0,.06);
}
.auth-card h2 { margin: 0 0 18px; font-size: 22px; }
.auth-form { display: flex; flex-direction: column; gap: 12px; }
.auth-form label { display: flex; flex-direction: column; gap: 4px; font-size: 13px; font-weight: 600; color: var(--muted); }
.auth-form input {
  padding: 10px 12px; border: 1px solid var(--border); border-radius: 6px;
  background: var(--surface, #fff); color: var(--text); font-size: 15px;
}
.auth-form input:focus { outline: 2px solid var(--teal); outline-offset: -1px; }
.auth-form .btn.primary { margin-top: 4px; padding: 10px 14px; font-size: 15px; }
.auth-alt { font-size: 13px; color: var(--muted); margin: 0; text-align: center; }
.auth-alt a { font-weight: 700; }

/* ---- Social sign-in buttons ---- */
.oauth-block { margin-top: 18px; }
.oauth-sep {
  display: flex; align-items: center; gap: 10px;
  color: var(--muted); font-size: 12px; margin: 4px 0 14px;
}
.oauth-sep::before, .oauth-sep::after {
  content: ""; flex: 1; height: 1px; background: var(--border);
}
.oauth-buttons { display: flex; flex-direction: column; gap: 10px; }
.btn-oauth {
  display: flex; align-items: center; justify-content: center; gap: 10px;
  padding: 10px 14px; border-radius: 6px; border: 1px solid var(--border);
  font-size: 15px; font-weight: 600; text-decoration: none;
  transition: filter .15s ease, box-shadow .15s ease;
}
.btn-oauth:hover { filter: brightness(0.96); box-shadow: 0 1px 4px rgba(0,0,0,.18); text-decoration: none; }
.btn-oauth .oauth-icon { display: inline-flex; line-height: 0; }
/* Google's button is white in both themes per its brand guidelines, so keep
   its border visible on the dark hero/card too. */
html.dark-mode .btn-oauth-google { box-shadow: 0 0 0 1px var(--border); }

/* ---- Saved-searches grid ---- */
.saved-list h2 { margin: 8px 0 18px; font-size: 22px; }
.saved-grid {
  list-style: none; padding: 0; margin: 0;
  display: grid; gap: 14px;
  grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
}
.saved-card {
  position: relative;
  border: 1px solid var(--border); border-radius: 10px;
  background: var(--surface, #fff); padding: 0;
  overflow: hidden;
  transition: transform .12s, box-shadow .12s, border-color .12s;
}
.saved-card:hover { transform: translateY(-1px); box-shadow: 0 6px 18px rgba(0,0,0,.10); border-color: var(--teal); }
.saved-card-body { display: block; padding: 16px 16px 56px; color: var(--text); }
.saved-card-body:hover { text-decoration: none; }
.saved-card h3 { margin: 0 0 10px; font-size: 16px; color: var(--text); }
.saved-summary { margin: 0 0 12px; display: flex; flex-wrap: wrap; gap: 6px 14px; font-size: 12px; }
.saved-summary > div { display: flex; gap: 4px; }
.saved-summary dt { color: var(--muted); text-transform: uppercase; letter-spacing: .03em; margin: 0; }
.saved-summary dd { margin: 0; color: var(--text); font-weight: 600; }
.saved-cta {
  position: absolute; left: 16px; bottom: 14px;
  color: var(--teal); font-weight: 700; font-size: 13px;
}
.saved-delete {
  position: absolute; right: 10px; bottom: 10px;
  padding: 4px 10px; font-size: 12px;
}

/* ---- Favorites page (light reuse of the feed grid) ---- */
.favorites-page .feed-header { margin-bottom: 14px; }
.favorites-page .grid { grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); display: grid; gap: 14px; }
.favorites-page .empty { padding: 40px 20px; text-align: center; }

html.dark-mode .saved-card,
html.dark-mode .auth-card { background: var(--surface); }

/* ── Premium tier pages & badges ─────────────────────────────────────── */
.upgrade-wrap { max-width: 980px; margin: 24px auto; padding: 0 16px; }
.upgrade-wrap h1 { font-size: 22px; margin: 0 0 4px; }
.tier-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); gap: 16px; margin: 24px 0; }
.tier-card { background: var(--surface); color: var(--text); border: 1px solid var(--border); border-radius: 10px; padding: 18px; position: relative; }
.tier-card.highlight { border-color: var(--teal); box-shadow: 0 0 0 1px var(--teal); }
.tier-card.current::after { content: "your tier"; position: absolute; top: 10px; right: 10px;
  background: var(--teal); color: white; font-size: 10px; font-weight: 700;
  padding: 2px 8px; border-radius: 999px; text-transform: uppercase; letter-spacing: .04em; }
.tier-name { font-size: 11px; color: var(--muted); text-transform: uppercase; letter-spacing: .05em; font-weight: 700; margin-bottom: 4px; }
.tier-price { font-size: 26px; font-weight: 700; margin-bottom: 12px; }
.tier-card ul { list-style: none; padding: 0; margin: 0 0 16px; font-size: 13px; }
.tier-card li { padding: 4px 0; border-bottom: 1px dashed var(--border); }
.tier-card li:last-child { border-bottom: 0; }
.tier-card .btn { display: inline-block; padding: 8px 16px; border-radius: 6px;
  background: var(--bg-soft); color: var(--text); text-decoration: none; font-weight: 600; font-size: 13px;
  border: 1px solid var(--border); }
.tier-card .btn-primary { background: var(--teal); color: white; border-color: var(--teal); }

/* "Verified" rent badge (Pro tier surfacing) */
.rent-verified { display: inline-block; padding: 1px 8px; margin-left: 6px; border-radius: 999px;
  background: #dff4e3; color: var(--positive); font-size: 10px; font-weight: 700; letter-spacing: .03em; }
.rent-ci { margin-left: 6px; color: var(--muted); font-size: 12px; font-variant-numeric: tabular-nums; }

/* "Add to portfolio" button on detail */
.portfolio-add { display: inline-flex; align-items: center; gap: 6px;
  background: var(--bg-soft); color: var(--text);
  border: 1px solid var(--border); border-radius: 6px;
  padding: 6px 12px; font-size: 12px; font-weight: 600; cursor: pointer; }
.portfolio-add:hover { background: var(--surface); color: var(--text); border-color: var(--teal); }
.portfolio-add.is-owned { color: var(--positive); border-color: var(--positive); cursor: default; }

/* Portfolio page */
.portfolio-page { max-width: 1080px; margin: 24px auto; padding: 0 16px; }
.portfolio-aggregates { display: grid; grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); gap: 12px; margin: 16px 0 24px; }
.portfolio-agg-cell { background: var(--surface); color: var(--text); border: 1px solid var(--border); border-radius: 8px; padding: 14px; }
.portfolio-agg-cell .label { font-size: 11px; color: var(--muted); text-transform: uppercase; letter-spacing: .04em; font-weight: 600; }
.portfolio-agg-cell .big { font-size: 22px; font-weight: 700; margin-top: 2px; font-variant-numeric: tabular-nums; }
.portfolio-list { list-style: none; padding: 0; margin: 0; }
.portfolio-list li { display: grid; grid-template-columns: 1fr repeat(10, auto); gap: 14px;
  padding: 12px; background: var(--surface); color: var(--text); border: 1px solid var(--border); border-radius: 8px; margin-bottom: 8px;
  align-items: baseline; font-size: 13px; }
/* LTV stoplight chip — green for refi candidates (LTV < 60%), red for
   high leverage (LTV > 80%). Mid-range LTVs render no chip. */
.pf-ltv-chip { display: inline-block; margin-top: 2px; padding: 1px 6px;
  border-radius: 999px; font-size: 9px; font-weight: 700;
  letter-spacing: .02em; }
/* 5-year cashflow trajectory table inside the row's expandable
   breakdown panel. Compact two-decimal tabular numerics, dashed-row
   striping that works in both themes. */
.pf-trajectory { width: 100%; margin-top: 10px; font-size: 11px;
  font-variant-numeric: tabular-nums; border-collapse: collapse; }
.pf-trajectory th,
.pf-trajectory td { padding: 4px 8px; text-align: right; border-bottom: 1px dashed var(--border); }
.pf-trajectory th { color: var(--muted); font-weight: 600; text-transform: uppercase;
  font-size: 10px; letter-spacing: .04em; }
.pf-trajectory td:first-child,
.pf-trajectory th:first-child { text-align: left; }
/* Three side-by-side donut cards above the property list. Each card
   sets a fixed canvas size so the Chart.js render is crisp on every
   theme; the responsive grid lets them stack on narrow screens. */
.pf-divers { display: grid; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
  gap: 12px; margin-bottom: 18px; }
.pf-divers-card { background: var(--surface); color: var(--text);
  border: 1px solid var(--border); border-radius: 8px; padding: 14px;
  display: flex; flex-direction: column; align-items: center; }
.pf-divers-card h4 { margin: 0 0 8px; font-size: 11px; text-transform: uppercase;
  letter-spacing: .04em; color: var(--muted); align-self: flex-start; }
.pf-divers-card canvas { display: block; }
.pf-ltv-refi { background: #d1fae5; color: #047857; }
.pf-ltv-high { background: #fee2e2; color: #b91c1c; }
html.dark-mode .pf-ltv-refi { background: #064e3b; color: #6ee7b7; }
html.dark-mode .pf-ltv-high { background: #7f1d1d; color: #fca5a5; }
/* On narrower screens (< 1100px), drop to two rows: address spans the
   full width on top, metric cells flex-wrap underneath. The cashflow
   breakdown <details> still gets its own row below the metrics. */
@media (max-width: 1100px) {
  .portfolio-list li { grid-template-columns: 1fr; gap: 8px; }
  .portfolio-list li > div:not(.pf-addr-block):not(.pf-actions):not(.pf-breakdown) {
    display: inline-block; min-width: 96px; margin-right: 14px; vertical-align: top;
  }
}
/* Cashflow breakdown — spans the full row width below the main grid.
   Uses <details><summary> for browser-native expand/collapse, no JS.
   The waterfall inside is a 2-3 column grid that flows responsively. */
.portfolio-list li > .pf-breakdown { grid-column: 1 / -1; margin-top: 8px;
  padding-top: 10px; border-top: 1px dashed var(--border); }
.pf-breakdown > summary { cursor: pointer; user-select: none; padding: 2px 0; }
.pf-breakdown > summary:hover { color: var(--text); }
.pf-breakdown-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(120px, 1fr)); gap: 10px;
  margin-top: 10px; font-variant-numeric: tabular-nums; }
.pf-breakdown-grid > div { padding: 6px 8px; background: var(--bg-soft); border-radius: 6px; }
.pf-breakdown-total { font-weight: 700; }
.pf-breakdown-total div:last-child { font-size: 14px; }
.portfolio-list .pf-addr { font-weight: 600; }
.portfolio-list .pf-num { text-align: right; font-variant-numeric: tabular-nums; }
.portfolio-list .pf-num.pos { color: var(--positive); }
.portfolio-list .pf-num.neg { color: var(--negative); }
.portfolio-empty { text-align: center; padding: 60px 20px; color: var(--muted); }

/* Portfolio pills (row-status badges next to address).
   Light mode uses the legacy amber / sky / soft-gray palette; dark mode
   tones them down so they don't glow on the dark surface. */
.pf-pill { display: inline-block; padding: 1px 6px; margin-left: 4px;
  border-radius: 999px; font-size: 9px; font-weight: 700; letter-spacing: .02em;
  vertical-align: middle; }
.pf-pill-muted { background: #f3f4f6; color: #6b7280; }
.pf-pill-warn  { background: #fef3c7; color: #92400e; }
.pf-pill-info  { background: #dbeafe; color: #1d4ed8; }
html.dark-mode .pf-pill-muted { background: var(--bg-soft); color: var(--muted); }
html.dark-mode .pf-pill-warn  { background: #3a2a08; color: #fcd34d; }
html.dark-mode .pf-pill-info  { background: #1e3a8a; color: #dbeafe; }

/* Aggregates: positive/negative coloring uses semantic tokens so the
   dark theme picks up the right green/red — was inheriting muted text
   colors that washed out the headline number. */
.portfolio-agg-cell .big.pos { color: var(--positive); }
.portfolio-agg-cell .big.neg { color: var(--negative); }

/* Edit-details modal — explicit dark-mode rules so the backdrop, panel,
   and form inputs all read correctly. The panel itself already uses
   var(--surface) etc; here we strengthen the backdrop for dark surfaces
   and ensure the input focus ring is visible. */
.pf-modal-panel input:focus { outline: 2px solid var(--teal); outline-offset: 1px; border-color: var(--teal); }
html.dark-mode .pf-modal-backdrop { background: rgba(0,0,0,.65); }
html.dark-mode .pf-modal-panel { box-shadow: 0 12px 32px rgba(0,0,0,.55); }

/* API keys page */
.api-keys-page { max-width: 760px; margin: 24px auto; padding: 0 16px; }
.api-keys-page table { width: 100%; border-collapse: collapse; margin-top: 12px; font-size: 13px; }
.api-keys-page th { text-align: left; padding: 8px; font-size: 11px; color: var(--muted);
  text-transform: uppercase; letter-spacing: .04em; border-bottom: 1px solid var(--border); }
.api-keys-page td { padding: 10px 8px; border-bottom: 1px solid var(--border); vertical-align: baseline; }
.api-keys-page code.key-once { display: block; padding: 12px; background: var(--bg-soft); color: var(--text); border: 1px solid var(--border);
  border-radius: 6px; font-size: 13px; word-break: break-all; margin-top: 8px; }
html.dark-mode .api-keys-page code.key-once { background: var(--bg-soft); }
.api-keys-page .warn-banner { background: #fef3c7; border: 1px solid #f59e0b; color: #92400e;
  padding: 12px 14px; border-radius: 6px; margin: 12px 0; font-size: 13px; }

/* ── Branded error pages (404 / 500) ─────────────────────────────────────── */
.error-page {
  max-width: 560px; margin: 8vh auto; padding: 0 20px; text-align: center;
}
.error-page .error-code {
  font-size: 72px; font-weight: 800; line-height: 1;
  color: var(--teal, #0d7377); letter-spacing: .02em; margin-bottom: 12px;
}
.error-page h1 { font-size: 24px; margin: 0 0 10px; }
.error-page .muted { font-size: 15px; line-height: 1.5; }
.error-page .error-actions {
  margin-top: 24px; display: flex; gap: 12px; justify-content: center; flex-wrap: wrap;
}

/* ── "Why this score?" explainability breakdown (property page) ──────────── */
.score-why { margin: 8px 0 14px; border: 1px solid var(--border); border-radius: 8px;
  background: var(--surface, #fff); overflow: hidden; }
.score-why > summary {
  cursor: pointer; padding: 9px 12px; font-weight: 700; font-size: 13px;
  color: var(--text, var(--ink)); list-style: none; user-select: none;
}
.score-why > summary::-webkit-details-marker { display: none; }
.score-why > summary::before { content: "▸ "; color: var(--teal); font-weight: 700; }
.score-why[open] > summary::before { content: "▾ "; }
.score-why > summary:hover { color: var(--teal); }
.why-hint { font-weight: 400; color: var(--muted); font-size: 12px; }
.why-body { padding: 4px 12px 12px; }
.why-intro { font-size: 11.5px; color: var(--muted); margin: 0 0 10px; line-height: 1.45; }
.why-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 6px; }
.why-row { display: grid; grid-template-columns: 96px 1fr 60px; align-items: center; gap: 8px; }
.why-label { font-size: 12px; color: var(--text, var(--ink)); }
.why-bar { height: 8px; background: var(--bg-soft); border-radius: 4px; overflow: hidden; }
.why-fill { display: block; height: 100%; border-radius: 4px; transition: width .25s ease; }
.why-fill.why-good { background: var(--good, #1a9e54); }
.why-fill.why-mid  { background: var(--warn, #e0a116); }
.why-fill.why-low  { background: var(--bad, #d33); }
.why-pts { font-size: 12px; font-weight: 700; text-align: right; font-variant-numeric: tabular-nums; color: var(--text, var(--ink)); }
.why-pts small { font-weight: 400; color: var(--muted); }
.why-lean { font-size: 11px; color: var(--muted); margin: 10px 0 0; }
@media (max-width: 560px) { .why-row { grid-template-columns: 84px 1fr 52px; } }
