// Mexico interactive map — clean minimal style with pan/zoom + clustering.
// Uses real lat/lng projected to an SVG viewBox.

const MAP_BOUNDS = { north: 33.2, south: 14.2, west: -118.6, east: -86.4 };
const MAP_VIEW = { w: 1000, h: 600 };
const MIN_ZOOM = 1;
const MAX_ZOOM = 12;
const CLUSTER_PX = 28; // cluster radius in viewBox units at zoom=1

function project(lat, lng) {
  const x = ((lng - MAP_BOUNDS.west) / (MAP_BOUNDS.east - MAP_BOUNDS.west)) * MAP_VIEW.w;
  const y = ((MAP_BOUNDS.north - lat) / (MAP_BOUNDS.north - MAP_BOUNDS.south)) * MAP_VIEW.h;
  return [x, y];
}

// Mexico boundary — separate paths for mainland and Baja peninsula
// so the Gulf of California reads as water between them.
const MEXICO_MAINLAND = [
  [32.50, -114.70], [32.50, -114.10], [32.55, -113.30], [32.55, -111.10],
  [31.33, -111.10], [31.33, -109.10], [31.78, -108.20], [31.78, -106.50],
  [30.80, -105.55], [29.90, -104.55], [29.50, -103.30], [29.20, -102.30],
  [28.70, -100.50], [27.50, -99.55], [26.20, -98.20], [25.95, -97.15],
  [24.40, -97.55], [23.10, -97.75], [22.20, -97.90],
  [21.20, -97.20], [20.30, -96.70], [19.20, -96.10],
  [18.50, -95.00], [18.10, -94.40],
  [18.40, -93.20], [18.55, -92.05], [18.50, -91.10],
  [19.40, -90.50], [20.50, -90.40], [21.30, -89.95],
  [21.55, -88.40], [21.55, -87.05],
  [21.10, -86.75],
  [20.20, -87.05], [19.40, -87.50], [18.55, -87.80],
  [18.50, -88.30],
  [17.85, -89.15], [17.25, -90.40], [17.10, -91.40],
  [16.10, -91.70], [15.40, -92.20], [14.55, -92.20],
  [15.30, -92.55], [15.80, -93.80], [16.20, -95.20],
  [16.10, -96.50], [16.50, -98.50], [16.90, -99.90],
  [17.85, -101.50], [18.20, -103.50], [19.10, -104.35],
  [19.80, -105.20], [20.70, -105.30],
  [21.50, -105.40], [22.50, -105.70], [23.20, -106.42],
  [24.80, -108.00], [25.80, -109.10], [27.30, -110.55],
  [28.50, -111.50], [29.70, -112.30], [30.80, -112.80], [31.30, -113.20],
  [31.85, -114.45], [32.10, -114.85], [32.50, -114.95],
  [32.50, -114.70]
];

const MEXICO_BAJA = [
  [32.55, -117.10],
  [31.85, -116.70], [30.50, -116.05], [29.30, -115.70], [28.00, -114.50],
  [27.20, -114.20], [26.40, -113.20], [25.20, -112.20], [24.10, -111.20],
  [23.30, -110.60], [22.93, -110.05],
  [23.10, -109.50], [24.10, -110.30], [25.30, -110.95], [26.30, -111.20],
  [27.30, -112.30], [28.40, -112.90], [29.50, -113.50], [30.50, -114.00],
  [31.30, -114.55], [32.10, -114.85],
  [32.55, -116.00], [32.55, -117.10]
];

function projectPath(pts) {
  let d = "";
  pts.forEach(([lat, lng], i) => {
    const [x, y] = project(lat, lng);
    d += `${i === 0 ? "M" : "L"} ${x.toFixed(1)} ${y.toFixed(1)} `;
  });
  return d + "Z";
}

const MEXICO_MAINLAND_PATH = projectPath(MEXICO_MAINLAND);
const MEXICO_BAJA_PATH = projectPath(MEXICO_BAJA);

// === State name normalization ===
// Our location state values → GeoJSON state_name values.
const STATE_ALIAS = {
  "CDMX": "Distrito Federal",
  "EDOMEX": "México",
  "Estado de México": "México",
  "Veracruz": "Veracruz de Ignacio de la Llave",
  "Michoacán": "Michoacán de Ocampo",
  "Coahuila": "Coahuila de Zaragoza",
};
function canonState(s) { return STATE_ALIAS[s] || s; }

// Convert a geojson ring ([[lng,lat], ...]) into an SVG path segment
function ringToPath(ring) {
  let d = "";
  for (let i = 0; i < ring.length; i++) {
    const [lng, lat] = ring[i];
    const [x, y] = project(lat, lng);
    d += `${i === 0 ? "M" : "L"}${x.toFixed(2)} ${y.toFixed(2)} `;
  }
  return d + "Z ";
}
function geometryToPath(geom) {
  if (!geom) return "";
  if (geom.type === "Polygon") {
    return geom.coordinates.map(ringToPath).join("");
  }
  if (geom.type === "MultiPolygon") {
    return geom.coordinates.flat().map(ringToPath).join("");
  }
  return "";
}
function geometryCentroid(geom) {
  let polys;
  if (geom.type === "Polygon") polys = [geom.coordinates[0]];
  else if (geom.type === "MultiPolygon") {
    let best = geom.coordinates[0], bestN = best[0].length;
    for (const p of geom.coordinates) { if (p[0].length > bestN) { best = p; bestN = p[0].length; } }
    polys = [best[0]];
  } else return null;
  let sx = 0, sy = 0, n = 0;
  for (const ring of polys) for (const [lng, lat] of ring) { sx += lng; sy += lat; n++; }
  return [sy / n, sx / n];
}

// Shorten verbose official names for on-map labels
const LABEL_OVERRIDES = {
  "Distrito Federal": "CDMX",
  "México": "Edo. Méx.",
  "Veracruz de Ignacio de la Llave": "Veracruz",
  "Michoacán de Ocampo": "Michoacán",
  "Coahuila de Zaragoza": "Coahuila",
  "Baja California Sur": "B.C. Sur",
  "San Luis Potosí": "S.L.P.",
  "Aguascalientes": "Ags.",
  "Quintana Roo": "Q. Roo",
};

// Greedy clustering in viewBox space
function buildClusters(locations, zoom) {
  const threshold = CLUSTER_PX / zoom;
  const t2 = threshold * threshold;
  const clusters = [];
  for (const loc of locations) {
    const [x, y] = project(loc.lat, loc.lng);
    let added = false;
    for (const c of clusters) {
      const dx = c.x - x, dy = c.y - y;
      if (dx * dx + dy * dy < t2) {
        c.locations.push(loc);
        const n = c.locations.length;
        c.x = (c.x * (n - 1) + x) / n;
        c.y = (c.y * (n - 1) + y) / n;
        added = true;
        break;
      }
    }
    if (!added) clusters.push({ x, y, locations: [loc] });
  }
  return clusters;
}

function MexicoMap({ locations, copy, mapStyle = "clean" }) {
  const [active, setActive] = React.useState(null);
  const [activeCluster, setActiveCluster] = React.useState(null);
  const [filter, setFilter] = React.useState("all");
  const [hover, setHover] = React.useState(null);
  const [hoverState, setHoverState] = React.useState(null);
  const [statesGeo, setStatesGeo] = React.useState(null);

  // Load states geojson once
  React.useEffect(() => {
    let alive = true;
    fetch("assets/mexico-states.json")
      .then(r => r.json())
      .then(gj => {
        if (!alive) return;
        const features = gj.features.map(f => ({
          name: f.properties.name,
          path: geometryToPath(f.geometry),
          centroid: geometryCentroid(f.geometry),
        }));
        setStatesGeo(features);
      })
      .catch(() => { /* fall back to country outline */ });
    return () => { alive = false; };
  }, []);

  // Pan / zoom state. Transform is: translate(tx, ty) scale(zoom)
  // applied to a <g> with the base viewBox 0,0,1000,600.
  const [tx, setTx] = React.useState(0);
  const [ty, setTy] = React.useState(0);
  const [zoom, setZoom] = React.useState(1);

  const svgRef = React.useRef(null);
  const dragRef = React.useRef(null);

  const filtered = locations.filter(l => filter === "all" ? true : l.status === filter);
  const clusters = React.useMemo(() => buildClusters(filtered, zoom), [filtered, zoom]);

  // Set of states that have at least one (filtered) location
  const activeStates = React.useMemo(() => {
    const set = new Set();
    for (const l of filtered) set.add(canonState(l.state));
    return set;
  }, [filtered]);
  // Count of locations per state (any filter)
  const stateCount = React.useMemo(() => {
    const m = new Map();
    for (const l of filtered) {
      const k = canonState(l.state);
      m.set(k, (m.get(k) || 0) + 1);
    }
    return m;
  }, [filtered]);

  // Style presets
  const stylesByMode = {
    clean: {
      land: "#FBF5E8", landStroke: "#1c1c20", landStrokeW: 1.5,
      stateStroke: "rgba(28,28,32,0.18)", stateStrokeW: 0.8,
      stateActiveFill: "#E89AAE", stateActiveStroke: "#B23B5C",
      stateHoverFill: "#F1B5C5",
      water: "transparent", grid: "rgba(0, 129, 198, 0.05)",
      label: "rgba(28, 28, 32, 0.55)", labelActive: "rgba(28, 28, 32, 0.85)",
    },
    bold:  {
      land: "#FFD24F", landStroke: "#ED184A", landStrokeW: 3,
      stateStroke: "rgba(237, 24, 74, 0.35)", stateStrokeW: 1,
      stateActiveFill: "#FFE7A8", stateActiveStroke: "#ED184A",
      stateHoverFill: "#FFEFC2",
      water: "#0081C6", grid: "rgba(255, 255, 255, 0.15)",
      label: "rgba(28, 28, 32, 0.6)", labelActive: "rgba(28, 28, 32, 0.85)",
    },
    diner: {
      land: "#FBF5E8", landStroke: "#ED184A", landStrokeW: 2,
      stateStroke: "rgba(237, 24, 74, 0.25)", stateStrokeW: 1,
      stateActiveFill: "#F8D2DC", stateActiveStroke: "#ED184A",
      stateHoverFill: "#FAE2E8",
      water: "#E6F1F8", grid: "rgba(237, 24, 74, 0.06)",
      label: "rgba(28, 28, 32, 0.5)", labelActive: "rgba(28, 28, 32, 0.85)",
    },
  };
  const s = stylesByMode[mapStyle] || stylesByMode.clean;

  const openCount = locations.filter(l => l.status === "open").length;
  const soonCount = locations.filter(l => l.status === "soon").length;

  // ---- Pan / zoom interactions ----
  function clientToViewBox(clientX, clientY) {
    const svg = svgRef.current;
    if (!svg) return [0, 0];
    const r = svg.getBoundingClientRect();
    const scaleX = r.width / MAP_VIEW.w;
    const scaleY = r.height / MAP_VIEW.h;
    const scale = Math.min(scaleX, scaleY);
    const padX = (r.width - MAP_VIEW.w * scale) / 2;
    const padY = (r.height - MAP_VIEW.h * scale) / 2;
    return [(clientX - r.left - padX) / scale, (clientY - r.top - padY) / scale];
  }

  // Convert a "world" point (base viewBox coords) to current screen-mapped viewBox via tx/ty/zoom
  function applyZoomAt(newZoom, vbx, vby) {
    const z = Math.max(MIN_ZOOM, Math.min(MAX_ZOOM, newZoom));
    // Want vbx*z + tx' = vbx*zoom + tx  =>  tx' = tx + vbx*(zoom - z)
    setTx(prev => prev + vbx * (zoom - z));
    setTy(prev => prev + vby * (zoom - z));
    setZoom(z);
  }

  function onWheel(e) {
    e.preventDefault();
    const [vbx, vby] = clientToViewBox(e.clientX, e.clientY);
    // wheel up = zoom in
    const factor = Math.exp(-e.deltaY * 0.0015);
    applyZoomAt(zoom * factor, vbx, vby);
  }

  function onMouseDown(e) {
    if (e.button !== 0) return;
    dragRef.current = { x: e.clientX, y: e.clientY, tx, ty, moved: false };
    e.currentTarget.style.cursor = "grabbing";
  }
  function onMouseMove(e) {
    if (!dragRef.current) return;
    const r = svgRef.current.getBoundingClientRect();
    const scale = Math.min(r.width / MAP_VIEW.w, r.height / MAP_VIEW.h);
    const dx = (e.clientX - dragRef.current.x) / scale;
    const dy = (e.clientY - dragRef.current.y) / scale;
    if (Math.abs(dx) + Math.abs(dy) > 1) dragRef.current.moved = true;
    setTx(dragRef.current.tx + dx);
    setTy(dragRef.current.ty + dy);
  }
  function onMouseUp(e) {
    if (dragRef.current) e.currentTarget.style.cursor = "grab";
    setTimeout(() => { dragRef.current = null; }, 0);
  }

  // Touch: single-finger pan, two-finger pinch
  const touchRef = React.useRef(null);
  function onTouchStart(e) {
    if (e.touches.length === 1) {
      touchRef.current = { mode: "pan", x: e.touches[0].clientX, y: e.touches[0].clientY, tx, ty };
    } else if (e.touches.length === 2) {
      const [a, b] = [e.touches[0], e.touches[1]];
      const cx = (a.clientX + b.clientX) / 2;
      const cy = (a.clientY + b.clientY) / 2;
      const d = Math.hypot(a.clientX - b.clientX, a.clientY - b.clientY);
      const [vbx, vby] = clientToViewBox(cx, cy);
      touchRef.current = { mode: "pinch", d, vbx, vby, zoom };
    }
  }
  function onTouchMove(e) {
    if (!touchRef.current) return;
    e.preventDefault();
    if (touchRef.current.mode === "pan" && e.touches.length === 1) {
      const r = svgRef.current.getBoundingClientRect();
      const scale = Math.min(r.width / MAP_VIEW.w, r.height / MAP_VIEW.h);
      setTx(touchRef.current.tx + (e.touches[0].clientX - touchRef.current.x) / scale);
      setTy(touchRef.current.ty + (e.touches[0].clientY - touchRef.current.y) / scale);
    } else if (touchRef.current.mode === "pinch" && e.touches.length === 2) {
      const [a, b] = [e.touches[0], e.touches[1]];
      const d = Math.hypot(a.clientX - b.clientX, a.clientY - b.clientY);
      const ratio = d / touchRef.current.d;
      applyZoomAt(touchRef.current.zoom * ratio, touchRef.current.vbx, touchRef.current.vby);
    }
  }
  function onTouchEnd() { touchRef.current = null; }

  function zoomBy(factor) {
    // Zoom around viewBox center
    const cx = MAP_VIEW.w / 2;
    const cy = MAP_VIEW.h / 2;
    // Convert center to "world" coords: world = (cx - tx) / zoom
    const wx = (cx - tx) / zoom;
    const wy = (cy - ty) / zoom;
    applyZoomAt(zoom * factor, wx, wy);
  }
  function resetView() { setTx(0); setTy(0); setZoom(1); }

  function handleClusterClick(c) {
    if (c.locations.length === 1) {
      setActive(c.locations[0]);
      setActiveCluster(null);
      return;
    }
    // Zoom in around the cluster centroid (2x) if room; otherwise open list
    if (zoom < MAX_ZOOM * 0.6) {
      applyZoomAt(zoom * 2.2, c.x, c.y);
      setActiveCluster(null);
    } else {
      setActiveCluster(c);
      setActive(null);
    }
  }

  // Wheel listener needs to be non-passive to call preventDefault
  React.useEffect(() => {
    const svg = svgRef.current;
    if (!svg) return;
    const handler = (e) => onWheel(e);
    svg.addEventListener("wheel", handler, { passive: false });
    return () => svg.removeEventListener("wheel", handler);
  });

  // Pin radius and stroke widths stay constant in screen space
  // by dividing visual sizes by `zoom`.
  const z = zoom;
  const pinR = 8 / z;
  const pinStroke = 1 / z;
  const clusterR = (n) => Math.min(22, 11 + Math.log2(n) * 4) / z;
  const labelFont = 12 / z;

  return (
    <div className="jr-map">
      <div className="jr-map__stage">
        <svg
          ref={svgRef}
          viewBox={`0 0 ${MAP_VIEW.w} ${MAP_VIEW.h}`}
          className="jr-map__svg"
          style={{ background: s.water, cursor: "grab", touchAction: "none" }}
          onMouseDown={onMouseDown}
          onMouseMove={onMouseMove}
          onMouseUp={onMouseUp}
          onMouseLeave={onMouseUp}
          onTouchStart={onTouchStart}
          onTouchMove={onTouchMove}
          onTouchEnd={onTouchEnd}
        >
          <defs>
            <pattern id="grid" width="40" height="40" patternUnits="userSpaceOnUse">
              <path d="M 40 0 L 0 0 0 40" fill="none" stroke={s.grid} strokeWidth="1" />
            </pattern>
            <filter id="pinShadow" x="-50%" y="-50%" width="200%" height="200%">
              <feDropShadow dx="0" dy="2" stdDeviation="1.5" floodColor="#1c1c20" floodOpacity="0.35" />
            </filter>
            <clipPath id="mapClip">
              <rect width={MAP_VIEW.w} height={MAP_VIEW.h} />
            </clipPath>
          </defs>

          <g clipPath="url(#mapClip)">
            <g transform={`translate(${tx} ${ty}) scale(${zoom})`}>
              {/* Ocean handled by stage CSS — keep SVG background transparent in clean mode */}
              {s.water !== "transparent" && (
                <rect width={MAP_VIEW.w} height={MAP_VIEW.h} fill={s.water} />
              )}

              {statesGeo ? (
                <g className="jr-map__states">
                  {statesGeo.map((st) => {
                    const isActive = activeStates.has(st.name);
                    const isHover = hoverState === st.name;
                    const fill = isActive
                      ? (isHover ? s.stateHoverFill : s.stateActiveFill)
                      : (isHover ? "rgba(0,0,0,0.04)" : s.land);
                    const stroke = isActive ? s.stateActiveStroke : s.stateStroke;
                    const strokeW = isActive ? s.stateStrokeW * 1.6 : s.stateStrokeW;
                    return (
                      <path
                        key={st.name}
                        d={st.path}
                        fill={fill}
                        stroke={stroke}
                        strokeWidth={strokeW}
                        strokeLinejoin="round"
                        vectorEffect="non-scaling-stroke"
                        onMouseEnter={() => setHoverState(st.name)}
                        onMouseLeave={() => setHoverState(null)}
                        style={{ transition: "fill .2s ease" }}
                      />
                    );
                  })}
                </g>
              ) : (
                <React.Fragment>
                  <path
                    d={MEXICO_MAINLAND_PATH}
                    fill={s.land}
                    stroke={s.landStroke}
                    strokeWidth={s.landStrokeW}
                    strokeLinejoin="round"
                    strokeLinecap="round"
                    vectorEffect="non-scaling-stroke"
                  />
                  <path
                    d={MEXICO_BAJA_PATH}
                    fill={s.land}
                    stroke={s.landStroke}
                    strokeWidth={s.landStrokeW}
                    strokeLinejoin="round"
                    strokeLinecap="round"
                    vectorEffect="non-scaling-stroke"
                  />
                </React.Fragment>
              )}

              {/* State labels — only the ones with locations, at higher zoom show all */}
              {statesGeo && statesGeo.map((st) => {
                const hasLoc = activeStates.has(st.name);
                if (!hasLoc && zoom < 2.2) return null;
                if (!st.centroid) return null;
                const [lat, lng] = st.centroid;
                const [x, y] = project(lat, lng);
                const label = LABEL_OVERRIDES[st.name] || st.name;
                return (
                  <text
                    key={"lbl-" + st.name}
                    x={x}
                    y={y}
                    textAnchor="middle"
                    dominantBaseline="middle"
                    fontFamily="var(--font-body), Inter, sans-serif"
                    fontSize={(hasLoc ? 11 : 9) / zoom}
                    fontWeight={hasLoc ? 600 : 400}
                    fill={hasLoc ? s.labelActive : s.label}
                    style={{ pointerEvents: "none", letterSpacing: "0.04em", textTransform: "uppercase" }}
                  >{label}</text>
                );
              })}

              {/* Pins / clusters */}
              {clusters.map((c, i) => {
                const isSingle = c.locations.length === 1;
                const loc = c.locations[0];
                const isActive = isSingle && active && active.id === loc.id;
                const isHover = isSingle && hover === loc.id;
                const isOpen = isSingle ? loc.status === "open" : c.locations.every(l => l.status === "open");
                const hasSoon = c.locations.some(l => l.status === "soon");
                const color = isSingle
                  ? (isOpen ? "#ED184A" : "#0081C6")
                  : (hasSoon ? "#0081C6" : "#ED184A");

                if (isSingle) {
                  const r = (isActive ? 11 : isHover ? 10 : 8) / zoom;
                  return (
                    <g
                      key={i}
                      transform={`translate(${c.x}, ${c.y})`}
                      style={{ cursor: "pointer" }}
                      onClick={(e) => {
                        if (dragRef.current?.moved) return;
                        e.stopPropagation();
                        handleClusterClick(c);
                      }}
                      onMouseEnter={() => setHover(loc.id)}
                      onMouseLeave={() => setHover(null)}
                    >
                      {isActive && (
                        <circle r={22 / zoom} fill={color} opacity="0.18">
                          <animate attributeName="r" values={`${14/zoom};${26/zoom};${14/zoom}`} dur="1.8s" repeatCount="indefinite" />
                          <animate attributeName="opacity" values="0.25;0;0.25" dur="1.8s" repeatCount="indefinite" />
                        </circle>
                      )}
                      <circle r={r + 4 / zoom} fill="white" opacity="0.9" />
                      <circle r={r} fill={color} filter="url(#pinShadow)" />
                      <circle r={r - 4 / zoom} fill="white" />
                      {!isOpen && (
                        <circle r={r - 6.5 / zoom} fill={color}>
                          <animate attributeName="opacity" values="1;0.3;1" dur="2s" repeatCount="indefinite" />
                        </circle>
                      )}
                    </g>
                  );
                }

                // Cluster
                const n = c.locations.length;
                const rOuter = clusterR(n);
                return (
                  <g
                    key={i}
                    transform={`translate(${c.x}, ${c.y})`}
                    style={{ cursor: "pointer" }}
                    onClick={(e) => {
                      if (dragRef.current?.moved) return;
                      e.stopPropagation();
                      handleClusterClick(c);
                    }}
                  >
                    <circle r={rOuter + 6 / zoom} fill={color} opacity="0.18" />
                    <circle r={rOuter + 2 / zoom} fill="white" opacity="0.95" />
                    <circle r={rOuter} fill={color} filter="url(#pinShadow)" />
                    <text
                      textAnchor="middle"
                      dominantBaseline="central"
                      fontFamily="'Bebas Neue', 'Oswald', sans-serif"
                      fontSize={labelFont * 1.4}
                      fill="white"
                      style={{ pointerEvents: "none", userSelect: "none", letterSpacing: "0.05em" }}
                    >{n}</text>
                  </g>
                );
              })}
            </g>
          </g>
        </svg>

        {/* Cluster list card */}
        {activeCluster && (
          <div className="jr-map__card jr-map__card--list" key={`c-${activeCluster.x}-${activeCluster.y}`}>
            <button className="jr-map__close" onClick={() => setActiveCluster(null)} aria-label="Close">×</button>
            <div className="jr-map__status jr-map__status--open">
              <span className="jr-map__status-dot" />
              {activeCluster.locations.length} ubicaciones
            </div>
            <h3 className="jr-map__card-title retro">{activeCluster.locations[0].city}</h3>
            <div className="jr-map__cluster-list">
              {activeCluster.locations.map(l => (
                <button
                  key={l.id}
                  className="jr-map__cluster-item"
                  onClick={() => { setActive(l); setActiveCluster(null); }}
                >
                  <span className={`jr-map__cluster-dot jr-map__cluster-dot--${l.status}`} />
                  <span className="jr-map__cluster-venue">{l.venue}</span>
                  <span className="jr-map__cluster-type">{l.type}</span>
                </button>
              ))}
            </div>
          </div>
        )}

        {/* Detail card */}
        {active && (
          <div className="jr-map__card" key={active.id}>
            <button className="jr-map__close" onClick={() => setActive(null)} aria-label="Close">×</button>
            <div className={`jr-map__status jr-map__status--${active.status}`}>
              <span className="jr-map__status-dot" />
              {active.status === "open" ? copy.statusOpen : copy.statusSoon}
            </div>
            <h3 className="jr-map__card-title retro">{active.venue}</h3>
            <div className="jr-map__card-meta">
              <span>{active.city}</span>
              <span className="jr-map__sep">·</span>
              <span>{active.state}</span>
            </div>
            <div className="jr-map__card-row">
              <span className="jr-map__card-label">Tipo</span>
              <span>{active.type}</span>
            </div>
            {active.opened && (
              <div className="jr-map__card-row">
                <span className="jr-map__card-label">{copy.since}</span>
                <span>{active.opened}</span>
              </div>
            )}
          </div>
        )}
      </div>

      {/* Legend + zoom controls */}
      <div className="jr-map__legend">
        <div className="jr-map__legend-item">
          <span className="jr-map__legend-dot" style={{ background: "#ED184A" }} />
          {copy.statusOpen}
        </div>
        {soonCount > 0 && (
          <div className="jr-map__legend-item">
            <span className="jr-map__legend-dot" style={{ background: "#0081C6" }} />
            {copy.statusSoon}
          </div>
        )}
        <div className="jr-map__legend-item jr-map__legend-hint">
          Arrastra · Rueda para zoom · Click en grupos para acercar
        </div>

        <div className="jr-map__zoom" role="group" aria-label="Map zoom">
          <button className="jr-map__zoom-btn" onClick={() => zoomBy(1.6)} aria-label="Zoom in">+</button>
          <button className="jr-map__zoom-btn" onClick={() => zoomBy(1 / 1.6)} aria-label="Zoom out">−</button>
          <button className="jr-map__zoom-btn jr-map__zoom-btn--reset" onClick={resetView} aria-label="Reset view" title="Reset">⌂</button>
        </div>
      </div>
    </div>
  );
}

window.MexicoMap = MexicoMap;
