/* Cobertura — mapa de México vectorial, tilt 3D, marcadores animados + arcos */ (function () { const { useRef, useMemo, useState, useEffect } = React; /* Contorno de México (lon,lat). Lado E/S de datos reales (johan/world.geo.json), resto con anclas geográficas conocidas. Baja California va como polígono aparte (el hueco entre ambos lee como el Golfo de California) para evitar auto-cruces. */ const MX_MAINLAND = [ [-114.8,32.5],[-113.0,32.0],[-111.07,31.33],[-108.21,31.33],[-106.51,31.78], [-104.5,29.7],[-102.3,29.88],[-101.4,29.78],[-100.0,28.4],[-99.5,27.5],[-99.1,26.4],[-97.9,26.06], [-97.14,25.87],[-97.528,24.992],[-97.703,24.272],[-97.776,22.933],[-97.872,22.444], [-97.699,21.899],[-97.389,21.411],[-97.189,20.635],[-96.526,19.891],[-96.292,19.32], [-95.901,18.828],[-94.839,18.563],[-94.426,18.144],[-93.549,18.424],[-92.786,18.525], [-92.037,18.705],[-91.408,18.876],[-90.772,19.284],[-90.534,19.867],[-90.451,20.708], [-90.279,21.0],[-89.601,21.262],[-88.544,21.494],[-87.658,21.459],[-87.052,21.544], [-86.812,21.332],[-86.846,20.85],[-87.383,20.255],[-87.621,19.647],[-87.437,19.472], [-87.587,19.04],[-87.837,18.26],[-88.091,18.517],[-88.3,18.5],[-88.49,18.487], [-88.848,17.883],[-89.15,17.955],[-89.143,17.808], [-90.6,17.82],[-91.0,17.13],[-90.99,16.07],[-92.2,15.0],[-92.23,14.54], [-93.36,15.5],[-94.5,16.3],[-95.3,15.9],[-96.5,15.65],[-98.0,16.3],[-99.9,16.85], [-101.5,17.6],[-103.5,18.4],[-104.99,19.32],[-105.49,20.4],[-105.69,21.5], [-106.42,23.2],[-108.2,25.0],[-109.2,26.5],[-110.5,27.5],[-111.8,28.8], [-112.8,30.0],[-113.6,31.0],[-114.8,32.0], ]; const MX_BAJA = [ [-117.0,32.5],[-115.0,32.3],[-114.6,31.4],[-113.4,29.8],[-112.4,28.2], [-111.2,26.3],[-110.2,24.5],[-109.5,23.2],[-109.9,22.85], [-111.8,24.8],[-112.6,26.0],[-113.7,27.3],[-114.8,28.8],[-116.0,30.3],[-117.0,31.8], ]; function useProjection() { return useMemo(() => { const W = 1000; const lat0 = 24, k = Math.cos((lat0 * Math.PI) / 180); const raw = (lon, lat) => [lon * k, -lat]; const all = [...MX_MAINLAND, ...MX_BAJA].map(([lo, la]) => raw(lo, la)); let minX = Infinity, maxX = -Infinity, minY = Infinity, maxY = -Infinity; all.forEach(([x, y]) => { minX = Math.min(minX, x); maxX = Math.max(maxX, x); minY = Math.min(minY, y); maxY = Math.max(maxY, y); }); const pad = 40; const scale = (W - pad * 2) / (maxX - minX); const H = (maxY - minY) * scale + pad * 2; const project = (lon, lat) => { const [x, y] = raw(lon, lat); return [pad + (x - minX) * scale, pad + (y - minY) * scale]; }; const toPath = (ring) => ring.map(([lo, la], i) => (i ? "L" : "M") + project(lo, la).map((n) => n.toFixed(1)).join(" ")).join(" ") + " Z"; const pct = (lon, lat) => { const [x, y] = project(lon, lat); return { left: (x / W) * 100, top: (y / H) * 100 }; }; return { W, H, project, paths: [toPath(MX_MAINLAND), toPath(MX_BAJA)], pct }; }, []); } function Cobertura({ data }) { const c = data.coverage; const proj = useProjection(); const planeRef = useRef(null); const [active, setActive] = useState("CDMX"); useEffect(() => { const el = planeRef.current; if (!el) return; if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) return; const onMove = (e) => { const r = el.getBoundingClientRect(); const px = (e.clientX - r.left) / r.width - 0.5; const py = (e.clientY - r.top) / r.height - 0.5; el.style.transform = `rotateX(${18 - py * 10}deg) rotateZ(${px * 6}deg) rotateY(${px * 4}deg)`; }; const reset = () => { el.style.transform = "rotateX(18deg) rotateZ(0deg) rotateY(0deg)"; }; reset(); el.addEventListener("mousemove", onMove); el.addEventListener("mouseleave", reset); return () => { el.removeEventListener("mousemove", onMove); el.removeEventListener("mouseleave", reset); }; }, []); const actuales = c.states.filter((s) => !s.soon); const proximos = c.states.filter((s) => s.soon); const hub = c.states.find((s) => s.name === "CDMX") || c.states[0]; const hubP = proj.project(hub.ll[0], hub.ll[1]); const arcs = c.states.filter((s) => s.hub && !s.soon && s.name !== "CDMX").map((s) => { const p = proj.project(s.ll[0], s.ll[1]); const mx = (hubP[0] + p[0]) / 2, my = (hubP[1] + p[1]) / 2; const lift = Math.hypot(p[0] - hubP[0], p[1] - hubP[1]) * 0.18; return `M${hubP[0].toFixed(1)} ${hubP[1].toFixed(1)} Q${mx.toFixed(1)} ${(my - lift).toFixed(1)} ${p[0].toFixed(1)} ${p[1].toFixed(1)}`; }); return (
{c.eyebrow}

{c.title}

{c.lead}

{/* extrusión (grosor) */} {[10, 8, 6, 4, 2].map((dy, i) => ( proj.paths.map((d, j) => ( )) ))} {proj.paths.map((d, i) => ( ))} {/* arcos de red */} {arcs.map((d, i) => )}
{c.states.map((s) => { const p = proj.pct(s.ll[0], s.ll[1]); const on = active === s.name; return ( ); })}
); } window.Cobertura = Cobertura; })();