/* Map page styles. Global (not scoped) because Leaflet renders markers/popups/clusters into the DOM
   outside any Blazor component's scope. */
/* Design tokens — "Warm clay". The base palette is the warm-charcoal dark theme and [data-theme="light"] is
   warm paper; both reskin every panel surface. <html> always carries one of the two — the "Auto" setting is
   resolved to a real palette in JS, never left on the element.
   Semantic colours (tag palette, star gold, sold red/amber, cluster purple) are intentionally NOT
   tokenised — they mean the same thing in every theme. Set on <html> so body-level popovers inherit too.
   Surface tokens (--text-2, --card*, --chip*, --feat*, --sel, --thumb) cover the nested surfaces (source
   cards, tag chips, list selection, image placeholders). --feat-* stays green — "amenity" reads green in
   every theme. Accents are WCAG-AA checked against their surfaces. */
:root {
  --bg: #1c1815; --panel: #2a2420; --divider: #342d27; --border: #3d352d; --border-2: #4a4038;
  --hover: #262019; --text: #ece5da; --text-2: #d8cec0; --muted: #a89a88; --accent: #cf7143;
  --card: #241f1b; --card-hover: #2f2822; --sel: #3a2b22; --thumb: #3a332c;
  --chip-bg: #33291f; --chip-fg: #ecd9c9; --chip-bd: #5a4636;
  --feat-bg: #30412c; --feat-fg: #d3e8c8; --feat-bd: #4f7043;
  --mono: ui-monospace, SFMono-Regular, Menlo, monospace;
}
:root[data-theme="light"] {
  --bg: #faf7f2; --panel: #f2ece2; --divider: #ece3d5; --border: #e3d9c8; --border-2: #d4c7b2;
  --hover: #f0e8db; --text: #2b2520; --text-2: #4a4038; --muted: #6b6152; --accent: #a95324;
  --card: #f5efe6; --card-hover: #ede4d6; --sel: #f3e2d3; --thumb: #e6dccb;
  --chip-bg: #f3e7d8; --chip-fg: #7a3f1f; --chip-bd: #dcc3a8;
  --feat-bg: #e6f0dd; --feat-fg: #3c5a2c; --feat-bd: #b8d0a4;
}

/* dvh, not vh: on mobile 100vh is the LARGE viewport (URL bar hidden), so with the bar VISIBLE the page is
   taller than the screen and the document scrolls. The map's overlay controls are anchored to #map, so that
   scroll dragged them up past the top edge while the position:fixed #mobileBar opposite them stayed put —
   the top controls drifted apart and clipped as the URL bar came and went. dvh tracks the viewport that is
   actually showing, so the page exactly fills it and never scrolls. (Same reasoning as #sheet's heights.) */
.fbmr-map-page { display: flex; height: 100vh; height: 100dvh; width: 100%; font-family: "Nunito", system-ui, sans-serif; }

/* Inline SVG icons (see IC in map.js + the static ones in Map.razor): sized to ride along 12–13px text. */
.ic { width: 14px; height: 14px; flex: 0 0 auto; vertical-align: -2px; }
.group label .ic { margin-right: 3px; }

/* Marks any control only an admin can see (the Source filter, the sheet's region editor, …) — same red
   everywhere, unthemed like the other semantic colours above, so an admin never mistakes one for something
   every visitor sees. */
.admin-tag { color: #dc2626; }

#sidebar {
  width: 260px; flex: 0 0 260px; background: var(--bg); color: var(--text);
  overflow-y: auto; box-shadow: 2px 0 8px rgba(0,0,0,.4); z-index: 1000;
}
/* Preset trigger + match count stay pinned while the filter groups scroll under them. The drop shadow only
   appears once there's content scrolled up underneath — a flat header when nothing overlaps it. */
#sidebarHead { position: sticky; top: 0; z-index: 30; background: var(--bg); }
#sidebarHead.scrolled { box-shadow: 0 3px 8px rgba(0,0,0,.28); }
#map { flex: 1; height: 100%; }

.group { padding: 12px 14px; border-bottom: 1px solid var(--divider); }
.group > b { display: block; margin-bottom: 8px; font-size: 13px; }
.group label { display: block; cursor: pointer; font-size: 13px; line-height: 1.9; }
.group label input { vertical-align: -1px; margin-right: 6px; }
/* Every checkbox option is name-left / count-right, so the counts line up as one column the eye can scan.
   Qualified with .group to outrank `.group label { display: block }` above. */
.group label.opt-row { display: flex; align-items: center; gap: 0; }
.group label.opt-row .opt-name { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.opt-count { flex: 0 0 auto; margin-left: 8px; font-size: 11px; font-variant-numeric: tabular-nums; }
/* An option that would show nothing right now stays clickable but recedes. */
.group label.opt-none { opacity: .5; }
/* My-rating options are plain .opt-rows like every other group; only the star glyphs are special — gold up to
   the rating, the muted surface above it, so the row reads as a rating and not as the word "three". */
.stars-opt { letter-spacing: 1px; color: var(--border-2); }
.stars-opt .on { color: #f5c518; }
.price-row { display: flex; gap: 8px; align-items: center; }
/* One shared style for every text/number field in the sidebar — the bare #periodFilter input used to
   render unstyled (native white), and the per-row rules had drifted to slightly different paddings. */
.group input[type="number"], .group input[type="text"] { width: 100%; box-sizing: border-box;
  background: var(--panel); color: var(--text);
  border: 1px solid var(--border); border-radius: 4px; padding: 6px 8px; font-size: 13px; }
.group input:focus, .group select:focus { outline: none; border-color: var(--accent); }
.group select { width: 100%; background: var(--panel); color: var(--text);
  border: 1px solid var(--border); border-radius: 4px; padding: 6px 8px; font-size: 13px; }
/* The "Other…" free day-count input drops in under the min-stay dropdown. */
#periodFilter:not([hidden]) { margin-top: 6px; }
#shownCount { padding: 12px 14px; font-size: 13px; color: var(--muted); }
#shownCount b { color: var(--text); }
#shownCount .count-sub { font-size: 12px; opacity: .85; }
/* display:block (not the default inline-block) so the button doesn't sit in an anonymous line box that adds
   phantom vertical space above it — it now hugs the filter group directly above. margin-top:10px is the
   only intended gap. */
#resetBtn { display: block; margin: 10px 14px 14px; padding: 7px 10px; width: calc(100% - 28px);
  background: var(--panel); color: var(--text); border: 1px solid var(--border);
  border-radius: 4px; cursor: pointer; font-size: 13px; }
#resetBtn:hover { border-color: var(--accent); }

/* "Show my location" overlay button + the resulting GPS marker. Sized/rounded exactly like the mobile
   filter + List buttons opposite it (height + padding, not a fixed width, so an 18px icon lands on the same
   38px box) — the two top corners read as one row of controls. */
.locate-btn {
  display: flex; align-items: center; justify-content: center;
  height: 34px; padding: 0 9px; border-radius: 8px;
  background: var(--panel); color: var(--text); border: 1px solid var(--border);
  font-size: 20px; line-height: 1; cursor: pointer; box-shadow: 0 1px 6px rgba(0,0,0,.5);
}
.locate-btn:hover { border-color: var(--accent); color: var(--accent); }
.locate-btn.locating { opacity: .6; cursor: progress; }
.my-location-dot {
  width: 14px; height: 14px; border-radius: 50%;
  background: var(--accent); border: 2px solid #fff; box-shadow: 0 0 0 2px rgba(52,152,219,.5);
}

/* "Probably here" probe: the pin (draggable) + its arming control button. */
.probe-pin { display: flex; justify-content: center; cursor: grab; filter: drop-shadow(0 1px 2px rgba(0,0,0,.6)); }
.probe-pin .ic { width: 30px; height: 30px; color: var(--accent); }
.probe-btn .ic, .locate-btn .ic { width: 18px; height: 18px; }
#searchBtn .ic { width: 18px; height: 18px; }
#sidebarToggle .ic { width: 18px; height: 18px; }

/* The filter funnel's own signals: a dot while any filter is away from its default, and — for a first-time
   anonymous visitor facing too many / too few pins — a pulsing ring plus a bubble pointing at it. */
#sidebarToggle { position: relative; }
#sidebarToggle.has-filters::after {
  content: ""; position: absolute; top: -3px; right: -3px; width: 10px; height: 10px; border-radius: 50%;
  background: var(--accent); border: 2px solid var(--bg);
}
#sidebarToggle.nudge { animation: filter-ring 1.6s ease-out infinite; border-color: var(--accent); }
@keyframes filter-ring {
  0% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--accent) 70%, transparent); }
  70%, 100% { box-shadow: 0 0 0 12px transparent; }
}
.filter-nudge {
  position: absolute; top: calc(100% + 10px); left: 0; white-space: nowrap; z-index: 1;
  padding: 7px 11px; border-radius: 8px; background: var(--accent); color: #fff;
  font-size: 13px; font-weight: 600; box-shadow: 0 2px 10px rgba(0,0,0,.4);
  animation: filter-nudge-in .25s ease-out;
}
.filter-nudge::before {
  content: ""; position: absolute; top: -5px; left: 14px; width: 10px; height: 10px;
  background: var(--accent); transform: rotate(45deg); border-radius: 2px;
}
@keyframes filter-nudge-in { from { opacity: 0; transform: translateY(-4px); } }
@media (prefers-reduced-motion: reduce) {
  #sidebarToggle.nudge { animation: none; box-shadow: 0 0 0 4px color-mix(in srgb, var(--accent) 45%, transparent); }
  .filter-nudge { animation: none; }
}
.probe-btn {
  display: flex; align-items: center; justify-content: center;
  width: 40px; height: 40px; border-radius: 4px; margin-top: 8px;
  background: var(--panel); color: var(--text); border: 1px solid var(--border);
  font-size: 18px; line-height: 1; cursor: pointer; box-shadow: 0 1px 6px rgba(0,0,0,.5);
}
.probe-btn:hover { border-color: var(--accent); }
.probe-btn.armed { border-color: #e0a33a; box-shadow: 0 0 0 2px rgba(224,163,58,.5); }
.probe-btn.active { border-color: var(--accent); box-shadow: 0 0 0 2px rgba(52,152,219,.5); }
#map.probe-cursor { cursor: crosshair; }

/* Custom-area drawing: touch-action:none is what actually stops the browser eating the touch gesture (the
   Leaflet handler-disable stops panning; this stops scroll/zoom). Rectangle uses a crosshair; freehand uses a
   pencil cursor (no native keyword exists, so it's an inline SVG with the hotspot at the tip). */
.leaflet-container.drawing { touch-action: none; }
.leaflet-container.drawing.draw-rect { cursor: crosshair; }
.leaflet-container.drawing.draw-free {
  cursor: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='%232b2b2b' stroke='white' stroke-width='1.5' stroke-linejoin='round'%3E%3Cpath d='M17 3a2.85 2.83 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5Z'/%3E%3C/svg%3E") 2 22, crosshair;
}
/* On-map instruction pill shown while drawing, with a round ✕ to cancel (Esc also cancels). Above the mask
   and controls, below the sheet (1500). */
.zone-banner {
  position: absolute; top: 12px; left: 50%; transform: translateX(-50%); z-index: 1490;
  display: flex; align-items: center; gap: 10px; max-width: calc(100% - 24px);
  padding: 6px 6px 6px 14px; border-radius: 20px;
  background: var(--panel); color: var(--text); border: 1px solid var(--border);
  box-shadow: 0 2px 10px rgba(0,0,0,.5); font-size: 13px; white-space: nowrap;
}
.zone-banner span { overflow: hidden; text-overflow: ellipsis; }
.zone-banner-x {
  flex: 0 0 auto; display: flex; align-items: center; justify-content: center;
  width: 26px; height: 26px; border-radius: 50%; padding: 0; cursor: pointer;
  background: var(--hover); color: var(--text); border: 1px solid var(--border);
}
.zone-banner-x:hover { border-color: var(--accent); color: var(--accent); }
.zone-banner-x .ic { width: 15px; height: 15px; }

/* Full-map overlay shown while the region index downloads on a cold start. */
.map-loading {
  position: absolute; inset: 0; z-index: 1400;
  display: flex; align-items: center; justify-content: center;
  background: rgba(29, 31, 33, .55);
}
.map-loading-box {
  display: flex; align-items: center; gap: 10px;
  background: var(--panel); color: var(--text);
  border: 1px solid var(--border); border-radius: 4px; padding: 12px 18px;
  font-size: 14px; box-shadow: 0 2px 10px rgba(0,0,0,.5);
}
.map-loading-spinner {
  width: 18px; height: 18px; border-radius: 50%;
  border: 3px solid var(--border); border-top-color: var(--accent);
  animation: fbmr-spin .8s linear infinite;
}
@keyframes fbmr-spin { to { transform: rotate(360deg); } }

#mobileBar { display: none; }
#sidebarBackdrop { display: none; }
#sidebarClose { display: none; }

@media (max-width: 768px) {
  /* Floating filter + view-toggle pair. z sits above the full-screen list (1350) but below the filter
     drawer's backdrop (1355), so it rides over both views yet never fights the open drawer — which hides
     it outright via .hidden. */
  #mobileBar {
    display: flex; align-items: center; gap: 8px;
    position: fixed; top: 10px; left: 12px; z-index: 1352;
  }
  #mobileBar.hidden { display: none; }
  #mobileBar button {
    display: inline-flex; align-items: center; justify-content: center; gap: 6px;
    height: 34px; padding: 0 9px; border-radius: 8px;
    background: var(--panel); color: var(--text); border: 1px solid var(--border);
    font-family: inherit; font-size: 13px; font-weight: 600; line-height: 1;
    cursor: pointer; box-shadow: 0 1px 6px rgba(0,0,0,.5);
  }
  #mobileBar button:hover { border-color: var(--accent); }
  #mobileBar .ic { width: 18px; height: 18px; }
  /* The toggle names the view it switches TO — so "Map" only while the list is open. */
  #viewToggle span { display: none; align-items: center; gap: 6px; }
  #viewToggle .vt-list { display: inline-flex; }
  #viewToggle.list-open .vt-list { display: none; }
  #viewToggle.list-open .vt-map { display: inline-flex; }
  /* Two-finger pinch is universal on phones; the +/− control is desktop-only screen furniture. */
  .leaflet-control-zoom { display: none; }
  /* Above the full-screen list panel (z 1350), so opening filters from the list header shows over it. */
  #sidebarBackdrop.open {
    display: block; position: fixed; inset: 0; z-index: 1355; background: rgba(0,0,0,.45);
  }
  #sidebar {
    position: fixed; top: 0; left: 0; height: 100%; width: 85vw; max-width: 320px;
    transform: translateX(-100%); transition: transform .2s ease-out; z-index: 1360;
  }
  #sidebar.open { transform: translateX(0); }
  /* In-drawer "‹ Filters" back button, part of the sticky header so it's always reachable. */
  #sidebarClose {
    display: flex; align-items: center; gap: 4px; width: 100%; box-sizing: border-box;
    padding: 10px 14px 4px; background: none; border: none; color: var(--muted);
    font: 600 12px/1 "Nunito", system-ui, sans-serif; text-transform: uppercase; letter-spacing: .06em; cursor: pointer;
  }
  #sidebarClose:hover { color: var(--text); }
  #sidebarClose .ic { width: 16px; height: 16px; }
}

/* Map help ("?") button — same round-overlay-control look as locate/probe, parked bottom-right where the
   old always-open legend used to sit. Opens #mapHelpModal instead of printing the legend directly on the map. */
.help-btn {
  display: flex; align-items: center; justify-content: center;
  width: 30px; height: 30px; border-radius: 50%; margin: 0 8px 8px 0;
  background: var(--panel); color: var(--text); border: 1px solid var(--border);
  font-size: 15px; font-weight: 700; line-height: 1; cursor: pointer; box-shadow: 0 1px 6px rgba(0,0,0,.5);
}
.help-btn:hover { border-color: var(--accent); color: var(--accent); }

/* The stats button is the help button's twin (same .help-btn); its glyph is an SVG, not a "?" character. */
.help-btn .ic { width: 16px; height: 16px; }

/* Rent statistics popup (#statsModal): says what it is, then the one number most people came for. */
.stats-label { font-size: 15px; font-weight: 700; margin-top: 6px; }
.stats-headline { font-size: 38px; font-weight: 800; line-height: 1.15; }
.stats-range { font-size: 13px; color: var(--muted); margin-top: 4px; line-height: 1.4; }
.stats-table { width: 100%; table-layout: fixed; border-collapse: collapse; font-size: 13px; margin-top: 4px; }
.stats-table td { padding: 4px 0; border-bottom: 1px solid var(--border); }
.stats-table td:nth-child(1) { width: 38%; }
.stats-table td:nth-child(2) { font-weight: 700; text-align: right; }
.stats-table td:nth-child(3) { color: var(--muted); text-align: right; padding-left: 12px; white-space: nowrap; }
.stats-note { font-size: 11.5px; color: var(--muted); margin: 14px 0 0; line-height: 1.45; }

/* Pin-legend content inside #mapHelpModal: a swatch/description list, reusing the same rows for the
   confidence tiers (colour) and the other pin/cluster cues (ring, stars, dimming, cluster numbers). */
.help-rows { display: flex; flex-direction: column; gap: 9px; margin: 4px 0 2px; }
.help-row { display: flex; align-items: flex-start; gap: 10px; font-size: 13px; }
.help-swatch { flex: 0 0 auto; width: 18px; height: 18px; margin-top: 1px; }
.help-swatch.dot { border-radius: 50%; }
.help-row .ic { width: 18px; height: 18px; flex: 0 0 auto; color: var(--muted); }
.help-row-text b { display: block; margin-bottom: 1px; }
.help-row-text span { color: var(--muted); }
.help-section-title { font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: .06em;
  color: var(--muted); margin: 14px 0 2px; }
.help-section-title:first-of-type { margin-top: 4px; }

/* Fullscreen photo gallery (lightbox) opened from the details sheet. Above the sheet (z 1500); its own
   pointer swipe handling wants touch-action:none so horizontal drags don't scroll the page instead. */
.lightbox { position: fixed; inset: 0; z-index: 3600; display: flex; align-items: center; justify-content: center;
  background: rgba(0,0,0,.93); touch-action: none; user-select: none; }
.lightbox[hidden] { display: none; }
.lb-img { max-width: 96vw; max-height: 92vh; object-fit: contain; box-shadow: 0 6px 40px rgba(0,0,0,.6); -webkit-user-drag: none; }
/* Spinner shown only while the current photo is still downloading (see lbShow's .lb-loading toggle) — so a
   slow swap never leaves the previous photo on screen masquerading as the new one. */
.lb-spin { position: fixed; width: 44px; height: 44px; z-index: 1; pointer-events: none; display: none;
  border: 3px solid rgba(255,255,255,.25); border-top-color: #fff; border-radius: 50%; animation: lb-spin .8s linear infinite; }
.lightbox.lb-loading .lb-spin { display: block; }
@keyframes lb-spin { to { transform: rotate(360deg); } }
.lb-close, .lb-nav {
  position: fixed; z-index: 2; display: flex; align-items: center; justify-content: center;
  background: rgba(0,0,0,.45); color: #fff; border: none; border-radius: 50%; line-height: 1; cursor: pointer;
}
.lb-close:hover, .lb-nav:hover { background: rgba(0,0,0,.7); }
.lb-close { top: 14px; right: 16px; width: 40px; height: 40px; font-size: 26px; }
.lb-nav { top: 50%; transform: translateY(-50%); width: 46px; height: 46px; font-size: 30px; padding-bottom: 3px; }
.lb-nav[hidden] { display: none; }
.lb-prev { left: 12px; }
.lb-next { right: 12px; }
.lb-counter { position: fixed; bottom: 16px; left: 50%; transform: translateX(-50%); z-index: 2;
  color: #fff; font-size: 13px; background: rgba(0,0,0,.45); padding: 3px 11px; border-radius: 999px; }
.lb-counter:empty { display: none; }
/* "+N more photos in the original post" — links out to the source that advertised more than we downloaded. */
.lb-more { position: fixed; bottom: 46px; left: 50%; transform: translateX(-50%); z-index: 2;
  max-width: 92vw; color: #fff; font-size: 13px; text-decoration: none; white-space: nowrap;
  background: rgba(0,0,0,.55); padding: 6px 13px; border-radius: 999px; }
.lb-more:hover { background: rgba(0,0,0,.75); text-decoration: underline; }
.lb-more[hidden] { display: none; }

.marker-cluster-small        { background-color: hsla(265, 40%, 80%, .6); }
.marker-cluster-small div    { background-color: hsla(265, 40%, 68%, .85); }
.marker-cluster-medium       { background-color: hsla(265, 40%, 68%, .6); }
.marker-cluster-medium div   { background-color: hsla(265, 40%, 56%, .85); }
.marker-cluster-large        { background-color: hsla(265, 40%, 56%, .6); }
.marker-cluster-large div    { background-color: hsla(265, 40%, 44%, .85); }
.marker-cluster span         { color: #2b2440; font-weight: 600; }

/* Price pins: the wrapper carries the conic tag ring (N tags = N colored arcs) and the state classes;
   the inner rect's background lightness encodes the location-uncertainty radius (lighter = fuzzier). */
.pin-wrap {
  position: relative; display: inline-flex; align-items: center;
  border-radius: 14px; transform: translate(-50%, -50%);
}
.pin-wrap.ringed { padding: 3px; }
.price-pin {
  background: var(--pin-color, #888); color: #fff; font-weight: 600; font-size: 12px;
  padding: 2px 7px; border-radius: 12px; white-space: nowrap; border: 2px solid #fff;
  box-shadow: 0 1px 4px rgba(0,0,0,.5);
}
.pin-wrap.ringed .price-pin { border: none; padding: 4px 9px; }
/* Availability rides the pin's FILL colour (see PIN_COLORS), but hue alone fails for the ~8% of men with
   red-green colour blindness — and at low zoom the pin is too small to read its price at all. So the state
   keeps a second, non-colour channel: rented is struck through and clearly dimmer, probably slightly dimmer. */
.pin-wrap.sold .price-pin { color: #f2e8e8; opacity: .7; text-decoration: line-through; }
.pin-wrap.probably .price-pin { opacity: .85; }
/* Visited pins are NOT dimmed — instead an untagged pin's white border goes light-grey, so a fresh (unseen)
   pin keeps a bright white border and stands out. Tagged pins show their colour ring regardless (border:none),
   so this only affects untagged pins. */
.pin-wrap.visited .price-pin { border-color: #b9b9b9; }
/* Selected pin (its details sheet is open): scaled up ~30% and raised above neighbours — size and lift only.
   No coloured ring: an accent halo recoloured the pin's own state colour, which is the one thing the pin is
   there to say. transform must keep the translate that positions the divIcon.
   Baked into the icon (via .pin-wrap.selected in pinHtml) so it survives every refreshPin. */
.pin-wrap.selected { opacity: 1; z-index: 1000; transform: translate(-50%, -50%) scale(1.3); }
.pin-wrap.selected .price-pin { box-shadow: 0 3px 10px rgba(0,0,0,.6); }
/* Hovering a list row (desktop) glows the listing on the map in red — its own pin if it's drawn separately,
   or the cluster it's currently folded into (.map-hover is put on whichever leaflet-marker-icon is visible). */
.leaflet-marker-icon.map-hover { z-index: 1001 !important; }
.leaflet-marker-icon.map-hover .price-pin { box-shadow: 0 0 0 3px rgba(230,57,57,.95), 0 0 16px 5px rgba(230,57,57,.6) !important; }
.marker-cluster.map-hover { box-shadow: 0 0 0 4px rgba(230,57,57,.95), 0 0 18px 6px rgba(230,57,57,.6) !important; border-radius: 50%; }
/* When the selected listing's pin is folded into a CLUSTER, glow the cluster bubble instead (individual pins
   glow themselves via .pin-wrap.selected above), so switching between houses always shows where you're looking. */
.marker-cluster.map-selected { box-shadow: 0 0 0 4px var(--accent), 0 0 18px 6px color-mix(in srgb, var(--accent) 55%, transparent) !important; border-radius: 50%; }
/* Stars + notes marker sit ABOVE the pin, centred. */
.pin-top {
  position: absolute; bottom: calc(100% + 1px); left: 50%; transform: translateX(-50%);
  display: flex; align-items: center; gap: 3px; white-space: nowrap; pointer-events: none;
}
.pin-stars { color: #f5c518; font-size: 10px; line-height: 1; text-shadow: 0 1px 2px rgba(0,0,0,.9); }
.pin-note { display: inline-flex; }
.pin-note .ic { width: 9px; height: 9px; color: #fff; filter: drop-shadow(0 1px 1.5px rgba(0,0,0,.9)); }
#edgeArrow {
  position: absolute; z-index: 1400; color: var(--accent); font-size: 22px; pointer-events: none;
  text-shadow: 0 1px 3px rgba(0,0,0,.7);
}

.days-row { display: flex; gap: 8px; align-items: center; }
.days-row span { color: var(--muted); font-size: 12px; }
.tag-list { max-height: 140px; overflow-y: auto; margin-top: 4px; padding-left: 2px; }
.tag-list label { font-size: 12px; line-height: 1.7; }
/* No tags exist yet for this account (new user / anonymous) — the whole "only tagged" group has nothing to
   filter on, so it's dimmed rather than left clickable-but-empty. */
.group.opts-disabled > label.inline-check { opacity: .5; }
.subtle { color: var(--muted); font-size: 11px; margin: 2px 0 0; }
.dim { color: var(--muted); }
.sold-tag { color: #e07070; font-size: 11px; font-weight: 700; margin-left: 6px; white-space: nowrap; }
.sold-tag.probably { color: #e0a33a; }

/* ==== profile row (sidebar bottom) — the whole row is the settings modal's trigger ==== */
#profileZone {
  display: flex; align-items: center; gap: 10px; width: 100%; padding: 12px 14px;
  background: none; border: none; border-top: 1px solid var(--divider);
  color: var(--text); font: inherit; text-align: left; cursor: pointer;
}
#profileZone[hidden] { display: none; }
#profileZone:hover { background: var(--panel); }
#profileAvatar {
  width: 34px; height: 34px; border-radius: 50%; flex: 0 0 34px;
  background: var(--accent); color: #fff; font-size: 13px; font-weight: 700;
  display: flex; align-items: center; justify-content: center;
}
.profile-main { min-width: 0; flex: 1; font-size: 13px; }
#profileName, #profileEmail { display: block; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
#profileEmail { font-size: 11px; }
.profile-gear { display: flex; flex: 0 0 auto; color: var(--muted); }
.profile-gear .ic { width: 15px; height: 15px; display: block; }
#profileZone:hover .profile-gear { color: var(--accent); }
#profileAvatar img { width: 100%; height: 100%; border-radius: 50%; object-fit: cover; display: block; }

/* ==== wordmark (sidebar bottom, under the profile/sign-in row) ====
   RENT is the noun, MAPPED the claim: split across two faces and two weights so the halves read as one word
   without a separator. The plectrum sits between them as the seam. The mark's three dot groups are themed
   here — the source images/logo.svg paints its body near-white, which would vanish on the light theme.
   Tracking adds a trailing gap after the last letter of each half; the negative margins take it back so the
   lockup centres optically rather than mathematically. */
/* An <a>, not a <div>, since Brandmark.razor took an optional Href — without an href it is inert and picks up
   no link styling of its own, but these two declarations keep the LINKED case (the map sidebar's, pointing at
   "/") looking identical to the unlinked one instead of turning blue and underlined. */
.brandmark {
  display: flex; flex-direction: column; align-items: center; gap: 3px;
  padding: 11px 14px 14px; border-top: 1px solid var(--divider); user-select: none;
  color: inherit; text-decoration: none;
}
.brandmark[href]:hover .brandmark-rent { color: var(--accent); }
.brandmark-word { display: flex; align-items: center; gap: 6px; }
.brandmark-rent { font-size: 13px; font-weight: 700; letter-spacing: 1.6px; margin-right: -1.6px; color: var(--text); }
.brandmark-mapped { font-family: var(--mono); font-size: 11.5px; letter-spacing: 1.6px; margin-right: -1.6px; color: var(--muted); }
.brand-mark { width: 21px; height: 21px; display: block; flex: 0 0 21px; }
.brand-mark .lm-h { fill: var(--border-2); }
.brand-mark .lm-b { fill: var(--text); }
.brand-mark .lm-c { fill: var(--accent); }
.brandmark-tagline { font-size: 10.5px; font-style: italic; color: var(--muted); }

/* Anonymous visitors get this instead of the row above — same slot, same shape, so nothing shifts when
   auth.js swaps one for the other once /api/v1/me resolves. */
#signInZone {
  display: flex; align-items: center; gap: 10px; width: 100%; padding: 12px 14px;
  background: none; border: none; border-top: 1px solid var(--divider);
  color: var(--accent); font: inherit; font-weight: 600; text-align: left; cursor: pointer;
}
#signInZone[hidden] { display: none; }
#signInZone:hover { background: var(--panel); }
#signInZone .dim { display: block; font-weight: 400; font-size: 11px; }
.tag-mrow { display: flex; gap: 4px; align-items: center; margin: 3px 0; }
.tag-mrow .tag-dot.big { width: 14px; height: 14px; flex: 0 0 14px; margin: 0; border: none; cursor: pointer; padding: 0; }
.tag-mrow input { flex: 1; min-width: 0; background: var(--bg); color: var(--text);
  border: 1px solid var(--border); border-radius: 4px; padding: 3px 6px; font-size: 12px; }
.tag-mrow button[data-act] { display: flex; align-items: center; background: none; border: 1px solid var(--border); border-radius: 4px;
  color: var(--muted); cursor: pointer; padding: 4px 6px; }
.tag-mrow button[data-act] .ic { width: 12px; height: 12px; }
.tag-mrow button[data-act]:hover { border-color: var(--accent); color: var(--accent); }

/* History tab: one row per listing the user opened. Full-width buttons — the whole row is the target,
   which on a phone is the only comfortable way to hit it. */
.hist-row { display: flex; gap: 8px; align-items: center; width: 100%; text-align: left; margin: 2px 0;
  background: none; border: 1px solid transparent; border-radius: 6px; color: var(--text); cursor: pointer; padding: 5px; }
.hist-row:hover { border-color: var(--border); background: var(--bg-soft, rgba(127,127,127,.08)); }
.hist-thumb { width: 44px; height: 36px; flex: 0 0 44px; object-fit: cover; border-radius: 4px; background: var(--border); }
.hist-none { display: flex; align-items: center; justify-content: center; color: var(--muted); }
.hist-none .ic { width: 15px; height: 15px; }
.hist-main { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 1px; }
.hist-title { font-size: 12px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.hist-meta { font-size: 11px; color: var(--muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.hist-side { display: flex; align-items: center; gap: 6px; flex: 0 0 auto; font-size: 11px; }
.hist-out { display: inline-flex; align-items: center; gap: 3px; color: var(--accent); }
.hist-out .ic { width: 11px; height: 11px; }
.hist-count { color: var(--muted); }

/* Centered modal shell (settings, map help; generic). */
.modal-overlay { position: fixed; inset: 0; z-index: 4000; display: flex; align-items: center; justify-content: center; background: rgba(0,0,0,.45); }
.modal-overlay[hidden] { display: none; }
.modal-card { width: min(92vw, 420px); max-height: 80vh; overflow-y: auto; box-sizing: border-box;
  background: var(--bg); color: var(--text); border: 1px solid var(--border); border-radius: 6px;
  padding: 14px; font-size: 13px; box-shadow: 0 10px 40px rgba(0,0,0,.45); }
.modal-head { display: flex; align-items: center; margin-bottom: 6px; }
.modal-head b { flex: 1; font-size: 15px; }
.modal-close { background: none; border: none; color: var(--muted); font-size: 22px; line-height: 1; cursor: pointer; padding: 0 2px; }
.modal-close:hover { color: var(--text); }
.modal-error { color: #e07070; font-size: 12px; margin-bottom: 8px; }

/* ==== settings modal: vertical tab rail + the active tab's panel ==== */
.modal-card.settings-card { width: min(94vw, 660px); }
.settings-body { display: flex; gap: 14px; align-items: stretch; }
.settings-tabs { display: flex; flex-direction: column; gap: 2px; flex: 0 0 130px;
  border-right: 1px solid var(--divider); padding-right: 10px; }
.settings-tabs button { background: none; border: none; border-radius: 4px; color: var(--muted);
  font: inherit; font-size: 13px; text-align: left; cursor: pointer; padding: 7px 9px; }
.settings-tabs button:hover { background: var(--panel); color: var(--text); }
.settings-tabs button.on { background: var(--panel); color: var(--accent); font-weight: 600; }
.settings-panel { flex: 1; min-width: 0; min-height: 240px; }
.settings-title { font-size: 12px; font-weight: 700; margin-bottom: 4px; }
.settings-todo { color: var(--muted); font-size: 12px; margin-top: 10px; }
.settings-who { display: flex; align-items: center; gap: 10px; margin-top: 8px; }
.settings-who > span:last-child { min-width: 0; display: flex; flex-direction: column; }
.settings-avatar { width: 34px; height: 34px; border-radius: 50%; flex: 0 0 34px;
  background: var(--accent); color: #fff; font-size: 13px; font-weight: 700;
  display: flex; align-items: center; justify-content: center; }
img.settings-avatar { object-fit: cover; }
.settings-actions { margin-top: 14px; }
.settings-danger { margin-top: 22px; padding-top: 14px; border-top: 1px solid var(--divider); }
.settings-danger .btn-danger { margin-right: 6px; }

/* Sign-in modal: reuses .modal-overlay/.modal-card, so only its body needs its own rules. */
.signin-body { display: flex; flex-direction: column; align-items: center; gap: 12px; padding: 6px 0 2px; text-align: center; }
.signin-body .subtle { margin: 0; }
#googleSignInButton { min-height: 40px; }
#googleSignInButton:empty { display: none; }   /* nothing rendered yet (GSI script still loading/blocked) — the fallback link carries it */
#googleSignInButton:empty + #signinFallbackLink { display: inline; }
#signinFallbackLink { display: none; }   /* shown only when Google's own button never rendered */
/* Narrow screens: the rail turns into a horizontal strip above the panel. */
@media (max-width: 560px) {
  .settings-body { flex-direction: column; gap: 10px; }
  .settings-tabs { flex-direction: row; flex-basis: auto; padding: 0 0 8px;
    border-right: none; border-bottom: 1px solid var(--divider); }
  .settings-panel { min-height: 0; }
}

/* Tag manager rows: reuse .tag-mrow, a touch larger in the modal, with an inline recolour palette. */
#tagModalRows .tag-mrow { gap: 6px; margin: 5px 0; }
#tagModalRows .tag-mrow .tag-dot.big { width: 18px; height: 18px; flex-basis: 18px; border: 1px solid rgba(255,255,255,.35); }
#tagModalRows .tag-mrow input { font-size: 13px; padding: 6px 8px; }
#tagModalRows .tag-mrow button[data-act] { padding: 6px 8px; }
#tagModalRows .tag-mrow button[data-act] .ic { width: 14px; height: 14px; }
#tagModalRows .tag-mrow button[data-act]:disabled { opacity: .4; cursor: default; }
#tagModalRows .tag-mrow button[data-act]:disabled:hover { border-color: var(--border); color: var(--muted); }
/* Per-tag engagement count riding between the name field and the row actions. */
.tag-count { flex: 0 0 auto; min-width: 22px; text-align: center; color: var(--muted); font-size: 12px; font-variant-numeric: tabular-nums; }
/* Inline "merge into" picker (tag manager), styled to sit under its row like the recolour palette. */
.tag-merge { display: flex; align-items: center; gap: 6px; padding: 6px 2px 10px 24px; }
.tag-merge .tag-merge-label { color: var(--muted); font-size: 12px; flex: 0 0 auto; }
.tag-merge select { flex: 1; min-width: 0; background: var(--bg); color: var(--text);
  border: 1px solid var(--border); border-radius: 4px; padding: 5px 6px; font-size: 13px; }
.tag-merge button { display: flex; align-items: center; background: none; border: 1px solid var(--border);
  border-radius: 4px; color: var(--muted); cursor: pointer; padding: 6px 8px; }
.tag-merge button .ic { width: 14px; height: 14px; }
.tag-merge button:hover { border-color: var(--accent); color: var(--accent); }
.tag-empty { color: var(--muted); padding: 4px 2px; }
/* Inline palette (tag manager recolour + the sheet's new-tag swatch). */
.tag-palette { display: grid; grid-template-columns: repeat(6, 1fr); gap: 6px; padding: 8px 2px 10px 24px; }
.tag-palette button { width: 22px; height: 22px; border-radius: 50%; border: 2px solid transparent; cursor: pointer; padding: 0; }
.tag-palette button:hover { border-color: var(--text); }
.tag-palette button.on { border-color: var(--text); box-shadow: 0 0 0 2px var(--accent); }

/* Only the inline confirm ✓ rides inside the add-tag input now; the colour swatch moved into the "Create …"
   row of the dropdown, where it is the one control that actually has a choice behind it. */
.tagedit .tag-add input { padding-right: 34px; }
/* The confirm control shares the field's square corners (not a pill) and only shows when there's text. */
.tag-add-go { position: absolute; right: 5px; top: 50%; transform: translateY(-50%); z-index: 2;
  display: flex; align-items: center; justify-content: center; width: 24px; height: 24px; padding: 0;
  border: none; border-radius: 4px; background: var(--accent); color: #fff; cursor: pointer; }
.tag-add-go[hidden] { display: none; }
.tag-add-go .ic { width: 15px; height: 15px; }
/* Spinner shown while a tag write is in flight (add / pick suggestion / remove). It sits where the ✓ was: the
   field is cleared before the request goes out, so the two never want the slot at the same time. */
.tag-spin { position: absolute; right: 9px; top: 50%; margin-top: -7.5px; z-index: 2;
  width: 15px; height: 15px; border-radius: 50%;
  border: 2px solid var(--border); border-top-color: var(--accent); animation: fbmr-spin .7s linear infinite; }
.tag-spin[hidden] { display: none; }
/* Create row: the swatch is the colour the new tag will get, and opens the palette as a row of the dropdown
   directly beneath it. Existing tags never show one — their colour isn't chosen here. */
.tag-create { display: flex; align-items: center; gap: 7px; padding: 6px 9px; }
.tag-create:hover { background: var(--hover); }
.tag-newdot { flex: none; width: 15px; height: 15px; border-radius: 50%; padding: 0;
  border: 1px solid rgba(255,255,255,.45); cursor: pointer; }
.tag-create-go { flex: 1; min-width: 0; text-align: left; background: none; border: none; padding: 0;
  color: var(--text); font: inherit; font-size: 13px; cursor: pointer; }
.tag-palette-row { display: grid; grid-template-columns: repeat(6, 1fr); gap: 6px; padding: 4px 9px 8px 31px;
  border-bottom: 1px solid var(--border); }
.tag-palette-row button { width: 20px; height: 20px; border-radius: 50%; border: 2px solid transparent; cursor: pointer; padding: 0; }
.tag-palette-row button:hover { border-color: var(--text); }
.tag-palette-row button.on { border-color: var(--text); box-shadow: 0 0 0 2px var(--accent); }

/* Details sheet: report form + the single-request photo collage. Send sits UNDER the box, left-aligned with
   it — side by side it floated against the tall textarea's top edge with no edge to line up on. */
.report-form { display: flex; flex-direction: column; align-items: flex-start; gap: 8px; margin: 8px 0; max-width: 640px; }
.report-form textarea { width: 100%; box-sizing: border-box; background: var(--panel); color: var(--text);
  border: 1px solid var(--border); border-radius: 4px; padding: 6px 8px; font-size: 13px; resize: vertical; }
#sheet .collage { width: 100%; border-radius: 4px; cursor: pointer; display: block; }
/* The collage is one flat image, so nothing on it said a gallery was behind it, or how big. pointer-events:none
   keeps the badge from eating the tap that opens it. */
.collage-wrap { position: relative; }
.photo-badge { position: absolute; right: 8px; bottom: 8px; pointer-events: none;
  display: inline-flex; align-items: center; gap: 4px; padding: 3px 8px; border-radius: 999px;
  background: rgba(0,0,0,.62); color: #fff; font-size: 11px; font-weight: 600; }
.photo-badge .ic { width: 13px; height: 13px; }
/* The merged collage now leads the sheet body — no top margin above it, a small gap below. */
#sheetPhotos { margin: 0 0 10px; }

/* Tag editor autocomplete: a dropdown of the user's own tags under the add-tag input. */
/* Full width, not a flex share: the tag editor is a column now, so the field keeps its size no matter how many
   chips sit above it. */
.tagedit .tag-add { position: relative; width: 100%; }
.tagedit .tag-add input { width: 100%; box-sizing: border-box; }
.tag-suggest {
  position: absolute; top: calc(100% + 2px); left: 0; right: 0; z-index: 60;
  background: var(--panel); border: 1px solid var(--border); border-radius: 4px;
  overflow-y: auto; max-height: 190px; box-shadow: 0 4px 14px rgba(0,0,0,.5);
}
.tag-suggest[hidden] { display: none; }
.tag-opt {
  display: flex; align-items: center; gap: 7px; width: 100%; text-align: left;
  background: none; border: none; color: var(--text); font-size: 13px; cursor: pointer; padding: 6px 9px;
}
.tag-opt:hover { background: var(--hover); }
.tag-opt .tag-dot { margin: 0; }

/* ==== collapsible filter groups (Basic / Additional / My filters) ==== */
details.fgroup { border-bottom: 1px solid var(--divider); }
/* Section headers read as headers — a tinted bar, uppercase small caps, accent chevron — not like the filter
   rows underneath them. */
details.fgroup > summary {
  padding: 9px 14px; font-size: 11px; font-weight: 700; letter-spacing: .07em; text-transform: uppercase;
  color: var(--muted); background: var(--hover); border-bottom: 1px solid var(--divider);
  cursor: pointer; user-select: none; list-style: none; display: flex; align-items: center;
}
details.fgroup:not([open]) > summary { border-bottom-color: transparent; }
details.fgroup > summary:hover { color: var(--text); }
details.fgroup > summary::-webkit-details-marker { display: none; }
details.fgroup > summary::before { content: "▸"; margin-right: 8px; color: var(--accent); font-size: 11px; transition: transform .12s; }
details.fgroup[open] > summary::before { transform: rotate(90deg); }
details.fgroup .group { border-bottom: none; padding-top: 4px; }
.inline-check { display: inline !important; }

/* ⓘ hint buttons + their shared popover. */
button.hint {
  display: inline-flex; align-items: center; justify-content: center;
  width: 15px; height: 15px; margin-left: 4px; padding: 0; vertical-align: 1px;
  background: none; border: 1px solid var(--border-2); border-radius: 50%;
  color: var(--muted); font-size: 10px; line-height: 1; cursor: help;
}
button.hint:hover { border-color: var(--accent); color: var(--accent); }
#hintPop {
  position: fixed; z-index: 3000; max-width: 260px;
  background: var(--panel); color: var(--text); border: 1px solid var(--border); border-radius: 4px;
  padding: 8px 10px; font: 12px/1.45 "Nunito", system-ui, sans-serif; box-shadow: 0 3px 14px rgba(0,0,0,.55);
}
/* Address action menu (Show on map / Open in Google Maps), anchored under the clicked location name. */
.loc-menu {
  position: fixed; z-index: 3000; min-width: 190px; padding: 4px;
  background: var(--panel); border: 1px solid var(--border); border-radius: 6px; box-shadow: 0 4px 16px rgba(0,0,0,.55);
}
.loc-menu[hidden] { display: none; }
.loc-menu-item {
  display: flex; align-items: center; gap: 8px; width: 100%; box-sizing: border-box;
  background: none; border: none; border-radius: 4px; padding: 8px 10px; cursor: pointer;
  color: var(--text); font: 13px/1 "Nunito", system-ui, sans-serif; text-align: left; text-decoration: none;
}
.loc-menu-item:hover { background: var(--hover); }
.loc-menu-item .ic { width: 15px; height: 15px; flex: 0 0 auto; color: var(--muted); }

/* "Pin it here" preview: a draggable accent marker + a floating confirm bar, shown while the user is
   correcting an address before it's committed. */
.loc-pin { display: flex; justify-content: center; cursor: grab; filter: drop-shadow(0 1px 3px rgba(0,0,0,.7)); }
.loc-pin .ic { width: 34px; height: 34px; color: var(--accent); }
.pin-confirm-bar {
  position: fixed; left: 50%; bottom: 26px; transform: translateX(-50%); z-index: 3000;
  display: flex; flex-direction: column; align-items: stretch; gap: 9px;
  width: min(330px, calc(100vw - 24px));
  padding: 12px 14px; background: var(--panel); color: var(--text);
  border: 2px solid var(--accent); border-radius: 10px;
  box-shadow: 0 6px 24px rgba(0,0,0,.55), 0 0 0 4px color-mix(in srgb, var(--accent) 20%, transparent);
  animation: pin-confirm-in .18s ease-out;
}
@keyframes pin-confirm-in { from { transform: translateX(-50%) translateY(10px); opacity: 0; } to { transform: translateX(-50%) translateY(0); opacity: 1; } }
.pin-confirm-bar[hidden] { display: none; }
/* Instructions, not an echo of the address. The address was already typed into the field the user is looking
   at, and on a phone it was ellipsised down to nothing useful anyway; what nobody knew was that the pin can be
   dragged. Wraps freely — the old nowrap + 42vw cap is what made it useless on a narrow screen. */
.pin-confirm-label { font-size: 12px; line-height: 1.35; color: var(--muted); }
.pin-confirm-actions { display: flex; align-items: center; gap: 14px; }
.pin-confirm-bar .btn { flex: 0 0 auto; }
/* Cancel is a quiet link, not a second pill — two pills side by side read as two equal choices. */
.pin-confirm-cancel { background: none; border: none; padding: 0; cursor: pointer;
  font: 12px/1 "Nunito", system-ui, sans-serif; color: var(--muted); text-decoration: underline; }
.pin-confirm-cancel:hover { color: var(--text); }
/* The primary action is filled solid accent (not the outline .btn style) so it reads as THE thing to press. */
.pin-confirm-go { background: var(--accent); color: #fff; border-color: var(--accent); }
.pin-confirm-go:hover { background: color-mix(in srgb, var(--accent) 85%, black); }
.pin-confirm-go:disabled { opacity: .6; cursor: progress; }

/* Location-change history popover (previous → new address, when, who — email masked). */
.loc-hist-pop {
  position: fixed; z-index: 3000; width: 280px; max-width: calc(100vw - 16px); padding: 8px;
  background: var(--panel); border: 1px solid var(--border); border-radius: 6px; box-shadow: 0 4px 16px rgba(0,0,0,.55);
  font: 12px/1.45 "Nunito", system-ui, sans-serif; color: var(--text);
}
.loc-hist-pop[hidden] { display: none; }
.loc-hist-h { font-size: 11px; font-weight: 700; letter-spacing: .06em; text-transform: uppercase; color: var(--muted); padding: 2px 4px 6px; }
.loc-hist-body { max-height: 260px; overflow-y: auto; }
.loc-hist-row { padding: 6px 4px; border-top: 1px solid var(--divider); }
.loc-hist-row:first-child { border-top: none; }
.loc-hist-move { color: var(--text); }
.loc-hist-move .arr { color: var(--muted); }
.loc-hist-meta { margin-top: 2px; color: var(--muted); }
.attr-row { display: flex; align-items: center; }
.attr-row label { flex: 1; }

/* ==== filter presets: a trigger button with an anchored dropdown menu ==== */
.preset-anchor { position: relative; }
.preset-trigger {
  width: 100%; box-sizing: border-box; display: flex; align-items: center; gap: 8px;
  background: var(--panel); color: var(--text); border: 1px solid var(--border);
  border-radius: 4px; padding: 8px 10px; font-size: 13px; cursor: pointer; text-align: left;
}
.preset-trigger:hover { border-color: var(--accent); }
.preset-trigger .ic { color: var(--muted); }
.preset-trigger-ic { display: inline-flex; flex: 0 0 auto; }
/* A preset is applied → the trigger's bookmark reads as "filled + active" in the accent colour. */
.preset-trigger.has-preset .preset-trigger-ic .ic { color: var(--accent); }
.preset-trigger-label { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.preset-caret { transition: transform .15s; }
.preset-trigger[aria-expanded="true"] .preset-caret { transform: rotate(180deg); }
/* Live filters drifted from the applied preset → an amber dot (distinct from the blue "selected" accent). */
.preset-dirty-dot { width: 7px; height: 7px; border-radius: 50%; background: #e0a33a; flex: 0 0 auto; }

.preset-menu {
  position: absolute; top: calc(100% + 4px); left: 0; right: 0; z-index: 2600;
  max-height: 60vh; overflow-y: auto; box-sizing: border-box;
  background: var(--panel); border: 1px solid var(--border); border-radius: 4px;
  box-shadow: 0 8px 26px rgba(0, 0, 0, .5); padding: 4px;
}
.preset-menu[hidden] { display: none; }
.pm-error { color: #e07070; font-size: 12px; padding: 6px 8px; }
.pm-empty { color: var(--muted); font-size: 12px; line-height: 1.5; padding: 8px; }
.pm-save-current {
  width: 100%; box-sizing: border-box; display: flex; align-items: center; gap: 8px; text-align: left;
  background: none; border: none; color: var(--accent); font-size: 13px; cursor: pointer;
  padding: 8px; border-radius: 0 0 6px 6px; border-top: 1px solid var(--divider); margin-top: 2px;
}
.pm-save-current:hover { background: var(--hover); }
.pm-list { display: flex; flex-direction: column; gap: 2px; }

/* One preset = a name row + (only when applied) an actions sub-row, both inside .pm-item. */
.pm-item { border-radius: 4px; }
.pm-item.active { background: var(--sel); }
.pm-row { display: flex; align-items: flex-start; gap: 4px; border-radius: 4px; }
.pm-item:not(.active) .pm-row:hover { background: var(--hover); }
.pm-apply {
  flex: 1; min-width: 0; display: flex; align-items: flex-start; gap: 8px; text-align: left;
  background: none; border: none; color: var(--text); font-size: 13px; line-height: 1.35; cursor: pointer; padding: 8px;
}
.pm-apply .ic { color: var(--muted); margin-top: 1px; }
.pm-item.active .pm-apply .ic { color: var(--accent); }
/* Long names wrap in full instead of ellipsizing. */
.pm-apply span { min-width: 0; overflow-wrap: anywhere; }
/* Management actions for the applied preset: icon link-buttons on one right-aligned row under the name. */
.pm-actions { display: flex; flex-wrap: nowrap; justify-content: flex-end; align-items: center; gap: 2px; padding: 0 6px 7px; }
.pm-link { display: inline-flex; align-items: center; gap: 5px; background: none; border: none;
  color: var(--muted); font-size: 11px; cursor: pointer; padding: 4px 8px; border-radius: 4px; }
.pm-link .ic { width: 13px; height: 13px; }
.pm-link:hover { color: var(--accent); background: var(--hover); }
.pm-link.accent { color: var(--accent); }
.pm-input {
  flex: 1; min-width: 0; background: var(--bg); color: var(--text);
  border: 1px solid var(--accent); border-radius: 4px; padding: 6px 8px; font-size: 13px; margin: 2px;
}
.pm-input:focus { outline: none; }
.pm-primary, .pm-danger {
  flex: 0 0 auto; border: 1px solid var(--accent); background: var(--accent); color: #fff;
  border-radius: 4px; padding: 6px 11px; font-size: 12px; cursor: pointer; margin-right: 2px;
}
.pm-primary:hover, .pm-danger:hover { filter: brightness(1.1); }
.pm-danger { background: #a32d2d; border-color: #a32d2d; }
.pm-ghost {
  flex: 0 0 auto; background: none; border: 1px solid var(--border); color: var(--muted);
  border-radius: 4px; padding: 6px 11px; font-size: 12px; cursor: pointer; margin-right: 2px;
}
.pm-ghost:hover { border-color: var(--accent); color: var(--accent); }
.pm-icon { flex: 0 0 auto; display: flex; align-items: center; background: none; border: none; color: var(--muted); cursor: pointer; padding: 6px; border-radius: 4px; }
.pm-icon:hover { color: var(--text); }
.pm-confirm-q { flex: 1; min-width: 0; font-size: 12.5px; color: var(--text); padding: 0 4px 0 8px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* Tag color dot in the filter panel's tag checkbox lists. */
.tag-dot { display: inline-block; width: 9px; height: 9px; border-radius: 50%; margin-right: 5px; vertical-align: -1px; }

/* Tag recolor swatch popover. */
#swatchPop {
  position: fixed; z-index: 3000; display: grid; grid-template-columns: repeat(6, 24px); gap: 6px;
  background: var(--panel); border: 1px solid var(--border); border-radius: 4px; padding: 8px;
  box-shadow: 0 3px 14px rgba(0,0,0,.55);
}
#swatchPop button { width: 24px; height: 24px; border-radius: 50%; border: 2px solid transparent; cursor: pointer; }
#swatchPop button:hover { border-color: #fff; }
#swatchPop button.on { border-color: #fff; box-shadow: 0 0 0 2px var(--accent); }

/* Segmented control (the "My rating" filter). */
/* Availability filter: one row per lifecycle state, each wearing that state's own pin colour so the sidebar
   teaches the map's colour language. The count is pushed right and stays quiet — it's reassurance about what
   a tick is worth, not a number to read. */
/* Qualified with .group so it outranks `.group label { display: block }`, which was flattening the row and
   leaving the count glued to the label instead of pushed to the right edge. */
.group label.avail-row { display: flex; align-items: center; gap: 7px; }
.avail-row .avail-dot { width: 10px; height: 10px; border-radius: 50%; flex: 0 0 auto; }
.avail-row .avail-name { flex: 1; min-width: 0; }
.avail-row .avail-count { flex: 0 0 auto; margin-left: 8px; font-size: 11px; font-variant-numeric: tabular-nums; }

.seg { display: flex; flex-wrap: wrap; gap: 4px; }
.seg button {
  background: var(--panel); color: var(--text); border: 1px solid var(--border);
  border-radius: 4px; padding: 4px 8px; font-size: 12px; cursor: pointer;
}
.seg button.on { border-color: var(--accent); color: var(--accent); }

/* Custom-area filter block. Draw buttons carry an icon; .armed (while that mode is being drawn) mirrors the
   probe's amber highlight. The active sub-block appears only once an area is drawn. */
.zone-draw button { display: inline-flex; align-items: center; gap: 5px; }
.zone-draw button .ic { width: 14px; height: 14px; }
.zone-draw button.armed { border-color: #e0a33a; color: var(--text); box-shadow: 0 0 0 2px rgba(224,163,58,.5); }
.zone-active { margin-top: 10px; }
/* Option B: the drawn area shown as one compact accent chip; its ✕ clears the area. */
.zone-chip {
  display: inline-flex; align-items: center; gap: 7px; max-width: 100%;
  padding: 5px 6px 5px 10px; border-radius: 16px;
  border: 1px solid var(--accent); color: var(--accent); background: transparent; font-size: 12px;
}
.zone-chip-ic { display: inline-flex; }
.zone-chip-ic .ic { width: 13px; height: 13px; }
.zone-chip > span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.zone-chip-x {
  flex: 0 0 auto; display: inline-flex; align-items: center; justify-content: center;
  width: 18px; height: 18px; border-radius: 50%; padding: 0; border: none; cursor: pointer;
  background: var(--hover); color: var(--text-2);
}
.zone-chip-x:hover { background: var(--card-hover); color: var(--text); }
.zone-chip-x .ic { width: 12px; height: 12px; }
/* Selectors are qualified with .group to outrank `.group label { line-height: 1.9 }` (line 50), which would
   otherwise win on specificity and stretch these wrapped lines. */
.group label.zone-uncertain { display: flex; align-items: flex-start; gap: 6px; margin-top: 10px; cursor: pointer; font-size: 12px; line-height: 1.15; color: var(--text-2); }
.group label.zone-uncertain input { flex: 0 0 auto; margin: 1px 0 0; }
.group label.zone-uncertain span { line-height: 1.15; }

/* ==== details sheet ==== */
#sheet {
  position: fixed; z-index: 1500; background: var(--bg); color: var(--text);
  box-shadow: 0 -4px 20px rgba(0,0,0,.5); display: flex; flex-direction: column;
}
#sheet[hidden] { display: none; }
#sheetGrip { display: none; flex: 0 0 auto; padding: 8px 0 4px; cursor: grab; touch-action: none; }
#sheetGrip span { display: block; width: 36px; height: 4px; border-radius: 2px; background: var(--border-2); margin: 0 auto; }
/* touch-action pan-y: vertical scrolling stays native, but a HORIZONTAL touch gesture is left to JS — without
   this the browser claims every touch for scrolling (pointercancel), and the carousel swipe never fires. */
#sheetBody { flex: 1; overflow-y: auto; overscroll-behavior: contain; touch-action: pan-y pinch-zoom; }
/* Reuse the old details-page styling for the sheet's content, minus its full-page framing. NO top padding:
   the sticky .sheet-top must sit flush with the scroll top, otherwise it sticks at the padding edge and a
   strip of scrolling photo shows in the gap above it. */
#sheet .details-page { min-height: 0; padding: 0 14px 20px; }
#sheet .details-page .grid img { height: 160px; }

/* The header, in three rows: price and the ‹ counter › × controls on the top line, then the facts across the
   FULL width, then the availability badge. As two columns the facts had to fit beside the controls — about half
   the sheet — so a named fact list wrapped while the width beside it sat empty.
   Only the TOP line sticks. The facts and the badge are read once and then wanted out of the way; pinned, they
   spent a third of a phone screen on the same four words for the whole scroll. Which means the sticky element
   owns the top padding — sticking at a padding edge would leave a gap with content scrolling through it. */
/* The divider rides on the STICKY row, not under the whole header: wherever the scroll is, it marks the line
   between what stays and what moves.
   Two things keep content from bleeding around it while it holds the top:
   - Negative side margins cancel .details-page's 14px padding so the bar paints edge to edge. Inset, it left a
     transparent gutter down each side that anything scrolling past showed through.
   - The divider is a box-shadow, not a border, with a second shadow of the background colour under it. The
     bar's bottom lands on a fractional device pixel (measured .05 at dpr 2), and that half-covered row rendered
     as a hairline of whatever was behind it — a photo, usually. The shadows paint 2px past the box, so the
     sliver gets the bar's own colour instead. */
.sheet-top { position: sticky; top: 0; z-index: 20; background: var(--bg);
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
  margin: 0 -14px; padding: 12px 14px 10px;
  box-shadow: 0 1px 0 var(--divider), 0 2px 0 var(--bg); }
.sheet-peek { display: flex; flex-direction: column; gap: 6px; padding: 8px 0 0; }
.sheet-nav { flex: 0 0 auto; display: flex; align-items: center; gap: 6px; }
/* Two lines: the position, and the noun it counts. Sitting bare over the photo collage the digits alone read
   as a photo counter. */
.sheet-pos { display: flex; flex-direction: column; align-items: center; justify-content: center;
  color: var(--muted); min-width: 44px; line-height: 1; white-space: nowrap; }
.sheet-pos b { font-size: 12px; font-weight: 600; }
.sheet-pos small { font-size: 8.5px; text-transform: uppercase; letter-spacing: .06em; opacity: .75; margin-top: 2px; }
.sheet-price { min-width: 0; font-size: 16px; font-weight: 700; white-space: nowrap;
  overflow: hidden; text-overflow: ellipsis; }
/* Every fact is spelled out now (Condo · Studio · 2 baths · Furnished). On its own full-width row that fits on
   one line, so the pills can breathe; it stays a size below the price rather than becoming a second heading. */
.sheet-facts { font-size: 11px; display: flex; align-items: center; flex-wrap: wrap; gap: 3px 14px; }
/* Circular controls — the ‹ › carousel arrows and the × close share one look; a bare glyph read as decorative. */
.sheet-navbtn, .sheet-close {
  flex: 0 0 auto; display: flex; align-items: center; justify-content: center;
  width: 30px; height: 30px; background: var(--hover); border: 1px solid var(--border); border-radius: 50%;
  color: var(--text); font-size: 20px; line-height: 1; cursor: pointer; padding: 0;
}
.sheet-navbtn:hover:not(:disabled), .sheet-close:hover { background: var(--card-hover); border-color: var(--accent); }
.sheet-navbtn:disabled { opacity: .35; cursor: default; }
.starbar button {
  background: none; border: none; color: var(--border-2); font-size: 24px; line-height: 1;
  cursor: pointer; padding: 0 2px; transition: color .1s, transform .1s;
}
.starbar button:hover { color: #f5c518; transform: scale(1.12); }
.starbar button.on { color: #f5c518; }
.sheet-full { padding-top: 8px; }
.descr { white-space: pre-wrap; font-size: 13px; color: var(--text-2); margin: 10px 0; }
.notes-block { margin: 8px 0; }
.notes-view { position: relative; font-size: 13px; color: var(--text); background: var(--panel); border: 1px solid var(--border);
  border-radius: 4px; padding: 7px 26px 7px 9px; min-height: 40px; cursor: text; }
.notes-view .notes-text { white-space: pre-wrap; }
.notes-view .notes-text.clamped { display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden; }
.notes-view .notes-more { background: none; border: none; color: var(--accent); font-size: 12px; cursor: pointer; padding: 2px 0 0; }
.notes-view .notes-edit { position: absolute; top: 4px; right: 6px; background: none; border: none; color: var(--muted); cursor: pointer; }
/* Unwritten, the note is an action rather than an empty field — no box, no placeholder pretending to be a value. */
.notes-view.empty { background: none; border: none; padding: 0; min-height: 0; }
.notes-add { display: inline-flex; align-items: center; gap: 5px; background: none; border: none; padding: 2px 0;
  color: var(--accent); font: inherit; font-size: 12px; font-weight: 600; cursor: pointer; }
.notes-add .ic { width: 13px; height: 13px; }
#sheet .chip.mini { padding: 0 7px; font-size: 11px; }

/* Desktop: right-side panel. */
@media (min-width: 769px) {
  #sheet { top: 0; right: 0; bottom: 0; width: 440px; max-width: 45vw; border-left: 1px solid var(--divider); }
}
/* Mobile: bottom sheet with three snap heights. Opens at half so the photos are visible straight away. */
@media (max-width: 768px) {
  #sheet { left: 0; right: 0; bottom: 0; border-radius: 8px 8px 0 0; transition: height .18s ease-out; }
  #sheet.peek { height: 92px; }
  #sheet.peek #sheetBody { overflow: hidden; }
  /* PRODUCT REQUIREMENT — do not shrink: expanded (.full) must reach ALMOST THE TOP OF THE SCREEN, leaving
     only a sliver of map. This has been lost twice; if you touch these numbers, keep that true.
     The unit is the whole trick, so don't "simplify" it back:
       - vh  measures the LARGE viewport (URL bar hidden). With the bar VISIBLE a 94vh sheet pushes its top —
             the sticky strip carrying the × — off-screen, so it can't be collapsed.
       - svh measures the SMALL viewport (URL bar visible). That fixed the above, but with the bar HIDDEN the
             sheet stops ~17% short of the top: a dead band of map where the details should be. That is the
             bug this comment exists to prevent coming back.
       - dvh tracks whichever viewport is CURRENTLY showing, so it satisfies both: never off-screen, never
             short. vh first is the fallback for browsers without dvh. */
  #sheet.half { height: 58vh; height: 58dvh; }
  #sheet.full { height: 96vh; height: 96dvh; }
  /* While the grip is held the sheet tracks the finger 1:1 — easing would make it lag behind. */
  #sheet.dragging { transition: none; }
  /* Obviously-draggable handle — a wider, higher-contrast bar with a brief bounce on open that signals the sheet
     drags up. The bar is small but the padding is the touch target: at 13px tall a finger aimed at the bar landed
     on the header below it, so the drag never started. */
  #sheetGrip { display: block; padding: 10px 0 9px; }
  #sheetGrip span { width: 46px; height: 5px; background: var(--muted); }
  #sheet:not(.full) #sheetGrip span { animation: fbmr-grip-hint 1.3s ease-in-out 2; }
  /* Compact header: minimal padding so photos start as high as possible under the sticky price strip. */
  .sheet-peek { padding: 6px 0 7px; }
  .sheet-full { padding-top: 6px; }
}
@keyframes fbmr-grip-hint {
  0%, 100% { transform: translateY(0); opacity: .6; }
  50% { transform: translateY(-3px); opacity: 1; }
}

/* ==== top search: a round magnifier that swaps into the field (inline clear × + submit), dropdown under it ==== */
/* Parked one row DOWN from the top-right corner, under the locate control that now owns it. That single
   offset is what keeps the opened field off the filter + List buttons on mobile: expanded it spans the full
   width, so at the corner it covered them — starting below their row it can't. */
#searchBar {
  position: absolute; top: 52px; right: 10px; z-index: 1300;
  display: flex; align-items: flex-start;
}
#searchBtn {
  display: flex; align-items: center; justify-content: center;
  height: 34px; padding: 0 9px; border-radius: 8px; flex: 0 0 auto;
  background: var(--panel); color: var(--text); border: 1px solid var(--border);
  font-size: 16px; line-height: 1; cursor: pointer; box-shadow: 0 1px 6px rgba(0,0,0,.5);
}
#searchBtn:hover { border-color: var(--accent); }
#searchBar.open #searchBtn { display: none; }
/* The field: a rounded shell holding an upload-image button, a borderless input, an inline clear ×, and a
   distinct labelled "Search" action (accent-filled — unambiguously the thing to press, not another toggle). */
#searchField {
  display: flex; align-items: center; gap: 2px;
  width: min(440px, calc(100vw - 90px));
  background: var(--panel); border: 1px solid var(--border); border-radius: 6px;
  box-shadow: 0 1px 6px rgba(0,0,0,.5); padding: 3px;
}
#searchField[hidden] { display: none; }
#searchBar.open #searchField { border-color: var(--accent); }
#searchInput {
  flex: 1; min-width: 0; background: transparent; color: var(--text);
  border: none; outline: none; padding: 6px 4px; font-size: 13px;
}
#searchInput::-webkit-search-cancel-button { -webkit-appearance: none; appearance: none; }  /* our own × instead */
#searchUpload, #searchClear {
  display: flex; align-items: center; justify-content: center; flex: 0 0 auto;
  width: 32px; height: 32px; padding: 0; border: none; border-radius: 5px; background: transparent; cursor: pointer;
  color: var(--muted);
}
#searchUpload:hover, #searchClear:hover { color: var(--text); background: var(--hover); }
#searchUpload .ic, #searchClear .ic { width: 18px; height: 18px; }
#searchClear[hidden] { display: none; }
/* Primary action. */
#searchGo {
  display: flex; align-items: center; justify-content: center; gap: 6px; flex: 0 0 auto;
  height: 32px; padding: 0 14px; border: none; border-radius: 5px;
  background: var(--accent); color: #fff; font-size: 13px; font-weight: 600; cursor: pointer;
}
#searchGo:hover { filter: brightness(1.08); }
#searchGo:disabled { opacity: .7; cursor: default; }
/* The button shows a spinner in place of its label while a search is in flight. */
#searchGo .fbmr-spinner { display: none; width: 14px; height: 14px; margin: 0; border-color: rgba(255,255,255,.4); border-top-color: #fff; }
#searchGo.searching .search-go-label { display: none; }
#searchGo.searching .fbmr-spinner { display: block; }
/* Dropdown: left-aligned under the field, same width; a sticky "within filters" toggle above scrollable results. */
#searchDropdown {
  position: absolute; top: 46px; left: 0; right: 0;
  max-height: min(74vh, 600px);
  background: var(--bg); color: var(--text); border: 1px solid var(--border); border-radius: 6px;
  box-shadow: 0 4px 18px rgba(0,0,0,.6);
  display: flex; flex-direction: column; overflow: hidden;
}
#searchDropdown[hidden] { display: none; }
#searchWithin {
  display: flex; align-items: center; gap: 8px; flex: 0 0 auto;
  padding: 9px 40px 9px 12px; font-size: 12px; cursor: pointer; border-bottom: 1px solid var(--divider);
}
#searchWithin input { cursor: pointer; }
/* Close-the-results button, top-right of the dropdown; shown only while a multi-result list is displayed. */
.search-results-close {
  position: absolute; top: 6px; right: 7px; z-index: 2;
  display: flex; align-items: center; justify-content: center; width: 26px; height: 26px;
  padding: 0; border: none; border-radius: 5px; background: transparent; color: var(--muted); cursor: pointer;
}
.search-results-close:hover { color: var(--text); background: var(--hover); }
.search-results-close[hidden] { display: none; }
.search-results-close .ic { width: 16px; height: 16px; }
/* Chip naming the uploaded image whose results are shown, with a × to drop it and return to the hint. */
#searchFileChip {
  display: flex; align-items: center; gap: 6px; flex: 0 0 auto;
  padding: 7px 10px; font-size: 12px; border-bottom: 1px solid var(--divider); color: var(--text);
}
#searchFileChip[hidden] { display: none; }
#searchFileChip .ic { width: 15px; height: 15px; flex: none; color: var(--accent); }
#searchFileName { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
#searchFileClear {
  display: flex; align-items: center; justify-content: center; flex: none; width: 22px; height: 22px;
  padding: 0; border: none; border-radius: 4px; background: transparent; color: var(--muted); cursor: pointer;
}
#searchFileClear:hover { color: var(--text); background: var(--hover); }
#searchFileClear .ic { width: 14px; height: 14px; color: currentColor; }
/* "Upload an image" button inside the photo hint item. */
.search-hint-upload {
  margin-top: 6px; display: inline-flex; align-items: center; gap: 6px;
  padding: 5px 11px; border: 1px solid var(--accent); border-radius: 999px;
  background: transparent; color: var(--accent); font-size: 12px; font-weight: 600; cursor: pointer;
}
.search-hint-upload:hover { background: color-mix(in srgb, var(--accent) 14%, transparent); }
.search-hint-upload .ic { width: 14px; height: 14px; }
/* container-type so the shared result card (.dup-candidate) stacks in this narrow dropdown while staying a row
   in the wider duplicate modal — both on the same desktop screen (see details.css @container). */
#searchResults { flex: 1; min-height: 0; overflow-y: auto; overflow-x: hidden; display: flex; flex-direction: column; gap: 6px; padding: 6px; container-type: inline-size; }
#searchResults:empty { display: none; }
/* Rich result rows reuse the duplicate-candidate card (.dup-candidate, details.css); make the whole row clickable. */
#searchResults .dup-candidate { cursor: pointer; margin: 0; }
#searchResults .dup-candidate:hover { border-color: var(--accent); }
.search-empty { padding: 10px 12px; font-size: 12px; }
.search-loading { display: flex; flex-direction: column; align-items: center; gap: 8px; padding: 16px 12px; font-size: 12px; text-align: center; }
.search-loading-note { font-size: 11px; }
/* What-can-I-search hint, shown when the box opens or the query is too short. */
.search-hint { display: flex; flex-direction: column; gap: 12px; padding: 12px 12px 14px; }
.search-hint-item { display: flex; gap: 10px; align-items: flex-start; font-size: 12px; line-height: 1.35; }
.search-hint-item b { font-weight: 600; }
.search-hint-ic { flex: none; display: flex; width: 26px; height: 26px; align-items: center; justify-content: center;
  border-radius: 7px; background: var(--hover); color: var(--accent); }
.search-hint-ic .ic { width: 15px; height: 15px; }
.search-hint-eg { margin-top: 4px; font-size: 10.5px; color: var(--muted); font-family: var(--mono); line-height: 1.5; word-break: break-all; }
/* Mobile: the opened search spans the full top width (below the ☰ / List controls); collapsed, it stays a
   right-aligned magnifier. */
@media (max-width: 768px) {
  #searchBar.open { left: 10px; right: 10px; }
  #searchBar.open #searchField { width: 100%; }
  /* The map fills the screen here, so pin these to the VIEWPORT — the same frame #mobileBar opposite them
     already uses — instead of to #map. Anchored to #map they moved with any page scroll while the fixed
     left-hand pair did not, so the two halves of the top row drifted apart and clipped off the top edge.
     Sharing one frame makes that impossible rather than merely unlikely. */
  .leaflet-top.leaflet-right { position: fixed; right: 0; }
  #searchBar { position: fixed; }
}
/* Ghost pin: a search hit the current filters exclude — visible but clearly not "one of the results". */
.pin-wrap.ghost { opacity: .5; }
.pin-wrap.ghost .price-pin { border-style: dashed; }
/* The locate control sits at Leaflet's own 10px inset — the top-right corner is its, and #searchBar is the
   one offset below (it used to be the other way round). */
.leaflet-top.leaflet-right { top: 0; }

/* ==== list panel ==== */
#listToggle {
  position: absolute; top: 10px; left: 272px; z-index: 1300;
  display: flex; align-items: center; gap: 6px;
  background: var(--panel); color: var(--text); border: 1px solid var(--border); border-radius: 4px;
  padding: 8px 12px; font-size: 13px; cursor: pointer; box-shadow: 0 1px 6px rgba(0,0,0,.5);
}
#listToggle:hover { border-color: var(--accent); }
#listToggle.on { display: none; }   /* the open panel has its own × */
#listPanel {
  background: var(--bg); color: var(--text); display: flex; flex-direction: column;
  border-right: 1px solid var(--divider); z-index: 900;
}
#listPanel[hidden] { display: none; }
.list-head { display: flex; align-items: center; gap: 8px; padding: 10px 12px; border-bottom: 1px solid var(--divider); }
.list-head select { background: var(--panel); color: var(--text); border: 1px solid var(--border); border-radius: 4px; padding: 4px 6px; font-size: 12px; }
.list-inview { font-size: 12px; color: var(--muted); white-space: nowrap; }
#listClose { margin-left: auto; background: none; border: none; color: var(--muted); font-size: 20px; cursor: pointer; }
#listRows { flex: 1; overflow-y: auto; }
#listMore { padding: 8px 12px; border-top: 1px solid var(--divider); }
.list-row { display: flex; gap: 10px; padding: 8px 12px; cursor: pointer; border-bottom: 1px solid var(--divider); }
.list-row:hover { background: var(--hover); }
.list-row.selected { background: var(--sel); box-shadow: inset 3px 0 0 var(--accent); }
.list-row.visited { opacity: .8; }
.row-thumb { width: 56px; height: 46px; flex: 0 0 56px; border-radius: 4px; overflow: hidden; background: var(--thumb); }
.row-thumb img { width: 100%; height: 100%; object-fit: cover; }

/* Photo placeholders — a muted glyph on the thumb surface, for listings with no image mirrored to disk.
   .ph fills its container (the 56×46 thumb); .ph-big is the sheet's lead slot (icon + caption, stacked). */
.ph { width: 100%; height: 100%; display: flex; align-items: center; justify-content: center; gap: 6px;
  color: var(--muted); background: var(--thumb); }
.ph .ic { width: 22px; height: 22px; }
/* 2 / 1 is the generated collage's exact ratio (Collage.RenderAsync: 4 cells wide × 2 tall) — the placeholder
   occupies the same lead slot, so the sheet body doesn't shift depending on whether photos were mirrored. */
.ph-big { flex-direction: column; aspect-ratio: 2 / 1; border-radius: 4px; padding: 12px; text-align: center; }
.ph-big .ic { width: 40px; height: 40px; }
.ph-big span { font-size: 12px; color: var(--muted); }
.row-main { min-width: 0; flex: 1; font-size: 12px; }
.row-top { display: flex; align-items: baseline; gap: 6px; font-size: 13px; }
.row-stars { color: #f5c518; font-size: 11px; }
.row-tags { display: flex; flex-wrap: wrap; gap: 3px; margin: 2px 0; }
.row-tags .chip { border: 1px solid; border-radius: 9px; padding: 0 6px; font-size: 10px; background: var(--hover); }
.row-loc, .row-date { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
@media (min-width: 769px) {
  #listPanel { width: 300px; flex: 0 0 300px; }
}

/* Server-rendered SEO seed (Map.razor) — real <a> rows behind the List button, replaced by list.js's own
   .list-row markup on first open (list.js:60). .list-row already covers layout; these add only what the
   anchor needs on top and the intro/nav copy this seed carries that the JS-rendered rows don't. */
#listRows h1 { font-size: 15px; font-weight: 600; padding: 10px 12px 0; margin: 0; }
#listRows h1 + p { font-size: 12px; color: var(--muted); padding: 4px 12px 8px; margin: 0; }
.list-row.seo-row { color: inherit; text-decoration: none; }
.seo-place-nav { display: flex; flex-wrap: wrap; gap: 6px; padding: 12px; border-top: 1px solid var(--divider); }
.seo-place-nav a { font-size: 12px; color: var(--accent); text-decoration: none; }
.seo-place-nav a:hover { text-decoration: underline; }

/* ==== mobile list view: full-screen overlay driven by the floating #viewToggle. On desktop the list is a
   side-by-side column driven by #listToggle instead. ==== */
@media (max-width: 768px) {
  /* The floating toggle replaces the top-left List button on mobile. */
  #listToggle { display: none; }
  /* padding-top clears the floating #mobileBar, which sits over this panel at the same spot it occupies on
     the map — so the list header starts below it instead of underneath it. */
  #listPanel { position: fixed; inset: 0; z-index: 1350; padding-top: 52px; }
  #listClose { display: none; }                 /* the floating toggle is the way back to the map */
}

/* ==== toasts: non-blocking failure notices (replaced alert()) ==== */
.toast-host {
  position: fixed; left: 50%; bottom: 24px; transform: translateX(-50%);
  z-index: 3000; display: flex; flex-direction: column; gap: 8px; align-items: center;
  pointer-events: none; max-width: min(92vw, 460px);
}
.toast {
  pointer-events: auto; cursor: pointer; display: flex; align-items: flex-start; gap: 10px;
  background: var(--panel); color: var(--text); border: 1px solid var(--divider);
  border-left: 3px solid #e07070; border-radius: 6px; box-shadow: 0 4px 16px rgba(0, 0, 0, .35);
  font-size: 13px; line-height: 1.45; padding: 10px 12px;
  animation: toast-in .16s ease-out;
}
.toast-info { border-left-color: var(--accent); }
.toast-error span:first-child { color: #e07070; }
.toast-x { background: none; border: none; color: var(--muted); cursor: pointer; font-size: 12px; line-height: 1; padding: 2px; }
.toast-x:hover { color: var(--text); }
.toast-out { opacity: 0; transform: translateY(6px); transition: opacity .16s, transform .16s; }
@keyframes toast-in { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: none; } }

/* Credit stays (OSM requires it) but recedes: the map is the content. */
.leaflet-control-attribution { font-size: 8px; line-height: 1.2; padding: 0 3px; opacity: .65; }
.leaflet-control-attribution svg { width: 8px !important; height: 6px !important; }
