/* ============================================================
   /WhatsUpMap — page-local layout. Component primitives (.chip,
   .filt-row, .filt-head, .mb-nearby-list) come from
   components.css; the map shell, pin styling, legend, locate
   button, and mobile bottom sheet live here.
   ============================================================ */

/* ---- Page shell -------------------------------------------- */
/* The map page consumes the full viewport below the topbar (or
   above the mobile tabbar). Body's default tabbar reserve sits
   under the sheet on mobile, which is what we want. */
.map-wrap {
  display: grid;
  grid-template-columns: 320px 1fr;
  height: calc(100dvh - 60px); /* 60px = topbar */
  overflow: hidden;
}

/* ---- Desktop rail ------------------------------------------ */
.map-rail {
  background: var(--surface-raised);
  border-right: 1px solid var(--border-subtle);
  display: flex;
  flex-direction: column;
  overflow: hidden;
}
.map-rail-head {
  padding: 16px 18px 12px;
  border-bottom: 1px solid var(--border-subtle);
}
.map-rail-title {
  margin: 0 0 6px;
  font-size: 18px;
  font-weight: 600;
  letter-spacing: -0.015em;
}
.map-rail-sub {
  margin: 0;
  color: var(--text-muted);
  font-family: var(--font-mono);
  font-size: 11.5px;
  letter-spacing: 0.04em;
}
.map-rail-filters { border-bottom: 1px solid var(--border-subtle); }
.map-filter-bar { padding: 12px 16px; gap: 8px; }
.map-filter-bar .filt-head { font-size: 10px; }

/* The rail's section list scrolls independently of the page.
   Rows reuse the `.mb-row` vocabulary (rv / nm / fm / pill) —
   320px is too narrow for /WhatsUpList's 7-column `.row`, and
   `.mb-row`'s inline `fm` (flow · grade · freshness) already
   degrades cleanly when any field is absent. Data density
   priority per the brief: flow + pill first, then grade,
   freshness included when the payload carries it. */
.map-rail-list {
  flex: 1;
  overflow-y: auto;
  padding: 4px 0 12px;
}
/* The rail emits `<button class="mb-row map-rail-row">`. The
   `.mb-row` base rules live in components.css; these overrides
   tighten the padding and add the selection indicator the rail
   needs for map ↔ rail sync. */
.map-rail-list .mb-row {
  /* `<button>` defaults (background / border / font) need
     unsetting so the row inherits the mobile-row presentation. */
  background: transparent;
  border-left: 0;
  border-right: 0;
  border-top: 0;
  text-align: left;
  width: 100%;
  font: inherit;
  color: inherit;
  cursor: pointer;
  padding: 10px 16px;
}
.map-rail-list .mb-row:hover,
.map-rail-list .map-rail-row-wrap:hover .mb-row,
.map-rail-list .mb-row.is-selected { background: var(--brand-050); }
.map-rail-list .map-rail-row-wrap.is-selected .mb-row,
.map-rail-list .mb-row.is-selected {
  border-left: 3px solid var(--brand-500);
  padding-left: 13px;  /* offset the extra 3px border */
}

/* Desktop rail row wrapper — pairs the button-as-body (selects +
   pans, syncs the pin) with a trailing anchor (navigates to
   /section/<id>). Mirrors the /river/<id> `.river-sec-row` +
   `.sec-detail` split: the wide hit zone toggles map state, the
   trailing chevron is the canonical path forward. Buttons can't
   contain anchors, so the wrapper is what makes the two cohabit.
   The `.mb-row` border-bottom moves up to the wrapper so adjacent
   rows still get a single divider. */
.map-rail-list .map-rail-row-wrap {
  display: flex;
  align-items: stretch;
  border-bottom: 1px solid var(--border-subtle);
}
.map-rail-list .map-rail-row-wrap:last-child { border-bottom: none; }
.map-rail-list .map-rail-row-wrap .mb-row {
  border-bottom: 0;
  flex: 1 1 auto;
  min-width: 0;  /* allow grid cells to shrink inside flex */
}
.map-rail-detail {
  display: flex;
  align-items: center;
  justify-content: center;
  flex: 0 0 auto;
  width: 36px;
  color: var(--text-muted);
  text-decoration: none;
  border-left: 1px solid var(--border-subtle);
  transition: background 0.12s ease, color 0.12s ease;
}
.map-rail-detail:hover,
.map-rail-detail:focus-visible {
  background: var(--brand-050);
  color: var(--brand-500);
  text-decoration: none;
}
.map-rail-detail:focus-visible {
  outline: 2px solid var(--brand-500);
  outline-offset: -2px;
}
.map-rail-list .empty {
  padding: 24px 18px;
  font-size: 13px;
  color: var(--text-muted);
  text-align: center;
}

/* ---- Map canvas + chrome ----------------------------------- */
.map-canvas-wrap { position: relative; }
.map-canvas {
  width: 100%;
  height: 100%;
  background: var(--surface-sunken);
}
/* Leaflet default tile attribution is fine; ensure the popup link
   colour matches our token family. */
.leaflet-popup-content a { color: var(--link); }

/* Pin popup — opened on click, mirrors the hover tooltip's
   content (name / river / status / flow) plus a trailing
   "Open section →" link. The link is the canonical navigation
   path from the map; tapping the marker anywhere opens the
   popup, the link inside is the explicit forward action.
   `.map-pin-popup-cta` sits below the info block on its own
   row so it's hit-testable as a discrete tap target. */
.map-pin-popup { font-size: 12.5px; line-height: 1.45; }
.map-pin-popup-cta {
  margin-top: 8px;
  padding-top: 6px;
  border-top: 1px solid var(--border-subtle);
}
.map-pin-popup-link {
  display: inline-block;
  font-weight: 600;
  font-size: 12.5px;
  color: var(--brand-500);
  text-decoration: none;
}
.map-pin-popup-link:hover,
.map-pin-popup-link:focus-visible { text-decoration: underline; }

/* ---- Pin vocabulary (Round 3 §03b) ------------------------- */
/* divIcon-based pins so we can style with our colour tokens. The
   icon's own width/height is set on the L.divIcon options; the
   class names below paint the visible disc + ring. */
.map-pin {
  width: 18px;
  height: 18px;
  border-radius: 50%;
  border: 2px solid var(--surface-raised);
  box-shadow: 0 1px 4px rgba(14, 26, 38, 0.25);
  background: var(--text-disabled);  /* fallback */
  cursor: pointer;
}
.map-pin.status-too-low   { background: var(--status-too-low-dot); }
.map-pin.status-low       { background: var(--status-low-dot); }
.map-pin.status-good      { background: var(--status-good-dot); }
.map-pin.status-high      { background: var(--status-high-dot); }
.map-pin.status-very-high { background: var(--status-very-high-dot); }
.map-pin.status-unknown   { background: var(--border-strong); }
/* Gauge-less sections get a neutral hollow ring — no status
   colour implies no flow measurement. Interior stays transparent
   so the map tile shows through; the border reads "informational". */
.map-pin.status-no-gauge {
  background: transparent;
  border-color: var(--border-strong);
  box-shadow: 0 0 0 1px var(--surface-raised);
}
/* Proxy-gauge overlay — dashed outer ring layered over the
   normal status-coloured disc via `box-shadow`. Communicates
   "flow is estimated, not measured" without repainting the pin. */
.map-pin.is-proxy {
  box-shadow:
    0 1px 4px rgba(14, 26, 38, 0.25),
    0 0 0 2px var(--surface-raised),
    0 0 0 3px currentColor;
  outline: 1.5px dashed var(--text-secondary);
  outline-offset: 2px;
}
/* Hollow ring — default per Round 3, swaps to solid disc when
   the row in the rail (or a click) selects it. */
.map-pin {
  background-clip: padding-box;
}
.map-pin.is-selected {
  transform: scale(1.25);
  box-shadow: 0 2px 8px rgba(14, 26, 38, 0.35), 0 0 0 3px var(--brand-500);
}

/* ---- Legend (bottom-left, elevated surface) ---------------- */
.map-legend {
  position: absolute;
  left: 12px;
  bottom: 16px;
  background: var(--surface-overlay);
  -webkit-backdrop-filter: blur(8px);
  backdrop-filter: blur(8px);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-md);
  padding: 8px 10px;
  display: flex;
  align-items: center;
  gap: 10px;
  font-family: var(--font-mono);
  font-size: 11px;
  color: var(--text-secondary);
  z-index: 500;  /* above Leaflet tiles, below popups */
  box-shadow: var(--elev-2);
}
.map-legend-pin {
  display: inline-block;
  width: 9px;
  height: 9px;
  border-radius: 50%;
  margin-right: 4px;
  vertical-align: middle;
  border: 1.5px solid var(--surface-raised);
  box-shadow: 0 0 0 1px var(--border-subtle);
}
/* Monotone order — Too low → Very high. See DESIGN_NOTES
   §"Status display order". */
.map-legend-pin.status-too-low   { background: var(--status-too-low-dot); }
.map-legend-pin.status-low       { background: var(--status-low-dot); }
.map-legend-pin.status-good      { background: var(--status-good-dot); }
.map-legend-pin.status-high      { background: var(--status-high-dot); }
.map-legend-pin.status-very-high { background: var(--status-very-high-dot); }

/* ---- Locate button (placeholder until Nearby PR) ----------- */
.map-locate-btn {
  position: absolute;
  top: 12px;
  right: 12px;
  background: var(--surface-overlay);
  -webkit-backdrop-filter: blur(8px);
  backdrop-filter: blur(8px);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-md);
  padding: 8px 12px;
  font-size: 12.5px;
  color: var(--text-primary);
  cursor: pointer;
  z-index: 500;
  box-shadow: var(--elev-2);
}
.map-locate-btn:hover { background: var(--surface-raised); }
.map-locate-btn.is-active { color: var(--brand); }

/* Insecure-context banner — sits under the locate button in the
   top-right corner and explains why clicking doesn't prompt. Same
   mechanic as Home's `.nearby-insecure`; tuned for the map chrome
   weight. */
.map-insecure-notice {
  position: absolute;
  top: 52px;
  right: 12px;
  max-width: 280px;
  background: var(--surface-overlay);
  -webkit-backdrop-filter: blur(8px);
  backdrop-filter: blur(8px);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-md);
  padding: 8px 12px;
  font-size: 12px;
  line-height: 1.4;
  color: var(--text-primary);
  z-index: 500;
  box-shadow: var(--elev-2);
}
.map-insecure-notice a { color: var(--brand-500); }

/* Error toast — inline notice below the locate button when
   getCurrentPosition rejects. Same visual weight as the insecure
   notice (overlay chrome), auto-dismisses in the script. */
.map-locate-toast {
  position: absolute;
  top: 52px;
  right: 12px;
  max-width: 280px;
  background: var(--surface-overlay);
  -webkit-backdrop-filter: blur(8px);
  backdrop-filter: blur(8px);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-md);
  padding: 8px 12px;
  font-size: 12px;
  line-height: 1.4;
  color: var(--text-primary);
  z-index: 500;
  box-shadow: var(--elev-2);
}
.map-locate-toast a { color: var(--brand-500); }

/* "You are here" marker — a blue dot with a soft accuracy halo.
   The Leaflet divIcon host is empty; we just pin the visuals here
   so the markup stays trivial (no vendored plugin). */
.map-user-dot {
  width: 14px;
  height: 14px;
  border-radius: 50%;
  background: #1e6feb;
  border: 2px solid #fff;
  box-shadow: 0 0 0 2px rgba(30, 111, 235, 0.35);
}

/* ---- Mobile bottom sheet ----------------------------------- */
/* Hidden on desktop, visible on mobile. Two states: peek (~44%)
   and expanded (~88%). Pure CSS transform so the slide is smooth
   without observers. */
.map-sheet { display: none; }

@media (max-width: 767.98px) {
  /* Mobile reflow: rail collapses, map fills, sheet floats. */
  .map-wrap {
    grid-template-columns: 1fr;
    height: calc(100dvh - var(--tabbar-height) - env(safe-area-inset-bottom));
    /* `isolation: isolate` clamps Leaflet's internal pane z-indices
       (tile 200, overlay 400, shadow 500, marker 600, tooltip 650,
       popup 700, controls 800) inside this container. Without it, those
       indices live in the root stacking context and paint over the
       tabbar (z-100) and sheet (z-50) — the observed regression where
       tiles painted over tabbar text at narrow Safari. `position:
       relative` is the anchor for that stacking context and for any
       Leaflet descendants that position themselves against the
       container. Base `overflow: hidden` (line 16) stays, belt-and-
       braces against any internal overflow. */
    position: relative;
    isolation: isolate;
  }
  .map-rail { display: none; }

  .map-sheet {
    position: fixed;
    left: 0;
    right: 0;
    /* Anchor at the tabbar's top edge. With the bottom offset carrying
       the tabbar + safe-area reserve, the sheet's bottom edge always
       lands on the tabbar top — no transform math needs to know about
       the tabbar, and the sheet can never overlap it regardless of
       height. */
    bottom: calc(var(--tabbar-height) + env(safe-area-inset-bottom));
    background: var(--surface-raised);
    border-top: 1px solid var(--border-subtle);
    border-top-left-radius: 18px;
    border-top-right-radius: 18px;
    /* Fixed height (not `auto`) so the peek transform
       `translateY(100% - 144px)` always evaluates positive. When height
       was content-driven, an empty-state body (~105px content) made
       `100% - 144px` go negative — the sheet shifted UP and exposed a
       strip of map tiles between its bottom edge and the tabbar. Fixed
       height also gives the empty state deliberate vertical room.
       `max-height` caps to the visible viewport on short windows so the
       sheet can't grow past `100dvh - tabbar`. */
    height: 440px;
    max-height: calc(100dvh - var(--tabbar-height) - env(safe-area-inset-bottom));
    /* `overflow: hidden` prevents tall inner content (long sections
       list) from forcing the sheet itself to grow past its max-height.
       Inner scroll lives on `.map-sheet-list`. */
    overflow: hidden;
    /* Peek: slide the sheet down so only 144px at the top stays in
       view above the tabbar. The bottom anchor already clears the
       tabbar, so the transform no longer carries tabbar reserve. */
    transform: translateY(calc(100% - 144px));
    transition: transform 0.22s ease-out;
    /* z-index above Leaflet's bottom-right attribution wrapper
       (default 1000, lifted to bottom: 154px on mobile) so the
       sheet's content is never occluded; below the tabbar (z-100)
       which must always stay tappable. The transform creates a
       stacking context — `isolation: isolate` makes that explicit
       and stops Safari from reordering it under same-z siblings. */
    z-index: 50;
    isolation: isolate;
    display: flex;
    flex-direction: column;
    box-shadow: 0 -10px 30px -10px rgba(14, 26, 38, 0.15);
  }
  /* Expanded: slide the sheet fully into view. Bottom is already at
     the tabbar top, so a zero translate leaves the sheet's bottom
     edge on the tabbar — no extra lift needed. */
  .map-sheet.is-expanded {
    transform: translateY(0);
  }
  .map-sheet-handle {
    background: transparent;
    border: 0;
    padding: 8px 0 6px;
    width: 100%;
    cursor: pointer;
    display: flex;
    justify-content: center;
  }
  .map-sheet-grab {
    width: 36px;
    height: 4px;
    background: var(--border-strong);
    border-radius: 2px;
  }
  .map-sheet-sub {
    margin: 0 16px 8px;
    font-family: var(--font-mono);
    font-size: 11.5px;
    color: var(--text-muted);
  }
  .map-sheet-filters {
    display: flex;
    gap: 6px;
    flex-wrap: wrap;
    padding: 0 16px 10px;
    border-bottom: 1px solid var(--border-subtle);
  }
  .map-sheet-list {
    flex: 1;
    /* `min-height: 0` overrides the flex-default `min-height: auto`
       so this flex child can shrink below its content size, letting
       the overflow scroll happen inside the list instead of growing
       the sheet. Classic flex scroll pairing with the parent's
       `overflow: hidden` above. */
    min-height: 0;
    overflow-y: auto;
    border-radius: 0;
    border: none;
    box-shadow: none;
  }
  /* Empty state — modelled on Home's `.fav-empty` pattern (primary
     line + muted-mono CTA) so the vocabulary is consistent. Anchored
     at the top of the list area (not vertically centred): at peek
     height only the first ~40–54px of the list is visible, and a
     centre-aligned block hides below the visible zone, leaving the
     peek view blank where the empty-state should greet the user.
     Top-anchored text lands just below the filter chips at peek and
     still reads as a calm intro at expanded height. */
  .map-sheet-list .sheet-empty {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 6px;
    padding: 14px 24px 24px;
    text-align: center;
  }
  .sheet-empty-line {
    font-size: 15px;
    color: var(--text-primary);
    margin: 0;
  }
  .sheet-empty-cta {
    font-family: var(--font-mono);
    font-size: 12.5px;
    color: var(--text-muted);
    margin: 0;
  }
  /* Mobile legend — hidden entirely. The chip row at the top of
     the bottom sheet (inside `.map-sheet-filters`) shows every
     status colour next to its label, so an additional legend on
     the map chrome would duplicate the same vocabulary on a
     surface where every pixel of the map is already at a premium.
     One source of truth, sheet-side. */
  .map-legend { display: none; }

  .map-locate-btn {
    top: 12px;
    right: 12px;
    /* Above the sheet's stacking context (z-50) so the button
       stays tappable even when the sheet is fully expanded. */
    z-index: 1000;
  }

  /* Lift Leaflet's bottom-right attribution above the peeked bottom
     sheet so the © OpenStreetMap credit stays visible (CC requirement)
     without overlapping the sheet's filter row. The sheet's peek
     visible top sits ~144px above the sheet origin — 154px clears it
     with a small buffer on devices with no safe-area inset. */
  .leaflet-bottom.leaflet-right { bottom: 154px; }
}

/* The default body padding-bottom for tabbar applies on mobile;
   the map page wants the canvas to fill exactly to the tabbar so
   it's already accounted for via the `.map-wrap` height rule above.
   Override the body reserve here so the map doesn't scroll. */
body:has(.map-wrap) {
  padding-bottom: 0 !important;
  overflow: hidden;
}
