// 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: `
${count}
`, className: "sn-cluster-wrap", iconSize: [44, 44], }); }, }) : window.L.layerGroup(); items.forEach((it) => { const initials = (it.init || (it.name || "??").split(" ").map(w => w[0]).join("")).slice(0, 2).toUpperCase(); const artIdx = (it.art ?? 0) % 6; const isStudio = type === "producer" && !!it.studio; const icon = window.L.divIcon({ className: "sn-pin", html: `
${initials}
${it.verified ? '' : ""} ${isStudio ? 'S' : ""}
`, iconSize: [48, 54], iconAnchor: [24, 54], }); const marker = window.L.marker([it.lat, it.lng], { icon, riseOnHover: true }); marker.on("click", () => setActivePin(it)); cluster.addLayer(marker); }); m.addLayer(cluster); clusterRef.current = cluster; }, [items, type]); // Apply "near me" radius circle React.useEffect(() => { const m = mapInst.current; if (!m || !window.L) return; if (circleRef.current) { m.removeLayer(circleRef.current); circleRef.current = null; } if (nearMe && myPos) { circleRef.current = window.L.circle(myPos, { radius: radius * 1000, color: "#7b55d0", fillColor: "#7b55d0", fillOpacity: 0.06, weight: 1.5, dashArray: "4 6", }).addTo(m); } }, [nearMe, myPos, radius]); const onNearMe = () => { if (nearMe) { setNearMe(false); setMyPos(null); return; } if (!navigator.geolocation) { // Fallback to Milano setMyPos([45.46, 9.19]); setNearMe(true); mapInst.current?.setView([45.46, 9.19], 10); return; } navigator.geolocation.getCurrentPosition( (p) => { const pos = [p.coords.latitude, p.coords.longitude]; setMyPos(pos); setNearMe(true); mapInst.current?.setView(pos, 10); }, () => { setMyPos([45.46, 9.19]); setNearMe(true); mapInst.current?.setView([45.46, 9.19], 10); }, { timeout: 5000 } ); }; const detailHref = (it) => { const base = window.ESPLORA_DETAIL_BASE || ""; if (type === "producer") return base + "Producer.html"; return base + "Artista.html#id=a" + it.id; }; const totalCount = (type === "producer" ? window.ALL_PRODUCERS : window.ALL_ARTISTS)?.length || 0; const pct = Math.round((items.length / Math.max(totalCount, 1)) * 100); return (
{type === "producer" && window.ProducerFilterRail ? : type === "artisti" && window.ArtistsFilterRail ? : null}
{items.length} {type === "producer" ? "producer" : "artisti"} sulla mappa · {pct}% del marketplace ha attivato "Apparire sulla mappa".
Abilita anche tu →
{nearMe && (
Raggio setRadius(Number(e.target.value))}/> {radius} km
)}
{type === "producer" ? ( <> Producer SStudio Verified ) : ( <> Artista Verified )}
{activePin && (
{(activePin.init || (activePin.name || "??").split(" ").map(w=>w[0]).join("")).slice(0, 2).toUpperCase()}
{activePin.name} {activePin.verified && }
{activePin.city}{activePin.age ? ` · ${activePin.age} anni` : ""}
{activePin.rating != null ? ( <> {activePin.rating.toFixed(1)} ) : nuovo}
{activePin.genres && (
{activePin.genres.slice(0, 3).map(g => {g})} {type === "producer" && activePin.studio && Studio}
)} {type === "producer" && (
{activePin.projects} progetti · {activePin.responseHrs}h risposta · da €{activePin.fromPrice}
)} {type === "artisti" && (
{activePin.followers >= 1000 ? (activePin.followers/1000).toFixed(1) + "k" : activePin.followers} follower · {activePin.tracks} tracce · {activePin.collabs} collab
)} {activePin.sigTitle && ( )}
Vedi profilo
)}
); } window.MapView = MapView;