/* ═══════════════════════════════════════════════════════════════════
   Mocca — MapView : Leaflet map with location pins + zones
   Real Leaflet (v1.9) + OpenStreetMap tiles. Tokens for chrome. Themed L/D.
   State-conditioned: ready · loading(skeleton) · empty · denied(read-only) · error
   ═══════════════════════════════════════════════════════════════════ */

.mk-map{ width:100%;font-family:var(--mk-font-ui);background:var(--bg-1);border:1px solid var(--border);
  border-radius:var(--mk-radius-lg);overflow:hidden;display:flex;flex-direction:column; }
.mk-map-head{ display:flex;align-items:center;gap:10px;padding:13px 16px;border-bottom:1px solid var(--divider); }
.mk-map-head .t{ font:700 14px var(--mk-font-ui);color:var(--fg-1); }
.mk-map-head .grow{ flex:1; }
.mk-map-tool{ display:inline-flex;align-items:center;gap:6px;height:32px;padding:0 12px;border-radius:999px;
  background:var(--bg-1);border:1px solid var(--border);color:var(--fg-2);font:600 12px var(--mk-font-ui);cursor:pointer; }
.mk-map-tool:hover{ color:var(--fg-1);border-color:var(--fg-3); }
.mk-map-tool .mi{ font-size:16px; }
/* B54 (Pablo: "I don't want the 'Continent 1/5' chip. I didn't ask for it and
   it's behind the zoom buttons") — the level chip is retired; the host's layer
   selector already names the level. `.mk-map-levelchip` / `.mk-lvl-idx` gone. */
.mk-map-denied{ display:flex;align-items:center;gap:6px;padding:9px 16px;font:500 12px var(--mk-font-ui);color:var(--fg-3);background:var(--bg-2);border-bottom:1px solid var(--divider); }
.mk-map-denied .mi{ font-size:15px; }

.mk-map-grid{ display:grid;grid-template-columns:1.6fr 1fr;min-height:420px;
  /* B46 C01 ("not taking all the space") — grow into the card's full height
     instead of stopping at the 420px floor and leaving dead space below. */
  flex:1 1 auto; }
/* Stack the map above the region list on mobile-portrait AND tablet (Pablo
   2026-06-22). Mobile-landscape + desktop keep the side-by-side row.
   B10-12 (Pablo): the stacked list was DISAPPEARING — canvas(420) + side(320)
   overflowed the height-constrained card and `.mk-map`'s overflow:hidden
   clipped the list off the bottom. Make the stacked grid a flex COLUMN that
   shares the card height: the canvas takes ~56% and the region list flexes
   into the remainder with its own scroll, so both are always visible. */
@media (max-width:600px) and (orientation:portrait),
       (min-width:601px) and (max-width:1024px) and (min-height:600px){
  .mk-map-grid{ display:flex;flex-direction:column;min-height:0;flex:1; }
  .mk-map-canvas{ min-height:0;flex:0 0 56%; }
  .mk-map-side{ border-inline-start:0;border-top:1px solid var(--divider);
    flex:1 1 auto;min-height:130px;max-height:none; }
}
/* B54 (Pablo: "The map should never shrink smaller than the edges of the frame.
   it looks cropped and horrible, let's fix the height width to whatever the
   height of the parent frame is") — the canvas fills its grid cell at every
   breakpoint, and the Leaflet pane is pinned to the canvas's edges instead of
   sitting inset inside it. The rounded corner moves onto the canvas so the map
   still reads as part of the card. */
.mk-map-canvas{ position:relative;background:var(--bg-2);min-height:420px;height:100%;
  border-radius:var(--mk-radius-md);overflow:hidden; }
/* B46 P6-ML03 — mobile LANDSCAPE matched neither branch above (portrait
   fails, and the tablet branch needs min-height:600px), so a 240px-tall
   phone got the desktop grid with its 420px floor: the map alone was
   ~1.8 screens tall and the region list beside it was cut off the same
   way. Landscape keeps the side-by-side split — width is what this shape
   has to spend — and drops the floor so map + list share the fold. */
@media (max-height:599px) and (orientation:landscape){
  .mk-map-grid{ min-height:0;height:min(420px, calc(100vh - 96px)); }
  .mk-map-canvas{ min-height:0; }
  .mk-map-side{ max-height:none; }
}
/* Round the live tile pane into a distinct map tile inset within the card
   (Pablo 2026-06-22: "can we put some rounded corners on the map?"). The
   small inset lets the card surface frame the rounded rectangle; overflow
   clips the tiles to the radius. Leaflet markers live inside this pane so
   pins stay aligned. */
.mk-map-leaflet{ position:absolute;inset:0;border-radius:inherit;overflow:hidden;
  width:100%;height:100%; }
/* clean monochrome basemap (CartoDB Positron / Dark Matter) — no filter needed */
.mk-map-leaflet.dim .leaflet-tile-pane{ opacity:.85; }

/* "Search this area" pill — floats top-center over the canvas, slides in
   when the user pans/zooms (Pablo 2026-06-21). */
.mk-map-search-area{
  display:none;align-items:center;gap:6px;
  height:34px;padding:0 16px;border-radius:999px;
  background:var(--bg-1);border:1px solid var(--border);color:var(--fg-1);
  font:600 12.5px var(--mk-font-ui);cursor:pointer;
  box-shadow:var(--shadow-md);
  opacity:0;pointer-events:none;
  transition:opacity .2s var(--mk-ease),transform .2s var(--mk-ease);
}
.mk-map-search-area.show{ display:inline-flex;opacity:1;pointer-events:auto; }
.mk-map-search-area:hover{ border-color:var(--accent);color:var(--accent-text); }
.mk-map-search-area .mi{ font-size:16px; }
.mk-map-side-empty{ display:flex;align-items:center;gap:8px;padding:18px 14px;
  font:500 12px var(--mk-font-ui);color:var(--fg-3);border-top:1px solid var(--divider); }
.mk-map-side-empty .mi{ font-size:16px; }

/* Minimal map dot (Pablo 2026-06-21: "simpler, cleaner, more minimalistic
   dots" — dropped the heavy always-on halo ring). A clean solid dot with a
   thin white ring + soft shadow; no permanent glow. */
.mk-pin{ position:relative;width:14px;height:14px;cursor:pointer; }
.mk-pin .pin{ position:absolute;inset:0;display:block;
  border-radius:50%;
  background:var(--accent);border:1.5px solid #fff;
  box-shadow:0 1px 3px rgba(13,37,48,.4);
  transition:transform .14s var(--mk-ease);
  z-index:2; box-sizing:border-box; }
.mk-pin.busy .pin{ background:var(--mk-warning); }
.mk-pin.down .pin{ background:var(--mk-danger); }
/* highlighted from a region-list hover — the dot grows slightly AND a clearly
   visible radial pulse ripples out (Pablo 2026-06-21: "when I hover I see
   something but not the pulsation"). The ripple sits ABOVE the tiles (z-index
   positive) but below the dot. */
.mk-pin.hot .pin{ transform:scale(1.2); }
.mk-pin.hot::before,
.mk-pin.hot::after{
  content:"";position:absolute;top:50%;left:50%;width:14px;height:14px;
  border-radius:50%;z-index:1;
  border:2px solid var(--accent);
  transform:translate(-50%,-50%);
  animation:mkPinPulse 1.4s var(--mk-ease) infinite;
}
.mk-pin.hot::after{ animation-delay:.7s; }
.mk-pin.hot.busy::before,.mk-pin.hot.busy::after{ border-color:var(--mk-warning); }
.mk-pin.hot.down::before,.mk-pin.hot.down::after{ border-color:var(--mk-danger); }
@keyframes mkPinPulse{ 0%{transform:translate(-50%,-50%) scale(.7);opacity:.9} 100%{transform:translate(-50%,-50%) scale(3.4);opacity:0} }
@media (prefers-reduced-motion:reduce){ .mk-pin.hot::before,.mk-pin.hot::after{ animation:none;opacity:0; } }
/* B63-120 — a cluster dot: same accent-filled circle language as a single
   pin, sized (inline, per-instance) to the count it stands in for, with the
   count printed inside. */
.mk-pin-cluster{ display:flex;align-items:center;justify-content:center;
  border-radius:50%;background:var(--accent);color:#fff;border:1.5px solid #fff;
  box-shadow:0 1px 4px rgba(13,37,48,.45);cursor:pointer;
  font:700 11px/1 var(--mk-font-ui);box-sizing:border-box; }
/* hover/tap tooltip */
.mk-map-tip.leaflet-tooltip{
  background:var(--bg-1);color:var(--fg-1);border:1px solid var(--border);
  border-radius:var(--mk-radius-md);box-shadow:var(--shadow-pop);
  font:600 12px var(--mk-font-ui);padding:7px 10px;line-height:1.45;
}
.mk-map-tip.leaflet-tooltip b{ font-weight:700; }
.mk-map-tip.leaflet-tooltip-top::before{ border-top-color:var(--border); }

/* side list */
.mk-map-side{ border-inline-start:1px solid var(--divider);display:flex;flex-direction:column;min-height:0;overflow-y:auto;background:var(--bg-1); }
.mk-map-side-h{ padding:12px 14px 6px;font:600 10.5px var(--mk-font-ui);letter-spacing:.06em;text-transform:none;color:var(--fg-3); }
.mk-region{ display:flex;align-items:center;gap:11px;padding:11px 14px;border-top:1px solid var(--divider);cursor:pointer;transition:background .12s; }
.mk-region:hover,.mk-region.hot{ background:var(--bg-2); }
.mk-region.hot{ box-shadow:inset 2px 0 0 var(--accent); }
.mk-region .pillbox{ flex:0 0 auto; }
.mk-region-pill{ display:inline-flex;align-items:center;gap:5px;font:600 11px var(--mk-font-ui); }
.mk-region-pill .dot{ width:8px;height:8px;border-radius:50%;background:currentColor; }
.mk-region-pill.ok{ color:var(--mk-success); } .mk-region-pill.warn{ color:var(--mk-warning); } .mk-region-pill.err{ color:var(--mk-danger); }
.mk-region .main{ flex:1;min-width:0; }
.mk-region .nm{ font:600 13px var(--mk-font-ui);color:var(--fg-1); }
.mk-region .sub{ font:500 11px var(--mk-font-ui);color:var(--fg-3); }
.mk-region .v{ font:700 14px var(--mk-font-ui);color:var(--fg-1);font-variant-numeric:tabular-nums; }

/* overlays: empty (card over a live map) / error (full cover) */
.mk-map-overlay{ position:absolute;inset:0;z-index:500;display:flex;align-items:center;justify-content:center;padding:24px; }
.mk-map-overlay-in{ display:flex;flex-direction:column;align-items:center;gap:8px;text-align:center; }
.mk-map-overlay .mi{ font-size:34px;color:var(--fg-3); } .mk-map-overlay.err .mi{ color:var(--mk-danger); }
.mk-map-overlay .h{ font:600 15px var(--mk-font-ui);color:var(--fg-1); }
.mk-map-overlay .p{ font:500 13px var(--mk-font-ui);color:var(--fg-3);max-width:260px;line-height:1.5; }
.mk-map-overlay .retry{ margin-top:6px;display:inline-flex;align-items:center;gap:6px;padding:8px 15px;border-radius:999px;border:1px solid var(--border-strong);background:var(--bg-1);color:var(--fg-1);cursor:pointer;font:600 12.5px var(--mk-font-ui); }
/* error: opaque, the map failed so nothing to show behind */
.mk-map-overlay.err{ background:var(--bg-2); }
/* empty: the map still exists — float a card, let the world show behind */
.mk-map-overlay.empty{ background:color-mix(in srgb,var(--bg-1) 30%,transparent);pointer-events:none; }
.mk-map-overlay.empty .mk-map-overlay-in{ pointer-events:auto;background:var(--bg-1);border:1px solid var(--border);
  border-radius:var(--mk-radius-md);box-shadow:var(--shadow-pop);padding:20px 26px; }
.mk-map-overlay.empty .mi{ font-size:30px; }

/* loading: a small chip over the live (world) map */
.mk-map-chip{ position:absolute;top:12px;left:50%;transform:translateX(-50%);z-index:500;
  display:inline-flex;align-items:center;gap:8px;padding:7px 14px;border-radius:999px;
  background:var(--bg-1);border:1px solid var(--border);box-shadow:var(--shadow-sm);
  font:600 12px var(--mk-font-ui);color:var(--fg-2); }
.mk-map-spin{ width:13px;height:13px;border-radius:50%;border:2px solid var(--accent);border-top-color:transparent;animation:mkMapSpin .7s linear infinite; }
@keyframes mkMapSpin{ to{ transform:rotate(360deg); } }
@media (prefers-reduced-motion:reduce){ .mk-map-spin{ animation-duration:1.6s; } }
.mk-map-sk{ position:absolute;inset:0;background:linear-gradient(90deg,var(--bg-3) 25%,var(--bg-2) 37%,var(--bg-3) 63%);background-size:400% 100%;animation:mkMapShim 1.4s var(--mk-ease) infinite;z-index:400; }
@keyframes mkMapShim{ 0%{background-position:100% 0} 100%{background-position:-100% 0} }
@media (prefers-reduced-motion:reduce){ .mk-map-sk{ animation:none; } }

/* leaflet popup theming */
.leaflet-popup-content-wrapper{ background:var(--bg-1);color:var(--fg-1);border-radius:var(--mk-radius-md);box-shadow:var(--shadow-pop); }
.leaflet-popup-content{ font:600 12.5px var(--mk-font-ui);margin:10px 12px; }
.leaflet-popup-tip{ background:var(--bg-1); }
.leaflet-container{ background:var(--bg-2);font-family:var(--mk-font-ui); }

/* tablet + mobile-vertical (harness device frame): stack map over the region list. */
.px-frame[data-size="T"] .mk-map-grid,
.px-frame[data-size="V"] .mk-map-grid{ grid-template-columns:1fr;min-height:0; }
.px-frame[data-size="T"] .mk-map-canvas,
.px-frame[data-size="V"] .mk-map-canvas{ min-height:300px; }
.px-frame[data-size="T"] .mk-map-side,
.px-frame[data-size="V"] .mk-map-side{ border-inline-start:0;border-top:1px solid var(--divider);max-height:220px; }

/* demo scaffolding */
.mk-demo{ padding:24px;display:flex;flex-direction:column;gap:16px; }
.mk-demo-head{ display:flex;align-items:center;justify-content:space-between;gap:12px;flex-wrap:wrap; }
.mk-demo-title{ font:700 20px/1.2 var(--mk-font-ui);letter-spacing:-.02em;color:var(--fg-1);margin:0; }
.mk-demo-sub{ font:500 13px var(--mk-font-ui);color:var(--fg-3);margin:2px 0 0; }
.mk-state-tag{ display:inline-flex;align-items:center;gap:7px;padding:6px 13px;border-radius:999px;
  font:600 12px var(--mk-font-ui);background:var(--accent-soft);color:var(--accent-text); }
.mk-state-tag .mi{ font-size:16px; }

/* C2/C4 (Pablo B15) — zone drawing + travelling hover border */
.mk-map-leaflet.mk-drawing,
.mk-map-leaflet.mk-drawing .leaflet-interactive{ cursor:crosshair !important; }
.mk-draw-hint{ display:inline-flex;align-items:center;gap:7px; }
.mk-draw-hint .mi{ font-size:16px; }
.mk-draw-hint.reject{ color:#fff;background:#d64545;border-color:#d64545; }
/* travelling bright border around a hovered zone polygon (SVG path).
   B50-21 — this animation was effectively unreachable by hovering the polygon
   itself: the highlight re-parented the path, the browser fired an immediate
   mouseout, and the class was removed a frame after being added. Hover is now
   pointer-driven (map.js _wireZoneHover) and never re-parents, so the marching
   outline runs for as long as the pointer is inside the zone — the same feedback
   the side-list hover gives. */
.mk-zone-hot{
  stroke-dasharray:10 6;
  animation:mk-zone-march .8s linear infinite;
  filter:drop-shadow(0 0 4px var(--accent)) drop-shadow(0 0 7px var(--accent));
}
@keyframes mk-zone-march{ to{ stroke-dashoffset:-32; } }
@media (prefers-reduced-motion:reduce){ .mk-zone-hot{ animation:none; } }


/* ── B44 — top-center frame overlay: Search-this-area + Fit all ── */
.mk-map-topctr{ position:absolute;top:14px;left:50%;transform:translateX(-50%);z-index:600;display:flex;align-items:center;gap:8px; }
.mk-map-fitbtn{ display:inline-flex;align-items:center;gap:6px;height:34px;padding:0 14px;border-radius:999px;
  background:var(--bg-1);border:1px solid var(--border);color:var(--fg-1);
  font:600 12.5px var(--mk-font-ui);cursor:pointer;box-shadow:var(--shadow-md);
  /* B62 UI-58 (Pablo: "The text in this button wraps on mobile portrait… it
     looks bad") — the pill sits in a centred overlay beside "Search this area",
     so on a narrow frame it is squeezed until "Fit all" breaks over two lines
     and the 34px pill grows into a lozenge. A label of two words never wraps. */
  white-space:nowrap;flex:0 0 auto; }
.mk-map-search-area{ white-space:nowrap; }
@media (max-width:600px){
  /* Narrower frame: shrink the type and the padding rather than the label. */
  .mk-map-topctr{ max-width:calc(100% - 24px); }
  .mk-map-fitbtn{ font-size:11.5px;padding:0 11px;gap:5px; }
  .mk-map-fitbtn .mi{ font-size:15px; }
}
.mk-map-fitbtn:hover{ border-color:var(--accent);color:var(--accent-text); }
.mk-map-fitbtn .mi{ font-size:16px; }
/* B44 — body-level pin tooltip (overlays the map frame; never clipped) */
.mk-map-tip-body{ position:fixed;z-index:4000;display:none;pointer-events:none;
  background:var(--bg-1);border:1px solid var(--border);border-radius:8px;
  padding:6px 10px;font:500 12px var(--mk-font-ui);color:var(--fg-1);
  box-shadow:var(--shadow-md);text-align:center;line-height:1.4; }
