/* ============================================================
   San Andreas — interactive map
   Same HUD tokens as the checklist. Sidebar = quiet instrument
   panel, map = hero. No gradients, hairline borders.
   ============================================================ */

:root {
  --bg:      #111214;
  --panel:   #1A1C20;
  --panel-2: #1F2126;
  --text:    #EDEAE2;
  --muted:   #868D98;
  --accent:  #63C15A;
  --accent-hi: #85d47e;
  --accent-rgb: 99,193,90;
  --line:    #2A2D33;

  --water:   #0d1a26;

  --sans: "IBM Plex Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  --mono: "IBM Plex Mono", ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  --display: "Anton", "Arial Narrow", Impact, var(--sans);

  --radius: 6px;
  --side-w: 330px;
}

*, *::before, *::after { box-sizing: border-box; }
html, body { height: 100%; }

body.map-page {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font-family: var(--sans);
  font-size: 15px;
  line-height: 1.55;
  -webkit-font-smoothing: antialiased;
  overflow: hidden;
}

.num { font-family: var(--mono); font-variant-numeric: tabular-nums; }

.sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

a { color: var(--accent); text-underline-offset: 2px; }
a:hover { color: var(--accent-hi); }

:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 3px; }

.skip-link {
  position: absolute; left: 12px; top: -60px; z-index: 1200;
  background: var(--accent); color: #111214; font-weight: 600;
  padding: 10px 14px; border-radius: var(--radius); text-decoration: none;
  transition: top .15s;
}
.skip-link:focus { top: 12px; }

/* ---------------- Shell ---------------- */

.map-shell { display: flex; height: 100%; width: 100%; }

#map {
  flex: 1 1 auto;
  min-width: 0;
  height: 100%;
  background: var(--water);
  outline: none;
}

/* ---------------- Sidebar ---------------- */

.side {
  flex: 0 0 var(--side-w);
  width: var(--side-w);
  height: 100%;
  display: flex;
  flex-direction: column;
  background: var(--panel);
  border-right: 1px solid var(--line);
  z-index: 900;
}

.side-head { padding: 14px 16px 13px; border-bottom: 1px solid var(--line); }

.side-title-row { display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; row-gap: 8px; }

.side-title {
  font-family: var(--display); font-weight: 400;
  font-size: 27px; line-height: 1.05;
  letter-spacing: .075em; text-transform: uppercase;
  margin: 0; transform: skewX(-6deg);
  flex: 1 1 auto; min-width: 0;
}

.side-nav { display: flex; align-items: center; gap: 7px; flex: none; flex-wrap: wrap; row-gap: 6px; }
.side-nav a, .side-nav span {
  font-size: 10.5px; letter-spacing: .13em; text-transform: uppercase;
  text-decoration: none; color: var(--muted);
  padding: 4px 7px; border: 1px solid var(--line); border-radius: 4px;
  white-space: nowrap;
}
.side-nav a:hover { color: var(--text); border-color: #3A3E46; }
.side-nav .here { color: #111214; background: var(--accent); border-color: var(--accent); font-weight: 600; }

.side-sub {
  margin: 7px 0 8px;
  font-size: 11.5px; color: var(--muted);
  letter-spacing: .06em; text-transform: uppercase;
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
}
.side-sub .dot { color: var(--line); }
.side-sub .num { color: var(--text); font-weight: 600; font-size: 12.5px; }

.side-bar { height: 4px; background: var(--line); border-radius: 2px; overflow: hidden; }
.side-bar > span { display: block; height: 100%; width: 0%; background: var(--accent); transition: width .28s ease; }

/* search */
.side-search { padding: 12px 16px; border-bottom: 1px solid var(--line); position: relative; }
.search-field { position: relative; display: flex; align-items: center; }
.search-ico { position: absolute; left: 11px; color: var(--muted); display: flex; pointer-events: none; }

.side-search input[type="search"] {
  width: 100%;
  background: #141518; border: 1px solid var(--line); color: var(--text);
  font-family: var(--sans); font-size: 14px;
  padding: 9px 12px 9px 34px; border-radius: var(--radius);
}
.side-search input[type="search"]::placeholder { color: #6C737D; }
.side-search input[type="search"]:focus { border-color: var(--accent); }

.results {
  margin: 10px -16px -12px;
  border-top: 1px solid var(--line);
  max-height: 46vh; overflow-y: auto;
}
.results[hidden] { display: none; }
.result {
  display: flex; align-items: center; gap: 10px; width: 100%;
  background: none; border: 0; border-bottom: 1px solid var(--line);
  padding: 9px 16px; text-align: left; cursor: pointer;
  color: var(--text); font-family: var(--sans); font-size: 13.5px;
}
.result:hover, .result:focus-visible { background: var(--panel-2); }
.result-meta { display: block; font-size: 11px; color: var(--muted); letter-spacing: .04em; }
.result.found .result-title { color: var(--muted); text-decoration: line-through; text-decoration-thickness: 1px; }
.results-empty { padding: 14px 16px; color: var(--muted); font-size: 13px; }
.results-more { padding: 8px 16px 10px; color: #5F656E; font-size: 11.5px; font-family: var(--mono); }

/* tree */
.side-tree { flex: 1 1 auto; overflow-y: auto; overscroll-behavior: contain; }

.grp { border-bottom: 1px solid var(--line); }
.grp-head {
  display: flex; align-items: center; gap: 10px;
  padding: 12px 16px 8px;
}
.grp-title {
  margin: 0; flex: 1 1 auto; min-width: 0;
  font-size: 10.5px; font-weight: 600; letter-spacing: .16em;
  text-transform: uppercase; color: var(--muted);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.grp-pip { width: 8px; height: 8px; flex: none; background: var(--cat-color, var(--line)); }

/* group fold — 8 groups / 63 categories need to collapse */
.grp-toggle {
  display: flex; align-items: center; gap: 10px;
  flex: 1 1 auto; min-width: 0;
  background: none; border: 0; padding: 0; margin: 0;
  cursor: pointer; text-align: left; font: inherit; color: inherit;
}
.grp-toggle:hover .grp-title { color: var(--text); }
.grp-chev {
  flex: none; width: 12px; height: 12px;
  display: inline-flex; align-items: center; justify-content: center;
  color: var(--muted);
  transition: transform .15s ease;
}
.grp-toggle[aria-expanded="true"] .grp-chev { transform: rotate(90deg); }
.grp-n {
  flex: none; font-size: 10.5px; color: #5F656E;
  font-variant-numeric: tabular-nums;
}
.grp.is-collapsed .grp-head { padding-bottom: 12px; }
.grp.is-collapsed .grp-actions { opacity: .35; }
.grp-actions { display: flex; align-items: center; gap: 6px; flex: none; }
.linkbtn {
  background: none; border: 0; padding: 0; cursor: pointer;
  color: var(--muted); font-family: var(--sans); font-size: 10.5px;
  letter-spacing: .08em; text-transform: uppercase;
}
.linkbtn:hover { color: var(--accent); }
.sep { color: var(--line); font-size: 10px; }

.grp-cats { padding-bottom: 8px; }

.cat {
  display: flex; align-items: center; gap: 10px; width: 100%;
  background: none; border: 0; border-left: 2px solid transparent;
  padding: 7px 16px 7px 14px;
  cursor: pointer; text-align: left;
  color: var(--text); font-family: var(--sans); font-size: 13.5px;
  transition: background .12s, opacity .12s, border-color .12s;
}
.cat:hover { background: var(--panel-2); }
.cat[aria-pressed="true"] { border-left-color: #3A3E46; }
.cat[aria-pressed="false"] { opacity: .42; }
.cat-ico {
  flex: none; width: 22px; height: 22px;
  display: inline-flex; align-items: center; justify-content: center;
  font-size: 13px; color: var(--cat-color, #B9BEC7);
  background: #141518; border: 1px solid var(--line); border-radius: 50%;
}
.cat-title { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cat-count {
  flex: none; font-family: var(--mono); font-size: 11.5px;
  font-variant-numeric: tabular-nums; color: var(--muted); letter-spacing: -.01em;
}
.cat.done .cat-count { color: var(--accent); }
.cat.done .cat-ico { color: var(--accent); border-color: rgba(var(--accent-rgb),.4); }

.side-foot {
  flex: none; padding: 10px 16px 12px;
  border-top: 1px solid var(--line);
  color: #666C75; font-size: 11.5px;
}
.side-foot p { margin: 0 0 3px; }
.side-foot code { font-family: var(--mono); font-size: 10.5px; color: var(--muted); }

/* mobile toggle */
.side-toggle {
  display: none;
  position: absolute; left: 12px; top: 12px; z-index: 1000;
  align-items: center; gap: 8px;
  background: rgba(26,28,32,.95);
  border: 1px solid var(--line); border-radius: var(--radius);
  color: var(--text); font-family: var(--sans);
  font-size: 11.5px; font-weight: 600; letter-spacing: .08em; text-transform: uppercase;
  padding: 9px 12px; cursor: pointer;
  -webkit-backdrop-filter: blur(8px); backdrop-filter: blur(8px);
}
.side-scrim { display: none; }

/* ---------------- Markers ---------------- */

/* Compositor isolation for the marker layer.
   Measured: at z11 all 728 chips fit the viewport, and every drag frame cost
   110-130ms with *zero* marker JS running — the whole bill was the browser
   re-rasterising 728 bordered, round, glyph-bearing chips as the pane
   translated. Promoting the pane itself (ONE element, one extra texture) means
   its children are rasterised once and the drag only moves the finished layer.
   Deliberately just `will-change: transform`: `backface-visibility` /
   `perspective` hints are legacy WebKit workarounds, and each one is another
   property the compositor has to reason about per element. Nothing else in the
   sheet may take will-change — a hint on 728 chips would defeat the point by
   giving each one its own layer. */
.leaflet-marker-pane { will-change: transform; }

/* Hit-testing during movement.
   Every pointermove over the map otherwise walks 728 interactive icons, and a
   hit turns into a :hover match -> a transform transition -> a repaint, all
   while the pane is already translating. Neither hover nor a click is
   meaningful mid-gesture, so the pane simply opts out of the pointer for the
   duration. Leaflet puts `leaflet-dragging` on <body> (verified in
   vendor/leaflet.js, Draggable._onMove/finishDrag), hence the loose descendant
   selector; `is-zooming` is set by our own wheel handler on the container. */
.leaflet-dragging .leaflet-marker-pane,
.leaflet-container.is-zooming .leaflet-marker-pane { pointer-events: none; }

/* Up to 728 of these are on screen at once, so every declaration here is paid
   728 times on each paint. No box-shadow — a blurred layer per chip was the
   dominant paint cost while panning; the border carries the depth instead —
   and no transition outside a hover-capable pointer.
   `contain` is deliberately absent. Paint containment would clip the two things
   that overflow the 28x28 icon box, the found-check badge and the hover scale;
   layout containment would fit, but it also makes each marker a stacking
   context, and that is not a change worth making unmeasured. */
.mg-marker { background: none !important; border: 0 !important; }

.mg-chip {
  position: relative;
  width: 28px; height: 28px;
  display: flex; align-items: center; justify-content: center;
  background: #1A1C20;                              /* chip stays dark — glyph carries the hue */
  border: 1.5px solid var(--cat-line, #2A2D33);     /* subtle tint of the group hue */
  border-radius: 50%;
  color: var(--cat-color, #EDEAE2);
}
.mg-chip i { font-size: 14px; line-height: 1; display: block; color: inherit; }

.leaflet-marker-icon:focus-visible .mg-chip,
.mg-marker:focus-visible .mg-chip { outline: 2px solid var(--accent); outline-offset: 2px; }

/* found state always wins over the group hue */
.mg-marker.is-found .mg-chip { opacity: .4; color: var(--accent); border-color: rgba(var(--accent-rgb),.45); }

/* Hover-only affordances, and the only transition markers carry. Scoped to real
   pointers so touch devices never hold a transition on 728 elements, and kept
   after the found-state rule so its opacity override still wins. Nothing here
   can fire from the pane transform used during a zoom gesture — an inherited
   transform does not start a child transition, and none of these properties
   change while the pane moves. */
@media (hover: hover) {
  /* transform + border-color only. `opacity` used to be in this list, which put
     a third animatable property on all 728 chips for the sake of a hover step
     the eye reads as instant anyway — it now snaps. Never `transition: all`:
     that makes every property a candidate and Chrome tracks the lot. */
  .mg-chip { transition: transform .1s ease, border-color .1s ease; }
  .mg-marker:hover .mg-chip { transform: scale(1.14); border-color: var(--cat-color, #4A4F58); z-index: 2; }
  .mg-marker.is-found:hover .mg-chip { opacity: .75; color: var(--accent); border-color: rgba(var(--accent-rgb),.45); }
}

.mg-check {
  position: absolute; right: -3px; bottom: -3px;
  width: 13px; height: 13px; border-radius: 50%;
  background: var(--accent); color: #111214;
  display: none; align-items: center; justify-content: center;
  font-size: 9px; font-weight: 700; line-height: 1;
  border: 1.5px solid #111214;
}
.mg-marker.is-found .mg-check { display: flex; }

.mg-marker.is-active .mg-chip {
  border-color: var(--accent);
  box-shadow: 0 0 0 3px rgba(var(--accent-rgb),.2), 0 2px 8px rgba(0,0,0,.6);
  opacity: 1;
}

/* ---- low-zoom dot mode ---------------------------------------------------
   Below zoom 12 the whole city is on screen and all 728 chips are painted at
   once — exactly the band where the drag lag was felt. At that scale a 14px
   icomoon glyph is unreadable anyway, so the chip collapses to a bare 12px
   dot: no text run to shape and raster, and the painted disc drops from
   28x28 to 12x12, roughly a fifth of the pixels. The hue moves from the glyph
   to the fill so categories stay distinguishable.
   `zoomed-out` is set on the map container by map.js on zoomend / gesture
   settle — never per frame. Nothing here changes geometry that Leaflet
   measures: the icon box stays 28x28 and the dot is centred inside it with a
   plain margin, so iconAnchor, popupAnchor and hit-testing are untouched. */
.leaflet-container.zoomed-out .mg-chip {
  width: 12px; height: 12px;
  margin: 8px;                                   /* (28 - 12) / 2 — stays on the anchor */
  border-width: 1px;
  border-color: #111214;
  background: var(--cat-color, #9AA1AB);
}
.leaflet-container.zoomed-out .mg-chip i { display: none; }
.leaflet-container.zoomed-out .mg-check { display: none; }

/* found: still amber, still dimmed — the one distinction worth keeping here */
.leaflet-container.zoomed-out .mg-marker.is-found .mg-chip {
  background: var(--accent); border-color: #111214; opacity: .4;
}
.leaflet-container.zoomed-out .mg-marker.is-active .mg-chip {
  border-color: var(--accent);
  box-shadow: 0 0 0 3px rgba(var(--accent-rgb),.25);
  opacity: 1;
}
@media (hover: hover) {
  .leaflet-container.zoomed-out .mg-marker:hover .mg-chip { transform: scale(1.35); }
  .leaflet-container.zoomed-out .mg-marker.is-found:hover .mg-chip { opacity: .8; }
}

/* ---- satellite marker contrast -------------------------------------------
   Everything above is tuned against the atlas pyramid: flat, low-frequency
   fills — pale land, blue water, white road casing — that the dark #1A1C20
   chip separates from on its own. The satellite pyramid is the opposite:
   mid-tone, high-frequency olive scrub, tan dirt and grey tarmac in roughly
   the same luminance band as the chip body, cut by near-white road paint and
   near-black tree shadow. Two things fail there.

   1. The chip's border is `--cat-line`, a low-alpha tint of the group hue. It
      is a whisper against flat scrub and invisible against texture, so the
      only per-group cue at chip zoom disappears. On satellite the border takes
      the full `--cat-color` instead.
   2. Both the chip and the 12px dot lose their edge wherever the imagery
      happens to match them. A dark halo ring restores a constant outline over
      any background.

   Deliberately geometry-free: box-shadow paints outside the border box, and
   the border-width and 28x28 icon box are untouched, so iconAnchor, the dot's
   centring margin and hit-testing stay exactly as they are in atlas mode.
   Scoped to `.style-satellite`, set on the map container by map.js, so atlas
   mode renders byte-for-byte as before.

   Every border-colour rule below is gated on :not(.zoomed-out): in dot mode the
   border is the dark #111214 separator that holds the hue-filled disc off the
   terrain, and recolouring it to the hue would erase exactly the contrast this
   block exists to add. The halo is the dot's fix; the border is the chip's. */
.leaflet-container.style-satellite:not(.zoomed-out) .mg-chip {
  border-color: var(--cat-color, #4A4F58);
}
.leaflet-container.style-satellite .mg-chip {
  box-shadow: 0 0 0 2px rgba(10, 11, 13, .55);
}
/* Dots: a 12px hue disc sits in the same luminance band as scrub and tarmac, so
   this is the weaker case of the two. A tight ring, not the chip's 2px — at
   12px a wide halo reads as a bigger dot and the group hues start to crowd.
   San Andreas turns dots on at zoom 13, a whole city's worth of markers at
   once, which makes the crowding argument stronger here than in GTA V. */
.leaflet-container.style-satellite.zoomed-out .mg-chip {
  box-shadow: 0 0 0 1.5px rgba(10, 11, 13, .7);
}
/* found stays dimmed and accent-bordered; it only gains the halo */
.leaflet-container.style-satellite:not(.zoomed-out) .mg-marker.is-found .mg-chip {
  border-color: rgba(var(--accent-rgb), .55);
}
/* active already carries a ring — keep it, and seat it on the dark halo so the
   accent glow reads against bright tarmac as well as against dark water */
.leaflet-container.style-satellite:not(.zoomed-out) .mg-marker.is-active .mg-chip {
  box-shadow: 0 0 0 2px rgba(10, 11, 13, .8), 0 0 0 5px rgba(var(--accent-rgb), .3), 0 2px 8px rgba(0, 0, 0, .6);
}
.leaflet-container.style-satellite.zoomed-out .mg-marker.is-active .mg-chip {
  box-shadow: 0 0 0 1.5px rgba(10, 11, 13, .8), 0 0 0 4px rgba(var(--accent-rgb), .35);
}

/* ---------------- Popup ---------------- */

.leaflet-popup-content-wrapper {
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  color: var(--text);
  box-shadow: 0 10px 30px rgba(0,0,0,.6);
  padding: 0;
}
.leaflet-popup-content { margin: 0; width: 288px !important; line-height: 1.5; }
.leaflet-popup-tip { background: var(--panel); border: 1px solid var(--line); box-shadow: none; }
.leaflet-popup-close-button {
  color: var(--muted) !important;
  width: 26px !important; height: 26px !important;
  font-size: 18px !important; padding: 3px 0 0 !important;
}
.leaflet-popup-close-button:hover { color: var(--accent) !important; background: none !important; }

.pop-head { padding: 13px 34px 11px 15px; border-bottom: 1px solid var(--line); }
.pop-title { margin: 0 0 5px; font-size: 15px; font-weight: 600; line-height: 1.3; }
.pop-tags { display: flex; flex-wrap: wrap; gap: 6px; }
.pop-tag {
  display: inline-flex; align-items: center; gap: 5px;
  font-size: 11px; line-height: 1.3; padding: 3px 7px;
  border: 1px solid var(--line); border-radius: 4px;
  background: #17181C; color: var(--muted); white-space: nowrap;
}
.pop-tag i { font-size: 11px; }

.pop-body {
  padding: 12px 15px;
  font-size: 13px; color: #C7CBD2;
  max-height: 260px; overflow-y: auto;
}
.pop-body > *:first-child { margin-top: 0; }
.pop-body > *:last-child { margin-bottom: 0; }
.pop-body p { margin: 0 0 .6em; }
.pop-body ul { margin: .3em 0 .7em; padding-left: 1.15em; }
.pop-body li { margin: .18em 0; }
.pop-body strong { color: var(--text); font-weight: 600; }
.pop-body em { color: #D6D9DE; font-style: italic; }
.pop-body a { color: var(--accent); }
.pop-body .pop-none { color: #5F656E; font-family: var(--mono); font-size: 12.5px; }

.pop-foot { padding: 0 15px 13px; }
.pop-btn {
  width: 100%;
  font-family: var(--sans); font-size: 12px; font-weight: 600;
  letter-spacing: .08em; text-transform: uppercase;
  color: var(--text); background: #202329;
  border: 1px solid var(--line); border-radius: var(--radius);
  padding: 9px 12px; cursor: pointer;
  transition: background .12s, border-color .12s, color .12s;
}
.pop-btn:hover { background: #282C33; border-color: #3A3E46; }
.pop-btn[aria-pressed="true"] {
  background: var(--accent); border-color: var(--accent); color: #111214;
}
.pop-btn[aria-pressed="true"]:hover { background: var(--accent-hi); border-color: var(--accent-hi); }

/* ---------------- Leaflet chrome ---------------- */

.leaflet-container { background: var(--water); font-family: var(--sans); outline: none; }
.leaflet-container a { color: var(--accent); }

.leaflet-bar { border: 0; box-shadow: none; }
.leaflet-bar a, .leaflet-bar a:hover {
  background: rgba(26,28,32,.95);
  border: 1px solid var(--line);
  color: var(--text);
  width: 32px; height: 32px; line-height: 30px;
}
.leaflet-bar a:first-child { border-radius: var(--radius) var(--radius) 0 0; }
.leaflet-bar a:last-child { border-radius: 0 0 var(--radius) var(--radius); border-top: 0; }
.leaflet-bar a:hover { background: #282C33; color: var(--accent); }
.leaflet-bar a.leaflet-disabled { color: #4A4F58; background: rgba(26,28,32,.7); }

.leaflet-control-attribution {
  background: rgba(17,18,20,.86) !important;
  color: #666C75 !important;
  border: 1px solid var(--line); border-right: 0; border-bottom: 0;
  border-radius: var(--radius) 0 0 0;
  font-size: 10.5px; padding: 3px 8px;
}
.leaflet-control-attribution a { color: var(--muted) !important; }

/* readout */
.map-readout {
  background: rgba(17,18,20,.9);
  border: 1px solid var(--line); border-radius: var(--radius);
  padding: 6px 10px;
  font-family: var(--mono); font-size: 11px;
  color: var(--muted); letter-spacing: .04em;
  -webkit-backdrop-filter: blur(8px); backdrop-filter: blur(8px);
}
.map-readout b { color: var(--text); font-weight: 600; }

/* ---------------- Tile style switch ----------------
   The bottom-left corner holds two controls now, and Leaflet stacks corner
   controls vertically by floating them. A flex row puts the style pill beside
   the zoom readout instead of above it; the 10px gap is Leaflet's own
   `.leaflet-left .leaflet-control` margin, so nothing here re-specifies it. */
.map-shell .leaflet-bottom.leaflet-left { display: flex; align-items: flex-end; }
.map-shell .leaflet-bottom.leaflet-left > .leaflet-control { float: none; clear: none; }

.map-style {
  display: inline-flex; gap: 3px; padding: 3px;
  background: rgba(17,18,20,.9);
  border: 1px solid var(--line); border-radius: var(--radius);
  -webkit-backdrop-filter: blur(8px); backdrop-filter: blur(8px);
}
.map-style-btn {
  appearance: none; -webkit-appearance: none;
  border: 0; background: transparent; cursor: pointer;
  font-family: var(--mono); font-size: 10.5px; font-weight: 600;
  letter-spacing: .08em; text-transform: uppercase;
  color: var(--muted);
  padding: 4px 9px; border-radius: 4px; line-height: 1.5;
}
.map-style-btn:hover { color: var(--text); background: var(--panel-2); }
/* after :hover so the filled segment keeps the accent while the pointer is on it */
.map-style-btn[aria-pressed="true"] { background: var(--accent); color: #111214; }
.map-style-btn[aria-pressed="true"]:hover { background: var(--accent-hi); color: #111214; }
.map-style-btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* ---------------- Responsive ---------------- */

@media (max-width: 860px) {
  .side-toggle { display: inline-flex; }
  .map-shell { position: relative; }

  .side {
    position: absolute; left: 0; top: 0; bottom: 0;
    z-index: 1100;
    box-shadow: 0 0 40px rgba(0,0,0,.6);
    transform: translateX(-101%);
    transition: transform .2s ease;
    max-width: 86vw;
  }
  body.side-open .side { transform: translateX(0); }
  body.side-open .side-scrim {
    display: block; position: absolute; inset: 0; z-index: 1050;
    background: rgba(0,0,0,.5);
  }
  body.side-open .side-toggle { left: auto; right: 12px; }
}

@media (max-width: 420px) {
  .side-title { font-size: 23px; }
  .leaflet-popup-content { width: 240px !important; }
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .001ms !important;
  }
}
