/* ============================================================================
   Explore view - split "list + Florida map" layout, county cluster bubbles,
   and the card/filter-bar polish that goes with them.

   Deliberately a SEPARATE stylesheet from styles.css rather than edits spread
   through it: everything the explore view adds (and the handful of overrides
   it applies to existing card/filter classes) lives in one file that can be
   removed in one line from index.html if it ever needs to come back out.
   It loads AFTER styles.css, so plain-specificity overrides win without
   needing !important anywhere.

   Uses the same palette variables styles.css defines (--ink, --card, --accent,
   --ok, --brass, ...) so it themes light/dark with the rest of the app for
   free - no second set of colors to keep in sync.
   ========================================================================= */

/* ---- Map page (Phase 54) ---------------------------------------------------
   A real top-level page (#pageMap), not a mode of Auctions - see the Phase 54
   comment on SHELL_PAGES/showPage() in app.js. Its header and toolbar are
   deliberately its OWN chrome, not the Auctions masthead/ledger-tabs/
   quick-controls reused with a flag flipped, so landing here reads as
   arriving somewhere rather than a panel swap. */
.map-page-head{padding:1.3rem 1.6rem .2rem}
.map-page-head h1{font-family:var(--font-sans);font-weight:800;
  font-size:1.5rem;letter-spacing:-.01em;margin:0 0 .2rem;color:var(--ink)}
.map-page-head p{margin:0;font-size:.82rem;color:var(--ink-soft)}
@media (max-width:640px){ .map-page-head{padding:1rem 1rem .1rem} }

.map-toolbar{display:flex;flex-wrap:wrap;align-items:center;gap:.5rem;
  padding:.9rem 1.6rem}
@media (max-width:640px){ .map-toolbar{padding:.8rem 1rem} }
.map-toolbar .qc-field{flex:1 1 220px;min-width:150px;max-width:340px}
.map-toolbar .qc-field.qc-county{flex:0 1 190px}
/* The pill row is wider than a phone screen at 4 ledgers + a watch pill -
   its own horizontal scroller (same fade-scroll pattern .ledger-tabs/
   .summary-strip already use) rather than letting it get clipped by the
   viewport edge. */
@media (max-width:640px){
  .map-ledger-pills{flex:1 1 100%;max-width:100%;overflow-x:auto;
    scrollbar-width:none;-webkit-mask-image:linear-gradient(to right,#000 calc(100% - 24px),transparent);
    mask-image:linear-gradient(to right,#000 calc(100% - 24px),transparent)}
  .map-ledger-pills::-webkit-scrollbar{display:none}
  .map-watch-pill{flex:1 1 100%;justify-content:center}
}

/* Colored-dot pills: one visual language for "which ledger" (matching the
   ledger's own accent colour - see --led-accent in styles.css) and a
   separate, deliberately different colour for "watchlist only", which is a
   cross-ledger flag rather than a ledger. */
.map-ledger-pills{flex:0 0 auto;display:inline-flex;align-items:center;
  background:var(--paper);border:1px solid var(--card-line);border-radius:999px;
  padding:3px;gap:2px}
.map-ledger-pills button{font-family:inherit;font-size:.74rem;font-weight:700;
  color:var(--ink-soft);background:none;border:none;border-radius:999px;
  padding:.42rem .72rem;cursor:pointer;white-space:nowrap;display:inline-flex;
  align-items:center;gap:.4rem;line-height:1}
.map-ledger-pills button:hover{color:var(--ink)}
.map-ledger-pills button.on{background:var(--card);color:var(--ink);
  box-shadow:var(--shadow)}
.map-watch-pill{flex:0 0 auto;font-family:inherit;font-size:.74rem;font-weight:700;
  color:var(--ink-soft);background:var(--paper);border:1px solid var(--card-line);
  border-radius:999px;padding:.44rem .8rem;cursor:pointer;white-space:nowrap;
  display:inline-flex;align-items:center;gap:.4rem;line-height:1}
.map-watch-pill:hover{color:var(--ink)}
.map-watch-pill.on{background:var(--watch-soft);color:var(--watch);
  border-color:var(--watch)}
.pill-dot{display:inline-block;width:8px;height:8px;border-radius:50%;
  background:var(--ink-soft)}
.pill-dot-all{background:linear-gradient(135deg,#2563EB 0 50%,#7C3AED 50%)}
.map-ledger-pills [data-ledger="auction"] .pill-dot{background:#2563EB}
.map-ledger-pills [data-ledger="laft"] .pill-dot{background:#047857}
.map-ledger-pills [data-ledger="certificate"] .pill-dot{background:#7C3AED}
:root[data-theme="dark"] .map-ledger-pills [data-ledger="auction"] .pill-dot{background:#60A5FA}
:root[data-theme="dark"] .map-ledger-pills [data-ledger="laft"] .pill-dot{background:#34D399}
:root[data-theme="dark"] .map-ledger-pills [data-ledger="certificate"] .pill-dot{background:#A78BFA}
@media (prefers-color-scheme:dark){
  :root:not([data-theme="light"]) .map-ledger-pills [data-ledger="auction"] .pill-dot{background:#60A5FA}
  :root:not([data-theme="light"]) .map-ledger-pills [data-ledger="laft"] .pill-dot{background:#34D399}
  :root:not([data-theme="light"]) .map-ledger-pills [data-ledger="certificate"] .pill-dot{background:#A78BFA}
}
.pill-dot-watch{background:var(--watch)}

/* Full-bleed stage: the map is the whole page now, not a companion to a
   list, so it gets the width and height budget that implies. */
.map-page-body{padding:0 1.6rem 1.6rem}
@media (max-width:640px){ .map-page-body{padding:0 1rem 1rem} }
@media (max-width:640px){
  .explore-map-head{flex-wrap:wrap;gap:.4rem .6rem}
  .explore-map-head-right{gap:.5rem}
  .map-style-toggle button{padding:.28rem .5rem;font-size:.64rem}
}
.map-page-map{max-width:none}

/* ---- map panel chrome ---------------------------------------------------- */
.explore-map{background:var(--card);border:1px solid var(--card-line);
border-radius:4px;box-shadow:var(--shadow);padding:.75rem .8rem .6rem;
display:flex;flex-direction:column;gap:.5rem}
.explore-map-head{display:flex;align-items:baseline;justify-content:space-between;
gap:.6rem;flex-wrap:wrap}
.explore-map-title{font-family:var(--font-sans);font-weight:800;font-size:.95rem;
color:var(--ink);margin:0}
.explore-map-count{font-family:var(--font-mono);
font-variant-numeric:tabular-nums;font-size:.74rem;color:var(--ink-soft)}
.explore-map-count b{color:var(--accent)}
.explore-map-head-right{display:flex;align-items:center;gap:.7rem}

/* Phase 55: Map / Satellite - same segmented-pill idiom as .map-ledger-pills,
   scaled down to fit a card header rather than the page toolbar. */
.map-style-toggle{flex:0 0 auto;display:inline-flex;align-items:center;
  background:var(--paper);border:1px solid var(--card-line);border-radius:999px;
  padding:2px;gap:2px}
.map-style-toggle button{border:none;background:none;font-family:var(--font-sans);
  font-weight:700;font-size:.68rem;color:var(--ink-soft);padding:.3rem .62rem;
  border-radius:999px;cursor:pointer;line-height:1}
.map-style-toggle button.on{background:var(--card);color:var(--ink);box-shadow:var(--shadow)}
.map-style-toggle button:focus-visible{outline:2px solid var(--accent);outline-offset:1px}
/* Honest about what the dots mean: with geocoding still sparse, a bubble is a
   county-level cluster, not a parcel pin. Saying so here is cheaper than
   someone assuming a dot is a street address and driving to it. */
.explore-map-note{font-size:.66rem;line-height:1.35;color:var(--ink-soft);margin:0}
.explore-map-canvas{position:relative;border-radius:3px;overflow:hidden;
background:var(--paper)}
/* fl-counties.svg's viewBox is 1170 x 1115. Kept as a variable so the
   height cap and the width cap can't drift apart - if they do, the element
   stops matching its content and the letterbox comes back. tx-counties.svg
   was built with a different viewBox (1000 x 1006, ~square rather than
   FL's tall/narrow shape) so the TX page overrides it below - a value tuned
   for FL's silhouette would letterbox or crop Texas's. */
/* Set on .explore-map (the card that wraps BOTH basemaps), not on
   .explore-map-canvas alone - Phase 55's satellite-map-canvas is a sibling of
   the outline canvas, not a descendant of it, so a custom property scoped to
   .explore-map-canvas would never reach it (custom properties inherit down
   the tree from whatever element declares them, not sideways to siblings).
   One definition here, both basemaps read it via var(--map-aspect) below. */
.explore-map{--map-aspect:1.0493}
body[data-state="TX"] .explore-map{--map-aspect:0.994}
.explore-map-canvas svg{width:100%;height:auto;display:block;
  max-height:64vh;max-width:calc(64vh * var(--map-aspect));
  border-radius:10px;border:1px solid var(--card-line)}

/* Phase 55: the satellite/terrain basemap shares the outline map's stage and
   aspect ratio (--map-aspect, set per state just above) so switching styles
   doesn't reflow the card around it. Real height (not height:auto like the
   SVG) because the map library needs a sized box to render into - it
   measures the container at init and on resize, it doesn't shrink-wrap
   content. Plain [hidden] would lose to the flex/aspect-ratio rules below on
   specificity (same selector weight, and this stylesheet loads after the UA
   sheet), so the attribute gets its own rule rather than relying on the
   default. */
.satellite-map-canvas{position:relative;width:100%;aspect-ratio:var(--map-aspect);
  max-height:64vh;border-radius:10px;border:1px solid var(--card-line);
  overflow:hidden;background:var(--map-land)}
.satellite-map-canvas[hidden]{display:none}
/* The outline canvas has always set display:flex directly (never toggled
   hidden before Phase 55's style switch existed) - same specificity tie as
   above, same fix: give [hidden] the extra selector weight it needs to win. */
.explore-map-canvas[hidden]{display:none}
/* Phase 56/57/60/61: this class is shared by BOTH providers' canvases, each
   its own DOM node as of Phase 61 (see satellite-map.js's header for why
   they used to share one node, and why that was a bug - only the active
   one is ever un-hidden, but each keeps its own live map/markers now, no
   hand-off needed). Google Maps renders into a plain internal div structure
   and fills its parent (.satellite-map-canvas, already rounded+clipped via
   overflow:hidden above) with no extra rule needed. MapLibre GL (Phase 60's
   replacement for Mapbox GL - same CSS class prefix, see satellite-map.js's
   header) draws its own canvas/container that needs its corners explicitly
   rounded to match. */
.satellite-map-canvas .mapboxgl-canvas-container,
.satellite-map-canvas .mapboxgl-map{border-radius:10px}
.satellite-map-setup{position:absolute;inset:0;display:flex;flex-direction:column;
  align-items:center;justify-content:center;gap:.4rem;text-align:center;
  padding:1.5rem;font-family:var(--font-sans);color:var(--ink-soft);font-size:.8rem;
  line-height:1.5}
.satellite-map-setup b{color:var(--ink);font-size:.85rem}
.satellite-map-setup code{font-family:var(--font-mono);background:var(--paper);
  border:1px solid var(--card-line);border-radius:4px;padding:.05rem .32rem;font-size:.76rem}

/* County bubbles and property pins are plain positioned HTML - the `content`
   of a google.maps.marker.AdvancedMarkerElement on the Google path, a
   mapboxgl.Marker's element on the Mapbox path, same idea, different
   library - not canvas drawing, specifically so they can be styled with the
   same CSS custom properties and data-ledger convention the SVG map's own
   .cluster-bubble/.map-pin already use (see draw()/drawPins() in
   explore.js) - one accent language for all three basemaps instead of a
   second hard-coded palette that could quietly drift from the first. */
.sat-county-bubble{display:flex;align-items:center;justify-content:center;
  border-radius:50%;background:var(--accent);color:#fff;border:2px solid var(--card);
  box-shadow:0 1px 4px rgba(0,0,0,.35);font-family:var(--font-sans);font-weight:800;
  cursor:pointer;user-select:none}
.satellite-map-canvas[data-ledger="laft"] .sat-county-bubble{background:var(--brass)}
.satellite-map-canvas[data-ledger="certificate"] .sat-county-bubble{background:#7C3AED}
.sat-county-bubble.sel{background:var(--ok) !important}
.sat-pin{width:16px;height:16px;border-radius:50% 50% 50% 0;transform:rotate(-45deg);
  background:var(--bad);border:1.5px solid #fff;box-shadow:0 1px 3px rgba(0,0,0,.4);
  cursor:pointer}
.sat-pin.sel{background:var(--accent)}
/* google.maps.InfoWindow's own chrome (bubble background, close button,
   pointer tail) is styled via its .gm-style-iw* classes - Google reserves
   these class names and can change them between releases, and they render
   inside a closed shadow root in current versions besides, so overriding
   them isn't reliable. Left at Google's own default chrome on that path.
   MapLibre's popup chrome IS plain, overridable CSS, so that path gets
   themed to match the app - both paths' actual content (passed to
   setContent()/setDOMContent()) is the same .sat-popup-* markup either way.
   Phase 63: these were still named .mapboxgl-popup-* from when this file's
   comment (and satellite-map.js's provider) meant Mapbox GL JS itself.
   Phase 60 swapped the provider for MapLibre GL JS - an independent fork,
   not a Mapbox build - and MapLibre 4.x's own stylesheet ships its popup
   chrome under a .maplibregl-popup-* prefix, not .mapboxgl-popup-* (verified
   against the actual maplibre-gl@4.7.1 package, the version pinned in
   satellite-map.js's MAPLIBRE_GL_VERSION). So these rules never matched
   anything: every MapTiler property-pin popup rendered in MapLibre's bare
   default chrome (plain white, square corners, no shadow) instead of the
   app's card theme, however Marc reported it - "preview" covers this popup,
   not just the pin. */
.maplibregl-popup-content{background:var(--card) !important;color:var(--ink) !important;
  font-family:var(--font-sans) !important;border-radius:8px !important;
  box-shadow:var(--shadow) !important;padding:.7rem .8rem !important}
.maplibregl-popup-tip{border-top-color:var(--card) !important;border-bottom-color:var(--card) !important}
.sat-popup-addr{font-weight:700;font-size:.8rem;margin:0 0 .2rem}
.sat-popup-meta{font-size:.72rem;color:var(--ink-soft);margin:0 0 .5rem}
.sat-popup-open{font-family:var(--font-sans);font-size:.72rem;font-weight:700;
  color:#fff;background:var(--accent);border:none;border-radius:6px;padding:.3rem .6rem;
  cursor:pointer}

/* County shapes: quiet by default so the data bubbles on top are what the eye
   lands on - the reverse of the filter-panel picker map, where the shapes
   themselves are the interface. */
/* ==========================================================================
   THE BASEMAP
   --------------------------------------------------------------------------
   This was a grey silhouette on a white page: no water, no neighbours, no
   coastline. Florida read as a smudge rather than a peninsula, and the
   panhandle's northern edge - a land border with Georgia and Alabama - had
   nothing on the other side of it at all.

   fl-counties.svg now ships a sea rect, the three neighbouring states as
   flat landmasses, and orientation labels, all in the same projection and
   all same-origin. No tiles, no third-party requests, no CSP change - see
   the header of build_basemap.py.

   One palette, defined once, so the map is not a pile of one-off colours.
   Water and land are deliberately NOT --accent: the accent is the ledger's
   own colour and it changes per page, while the Gulf of Mexico does not.
   ========================================================================== */
:root{
  --map-sea:#D6E6F2; --map-sea-deep:#C3D9EA;
  --map-land:#F4F1EA; --map-land-line:#DAD4C6;
  --map-neighbor:#E7E4DC; --map-neighbor-ink:#A9A296;
  --map-coast:rgba(31,58,92,.30);
  /* Watchlist's own colour on the Map page toolbar - deliberately distinct
     from the three ledger accents (blue/green/purple), since "watchlist
     only" is a cross-ledger flag, not a fourth ledger. */
  --watch:#BE185D; --watch-soft:#FCE7F3;
}
:root[data-theme="dark"]{
  --map-sea:#0C1B2E; --map-sea-deep:#091524;
  --map-land:#1B2434; --map-land-line:#2E3A4D;
  --map-neighbor:#161E2B; --map-neighbor-ink:#5A6577;
  --map-coast:rgba(0,0,0,.55);
  --watch:#F472B6; --watch-soft:#3B1330;
}
@media (prefers-color-scheme:dark){
  :root:not([data-theme="light"]){
    --map-sea:#0C1B2E; --map-sea-deep:#091524;
    --map-land:#1B2434; --map-land-line:#2E3A4D;
    --map-neighbor:#161E2B; --map-neighbor-ink:#5A6577;
    --map-coast:rgba(0,0,0,.55);
    --watch:#F472B6; --watch-soft:#3B1330;
  }
}

/* The map is FRAMED, not bled.
   The SVG letterboxes whenever the panel is wider than the map's own
   aspect, which on a desktop full-map view was ~350px of empty on each
   side. Painting that margin white made the map look like it was floating;
   painting it sea-coloured looked better but was a lie - it turned the
   west edge of Alabama into open water and left the land ending in the
   middle of an ocean that carried on past it.
   Capping the SVG's width to the same aspect its height is capped at makes
   the element exactly fit its content, so the sea stops at a real edge and
   the surround is honestly the card, not the Gulf. */
/* Map and rail share a row on a wide screen and stack (rail hidden) below
   it - see the media query further down. */
.explore-map-stage{display:flex;gap:.9rem;align-items:stretch;justify-content:center}
/* flex:1 1 auto, NOT 0 1 auto. The SVG inside is width:100%, so an
   auto-width flex item gives it nothing to be 100% OF and the whole map
   collapses to ~300px. The canvas takes the row and centres the map in it. */
.explore-map-canvas{display:flex;justify-content:center;background:var(--paper);flex:1 1 auto;min-width:0}
.explore-map-rail{display:none}

/* The Map page has the whole viewport to itself now, not a companion-panel
   sliver, so it gets the full-size treatment unconditionally rather than
   behind a data-mode selector - there is only one mode. Florida's outline is
   nearly square, so the map's width is set by its height; 64vh alone left
   ~230px of empty either side even with the rail in place. */
/* Phase 67: the rail lives in the side panel now (see .map-side below), so
   it is a plain block whenever it isn't hidden, at every width. The old
   desktop-only svg height cap is superseded by the workspace rules at the
   end of this file: the stage has a real height and the map fills it. */
.map-side .explore-map-rail:not([hidden]){display:block}
.rail-head{font-size:.66rem;font-weight:700;letter-spacing:.06em;text-transform:uppercase;
  color:var(--ink-soft);padding:.15rem .35rem .4rem}
.rail-list{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:.1rem}
.rail-row{position:relative;width:100%;display:flex;align-items:baseline;gap:.5rem;
  font-family:inherit;font-size:.76rem;color:var(--ink);background:none;border:none;
  border-radius:3px;padding:.32rem .4rem;cursor:pointer;text-align:left;overflow:hidden}
.rail-row:hover{background:var(--accent-soft)}
.rail-row.on{background:var(--accent-soft);font-weight:700}
.rail-name{flex:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.rail-n{font-family:var(--font-mono);font-variant-numeric:tabular-nums;
  font-size:.72rem;font-weight:700;color:var(--accent)}
/* A bar rather than a second number: the count is already the number, and
   the point of the rail is the SHAPE of the distribution at a glance. */
.rail-bar{position:absolute;left:.4rem;right:.4rem;bottom:.12rem;height:2px;border-radius:2px;
  background:linear-gradient(to right,var(--accent) var(--w),transparent var(--w));opacity:.5}
.rail-row:focus-visible{outline:2px solid var(--accent);outline-offset:1px}
.map-sea{fill:var(--map-sea)}
/* Flat, unstroked, and a shade off the Florida land tone: present enough
   that the state line is a border between two lands rather than a coast,
   quiet enough that it never competes with the counties. */
/* Stroked in its own fill colour, not left unstroked. The three states are
   concatenated county rings sharing edges, and two identically-filled
   polygons that share an edge leave a one-pixel antialiasing seam along it -
   invisible statewide, a set of grey creases across Alabama once a county
   zoom magnifies the map. A hairline stroke of the same colour closes them. */
.neighbor-land{fill:var(--map-neighbor);stroke:var(--map-neighbor);stroke-width:1;
  stroke-linejoin:round}
/* The coastline, for free: one shadow under the whole county group, always
   in register with the counties because it IS the counties. A stroked
   duplicate of all 67 paths would have added 33KB to the offline shell. */
.map-counties{filter:drop-shadow(0 0 2.5px var(--map-coast))}
/* ...but not while zoomed into a county. A CSS filter rasterises its whole
   group at the current scale and clips to a filter region computed from the
   group's bounding box - at county zoom that meant visible straight-edged
   seams across the map where the region ran out, and a full-state raster
   being regenerated on every frame of the zoom tween. The coastline halo is
   statewide scenery; zoomed in, the county's own outline does that job. */
.explore-map-canvas.zoomed .map-counties{filter:none}
.map-label{font-family:var(--font-sans);text-anchor:middle;pointer-events:none;
  font-weight:600;letter-spacing:.22em;font-size:19px}
.map-label-sea{fill:#5B87AE;opacity:.85}
.map-label-land{fill:var(--map-neighbor-ink);letter-spacing:.3em;font-size:17px}
:root[data-theme="dark"] .map-label-sea{fill:#4A6E92}
@media (prefers-color-scheme:dark){:root:not([data-theme="light"]) .map-label-sea{fill:#4A6E92}}
/* Statewide furniture. Blown up to a single county's scale, "GEORGIA" in
   90px letters across the top is absurd. */
.explore-map-canvas.zoomed .map-context{display:none}

.explore-map-canvas path[data-county]{fill:var(--map-land);stroke:var(--map-land-line);
stroke-width:1;transition:fill .12s}
.explore-map-canvas path[data-county].has-rows{fill:color-mix(in srgb,var(--accent) 22%,var(--map-land))}
.explore-map-canvas path[data-county].sel{fill:color-mix(in srgb,var(--accent) 42%,var(--map-land))}

/* ---- county cluster bubbles ----------------------------------------------
   One per county with matching rows, radius scaled by count (sqrt, so a
   40-property county doesn't drown a 4-property one at 10x the area). Same
   read as the numbered clusters on a listing map, drawn in SVG space over
   the state outline. */
.cluster-bubble{cursor:pointer}
.cluster-bubble circle{fill:var(--accent);stroke:var(--card);stroke-width:2;
transition:transform .12s,fill .12s;transform-box:fill-box;transform-origin:center}
.cluster-bubble text{font-family:var(--font-sans);font-weight:800;fill:#fff;
text-anchor:middle;dominant-baseline:central;pointer-events:none;
paint-order:stroke;letter-spacing:-.02em}
.cluster-bubble:hover circle{transform:scale(1.12)}
.cluster-bubble.sel circle{fill:var(--ok)}
.cluster-bubble.dim circle{opacity:.35}
/* Ledger-specific bubble colors, so switching tabs is visible on the map too
   and matches each ledger's own accent elsewhere in the app. */
.explore-map-canvas[data-ledger="laft"] .cluster-bubble circle{fill:var(--brass)}
.explore-map-canvas[data-ledger="certificate"] .cluster-bubble circle{fill:#7C3AED}
/* Scale (2026-10-02): pin clusters inside a zoomed county (explore.js
   drawPins) - one circle per screen cell with its exact count. */
.pin-cluster{cursor:pointer}
.pin-cluster circle{fill:var(--accent);fill-opacity:.88;stroke:var(--card);stroke-width:2;vector-effect:non-scaling-stroke}
.pin-cluster text{font-family:var(--font-sans);font-weight:800;fill:#fff;text-anchor:middle;dominant-baseline:central;pointer-events:none}
.pin-cluster:hover circle,.pin-cluster:focus-visible circle{fill-opacity:1;stroke:var(--ink)}
.explore-map-canvas[data-ledger="laft"] .pin-cluster circle{fill:var(--brass)}
.explore-map-canvas[data-ledger="certificate"] .pin-cluster circle{fill:#7C3AED}
.strip-more{flex:0 0 auto;align-self:stretch;padding:.4rem .7rem;font-size:.78rem;font-weight:600;color:var(--accent);background:transparent;border:1px dashed var(--accent);border-radius:8px;cursor:pointer}
.explore-map-canvas[data-ledger="laft"] .cluster-bubble.sel circle,
.explore-map-canvas[data-ledger="certificate"] .cluster-bubble.sel circle{fill:var(--ok)}

/* Tooltip follows the pointer rather than anchoring to the bubble - a bubble
   near the panel edge would otherwise push its own tooltip off-screen. */
.cluster-tip{position:absolute;pointer-events:none;z-index:5;
background:var(--header-bg);color:#fff;border-radius:8px;padding:.4rem .55rem;
font-size:.7rem;line-height:1.35;box-shadow:var(--shadow);opacity:0;
transition:opacity .1s;max-width:210px}
.cluster-tip.show{opacity:1}
.cluster-tip b{display:block;font-size:.76rem;margin-bottom:.1rem}
.cluster-tip span{color:#CBD5E1}

.explore-map-empty{padding:1.6rem .8rem;text-align:center;font-size:.78rem;
color:var(--ink-soft)}
/* Bubble-size key. Content and dot sizes are computed per-draw in
   renderBubbleLegend() (explore.js) from the counties actually on screen -
   this file only supplies the chrome. */
.explore-map-legend{display:flex;align-items:center;flex-wrap:wrap;gap:.55rem .9rem;
  padding:.1rem .1rem .05rem}
.explore-map-legend[hidden]{display:none}
.map-legend-label{font-size:.66rem;color:var(--ink-soft)}
.map-legend-item{display:inline-flex;align-items:center;gap:.32rem;
  font-family:var(--font-mono);font-variant-numeric:tabular-nums;
  font-size:.72rem;font-weight:700;color:var(--ink)}
.map-legend-dot{display:inline-block;width:var(--d,10px);height:var(--d,10px);
  border-radius:50%;background:var(--accent);flex:0 0 auto}
/* Match whichever ledger's bubbles are currently drawn (see the equivalent
   .cluster-bubble overrides above) - .explore-map-legend is a sibling of
   the canvas, not a descendant, so this keys off the canvas's own
   data-ledger via the shared .explore-map wrapper. */
.explore-map:has(.explore-map-canvas[data-ledger="laft"]) .map-legend-dot{background:var(--brass)}
.explore-map:has(.explore-map-canvas[data-ledger="certificate"]) .map-legend-dot{background:#7C3AED}
.explore-map-foot{display:flex;align-items:center;justify-content:space-between;
gap:.5rem;flex-wrap:wrap}
.explore-map-hint{font-size:.66rem;color:var(--ink-soft)}
.explore-map-reset{font-size:.7rem;font-weight:700;color:var(--accent);
background:none;border:none;cursor:pointer;padding:.2rem 0;font-family:inherit}
.explore-map-reset[hidden]{display:none}

/* ---- card polish ----------------------------------------------------------
   Overrides, not a rewrite: card()/certCard() in app.js keep emitting exactly
   the same markup. This tightens the visual hierarchy toward what a listing
   card should lead with - price first, address second, everything else
   quieter - and adds the hover affordance the old flat cards were missing. */
.prop-card{transition:border-color .12s,box-shadow .12s,transform .12s}
.prop-card:hover{border-color:color-mix(in srgb,var(--accent) 45%,var(--card-line));
box-shadow:0 1px 2px rgba(15,23,42,.06),0 8px 22px rgba(15,23,42,.10)}
@media (prefers-reduced-motion:no-preference){
  @media (min-width:1024px){ .prop-card:hover{transform:translateY(-1px)} }
}
/* Highlighted from the map: hovering a county bubble lifts that county's
   cards so the link between the two panes is visible, not implied. */
.county-group.map-focus{border-color:var(--accent);
box-shadow:0 0 0 2px color-mix(in srgb,var(--accent) 22%,transparent),var(--shadow)}

.prop-address{font-size:.95rem;letter-spacing:-.01em}
/* The two headline figures are the whole pitch of a listing (cost vs worth),
   so they get real size instead of sitting in same-weight boxes. */
.card-stat-headline .card-stat-val{font-size:1.18rem;letter-spacing:-.02em}
/* Phase 65: on a phone the two headline boxes are ~100-120px wide and a
   "$11,000.00" at 1.18rem mono wrapped mid-number ("$11,000.0 / 0"). Scale
   the figure with the viewport so a 9-10 character amount fits on one line
   at 360px (measured: box 103px); .card-stat-val's overflow-wrap stays as
   the last-resort fallback rather than clipping digits off a price. Lives
   here, not in styles.css, because this later-loaded rule above would
   otherwise override any same-specificity rule there. */
@media (max-width:480px){
  .card-stat-headline .card-stat-val{font-size:clamp(.82rem,4.8vw,1.18rem)}
}
.card-stat-label{font-size:.6rem}
.prop-county-tag{letter-spacing:.04em}

/* "No published price" is not a price. The LAFT sync writes bid = 0 to mean
   the county publishes no figure, and rendering that as "$0.00" in the same
   alarm red as a real opening bid states something false on a bidding tool.
   Muted, smaller, and in words - it should read as an absence. Words, not a
   figure, so it also drops the tabular monospace the numbers use: it reads
   better, and at roughly two-thirds the width "Not published" fits on one
   line inside a narrow certificate tile instead of wrapping and making that
   box taller than the two beside it. */
.card-stat-val.unpublished{font-family:inherit;color:var(--ink-soft);font-size:.85rem;
font-weight:600;letter-spacing:0;white-space:nowrap}
.card-stat-headline .card-stat-val.unpublished{font-size:1rem}

/* When there is no market/assessed figure at all, the second headline box
   was a large "N/A" occupying half the most valuable space on the card -
   most visible on exactly the LAFT rows that have the least data. Give the
   opening bid the full width instead of pairing it with a placeholder. */
.card-stat-grid.card-stat-grid-1{grid-template-columns:1fr}

/* ---- filter bar -----------------------------------------------------------
   Same controls, laid out as one clean horizontal strip: search grows, the
   county picker and the buttons hold their width, and everything shares a
   single height so the row reads as a toolbar instead of stacked widgets. */
.quick-controls{align-items:center;gap:.45rem;padding:.55rem .6rem;
border-radius:3px;}
.quick-controls .qc-field input,
.quick-controls .qc-field select{height:2.15rem;font-size:.8rem;border-radius:8px}
.quick-controls .expand-all-btn,
.quick-controls .export-btn{height:2.15rem;display:inline-flex;align-items:center;
border-radius:3px;}
.qc-field{flex:1 1 200px;min-width:150px}
.qc-field.qc-county{flex:0 1 190px}
@media (min-width:1024px){
  .quick-controls{flex-wrap:nowrap}
  .qc-field{flex:1 1 auto}
}
/* P2 fix (phase 33, see the "tax florida app" Project's phase-32e audit,
   which documented this but left it unfixed): the row above goes
   flex-wrap:nowrap at every width >=1024px, but the items in it
   (search, county picker, table-toggle, expand-all, export,
   filters-toggle) are mostly flex:0 0 auto - fixed width, no shrink. Their
   combined minimum width doesn't clear the container until roughly
   1305px, so from 1024-1305px the row overflows its own box and the page
   gains an unwanted horizontal scrollbar (worse once #filtersBadge is
   showing). Re-allowing wrap only in that narrow band - the same
   fallback this row already uses below 1024px - lets the row spill onto
   a second line instead of overflowing sideways. Bounded so it changes
   nothing above 1305px (already fits on one line) or below 1024px
   (already wraps via .quick-controls's own base rule in styles.css). */
@media (min-width:1024px) and (max-width:1305px){
  .quick-controls{flex-wrap:wrap}
}

/* Ledger tabs read as the primary switch they are: bigger, evenly weighted,
   with the count set apart rather than crammed against the label. */
.ledger-tab{padding:.62rem .8rem;font-size:.82rem;border-radius:3px;}
.ledger-tab.on{padding:.57rem .75rem}
.ledger-tab b{margin-left:.1rem;padding:.05rem .38rem;border-radius:999px;
background:rgba(255,255,255,.12);font-size:.78rem}
.ledger-tab.on b{background:rgba(96,165,250,.28)}

/* ---- focus visibility -----------------------------------------------------
   Both the filter chips and the map bubbles are controls built out of
   non-interactive elements (<span>, SVG <g>), so neither gets a focus ring
   from the browser. Once they're keyboard-operable they need a visible one,
   or focus becomes invisible - worse than not being focusable at all. */
.chipx:focus-visible,
.cluster-bubble:focus-visible{outline:2px solid var(--accent);outline-offset:2px;
border-radius:999px}
/* Firefox draws SVG focus outlines tight to the glyph; a ring on the circle
   itself reads more clearly at bubble sizes. */
.cluster-bubble:focus-visible circle{stroke:var(--ink);stroke-width:3}

/* ---- print / reduced motion ---------------------------------------------- */
@media (prefers-reduced-motion:reduce){
  .cluster-bubble circle,.prop-card{transition:none}
  .cluster-bubble:hover circle{transform:none}
}

/* ==========================================================================
   COMMAND-BAR HEADER
   --------------------------------------------------------------------------
   The header used to run roughly 700px before the first property card: a
   sticky brand strip, then a second 48px logo and <h1> repeating it, a
   marketing tagline, a boxed disclaimer, a timestamp line, ledger tabs, five
   status chips, a three-row control block, and - at >=1024px - the entire
   Filters & Sort panel expanded by default with no way to collapse it. On a
   915px-tall phone you scrolled a full screen and a half of chrome to reach
   one listing; on a 900px desktop you saw no listing and no map at all.

   Everything below is layout only. No control is removed, no label reworded,
   no legal text dropped - the disclaimer still shows on every page load, just
   as one line instead of a box. The pieces that were duplicating the sticky
   bar are the only things hidden.
   ========================================================================== */

/* 1. (was: hiding a second 48px brand lockup that the .topbar already
      showed - the masthead carries no brand at all now, so there is nothing
      left to hide. See index.html.) */
.masthead{padding:.55rem 1rem}
.header-btns .hbtn{font-size:.7rem;padding:.28rem .5rem}

/* 4. Ledger tabs: still the primary switch, just not 3rem tall. */
.ledger-tabs{margin:.6rem 0 .45rem;gap:.3rem}
/* Three tab labels plus icons and counts measured 445px against a 328-380px
   viewport, so the strip always started mid-scroll with a tab clipped off
   each edge. These are the app's primary navigation now - being able to see
   all three at once matters more here than it did when they were a
   secondary switcher. Tightening the icon, the type and the padding gets it
   under the fold on a 360px phone; the active tab is also scrolled into view
   from render() as a backstop for the very narrowest screens. */
.ledger-tab-icon{font-size:.82rem}
.ledger-tab{flex:1 1 0;justify-content:center;white-space:nowrap;
padding:.45rem .6rem;font-size:.76rem;border-radius:9px}
.ledger-tab.on{padding:.4rem .55rem}
.ledger-tab b{font-size:.72rem}

/* 5. Status chips: one scrollable row instead of wrapping onto two or three. */
.summary-strip{margin:0 0 .55rem;gap:.35rem;flex-wrap:nowrap}
.summary-strip .chip{flex:0 0 auto;font-size:.7rem;padding:.3rem .6rem}

/* 6. The control bar is the one thing that should stay put while the list
      scrolls, at every width - it was sticky on mobile only, and static on
      desktop where the filters panel above it was tall enough to push it off
      screen entirely. */
.quick-controls{position:sticky;top:0;z-index:30;margin:0;
border-radius:10px;box-shadow:0 1px 0 rgba(15,23,42,.04)}

/* 7. Filters & Sort now lives in the bar and stays closed until asked for.
      Below is the >=1024px rule that used to force the panel permanently open
      (`display:block`) and hide its toggle - which is why a desktop visitor
      landed on a screenful of sliders and checkboxes rather than on their
      properties. explore.css loads after styles.css, so these win at equal
      specificity.

      The toggle itself was styled for the dark masthead it used to sit on -
      translucent white fill, --header-ink text. Inside the light control bar
      that is white text on a white button, i.e. invisible. It matches its
      neighbours now. */
.quick-controls .filters-toggle{width:auto;flex:0 0 auto;height:2.15rem;
margin:0;padding:0 .7rem;gap:.45rem;border-radius:3px;font-size:.78rem;
font-weight:700;white-space:nowrap;
background:var(--paper);border:1px solid var(--card-line);color:var(--ink)}
.quick-controls .filters-toggle:hover{border-color:var(--accent);color:var(--accent)}
.quick-controls .filters-toggle .chevron{border-color:var(--ink-soft)}
.quick-controls .filters-toggle:hover .chevron{border-color:var(--accent)}
/* Open, or carrying active filters, it reads as engaged rather than idle. */
.quick-controls .filters-toggle.open{background:var(--accent);border-color:var(--accent);color:#fff}
.quick-controls .filters-toggle.open .chevron{border-color:#fff}
.quick-controls .filters-toggle .filters-badge{margin:0}

@media (min-width:1024px){
  .filters-panel{display:none;max-height:70vh;overflow-y:auto}
  .filters-panel.open{display:block}
  .quick-controls .filters-toggle{display:inline-flex}
  .masthead{padding:.75rem 1.6rem .6rem}
  .quick-controls{position:sticky}
}

/* 8. Phone: the bar wraps to two rows - search across the top, then the
      pickers and buttons - rather than three or four. Expand all and Export
      keep their labels; they are the two controls people reach for by name. */
@media (max-width:640px){
  .quick-controls{gap:.35rem;padding:.45rem .5rem}
  .qc-field{flex:1 1 100%}
  .qc-field.qc-county{flex:1 1 45%;min-width:0}
  .quick-controls .filters-toggle,
  .quick-controls .expand-all-btn,
  .quick-controls .export-btn{flex:1 1 auto;justify-content:center;
  padding:0 .55rem;font-size:.74rem}
}

/* 9. With the header down to a strip, the list and the map get the screen.
      The panel opens over the top of the list rather than shoving the whole
      page down, so the properties stay where the eye left them. */
.filters-panel{position:relative;z-index:29;margin-top:.45rem;
box-shadow:0 10px 30px rgba(15,23,42,.14)}

/* 11. The ledger tabs and the status chips are single scrolling rows on a
       phone - three tabs will not fit 412px at a readable size. Without an
       affordance the third one just looks clipped, so both rows fade out at
       the right edge while there is more to scroll to. */
@media (max-width:1023px){
  .ledger-tabs,
  .summary-strip{-webkit-mask-image:linear-gradient(to right,#000 calc(100% - 28px),transparent);
  mask-image:linear-gradient(to right,#000 calc(100% - 28px),transparent)}
}

/* 12. (was: masthead disclaimer + freshness sizing - both elements are gone) */

/* ==========================================================================
   COUNTY DETAIL: pins, the card strip, the preview card
   --------------------------------------------------------------------------
   Tapping a county bubble now zooms the map into that county and swaps the
   summary bubble for individual pins. Only about 2% of rows are geocoded
   (and none of the Lands Available ones), so the strip of cards below the
   map carries EVERY property in the county whether or not it has a pin, and
   the strip's first line says how many could be placed. A map that showed 8
   pins for a county of 241 without saying so would be worse than no map.
   ========================================================================== */

/* Zoomed in, the surrounding counties stay visible but recede - dropping
   them entirely loses the sense of where in Florida you are. */
/* opacity:.22 did not de-emphasise these counties so much as delete them:
   the fill went transparent and what showed through was the page, so the
   land around the county you zoomed into read as big blank panels. They are
   still land - drawn in the land colour, a step quieter, with their borders
   softened rather than their substance removed. */
.explore-map-canvas path[data-county].zoom-off{
  fill:var(--map-land);stroke:var(--map-land-line);opacity:.82}
.explore-map-canvas path[data-county].zoom-focus{fill:var(--accent-soft);
stroke:var(--accent);stroke-width:1.4}

.map-pin{cursor:pointer}
.map-pin path{fill:var(--bad);stroke:#fff;stroke-width:1.2;
paint-order:stroke;transition:fill .12s}
.map-pin .pin-dot{fill:#fff;pointer-events:none}
.map-pin:hover path,.map-pin.hover path{fill:var(--ink)}
.map-pin.sel path{fill:var(--accent);stroke:var(--ink);stroke-width:1.8}
/* Phase 66: halo ring behind the selected pin (drawPins() adds it only for
   the active property). Stroke-only so it never hides neighbouring pins. */
.map-pin .pin-halo{fill:color-mix(in srgb,var(--accent) 18%,transparent);stroke:var(--accent);
  stroke-width:1.5;pointer-events:none}
.map-pin:focus-visible{outline:none}
.map-pin:focus-visible path{stroke:var(--ink);stroke-width:2.4}

.explore-zoom-out{position:absolute;top:.55rem;left:.55rem;z-index:4;
border:1px solid var(--card-line);background:var(--card);color:var(--ink);
font-size:.72rem;font-weight:700;padding:.32rem .6rem;border-radius:3px;
cursor:pointer;box-shadow:var(--shadow)}
.explore-zoom-out:hover{border-color:var(--accent);color:var(--accent)}

/* ---- the card strip ------------------------------------------------------
   One horizontal rail rather than a grid: it has to sit under the map without
   pushing it off screen, and a rail makes "there are more" obvious. */
.explore-strip{border-top:1px solid var(--card-line);padding:.5rem .1rem 0;margin-top:.55rem}
.strip-note{margin:0 0 .4rem;font-size:.68rem;color:var(--ink-soft)}
.strip-note b{color:var(--ink)}
.strip-rail{display:flex;gap:.4rem;overflow-x:auto;padding-bottom:.35rem;
scrollbar-width:thin}
.strip-card{flex:0 0 auto;max-width:200px;display:flex;flex-direction:column;
align-items:flex-start;gap:.12rem;text-align:left;cursor:pointer;
background:var(--paper);border:1px solid var(--card-line);border-radius:3px;
padding:.4rem .55rem;position:relative}
/* Phase 65: .hover is the pin-side mirror of :hover - set by explore.js when
   the pointer is over that property's pin, so pin and card light up as one.
   .sel is the selection (pin, card and preview all show the same property). */
.strip-card:hover,.strip-card.hover{border-color:var(--accent)}
.strip-card.sel{border-color:var(--accent);background:var(--accent-soft);
  box-shadow:0 0 0 1px var(--accent)}
/* A card whose property is actually on the map gets the left edge, so the
   difference between "mapped" and "listed only" is visible at a glance. */
.strip-card.mapped{border-left:3px solid var(--bad)}
.strip-card.mapped.sel{border-left-color:var(--accent)}
.strip-title{font-size:.72rem;font-weight:700;color:var(--ink);
white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:100%}
.strip-bid{font-family:var(--font-mono);font-size:.7rem;
font-weight:700;color:var(--bad)}
.strip-card:not(.mapped) .strip-bid{color:var(--ink-soft)}
.strip-flag{position:absolute;top:.25rem;right:.35rem;font-size:.5rem;color:var(--bad)}
.strip-card:focus-visible{outline:2px solid var(--accent);outline-offset:2px}

/* ---- the preview card ----------------------------------------------------
   Sits over the map rather than beside it: the panel is already narrow in
   split view, and anchoring the card to the bottom keeps the pin you tapped
   in sight above it. */
/* Phase 67: the preview is the top block of the side panel (desktop) or a
   bottom sheet over the map (phone) - see the workspace rules at the end of
   this file. This is its content styling, shared by both. */
.explore-preview{position:relative;background:var(--card);border:1px solid var(--card-line);
  border-left:3px solid var(--accent);border-radius:4px;padding:.65rem .75rem .6rem;
  display:flex;flex-direction:column;gap:.5rem}
.explore-preview[hidden]{display:none}
.pv-head{position:relative;padding-right:1.6rem}
.pv-kicker{font-size:.62rem;font-weight:700;text-transform:uppercase;letter-spacing:.05em;
  color:var(--accent);margin-bottom:.15rem}
.preview-title{margin:0;font-size:1rem;font-weight:800;line-height:1.25;color:var(--ink)}
.preview-sub{margin:.1rem 0 0;font-size:.68rem;color:var(--ink-soft)}
.preview-close{position:absolute;top:-.15rem;right:-.2rem;border:none;background:none;
  color:var(--ink-soft);font-size:.95rem;cursor:pointer;line-height:1;padding:.2rem;
  min-width:32px;min-height:32px;display:inline-flex;align-items:center;justify-content:center}
.preview-close:hover{color:var(--ink)}
.pv-visual{border-radius:3px;overflow:hidden;background:var(--paper)}
.pv-visual.has-photo{aspect-ratio:2/1;max-height:150px;position:relative}
.pv-visual.has-photo img{width:100%;height:100%;object-fit:cover;display:block}
.pv-money{display:grid;grid-template-columns:1fr 1fr;gap:.5rem}
.pv-stat{background:var(--paper);border-radius:3px;padding:.45rem .55rem;min-width:0;
  border-left:3px solid var(--card-line)}
.pv-stat:first-child{border-left-color:var(--bad)}
.pv-label{display:block;font-size:.58rem;text-transform:uppercase;letter-spacing:.06em;
  font-weight:700;color:var(--ink-soft);margin-bottom:.1rem}
.pv-val{display:block;font-family:var(--font-mono);font-variant-numeric:tabular-nums;
  font-size:.98rem;font-weight:700;color:var(--ink);overflow-wrap:anywhere}
.pv-val.bid{color:var(--bad)}
.pv-val.muted,.explore-preview .muted{font-family:var(--font-sans);font-size:.74rem;font-weight:600;
  color:var(--ink-soft);font-style:italic}
.pv-stat small{display:block;font-size:.62rem;color:var(--ink-soft);margin-top:.1rem}
.pv-body{display:flex;flex-direction:column;gap:.45rem}
.pv-ids{margin:0;display:grid;grid-template-columns:repeat(auto-fit,minmax(90px,1fr));gap:.35rem .6rem}
.pv-ids div{min-width:0}
.pv-ids dt,.pv-more-list dt{font-size:.58rem;text-transform:uppercase;letter-spacing:.06em;
  font-weight:700;color:var(--ink-soft);margin:0}
.pv-ids dd,.pv-more-list dd{margin:0;font-family:var(--font-mono);font-variant-numeric:tabular-nums;
  font-size:.74rem;font-weight:700;color:var(--ink);overflow-wrap:anywhere}
.pv-loc,.pv-risk{font-size:.72rem;color:var(--ink);display:flex;flex-wrap:wrap;align-items:baseline;gap:.15rem .5rem}
.pv-loc .pv-label,.pv-risk .pv-label{display:inline;margin:0}
.pv-coords{font-family:var(--font-mono);font-variant-numeric:tabular-nums;font-weight:700}
.pv-center{font-family:inherit;font-size:.66rem;font-weight:700;color:var(--accent);background:none;
  border:1px solid var(--card-line);border-radius:3px;padding:.15rem .45rem;cursor:pointer;min-height:26px}
.pv-center:hover{border-color:var(--accent)}
.pv-risk .warn{color:var(--warn);font-weight:700}
.pv-note{flex:1 1 100%;font-size:.62rem;color:var(--ink-soft)}
/* Phase 72: the auction link row - same kind-driven label as the card and
   the full page; muted text when there is nothing to link to. */
.pv-auction{font-size:.72rem;color:var(--ink);display:flex;flex-wrap:wrap;align-items:baseline;gap:.15rem .5rem}
.pv-auction .pv-label{display:inline;margin:0}
.pv-auction a{color:var(--accent);font-weight:700;text-decoration:none}
.pv-auction a:hover{text-decoration:underline}
.pv-more{font-size:.72rem}
.pv-more summary{cursor:pointer;color:var(--ink-soft);font-size:.66rem;font-weight:700;
  text-transform:uppercase;letter-spacing:.04em;min-height:28px;display:flex;align-items:center}
.pv-more-list{margin:.35rem 0 0;display:grid;grid-template-columns:1fr 1fr;gap:.35rem .6rem}
.pv-more-list div{min-width:0}
.pv-expand{display:none}
.preview-open{width:100%;border:none;border-radius:3px;background:var(--accent);
  color:#fff;font-size:.76rem;font-weight:700;padding:.5rem;cursor:pointer;min-height:36px}
.preview-open:hover{filter:brightness(1.06)}

/* The panel needs to be the positioning context for the zoom-out button and
   the preview card. */
.explore-map{position:relative}

@media (prefers-reduced-motion:reduce){
  .map-pin path{transition:none}
}

/* The zoom-out button and the preview card are children of the CANVAS, not of
   the panel: anchored to the panel they sat on top of the "Where these are"
   head row and on top of the card strip respectively. */
.explore-map-canvas{position:relative}
/* Zoomed in, the canvas gives back some height so the strip of cards is on
   screen with it rather than below the fold of a sticky panel. (Phase 67:
   not on the Map page, where the stage has a fixed height - see below.) */
.explore-map-canvas.zoomed{min-height:clamp(230px,32vh,340px)}
.map-page-map .explore-map-canvas.zoomed{min-height:0}

.explore-zoom-out:focus-visible{outline:2px solid var(--accent);outline-offset:2px}
.preview-open:focus-visible,.preview-close:focus-visible{outline:2px solid var(--ink);outline-offset:2px}

/* The empty state now explains what search actually matches - "Nothing found"
   with no hint is a dead end when the field you tried is not one of them. */
/* .empty-state is centred, so the hint has to centre with it - left-aligned
   inside a centred block it reads as a stray paragraph. */
.empty-hint{display:block;margin:.45rem auto 0;font-size:.72rem;font-weight:500;
color:var(--ink-soft);line-height:1.5;max-width:52ch;text-align:center}

/* ---- city labels ----------------------------------------------------------
   The map was a blue silhouette with pins on it and nothing to say where in
   the county those pins are. Labels are drawn under the bubbles and pins, and
   carry a paint-order halo so a name stays readable where it crosses a county
   border or the water. */
.city-marker{pointer-events:none}
.city-marker circle{fill:var(--ink-soft);opacity:.75}
/* stroke-width was never set, so the halo was the SVG default of 1 USER
   unit - sub-pixel at the statewide zoom, i.e. no halo at all, which is why
   the names read as heavy black text dropped on top of the map rather than
   as part of it. Sized in em so it tracks the font size explore.js computes
   per zoom level, and coloured as land rather than as the card, because
   that is what is actually behind them now. */
.city-marker text{font-family:var(--font-sans);
font-weight:600;fill:#243044;stroke:var(--map-land);stroke-width:.3em;
paint-order:stroke fill;opacity:.92;letter-spacing:.01em}
:root[data-theme="dark"] .city-marker text{fill:#DCE4F0}
@media (prefers-color-scheme:dark){:root:not([data-theme="light"]) .city-marker text{fill:#DCE4F0}}
/* Zoomed in the county is filled, so the halo has to match that fill instead
   of the panel background or the text sits in a pale box of the wrong colour. */
.explore-map-canvas.zoomed .city-marker text{stroke:var(--accent-soft);stroke-width:.32em}
.explore-map-canvas.zoomed .city-marker circle{fill:var(--ink);opacity:.55}

/* The county you are looking at is still LAND. Filling it solid accent made
   it read as a selected UI element sitting on the map rather than as the
   place itself, and it left the ZIP boundaries drawn inside it as blue lines
   on a blue field. Land colour, warmed a touch so it separates from its
   neighbours, and the accent doing the work at the edge where it belongs. */
.explore-map-canvas path[data-county].zoom-focus{
fill:color-mix(in srgb,var(--accent) 9%,var(--map-land));
stroke:var(--accent);stroke-width:1.6;stroke-linejoin:round;
vector-effect:non-scaling-stroke}

/* A closed property's banner now carries WHY it closed. A sale that actually
   sold is the one outcome that is useful rather than merely final, so it
   reads as a result instead of a rejection. */
.closed-banner.sold{background:var(--ok);border-color:var(--ok)}
.closed-when{margin-left:auto;font-weight:600;opacity:.85;letter-spacing:0;
text-transform:none}

/* ---- ZIP areas ------------------------------------------------------------
   Structure inside the county, so a pin has somewhere to be rather than
   floating on a flat shape. Outline-only by default; an area holding a
   property we can place is tinted and carries its count. */
.zip-area{fill:none;stroke:var(--accent);stroke-opacity:.28;stroke-width:.6px;
vector-effect:non-scaling-stroke;pointer-events:none}
/* Was --bad. Red means closed/redeemed/withdrawn everywhere else in this
   app - on a card's left edge, on a status pill - so painting the ZIP that
   actually CONTAINS listings red said the opposite of what it meant. It is
   the ledger accent now, the same colour the pins and the county outline
   use, because it is pointing at the same thing they are. */
.zip-area.has-rows{fill:var(--accent);fill-opacity:.16;stroke:var(--accent);
stroke-opacity:.7;stroke-width:1.3px}
.zip-label{text-anchor:middle;dominant-baseline:middle;pointer-events:none;
font-family:var(--font-mono);font-weight:600;
fill:var(--ink-soft);opacity:.55;
stroke:var(--map-land);stroke-width:.28em;paint-order:stroke fill}
.zip-label.has-rows{fill:var(--accent);opacity:.95;font-weight:700}

/* ==========================================================================
   ACCOUNT BADGE + MENU
   --------------------------------------------------------------------------
   Theme, password, install and sign out were four loose buttons in the
   header, competing for width with the ledger tabs on a phone. They are all
   account concerns, so one badge holds them and the header gets its width
   back. Initials rather than a photo: nothing to upload or store, and it
   still says at a glance which account you are in.
   ========================================================================== */
/* Global state context (header): the one state selector, beside the
   account badge and the same height and chrome. A native <select> - the
   platform picker on a phone, keyboard-operable everywhere. */
.state-switch{position:relative;display:inline-flex;align-items:center}
.state-switch select{appearance:none;-webkit-appearance:none;font:inherit;font-size:.78rem;font-weight:700;
letter-spacing:.01em;color:var(--header-ink);background:rgba(255,255,255,.10);
border:1px solid var(--header-line);border-radius:3px;height:calc(1.55rem + .4rem + 2px);
padding:0 1.55rem 0 .6rem;cursor:pointer;max-width:9.5rem;text-overflow:ellipsis}
.state-switch select:hover{background:rgba(255,255,255,.18)}
.state-switch select:focus-visible{outline:2px solid var(--accent);outline-offset:2px}
.state-switch select option{color:var(--ink);background:var(--card)}
/* The select sizes to its longest state name ("Louisiana"), so on a narrow
   phone brand mark + wordmark + selector + account badge would overrun the
   row. Below 400px the logo alone carries the brand. */
@media (max-width:399px){.topbar .brand-name{display:none}}
.state-switch::after{content:"";position:absolute;right:.6rem;top:50%;width:6px;height:6px;
border-right:2px solid var(--header-ink-soft);border-bottom:2px solid var(--header-ink-soft);
transform:translateY(-70%) rotate(45deg);pointer-events:none}
.account{position:relative}
.account-badge{display:inline-flex;align-items:center;gap:.32rem;cursor:pointer;
background:rgba(255,255,255,.10);border:1px solid var(--header-line);
border-radius:3px;padding:.2rem .45rem .2rem .2rem;color:var(--header-ink)}
.account-badge:hover{background:rgba(255,255,255,.18)}
.account-badge:focus-visible{outline:2px solid var(--accent);outline-offset:2px}
.account-initials{display:inline-flex;align-items:center;justify-content:center;
width:1.55rem;height:1.55rem;border-radius:999px;background:var(--accent);
color:#fff;font-size:.68rem;font-weight:800;letter-spacing:.02em}
.account-chev{width:6px;height:6px;border-right:2px solid var(--header-ink-soft);
border-bottom:2px solid var(--header-ink-soft);transform:rotate(45deg);
margin-right:.15rem;transition:transform .15s}
.account-badge[aria-expanded="true"] .account-chev{transform:rotate(-135deg)}

.account-menu{position:absolute;right:0;top:calc(100% + .4rem);z-index:60;
min-width:14rem;background:var(--card);border:1px solid var(--card-line);
border-radius:4px;padding:.3rem;box-shadow:0 14px 38px rgba(15,23,42,.28)}
.account-head{padding:.45rem .55rem .5rem;border-bottom:1px solid var(--card-line);
margin-bottom:.25rem}
.account-name{margin:0;font-size:.82rem;font-weight:800;color:var(--ink);
overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.account-email{margin:.1rem 0 0;font-size:.68rem;color:var(--ink-soft);
overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
/* display:block on the item overrides the `hidden` attribute's UA display:none,
   so "Install app" showed even before a beforeinstallprompt ever fired. */
.account-item[hidden]{display:none}
.account-item{display:block;width:100%;text-align:left;cursor:pointer;
background:none;border:none;border-radius:3px;padding:.46rem .55rem;
font-size:.78rem;font-weight:600;color:var(--ink)}
.account-item:hover{background:var(--paper)}
.account-item:focus-visible{outline:2px solid var(--accent);outline-offset:-2px}
.account-item.danger{color:var(--bad)}
.account-item.danger:hover{background:var(--bad-bg)}

/* ---- terms ----------------------------------------------------------------
   The title-search warning used to sit in the header on every screen, where
   it was permanent furniture people stop reading. One button in the footer,
   full text in the modal. */
.footer-terms{margin:0 0 .8rem}
.terms-btn{cursor:pointer;background:var(--card);border:1px solid var(--card-line);
border-radius:3px;padding:.35rem .8rem;font-size:.74rem;font-weight:700;
color:var(--ink)}
.terms-btn:hover{border-color:var(--accent);color:var(--accent)}
.terms-btn:focus-visible{outline:2px solid var(--accent);outline-offset:2px}
.terms-body{font-size:.82rem;line-height:1.6;color:var(--ink)}
.terms-body h3{margin:1rem 0 .3rem;font-size:.78rem;text-transform:uppercase;
letter-spacing:.05em;color:var(--ink-soft)}
.terms-body p{margin:0 0 .6rem}
.terms-lead{padding:.6rem .7rem;background:var(--warn-bg);border:1px solid var(--warn);
border-radius:10px}
.terms-note{margin-top:.9rem;font-size:.74rem;color:var(--ink-soft)}

/* ---- edit profile form ---------------------------------------------------- */
.pf-row{display:flex;gap:.55rem}
.pf-row .pf-field{flex:1 1 0;min-width:0}
.pf-field{display:block;margin-bottom:.6rem}
.pf-field > span{display:block;margin-bottom:.2rem;font-size:.68rem;font-weight:700;
text-transform:uppercase;letter-spacing:.05em;color:var(--ink-soft)}
#profileModal input{width:100%;padding:.6rem .7rem;font-size:.9rem;
border:1px solid var(--card-line);border-radius:3px;background:var(--paper);
color:var(--ink);font-family:inherit}
#profileModal input:focus{outline:2px solid var(--accent);outline-offset:1px}
#profileModal button[type="submit"]{width:100%;padding:.7rem;font-size:.92rem;
font-weight:700;border:none;border-radius:3px;background:var(--header-bg);
color:#fff;cursor:pointer;margin-top:.3rem;letter-spacing:.01em}
#profileModal button[type="submit"]:hover{background:var(--ok)}
#profileModal button[type="submit"]:disabled{opacity:.5;cursor:default;background:var(--header-bg)}

/* ==========================================================================
   PHASE 67: THE MAP WORKSPACE
   --------------------------------------------------------------------------
   The Map page is [toolbar] over [large stage | side panel]. The stage has a
   REAL height (viewport minus chrome on desktop, a big fixed share on a
   phone) and every basemap fills it - the outline SVG scales to the stage's
   height and centres, Google and MapTiler fill it edge to edge. The side
   panel is the property-intelligence column: counties in view statewide,
   the county's property list once zoomed, and the selected property's
   preview on top. On a phone the panel flows under the map and the preview
   becomes a bottom sheet over the map's lower edge, above the tab bar.
   ========================================================================== */
.map-page{--map-side-w:380px;--map-chrome:8.6rem}
@media (min-width:1024px){ .page.map-page{padding:0} }
.map-toolbar{padding:.55rem 1rem;gap:.45rem;border-bottom:1px solid var(--card-line);
  background:var(--card);position:relative;z-index:6}
.map-page-title{font-family:var(--font-sans);font-weight:800;font-size:1rem;letter-spacing:-.01em;
  margin:0 .35rem 0 0;color:var(--ink);white-space:nowrap}
/* Unified navigation (2026-09-30): the title sits over a context line
   ("Ledger: Available · County: All counties") written by renderMapContext()
   in app.js - a label, not a control (the controls are the ledger pills and
   the county select beside it; the state is the header's #stateSelect). */
.map-page-titlebox{margin-right:.35rem}
.map-page-title{margin:0}
.map-toolbar .qc-field{flex:1 1 200px;max-width:300px}
.map-toolbar .qc-field.qc-county{flex:0 1 180px}
.map-toolbar .map-style-toggle{margin-left:auto}
@media (max-width:640px){
  .map-toolbar{padding:.5rem .6rem}
  .map-page-titlebox{flex:0 0 100%;margin:0}
  .map-page-title{font-size:.95rem;margin:0}
  .map-toolbar .qc-field{flex:1 1 100%;max-width:none}
  .map-toolbar .qc-field.qc-county{flex:1 1 44%}
  .map-toolbar .map-style-toggle{margin-left:0;flex:1 1 50%;justify-content:center}
  .map-watch-pill{flex:0 1 auto}
}

.map-workspace{display:grid;grid-template-columns:minmax(0,1fr) var(--map-side-w);
  height:calc(100vh - var(--map-chrome));min-height:560px}
@media (min-width:1600px){ .map-page{--map-side-w:420px} }
@media (max-width:1023px){
  .map-workspace{display:block;height:auto;min-height:0}
}

/* The stage: no card chrome, the map IS the surface. */
.map-page-map{border:none;border-radius:0;box-shadow:none;padding:0;gap:0;
  background:var(--map-sea);position:relative;display:flex;flex-direction:column;min-height:0;min-width:0}
.map-page-map .explore-map-stage{flex:1 1 auto;min-height:0;position:relative;display:block}
.map-page-map .explore-map-canvas{position:absolute;inset:0;display:flex;justify-content:center;
  align-items:center;background:var(--map-sea);border-radius:0}
.map-page-map .explore-map-canvas svg{width:auto;height:100%;max-height:100%;max-width:100%;
  border:none;border-radius:0}
.map-page-map .satellite-map-canvas{position:absolute;inset:0;width:100%;height:100%;
  aspect-ratio:auto;max-height:none;border:none;border-radius:0}
.map-page-map .satellite-map-canvas .mapboxgl-canvas-container,
.map-page-map .satellite-map-canvas .mapboxgl-map,
.map-page-map .satellite-map-canvas .maplibregl-map{border-radius:0}
/* The absolute/flex rules above outrank the plain [hidden] rules earlier in
   this file, so the basemap toggle's show/hide needs them restated here. */
.map-page-map .explore-map-canvas[hidden],
.map-page-map .satellite-map-canvas[hidden]{display:none}
@media (max-width:1023px){
  .map-page-map .explore-map-stage{height:58vh;min-height:340px}
}
@media (max-width:640px){
  .map-page-map .explore-map-stage{height:56vh;min-height:320px}
}

/* Overlays on the stage: the hint/reset strip bottom-left, the imagery
   toggle top-right, the zoom-out button top-left (existing). Compact, flat,
   no pill stack. */
.map-page-map .explore-map-foot{position:absolute;left:.6rem;bottom:.6rem;z-index:4;
  background:color-mix(in srgb,var(--card) 88%,transparent);border:1px solid var(--card-line);
  border-radius:3px;padding:.3rem .55rem;gap:.6rem;max-width:calc(100% - 1.2rem);
  backdrop-filter:blur(4px)}
.map-page-map .explore-map-hint{font-size:.66rem}
.map-imagery-toggle{position:absolute;top:.6rem;right:.6rem;z-index:4;display:inline-flex;
  background:var(--card);border:1px solid var(--card-line);border-radius:3px;overflow:hidden;
  box-shadow:var(--shadow)}
.map-imagery-toggle[hidden]{display:none}
.map-imagery-toggle button{font-family:inherit;font-size:.68rem;font-weight:700;color:var(--ink-soft);
  background:none;border:none;padding:.35rem .6rem;cursor:pointer;min-height:30px}
.map-imagery-toggle button+button{border-left:1px solid var(--card-line)}
.map-imagery-toggle button.on{background:var(--accent);color:#fff}
.map-page-map .cluster-tip{z-index:5}
/* Basemap toggle in the toolbar: same segmented idiom, flat corners. */
.map-toolbar .map-style-toggle{border-radius:3px;padding:2px}
.map-toolbar .map-style-toggle button{border-radius:2px;font-size:.72rem;padding:.38rem .65rem}

/* The side panel. */
.map-side{background:var(--card);border-left:1px solid var(--card-line);display:flex;
  flex-direction:column;min-height:0;min-width:0}
.map-side-head{display:flex;align-items:baseline;justify-content:space-between;gap:.6rem;
  padding:.7rem .9rem .55rem;border-bottom:1px solid var(--card-line);flex-shrink:0}
.map-side-head .explore-map-title{font-size:.95rem;margin:0}
.map-side-body{flex:1 1 auto;min-height:0;overflow-y:auto;padding:.7rem .9rem 1rem;
  display:flex;flex-direction:column;gap:.7rem}
.map-side .explore-map-rail{border:none;background:none;padding:0;max-height:none;overflow:visible}
.map-side .explore-map-legend{padding:0}
.map-side .explore-map-note{font-size:.66rem;line-height:1.4;color:var(--ink-soft);margin:0}
.map-side .explore-strip{border-top:none;padding:0;margin:0}
/* Desktop: the strip is a vertical list in the panel, not a horizontal rail. */
@media (min-width:1024px){
  .map-side .strip-rail{flex-direction:column;overflow-x:visible;overflow-y:visible;gap:.3rem;padding:0}
  .map-side .strip-card{max-width:none;width:100%;padding:.45rem .6rem .45rem .6rem}
}
.strip-sub{font-size:.62rem;color:var(--ink-soft);white-space:nowrap;overflow:hidden;
  text-overflow:ellipsis;max-width:100%}
@media (max-width:1023px){
  .map-side{border-left:none;border-top:1px solid var(--card-line)}
  .map-side-body{overflow:visible;padding:.6rem .8rem .9rem}
  .map-side .strip-card{min-width:170px;max-width:220px}
  /* The preview is a sheet over the lower edge of the map STAGE (explore.js
     re-homes the node there on a phone): collapsed it shows the header + the
     two money figures (~150px) and a Details button; expanded it grows to
     most of the stage and scrolls. The property list under the map is never
     covered, and the map stays visible around the sheet. */
  .map-page-map .explore-map-stage > .explore-preview{position:absolute;left:.5rem;right:.5rem;
    bottom:.5rem;z-index:8;border-radius:6px;border-left:none;border-top:3px solid var(--accent);
    box-shadow:0 10px 30px rgba(15,23,42,.28);max-height:calc(100% - 3.2rem);overflow-y:auto;
    padding:.55rem .7rem .6rem}
  .map-page-map .explore-map-stage:has(.explore-preview:not([hidden])) .explore-map-foot{display:none}
  .explore-preview .pv-body,.explore-preview .pv-visual,.explore-preview .preview-open{display:none}
  .explore-preview.expanded .pv-body,.explore-preview.expanded .preview-open{display:flex}
  .explore-preview.expanded .pv-visual{display:block}
  /* Collapsed, the sheet must leave more than half the stage to the map. With
     the real IBM Plex webfonts (the sandbox suite only ever saw the fallback
     face) the kicker wraps to two lines on a 360px phone and the sheet sat at
     exactly half the stage. Collapsed-only: the subtitle repeats the county
     and state the kicker already carries, so it waits for Details; spacing
     tightens a notch. Expanded, everything is back as designed. */
  .map-page-map .explore-map-stage > .explore-preview:not(.expanded){gap:.4rem;padding:.5rem .65rem .5rem}
  .explore-preview:not(.expanded) .preview-sub{display:none}
  .explore-preview:not(.expanded) .pv-stat{padding:.35rem .5rem}
  .pv-expand{display:inline-flex;align-items:center;justify-content:center;min-height:34px;
    font-family:inherit;font-size:.72rem;font-weight:700;color:var(--accent);background:var(--accent-soft);
    border:none;border-radius:3px;cursor:pointer}
}
