/* /map page chrome. All values derive from tokens.css; no new colors. */

html, body, .map-body {
  height: 100%;
  overflow: hidden;
}

.map-app {
  display: grid;
  grid-template-rows: 64px 64px 1fr;
  height: 100vh;
  background: var(--bg);
}

/* ─── Topbar parity (copied from layout.css) ───────────────────────────
   map.html now renders the same .topbar markup as index.html. These rules
   are copied verbatim so map.css can stand alone (no dependency on
   layout.css load order). Keep in sync with web/styles/layout.css. */
.topbar {
  display: flex;
  align-items: center;
  padding: 0 20px 0 28px;
  height: 64px;
  background: var(--bg);
  border-bottom: 1px solid var(--border-soft);
  position: sticky;
  top: 0;
  z-index: 30;
}
.logo {
  height: 24px;
  width: auto;
  display: block;
  margin-left: -4px;
}
.topbar-right {
  margin-left: auto;
  display: flex;
  gap: 6px;
  align-items: center;
}
.btn-sm {
  font-size: 15px;
  font-weight: 500;
  padding: 10px 24px;
  border-radius: var(--radius-pill);
  border: 1px solid var(--border);
  background: var(--bg);
  color: var(--fg);
  line-height: 1.2;
  transition: background .15s, border-color .15s, color .15s;
}
.btn-sm:hover { background: var(--hover); border-color: #b8b8b8; }
.btn-primary {
  background: var(--navy);
  color: #fff;
  border-color: var(--navy);
}
.btn-primary:hover { background: #002a55; border-color: #002a55; }

/* ─── Filterbar ────────────────────────────────────────────────────── */
.map-filterbar {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: 0 28px;
  background: var(--bg);
  border-bottom: 1px solid var(--border-soft);
  position: relative;
  z-index: 20;
}
.map-search-wrap {
  display: flex;
  align-items: center;
  gap: 6px;
  width: 323px;
  padding: 4px 4px 4px 16px;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: var(--radius-pill);
  box-shadow: var(--shadow-sm);
  transition: border-color .15s, box-shadow .15s;
}
.map-search-wrap:focus-within {
  border-color: var(--navy);
  box-shadow: 0 0 0 3px rgba(0, 58, 112, .12);
}
.map-search {
  flex: 1;
  min-width: 0;
  padding: 8px 0;
  border: none;
  outline: none;
  background: transparent;
  font-size: 14px;
  color: var(--fg);
  line-height: 1.2;
}
.map-search::-webkit-search-cancel-button { display: none; }
.map-search-go {
  flex-shrink: 0;
  width: 32px;
  height: 32px;
  border-radius: 50%;
  border: none;
  background: var(--navy);
  color: #fff;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  transition: background .15s;
}
.map-search-go:hover { background: #002a55; }

.map-fb-row {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  flex-wrap: wrap;
}

/* ─── Split layout ─────────────────────────────────────────────────── */
.map-split {
  display: grid;
  grid-template-columns: 580px 1fr;
  overflow: hidden;
  background: var(--bg-soft);
}
.map-leftcol {
  background: var(--bg);
  border-right: 1px solid var(--border-soft);
  /* Column layout: the results header is a fixed (non-scrolling) child and
     only .map-card-list scrolls, so the scrollbar runs alongside the cards
     and never next to the fixed stats header. */
  display: flex;
  flex-direction: column;
  overflow: hidden;
}
.map-rightcol {
  position: relative;
  overflow: hidden;
}

/* ─── LeftCol — viewport-aware card list (Phase 5) ── */
.map-card-list {
  padding: 12px 16px;
  display: flex;
  flex-direction: column;
  gap: 12px;
  /* The only scrolling region in the sidebar — flex:1 + min-height:0 lets it
     fill the space under the fixed header and own the scrollbar. */
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
}

.mc-card {
  display: grid;
  grid-template-columns: 168px 1fr;
  gap: 14px;
  padding: 8px;
  background: var(--bg);
  border: 1px solid var(--border-soft);
  border-radius: var(--radius);
  cursor: pointer;
  transition: border-color .15s, box-shadow .15s, transform .15s;
}
.mc-card:hover {
  border-color: #b8b8b8;
  box-shadow: var(--shadow-sm);
  transform: translateY(-1px);
}
.mc-thumb {
  position: relative;
  width: 168px;
  /* Fixed WIDTH, stretched HEIGHT. The image is object-fit:cover (below), so
     its natural aspect never drives layout. Stretching to the grid row makes
     the thumb exactly as tall as the card body, so the margin above and below
     the image equals the card's 8px padding on the left — even breathing room
     on all sides instead of the extra ~14px top/bottom gap a fixed height
     left. */
  height: auto;
  align-self: stretch;
  background: var(--gray);
  border-radius: var(--radius-sm);
  overflow: hidden;
  flex-shrink: 0;
}
/* Shimmer while the image is in flight — Airbnb-style polish that turns the
   dead gray box into a gentle "loading" indicator. The animation stops the
   instant .img-ready is added (by the inline onload). The shimmer is only
   visible until the image bytes arrive; once the <img> paints over the
   .mc-thumb background, the animation is hidden by the image. */
.mc-thumb:not(.img-ready) {
  background: linear-gradient(
    100deg,
    var(--gray) 0%,
    var(--gray) 35%,
    rgba(255, 255, 255, 0.55) 50%,
    var(--gray) 65%,
    var(--gray) 100%
  );
  background-size: 220% 100%;
  animation: mc-thumb-shimmer 1.6s linear infinite;
}
@keyframes mc-thumb-shimmer {
  0%   { background-position: 220% 0; }
  100% { background-position: -120% 0; }
}
.mc-thumb img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
  opacity: 1;
  position: relative;  /* sit ABOVE the shimmer background once loaded */
}
/* Backward-compat: an older deploy kept thumbs hidden via opacity:0 until JS
   added .img-ready. The current JS still adds that class on load, but the
   new CSS shows the image immediately — this rule is a no-op now, kept so
   any stale cached CSS reaching new JS still resolves to opacity:1. */
.mc-thumb.img-ready img { opacity: 1; }
.mc-thumb-placeholder {
  width: 100%; height: 100%;
  background: var(--gray);
}
.mc-body {
  min-width: 0;
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 2px;
  padding: 4px 4px 4px 0;
}
.mc-row1 {
  display: flex;
  align-items: baseline;
  gap: 8px;
}
.mc-price {
  font-family: var(--font-serif);
  font-size: 20px;
  font-weight: 400;
  color: var(--fg-strong);
  letter-spacing: -0.4px;
  font-feature-settings: 'lnum' 1, 'tnum' 1;
}
.mc-ppsf {
  font-size: 12px;
  color: var(--muted);
}
.mc-addr {
  font-size: 14px;
  font-weight: 500;
  color: var(--fg-strong);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.mc-loc {
  font-size: 13px;
  color: var(--fg);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.mc-specs {
  font-size: 13px;
  color: var(--muted);
  margin-top: 4px;
}
.mc-builder {
  font-size: 12px;
  color: var(--muted);
  margin-top: 2px;
  font-style: italic;
}
.mc-updated {
  font-size: 11px;
  color: var(--muted);
  margin-top: 2px;
}
.mc-empty {
  padding: 40px 24px;
  text-align: center;
  color: var(--muted);
  font-size: 14px;
}
/* ─── RightCol map placeholder + chrome overlays ───────────────────── */
.map-placeholder {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  background:
    linear-gradient(135deg, #eef1f4 0%, #e6eaef 100%);
  color: var(--muted);
  font-size: 14px;
}
/* Mapbox overrides position to `relative`, so absolute+inset:0 doesn't size
   the container. Use width/height: 100% on a positioned ancestor instead. */
.map-canvas {
  position: absolute;
  top: 0; left: 0;
  width: 100%;
  height: 100%;
}
.map-rightcol .mapboxgl-ctrl-attrib {
  font-size: 11px;
  color: var(--muted);
}
/* User-requested: hide the Mapbox watermark. NOTE: Mapbox's free-tier ToS
   requires the logo to remain visible; removing it requires a paid license.
   Remove this rule (or upgrade the Mapbox plan) before going live. */
.map-rightcol .mapboxgl-ctrl-logo { display: none !important; }
/* Hide the (i) attribution-info button in the bottom-right of the map.
   Same Mapbox-ToS caveat as the logo above — restore before going live
   on the free tier. */
.map-rightcol .mapboxgl-ctrl-attrib-button { display: none !important; }
.map-rightcol .mapboxgl-ctrl-attrib { display: none !important; }

/* Toasts for ActionBar feedback (Save view, Alert me, Search area). */
.map-toast {
  position: fixed;
  bottom: 24px;
  left: 50%;
  transform: translateX(-50%) translateY(10px);
  background: var(--fg-strong);
  color: #fff;
  font-size: 13px;
  font-weight: 500;
  padding: 10px 18px;
  border-radius: var(--radius-pill);
  box-shadow: var(--shadow-md);
  opacity: 0;
  transition: opacity .2s, transform .2s;
  z-index: 100;
  pointer-events: none;
}
.map-toast.is-on { opacity: 1; transform: translateX(-50%) translateY(0); }
.map-toast-err { background: var(--red); }

/* Builder legend (Phase 10). Sits bottom-left over the map, replaces the
   dead ActionBar slot. Collapsed by default. */
.map-builder-legend {
  position: absolute;
  bottom: 16px;
  left: 16px;
  /* Cap with BOTH the column-relative budget and a hard viewport-relative
     ceiling. The 100% calc keeps the legend short on small viewports; the
     viewport calc + fixed 480px ceiling prevent the panel from blanketing
     the map on tall windows AND act as a safety net in case the parent's
     height fails to resolve (a long-standing class of bugs with absolute
     children of grid cells). 76 = style-switcher top:16 + height:32 +
     gap:12 + bottom margin:16. */
  max-height: min(calc(100% - 76px), calc(100vh - 200px), 480px);
  display: flex;
  flex-direction: column;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: var(--shadow-md);
  padding: 10px;
  z-index: 5;
  min-width: 240px;
  max-width: 280px;
  font-size: 13px;
}
/* Collapsed pill — same fully-rounded look at EVERY breakpoint. Previously
   only tablet (max-width:820px) got the pill radius; the desktop collapsed
   state inherited the 12px panel radius, which read as inconsistent next
   to the smaller-breakpoint pill. */
.map-builder-legend.is-collapsed { border-radius: var(--radius-pill); }
/* Builder legend = edit panel inline. One component, always-visible search
   + checkbox list + bulk-action row. No more compact-vs-expanded toggle. */
.mbl-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 2px 2px 10px;
  gap: 8px;
}
.mbl-title { font-size: 14px; font-weight: 600; color: var(--fg-strong); display: inline-flex; gap: 6px; align-items: baseline; line-height: 1.2; }
.mbl-count { color: var(--muted); font-weight: 400; font-size: 13px; }
.mbl-header-actions {
  display: inline-flex;
  align-items: center;
  gap: 4px;
}
.mbl-reset {
  border: none;
  background: transparent;
  color: var(--muted);
  font-size: 12px;
  font-weight: 500;
  padding: 2px 4px;
  cursor: pointer;
  transition: color .12s;
}
.mbl-reset:hover { color: var(--navy); }

/* Chevron toggle — Airbnb-style collapse affordance. Always visible in the
   header; CSS rotates the icon based on .is-collapsed state. */
.mbl-toggle {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 24px;
  height: 24px;
  padding: 0;
  border: none;
  background: transparent;
  color: var(--muted);
  border-radius: 50%;
  cursor: pointer;
  transition: background .12s, color .12s;
}
.mbl-toggle:hover { background: var(--hover); color: var(--fg-strong); }
.mbl-toggle svg { transition: transform .15s ease; }
.map-builder-legend.is-collapsed .mbl-toggle svg { transform: rotate(-180deg); }

/* Desktop collapsed state — header pill at bottom-left, list hidden.
   Mobile/tablet have additional overrides inside their @media blocks. */
.map-builder-legend.is-collapsed {
  max-height: none;
  padding: 6px 10px 6px 14px;
}
.map-builder-legend.is-collapsed .mbl-search,
.map-builder-legend.is-collapsed .mbl-list,
.map-builder-legend.is-collapsed .mbl-foot {
  display: none;
}
.map-builder-legend.is-collapsed .mbl-header {
  padding: 0;
}
.map-builder-legend.is-collapsed .mbl-reset { display: none; }

.mbl-search {
  width: 100%;
  border: 1px solid transparent;
  background: var(--gray);
  border-radius: var(--radius-pill);
  padding: 7px 12px;
  font-size: 13px;
  font-family: var(--font);
  color: var(--fg);
  outline: none;
  margin-bottom: 8px;
  box-sizing: border-box;
  transition: background .15s, border-color .15s, box-shadow .15s;
}
.mbl-search::placeholder { color: var(--muted); }
.mbl-search:focus {
  background: var(--bg);
  border-color: var(--navy);
  box-shadow: 0 0 0 3px rgba(0, 58, 112, .12);
}
.mbl-search::-webkit-search-cancel-button { display: none; }

.mbl-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 0;
  /* flex:1+min-height:0 lets the list shrink first when the parent legend
     hits its own max-height. Header + search + footer ALWAYS stay visible.
     Removed the hard max-height: 320px — it was forcing the list to grow
     to 320px even when the legend container was clamped to ~280px,
     causing the footer to overflow. flex:1 with min-height:0 lets the
     list naturally fill whatever space remains. */
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  /* Soft fade-out at the top and bottom edges so the scroll cutoff reads
     as intentional and the user understands there's more content above/
     below. Without this, rows abruptly clip against the footer and the
     list felt "broken" rather than scrollable. */
  mask-image: linear-gradient(to bottom, transparent 0, #000 12px, #000 calc(100% - 12px), transparent 100%);
  -webkit-mask-image: linear-gradient(to bottom, transparent 0, #000 12px, #000 calc(100% - 12px), transparent 100%);
}
/* Thin, subtle scrollbar — Airbnb-style. WebKit (Chrome/Safari) only;
   Firefox falls back to scrollbar-width: thin (already supported). */
.mbl-list { scrollbar-width: thin; scrollbar-color: var(--border) transparent; }
.mbl-list::-webkit-scrollbar { width: 6px; }
.mbl-list::-webkit-scrollbar-track { background: transparent; }
.mbl-list::-webkit-scrollbar-thumb {
  background: var(--border);
  border-radius: 3px;
}
.mbl-list::-webkit-scrollbar-thumb:hover { background: var(--muted); }
.mbl-row {
  display: flex;
  align-items: center;
  gap: 10px;
  /* Extra right padding (14 vs 4) so the per-row count text doesn't sit
     UNDER the 6px scrollbar that the list reserves on the right edge.
     Without this, "203" / "467" etc. visually collide with the thumb. */
  padding: 6px 14px 6px 4px;
  border-radius: 6px;
  position: relative;
  background: transparent;
  transition: background .12s, opacity .12s;
}
.mbl-row:hover { background: var(--hover); }
.mbl-row.is-muted { opacity: 0.45; }
.mbl-row.is-muted:hover { opacity: 0.75; }
.mbl-empty {
  padding: 16px 8px;
  color: var(--muted);
  font-size: 12px;
  text-align: center;
}

/* Checkbox toggle — native + minimal */
.mbl-check {
  width: 14px;
  height: 14px;
  flex-shrink: 0;
  cursor: pointer;
  accent-color: var(--navy);
  margin: 0;
}

/* Swatch — 10px circle, softer than the old rounded-square */
button.mbl-swatch,
.mbl-swatch {
  width: 10px;
  height: 10px;
  border-radius: 50%;
  flex-shrink: 0;
  border: none;
  padding: 0;
  cursor: pointer;
  box-shadow: 0 0 0 1px rgba(0,0,0,0.06);
}
button.mbl-swatch:hover { transform: scale(1.25); transition: transform .12s; }

.mbl-name {
  flex: 1;
  color: var(--fg);
  font-size: 13px;
  font-weight: 400;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.mbl-n {
  color: var(--muted);
  font-size: 12px;
  font-variant-numeric: tabular-nums;
}

/* Bulk-action footer */
.mbl-foot {
  display: flex;
  gap: 6px;
  padding: 10px 0 2px;
  margin-top: 6px;
  border-top: 1px solid var(--border-soft);
}
.mbl-action {
  flex: 1;
  background: var(--gray);
  border: 1px solid transparent;
  color: var(--fg);
  font-size: 12px;
  font-weight: 500;
  font-family: var(--font);
  padding: 7px 10px;
  border-radius: var(--radius-pill);
  cursor: pointer;
  transition: background .12s, color .12s;
  line-height: 1.2;
}
.mbl-action:hover { background: var(--navy); color: #fff; }
.mbl-overflow {
  color: var(--muted);
  font-size: 12px;
  padding-left: 22px;
  font-style: italic;
}

/* Focus-star — hidden by default, revealed on row hover or when focused. */
.mbl-star {
  width: 14px;
  height: 14px;
  padding: 0;
  border: none;
  background: transparent;
  color: var(--muted);
  font-size: 13px;
  line-height: 1;
  cursor: pointer;
  opacity: 0;
  transition: opacity .12s, color .12s;
  flex-shrink: 0;
}
.mbl-row:hover .mbl-star,
.mbl-row.is-focused .mbl-star { opacity: 1; }
.mbl-row.is-focused .mbl-star { color: #f59e0b; }
.mbl-star:hover { color: #f59e0b; }

/* Color picker popover — anchored inside the row. */
.mbl-picker {
  position: absolute;
  /* Float to the RIGHT of the legend so it doesn't occlude builder rows */
  top: 0;
  left: calc(100% + 8px);
  margin-top: 0;
  z-index: 12;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: var(--shadow-md);
  padding: 8px;
  width: 168px;
}
.mbl-picker-title {
  font-size: 11px;
  font-weight: 600;
  color: var(--muted);
  text-transform: uppercase;
  letter-spacing: 0.05em;
  margin-bottom: 6px;
}
.mbl-picker-grid {
  display: grid;
  grid-template-columns: repeat(6, 1fr);
  gap: 4px;
}
.mbl-picker-swatch {
  width: 22px;
  height: 22px;
  border-radius: 4px;
  padding: 0;
  border: 1px solid rgba(0,0,0,0.08);
  cursor: pointer;
  transition: transform .12s;
}
.mbl-picker-swatch:hover { transform: scale(1.12); }
.mbl-picker-default {
  margin-top: 6px;
  width: 100%;
  padding: 4px 6px;
  font-size: 11px;
  background: transparent;
  border: 1px solid var(--border);
  border-radius: 4px;
  color: var(--muted);
  cursor: pointer;
}
.mbl-picker-default:hover { color: var(--fg); background: var(--hover); }

/* Optional secondary footer row — "Edit focused builders…" link + Reset.
   Uses .mbl-foot-row (not .mbl-foot) so it doesn't collide with the
   bulk-action footer above. */
.mbl-foot-row {
  display: flex;
  gap: 8px;
  align-items: center;
  justify-content: space-between;
  padding: 6px 4px 2px;
  border-top: 1px solid var(--border-soft);
  margin-top: 6px;
}
.mbl-edit {
  background: transparent;
  border: none;
  color: var(--navy);
  font-size: 12px;
  font-weight: 500;
  cursor: pointer;
  padding: 2px 4px;
  text-align: left;
}
.mbl-edit:hover { text-decoration: underline; }

/* "Edit focused builders…" panel — anchored to the legend, opens upward
   so it doesn't shoot off-screen at the bottom-left of the map. Fixed
   max height; scrollable list inside. */
.mbl-edit-panel {
  position: absolute;
  bottom: calc(100% + 8px);
  left: 0;
  width: 280px;
  max-height: calc(100vh - 120px);
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: var(--shadow-md);
  padding: 12px;
  z-index: 20;
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.mbl-edit-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
}
.mbl-edit-title {
  font-size: 13px;
  font-weight: 600;
  color: var(--fg-strong);
}
.mbl-edit-close {
  width: 22px;
  height: 22px;
  border-radius: 50%;
  border: none;
  background: transparent;
  color: var(--muted);
  font-size: 18px;
  line-height: 1;
  cursor: pointer;
}
.mbl-edit-close:hover { background: var(--hover); color: var(--fg-strong); }
.mbl-edit-sub {
  font-size: 11px;
  color: var(--muted);
  line-height: 1.4;
}
.mbl-edit-search {
  width: 100%;
  padding: 6px 10px;
  font-size: 12px;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--bg);
  color: var(--fg);
  outline: none;
  transition: border-color .15s, box-shadow .15s;
}
.mbl-edit-search:focus {
  border-color: var(--navy);
  box-shadow: 0 0 0 3px rgba(0, 58, 112, .12);
}
.mbl-edit-list {
  list-style: none;
  margin: 0;
  padding: 0;
  overflow-y: auto;
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: column;
}
.mbl-edit-row {
  padding: 0;
}
.mbl-edit-row + .mbl-edit-row { border-top: 1px solid var(--border-soft); }
.mbl-edit-label {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 6px 6px;
  cursor: pointer;
  font-size: 12px;
}
.mbl-edit-label:hover { background: var(--hover); }
.mbl-edit-check {
  width: 14px;
  height: 14px;
  accent-color: var(--navy);
  cursor: pointer;
  flex-shrink: 0;
}
.mbl-edit-swatch {
  width: 12px;
  height: 12px;
  border-radius: 3px;
  border: 1px solid rgba(0,0,0,0.06);
  flex-shrink: 0;
}
.mbl-edit-name {
  flex: 1;
  color: var(--fg);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.mbl-edit-n {
  flex-shrink: 0;
  color: var(--muted);
  font-size: 11px;
  font-variant-numeric: tabular-nums;
}
.mbl-edit-empty {
  padding: 16px 6px;
  text-align: center;
  color: var(--muted);
  font-size: 12px;
  font-style: italic;
}
.mbl-edit-foot,
.mbl-edit-actions {
  display: flex;
  gap: 4px;
  justify-content: space-between;
  padding-top: 4px;
  border-top: 1px solid var(--border-soft);
  flex-shrink: 0;
}
.mbl-edit-action {
  flex: 1;
  background: transparent;
  border: 1px solid var(--border);
  color: var(--fg);
  font-size: 11px;
  font-weight: 500;
  padding: 5px 6px;
  border-radius: var(--radius-sm);
  cursor: pointer;
  transition: background .12s, color .12s;
}
.mbl-edit-action:hover { background: var(--hover); color: var(--fg-strong); }

/* ─── Floating bar (Phase 14, post-rework) ─────────────────────────────
   Single shared spec for every floating control bar on top of the map
   (style switcher + inline gear, layer toggle, etc.).
   Anchor, position is set per-instance; HEIGHT + PADDING + RADIUS + SHADOW
   are unified so the bars read as one toolbar family. */
.map-style-switcher {
  position: absolute;
  top: 16px;
  left: 16px;
  height: 32px;
  display: inline-flex;
  align-items: center;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: var(--radius-pill);
  padding: 0 4px;
  box-shadow: var(--shadow-sm);
  /* Above the builder legend (z-index:5) so the settings popover, which is a
     child of this element, renders above the legend when both are open. Two
     absolute siblings at the same z-index stack by DOM order — the legend
     mounts last and was winning. */
  z-index: 20;
}
.mss-btn {
  height: 26px;
  border: none;
  background: transparent;
  padding: 0 12px;
  font-size: 12px;
  font-weight: 500;
  color: var(--fg);
  border-radius: var(--radius-pill);
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  transition: background .15s, color .15s;
}
.mss-btn:hover:not(.is-active) { background: var(--hover); color: var(--fg-strong); }
.mss-btn.is-active { background: var(--navy); color: #fff; }

/* Preview popover (Phase 13). Mapbox Popup with custom skin — Airbnb-style. */
.map-popover .mapboxgl-popup-content {
  padding: 0;
  border-radius: var(--radius);
  box-shadow: var(--shadow-md);
  background: var(--bg);
  overflow: hidden;
}
.map-popover .mapboxgl-popup-tip { border-top-color: var(--bg); border-bottom-color: var(--bg); }
.map-pop {
  width: 280px;
  position: relative;
}
.mp-close {
  position: absolute;
  top: 8px;
  right: 8px;
  width: 24px;
  height: 24px;
  border-radius: 50%;
  border: none;
  background: rgba(255,255,255,0.94);
  color: var(--fg-strong);
  font-size: 16px;
  line-height: 22px;
  cursor: pointer;
  z-index: 2;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  box-shadow: var(--shadow-sm);
}
.mp-close:hover { background: var(--bg); }
.mp-close,
.mp-close:focus,
.mp-close:focus-visible,
.mp-close:active {
  outline: none !important;
  outline-offset: 0 !important;
  -webkit-tap-highlight-color: transparent;
}
.mp-thumb {
  width: 100%;
  height: 160px;
  background: var(--gray);
  overflow: hidden;
}
.mp-thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
.mp-thumb-placeholder { width: 100%; height: 100%; background: var(--gray); }
.mp-body {
  padding: 12px 14px 14px;
}
.mp-price {
  font-size: 20px;
  font-weight: 600;
  color: var(--fg-strong);
  line-height: 1.2;
}
.mp-addr {
  font-size: 14px;
  font-weight: 500;
  color: var(--fg-strong);
  margin-top: 4px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.mp-loc {
  font-size: 12px;
  color: var(--muted);
  margin-top: 2px;
}
.mp-specs {
  font-size: 12px;
  color: var(--muted);
  margin-top: 6px;
}
.mp-builder {
  font-size: 12px;
  color: var(--muted);
  font-style: italic;
  margin-top: 2px;
}
.mp-updated {
  font-size: 11px;
  color: var(--muted);
  margin-top: 6px;
}
.mp-view {
  display: inline-block;
  margin-top: 12px;
  padding: 8px 14px;
  background: var(--navy);
  color: #fff;
  border-radius: var(--radius-pill);
  font-size: 13px;
  font-weight: 500;
  text-decoration: none;
  transition: background .15s;
}
.mp-view:hover { background: #002a55; color: #fff; text-decoration: none; }

.map-action-bar {
  position: absolute;
  bottom: 16px;
  left: 16px;
  display: inline-flex;
  align-items: center;
  gap: 12px;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: var(--radius-pill);
  padding: 7px 14px;
  font-size: 13px;
  box-shadow: var(--shadow-sm);
  z-index: 5;
}
.mab-static { color: var(--fg); font-weight: 500; }
.mab-link {
  border: none;
  background: transparent;
  color: var(--fg);
  font-size: 13px;
  font-weight: 500;
  padding: 0;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  gap: 4px;
}
.mab-link:hover { color: var(--navy); }
.mab-sep {
  width: 1px;
  height: 14px;
  background: var(--border);
  display: inline-block;
}

/* ─── Responsive shrink ────────────────────────────────────────────── */
@media (max-width: 1100px) {
  .map-split { grid-template-columns: 460px 1fr; }
  .topbar, .map-filterbar { padding: 0 18px; }
}

/* ─── Phase 12 — smarter multi-select & filter summary chip ──────────── */

/* Header row above a categorical popover list:
   `[n selected]                [Select all visible] [Clear]` */
.fb-cat-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 10px 12px 8px;
  border-bottom: 1px solid var(--border-soft);
  font-size: 13px;
}
.fb-cat-head-count {
  color: var(--muted);
  font-weight: 500;
}
.fb-cat-head-actions {
  display: inline-flex;
  gap: 12px;
}
.fb-cat-head-link {
  background: none;
  border: none;
  padding: 0;
  font-size: 13px;
  color: var(--navy);
  cursor: pointer;
  font-weight: 500;
}
.fb-cat-head-link:hover { text-decoration: underline; }

/* Trailing `(n)` count on each row in the categorical popover */
.fb-row-count {
  flex-shrink: 0;
  color: var(--muted);
  font-size: 12px;
  font-variant-numeric: tabular-nums;
  margin-left: auto;
  padding-left: 8px;
}

/* Zero-count rows sink to the bottom and grey out — still clickable so the
   user can deliberately broaden their selection. */
.fb-item.is-empty {
  opacity: 0.4;
}
.fb-item.is-empty:hover {
  opacity: 0.7;
}

/* ─── Phase 13 polish — hover / selected card states + popover anim ──── */

/* Hover sync: matching card lights up when the user is hovering its pin. */
.mc-card.is-hovered {
  background: var(--hover);
  border-color: #b8b8b8;
}

/* The card itself being hovered (mouse-over) — subtle ring to let the user
   know which card their mouse is on, even though deck.js can't yet amplify
   the matching pin. */
.mc-card.is-card-hovering {
  border-color: #b8b8b8;
  box-shadow: var(--shadow-sm);
}

/* Selected — locked-in ring after the user clicked the pin or the card. */
.mc-card.is-selected {
  border: 2px solid var(--navy);
  /* Compensate for the +1px border so the layout doesn't jiggle. */
  padding: 7px;
  transform: scale(1.01);
  box-shadow: var(--shadow-md);
}
.mc-card.is-selected:hover {
  border-color: var(--navy);
}

/* Popover open / close animation. Open uses a CSS keyframe animation that
   auto-plays the moment the element is inserted — no rAF dance, no
   .is-mounting class to remove, no risk of getting stuck at opacity 0 when
   the tab is backgrounded or DevTools is open. Close still uses the
   .is-closing class (the JS removes the element 130 ms later) so we can
   drop pointer-events synchronously and free the map area during the fade. */
@keyframes mapPopoverIn {
  from { opacity: 0; transform: scale(.96); }
  to   { opacity: 1; transform: scale(1); }
}
@keyframes mapPopoverTipIn {
  from { opacity: 0; }
  to   { opacity: 1; }
}
.map-popover.mapboxgl-popup .mapboxgl-popup-content {
  animation: mapPopoverIn .18s ease-out both;
  transition: opacity .12s ease-in, transform .12s ease-in;
}
.map-popover.mapboxgl-popup .mapboxgl-popup-tip {
  animation: mapPopoverTipIn .18s ease-out both;
}
.map-popover.is-closing .mapboxgl-popup-content {
  animation: none;            /* let transition take over for the fade-out */
  opacity: 0;
  transform: scale(.96);
}
.map-popover.is-closing .mapboxgl-popup-tip {
  animation: none;
  opacity: 0;
}
/* During the fade-out, the element is still in the DOM but invisible.
   Without this, the user can't pan/zoom over the spot where the popover
   used to be — the transparent .mapboxgl-popup-content swallows pointer
   events for the full 130 ms transition. */
.map-popover.is-closing,
.map-popover.is-closing .mapboxgl-popup-content {
  pointer-events: none;
}

/* Cluster pin — minimalist white circle. Single source of truth for "N
   homes in this area". No per-builder color (that lives on the individual
   home dot at zoom ≥ 11), no downward tail, no dot inside the circle. */
.mp-cluster {
  /* Sizing — variants applied via classlist by deck.js based on count.
     32px (≤ 10 homes), 40px (≤ 100), 52px (> 100). */
  width: 40px;
  height: 40px;
  border-radius: 50%;
  background: var(--bg);
  border: 1px solid var(--border);
  box-shadow: var(--shadow-sm);
  font-family: var(--font);
  font-weight: 600;
  font-size: 13px;
  color: var(--navy);
  font-variant-numeric: tabular-nums;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  transition: transform .14s ease, box-shadow .14s ease;
  user-select: none;
  margin-bottom: 0; /* anchor 'bottom' from Mapbox — no tail offset needed */
}
.mp-cluster.is-sm { width: 32px; height: 32px; font-size: 12px; }
.mp-cluster.is-md { width: 40px; height: 40px; font-size: 13px; }
.mp-cluster.is-lg { width: 52px; height: 52px; font-size: 14px; }
.mp-cluster:hover {
  transform: scale(1.06);
  box-shadow: var(--shadow-md);
  z-index: 2;
}
.mp-cluster-count {
  line-height: 1;
}

/* ─── Builder legend — eye toggle (replaces the old star) ───────────────
   The eye is anchored to the right of each row, low-opacity at rest, and
   brightens on row-hover. Click = un-focus that builder (it falls into
   the Other pin bucket on the map). Same prefs.focusBuilders persistence
   as the old star button. */
.mbl-eye {
  width: 22px;
  height: 22px;
  padding: 0;
  border: none;
  background: transparent;
  color: var(--muted);
  cursor: pointer;
  opacity: 0;
  transition: opacity .12s ease, color .12s ease, background .12s ease;
  flex-shrink: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: var(--radius-sm);
  margin-left: 2px;
}
.mbl-row:hover .mbl-eye {
  opacity: 0.7;
}
.mbl-eye:hover {
  opacity: 1;
  color: var(--fg-strong);
  background: var(--hover);
}
.mbl-eye:focus-visible {
  opacity: 1;
  outline: 2px solid var(--navy);
  outline-offset: 1px;
}

/* ─── Phase 16 — loader inside .map-rightcol ────────────────────────────
   loader.css ships #loading as a full-viewport block; on /map the loader
   lives inside .map-rightcol so it shouldn't fill the whole screen. */
.map-rightcol .loading {
  height: 100%;
  background: transparent;
}
.map-rightcol .loading .house-loader {
  width: 96px;
  height: 96px;
}

/* ─── Phase 16 — skeleton cards for leftcol pre-data render ─────────────
   Reuses .mc-card's 148px / 1fr grid. The shimmer overlay matches the
   card-img-shimmer keyframe in card.css. */
.mc-card-skeleton {
  display: grid;
  grid-template-columns: 148px 1fr;
  gap: 14px;
  padding: 8px;
  background: var(--bg);
  border: 1px solid var(--border-soft);
  border-radius: var(--radius);
}
.mc-skel-thumb {
  position: relative;
  width: 148px;
  height: 128px;
  background: var(--gray);
  border-radius: var(--radius-sm);
  overflow: hidden;
}
.mc-skel-thumb::after {
  content: '';
  position: absolute;
  inset: 0;
  background: linear-gradient(90deg, transparent 0%, rgba(255,255,255,.55) 50%, transparent 100%);
  background-size: 200% 100%;
  animation: card-img-shimmer 1.6s linear infinite;
  pointer-events: none;
}
.mc-skel-body {
  min-width: 0;
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 6px;
  padding: 4px 4px 4px 0;
}
.mc-skel-line {
  position: relative;
  height: 12px;
  border-radius: 4px;
  background: var(--gray);
  overflow: hidden;
}
.mc-skel-line::after {
  content: '';
  position: absolute;
  inset: 0;
  background: linear-gradient(90deg, transparent 0%, rgba(255,255,255,.55) 50%, transparent 100%);
  background-size: 200% 100%;
  animation: card-img-shimmer 1.6s linear infinite;
  pointer-events: none;
}
.mc-skel-price { width: 30%; height: 18px; margin-bottom: 6px; }
.mc-skel-addr  { width: 70%; }
.mc-skel-loc   { width: 55%; }
.mc-skel-specs { width: 45%; }

/* Builder legend skeleton — shown while loadRecords() is in flight so the
   right side of the map doesn't flash a "0 builders" empty list. Matches
   the sidebar shimmer so both sides read as one coherent loading frame. */
.mbl-row-skel {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 8px 4px;
  pointer-events: none;
}
.mbl-skel-check,
.mbl-skel-swatch,
.mbl-skel-name,
.mbl-skel-n {
  position: relative;
  overflow: hidden;
  border-radius: 4px;
  background: var(--gray);
}
.mbl-skel-check { width: 18px; height: 18px; border-radius: 4px; flex-shrink: 0; }
.mbl-skel-swatch { width: 12px; height: 12px; border-radius: 50%; flex-shrink: 0; }
.mbl-skel-name { flex: 1; height: 12px; max-width: 140px; }
.mbl-skel-n { width: 28px; height: 12px; margin-left: auto; flex-shrink: 0; }
.mbl-skel-check::after,
.mbl-skel-swatch::after,
.mbl-skel-name::after,
.mbl-skel-n::after {
  content: '';
  position: absolute;
  inset: 0;
  background: linear-gradient(90deg, transparent 0%, rgba(255,255,255,.55) 50%, transparent 100%);
  background-size: 200% 100%;
  animation: card-img-shimmer 1.6s linear infinite;
  pointer-events: none;
}

/* ─── Phase 16 — minimalist legend edit button (pencil icon) ────────── */
.mbl-edit-btn {
  width: 24px;
  height: 24px;
  border-radius: var(--radius-sm);
  border: none;
  background: transparent;
  color: var(--muted);
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  transition: background .12s, color .12s;
  padding: 0;
}
.mbl-edit-btn:hover {
  color: var(--navy);
  background: var(--hover);
}

/* ─── Phase 16 — mobile audit @media rules ──────────────────────────── */
@media (max-width: 820px) {
  /* Let the filterbar height GROW when its row wraps below 820px. The
     fixed 64px row caused the Filter/Sort chips to bleed DOWN into the
     map column where the style switcher (z-index 20) covered them. */
  .map-app { grid-template-rows: 64px auto 1fr; }
  .map-filterbar {
    flex-wrap: wrap;
    padding: 8px 16px;
    height: auto;
    min-height: 64px;
  }
  .map-search-wrap { width: 100%; }
  .map-fb-row { width: 100%; }

  /* Floating chrome */
  .map-style-switcher { top: 12px; left: 12px; }

  /* Tablet: the JS agent collapses the legend by default at ≤820px (see
     overlays.js COLLAPSE_BREAKPOINT). Tap-anywhere-to-expand on the pill
     is still wired in JS; the chevron toggle works the same on every
     viewport size. Tablet-specific pill geometry hugs the bottom-left
     edge so it doesn't blanket the map. */
  .map-builder-legend.is-collapsed {
    top: auto;
    bottom: 12px;
    left: 12px;
    right: auto;
    width: auto;
    border-radius: var(--radius-pill);
    cursor: pointer;
  }
  .map-builder-legend.is-collapsed .mbl-title {
    font-size: 13px;
  }

  /* Tablet expanded sheet — anchor with BOTH top AND bottom so the panel
     can never overflow the map column. The map-rightcol's overflow:hidden
     was clipping the legend's top edge under the style switcher when the
     sheet was sized via max-height (50vh > available column height at
     short viewports). top:60 clears the style switcher (top:16 + h:32 +
     gap:12); bottom:12 hugs the column bottom; the inner .mbl-list flexes
     and scrolls. */
  .map-builder-legend:not(.is-collapsed) {
    top: 60px;
    bottom: 12px;
    left: 12px;
    right: 12px;
    width: auto;
    max-height: none;
  }

  /* When a filter popover is open on narrow widths, dim the legend so the
     two surfaces don't fight for attention in the same screen quadrant. */
  body:has(.fb-popover:not([hidden])) .map-builder-legend {
    opacity: 0.25;
    pointer-events: none;
    transition: opacity .15s;
  }
}

/* Tablet portrait (iPad ~768) — side-by-side, NARROW LeftCol.
   Stacked layout was unusable (only 1 card visible below 50vh map).
   At 601-820 we keep the side-by-side grid with a compact LeftCol so the
   user sees the map + the card list at the same time. Card thumbnails
   shrink to keep body text room. */
@media (min-width: 601px) and (max-width: 820px) {
  .map-split { grid-template-columns: 320px 1fr; }
  /* Compact cards — 100x88 thumb gives the body ~200px of text room
     inside a 320px column (after gap+padding). */
  .mc-card { grid-template-columns: 100px 1fr; gap: 10px; padding: 6px; }
  .mc-thumb { width: 100px; height: 88px; }
  .mc-card-skeleton { grid-template-columns: 100px 1fr; gap: 10px; }
  .mc-skel-thumb { width: 100px; height: 88px; }
  .map-card-list { padding: 10px 12px; gap: 10px; }
}

/* Small laptop / tablet landscape — side-by-side, MEDIUM LeftCol.
   Between 821-1100 we want cards visible but with more map. LeftCol
   tightens to 420px (vs desktop's 560) and the legend shrinks to 200-220
   to stop occluding cluster pins. */
@media (min-width: 821px) and (max-width: 1100px) {
  .map-split { grid-template-columns: 420px 1fr; }
  .map-builder-legend { max-width: 220px; min-width: 200px; }
  /* Slightly compacter cards so 420px reads cleanly */
  .mc-card { grid-template-columns: 124px 1fr; gap: 12px; }
  .mc-thumb { width: 124px; height: 108px; }
  .mc-card-skeleton { grid-template-columns: 124px 1fr; gap: 12px; }
  .mc-skel-thumb { width: 124px; height: 108px; }
}

@media (max-width: 600px) {
  /* Topbar shrinks like layout.css's mobile rules */
  .topbar { height: 60px; padding: 0 14px; gap: 8px; }
  .logo { height: 22px; }
  .topbar-right { gap: 6px; }
  .btn-sm { font-size: 13px; padding: 8px 14px; }
  .map-app { grid-template-rows: 60px auto 1fr; }

  /* Filterbar chips: wrap and shrink */
  .map-filterbar { padding: 8px 14px; gap: 8px; }
  .map-fb-row { gap: 6px; }

  /* Phone — HIDE the map entirely and give the full screen to the list.
     The map adds visual weight without much utility at <600px (cluster
     pins crowd, no room for the legend, no value the cards don't already
     deliver). Filters still apply through the LeftCol header. */
  .map-split {
    grid-template-columns: 1fr;
    grid-template-rows: 1fr;
  }
  .map-leftcol {
    border-right: none;
    border-top: none;
  }
  .map-rightcol { display: none; }

  /* Floating bars — bumped to 32px+ minimum touch targets (was 28/22, fails
     Apple HIG). Style switcher pill is now 40h with 30h inner buttons. */
  .map-style-switcher { top: 10px; left: 10px; height: 40px; padding: 0 6px; }
  .mss-btn { height: 30px; padding: 0 12px; font-size: 13px; }

  /* Mobile-collapsed pill rules live in @media (max-width: 820px). Phone
     overrides below tighten geometry: edges hug at 10px instead of 12px.
     Same top+bottom anchoring as tablet — never overflow the map column. */
  .map-builder-legend:not(.is-collapsed) {
    top: 58px;
    bottom: 10px;
    left: 10px;
    right: 10px;
    max-height: none;
  }

  /* Legend interior — keep prior typography/padding tweaks for the expanded
     sheet (font-size:12, padding:8). */
  .map-builder-legend {
    min-width: 0;
    max-width: none;
    font-size: 12px;
    padding: 8px;
  }
  .mbl-list { max-height: 180px; }
  .mbl-search { font-size: 13px; padding: 8px 12px; }

  /* Touch targets (Apple HIG 32px min) — checkbox + row height + reset btn */
  .mbl-check { width: 22px; height: 22px; }
  .mbl-row { padding: 8px 4px; min-height: 36px; }
  .mbl-swatch,
  button.mbl-swatch { width: 14px; height: 14px; }
  .mbl-reset { padding: 8px 10px; font-size: 13px; }

  /* Color picker — no horizontal room to float right on mobile, anchor
     inline below the row (the desktop default before this wave). */
  .mbl-picker { left: 22px; top: 100%; margin-top: 4px; }

  /* Edit-builders panel: pin to viewport edges so it doesn't blow past */
  .mbl-edit-panel {
    width: calc(100vw - 24px);
    max-width: 320px;
    left: 0;
  }

  /* Loader is 136x136 in loader.css — too big on a phone */
  #loading .house-loader { width: 96px; height: 96px; }
  #loading .house-loader .hl-block { width: 18px; height: 18px; }

  /* LeftCol card padding — tighter so more cards fit before scrolling. */
  .mc-card { gap: 8px; padding: 6px; grid-template-columns: 100px 1fr; }
  .mc-thumb { width: 100px; height: 88px; }
  .map-card-list { padding: 8px 10px; gap: 8px; }
  .mc-card-skeleton { grid-template-columns: 100px 1fr; gap: 8px; padding: 6px; }
  .mc-skel-thumb { width: 100px; height: 88px; }
}

/* Individual home pin (DOM marker for unclustered homes in sparse views) */
.mp-pin {
  width: 14px;
  height: 14px;
  border-radius: 50%;
  border: 2px solid #fff;
  box-shadow: 0 1px 4px rgba(0, 0, 0, .35);
  cursor: pointer;
}
/* Pins stacked on the exact same coordinate as an already-shadowed pin drop
   their shadow so overlapping markers don't compound into one dark blob. */
.mp-pin--stacked {
  box-shadow: none;
}
