// SONARA — Shared Map view for Esplora (Producer + Artisti) // Uses Leaflet 1.9 + Leaflet.markercluster + CartoDB tiles. // Custom DivIcons (avatar-style, Strava-inspired). Popup card with mini profile. const MapIcons = window.SonaraIcons; // Italian city centroids (approximate) const CITY_COORDS = { Milano: [45.4642, 9.1900], Roma: [41.9028, 12.4964], Napoli: [40.8518, 14.2681], Torino: [45.0703, 7.6869], Bologna: [44.4949, 11.3426], Firenze: [43.7696, 11.2558], Bari: [41.1171, 16.8719], Palermo: [38.1157, 13.3613], Verona: [45.4384, 10.9916], Genova: [44.4056, 8.9463], Catania: [37.5079, 15.0830], Brescia: [45.5416, 10.2118], Padova: [45.4064, 11.8768], }; // Pseudo-random based on seed (no Math.random; stable across renders) function pr(seed) { return ((seed * 9301 + 49297) % 233280) / 233280; } // Adds lat/lng + onMap flag to each item based on its city + index function enrichForMap(items, seedOffset = 0) { return items.map((it, i) => { const city = (typeof it.city === "string" ? it.city : "").split(",")[0].trim(); const c = CITY_COORDS[city] || CITY_COORDS.Milano; const s = i * 11 + seedOffset; // jitter ~3-8km (depending on city precision) const dLat = (pr(s) - 0.5) * 0.030; const dLng = (pr(s + 1) - 0.5) * 0.040; return { ...it, lat: c[0] + dLat, lng: c[1] + dLng, onMap: (s * 13) % 10 < 7 }; }).filter(x => x.onMap); } function MapView({ type = "producer" }) { const mapRef = React.useRef(null); const mapInst = React.useRef(null); const clusterRef = React.useRef(null); const tileLayer = React.useRef(null); const circleRef = React.useRef(null); const [activePin, setActivePin] = React.useState(null); const [nearMe, setNearMe] = React.useState(false); const [myPos, setMyPos] = React.useState(null); const [radius, setRadius] = React.useState(50); const [theme, setTheme] = React.useState(() => document.documentElement.classList.contains("dark") ? "dark" : "light"); const items = React.useMemo(() => { if (type === "producer") return enrichForMap(window.ALL_PRODUCERS || [], 0); if (type === "artisti") return enrichForMap(window.ALL_ARTISTS || [], 100); return []; }, [type]); // Init map once React.useEffect(() => { if (!window.L || mapInst.current) return; const m = window.L.map(mapRef.current, { center: [42.5, 12.5], zoom: 6, zoomControl: false, scrollWheelZoom: true, attributionControl: false, }); window.L.control.zoom({ position: "bottomright" }).addTo(m); mapInst.current = m; }, []); // Theme observer React.useEffect(() => { const obs = new MutationObserver(() => { const isDark = document.documentElement.classList.contains("dark"); setTheme(isDark ? "dark" : "light"); }); obs.observe(document.documentElement, { attributes: true, attributeFilter: ["class"] }); return () => obs.disconnect(); }, []); // (Re)apply tile layer when theme changes React.useEffect(() => { const m = mapInst.current; if (!m) return; if (tileLayer.current) m.removeLayer(tileLayer.current); const url = theme === "dark" ? "https://{s}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}{r}.png" : "https://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}{r}.png"; tileLayer.current = window.L.tileLayer(url, { subdomains: "abcd", maxZoom: 18, detectRetina: true, }).addTo(m); }, [theme]); // (Re)apply markers when items change React.useEffect(() => { const m = mapInst.current; if (!m || !window.L) return; if (clusterRef.current) m.removeLayer(clusterRef.current); const cluster = window.L.markerClusterGroup ? window.L.markerClusterGroup({ showCoverageOnHover: false, spiderfyOnMaxZoom: true, maxClusterRadius: 50, iconCreateFunction: (c) => { const count = c.getChildCount(); return window.L.divIcon({ html: `