/* ============================================================
   Liberty City — 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:  #FFB13D;
  --accent-hi: #FFC266;
  --accent-rgb: 255,177,61;
  --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);
}
.grp-pip { width: 8px; height: 8px; flex: none; background: var(--cat-color, var(--line)); }
.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; }
}

/* ---------------- 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;
  overflow: hidden;              /* clips a screenshot to the rounded corners */
}
.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; }

/* Screenshot. Fixed 16:9 box so the popup does not reflow as the lazy image
   arrives; `contain` rather than `cover` because a few shots are not 16:9 and
   cropping one could cut out the very thing it is pointing at. */
.pop-shot {
  position: relative;
  aspect-ratio: 16 / 9;          /* fallback; the real ratio is set inline */
  max-height: 320px;             /* keeps a tall portrait shot from dominating */
  background: var(--bg);
  border-bottom: 1px solid var(--line);
  overflow: hidden;
}
.pop-shot img {
  display: block;
  width: 100%; height: 100%;
  object-fit: contain;
}
/* The close button sits over the image; this keeps it legible on a light shot. */
.pop-shot::after {
  content: '';
  position: absolute; inset: 0 0 auto 0; height: 40px;
  background: linear-gradient(rgba(17,18,20,.55), rgba(17,18,20,0));
  pointer-events: none;
}
@supports not (aspect-ratio: 16 / 9) {
  .pop-shot { height: 162px; }        /* 288px content width at 16:9 */
}

.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; }

/* ---------------- 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;
  }
}
