Select a location
DROMOS FESTIVAL
Click on a marker to discover the program.
document.addEventListener("DOMContentLoaded", () => { let mapInitialized = false; const wrapper = document.getElementById("dromos-map-wrapper"); function getCookie(name) { const match = document.cookie.match( new RegExp('(?:^|; )' + name.replace(/([.$?*|{}()[\]\\/+^])/g, '\\$1') + '=([^;]*)') ); return match ? decodeURIComponent(match[1]) : null; } function hasMapConsent() { const marketing = getCookie("cmplz_marketing"); return marketing === "allow"; } function setConsentState() { if (!wrapper) return; if (hasMapConsent()) { wrapper.classList.remove("map-consent-blocked"); wrapper.classList.add("map-consent-ready"); } else { wrapper.classList.remove("map-consent-ready"); wrapper.classList.add("map-consent-blocked"); } } function loadCssOnce(href, id) { if (document.getElementById(id)) return; const link = document.createElement("link"); link.id = id; link.rel = "stylesheet"; link.href = href; document.head.appendChild(link); } function loadScriptOnce(src, id) { return new Promise((resolve, reject) => { const existing = document.getElementById(id); if (existing) { if (existing.dataset.loaded === "true") { resolve(); } else { existing.addEventListener("load", resolve, { once: true }); existing.addEventListener("error", reject, { once: true }); } return; } const script = document.createElement("script"); script.id = id; script.src = src; script.async = true; script.addEventListener("load", () => { script.dataset.loaded = "true"; resolve(); }); script.addEventListener("error", reject); document.head.appendChild(script); }); } async function ensureOpenFreeMap() { loadCssOnce( "https://cdn.jsdelivr.net/npm/maplibre-gl@5.24.0/dist/maplibre-gl.css", "dromos-maplibre-css" ); if (!window.maplibregl) { await loadScriptOnce( "https://cdn.jsdelivr.net/npm/maplibre-gl@5.24.0/dist/maplibre-gl.js", "dromos-maplibre-js" ); } if (typeof L.maplibreGL !== "function") { await loadScriptOnce( "https://cdn.jsdelivr.net/npm/@maplibre/maplibre-gl-leaflet@0.1.4/leaflet-maplibre-gl.js", "dromos-maplibre-leaflet-js" ); } } async function initMap() { if (mapInitialized) return; try { await ensureOpenFreeMap(); } catch (error) { console.error("Errore caricamento OpenFreeMap / MapLibre:", error); return; } mapInitialized = true; const map = L.map("map", { zoomControl: false }).setView([39.903, 8.593], 9); const openFreeMapLayer = L.maplibreGL({ style: "https://tiles.openfreemap.org/styles/positron" }).addTo(map); const glMap = openFreeMapLayer.getMaplibreMap(); const hidePlaceLabels = () => { const style = glMap.getStyle(); if (!style?.layers) return; style.layers.forEach((layer) => { const isPlaceLabel = layer.type === "symbol" && layer["source-layer"] === "place"; if (!isPlaceLabel) return; try { glMap.setLayoutProperty(layer.id, "visibility", "none"); } catch (error) { console.warn("Impossibile nascondere il layer:", layer.id, error); } }); }; if (glMap.isStyleLoaded()) { hidePlaceLabels(); } else { glMap.once("load", hidePlaceLabels); } L.control.zoom({ position: "bottomright" }).addTo(map); const markerIcon = L.divIcon({ className: "", html: '
', iconSize: [14, 14], iconAnchor: [7, 7] }); // ---- SEARCH refs ---- const searchInput = document.getElementById("map-search-input"); const searchResults = document.getElementById("map-search-results"); const searchFilters = document.getElementById("map-search-filters"); // ---- PANEL refs ---- const panel = document.getElementById("map-panel"); const panelClose = document.getElementById("panel-close"); const panelHandle = document.getElementById("panel-handle"); const panelTitle = document.getElementById("panel-title"); const panelAddress = document.getElementById("panel-address"); const panelSubtitle = document.querySelector(".panel-subtitle"); const panelChips = document.querySelector(".panel-chips"); const panelEvents = document.querySelector(".panel-events"); const panelLink = document.getElementById("panel-link"); const panelCtaWrap = document.querySelector(".panel-cta"); const btnGmaps = document.getElementById("btn-gmaps"); const btnAmaps = document.getElementById("btn-amaps"); const panelHero = document.getElementById("panel-hero"); // INFO MENU refs const infoToggle = document.getElementById("panel-info-toggle"); const infoMenu = document.getElementById("panel-info-menu"); const infoList = document.getElementById("panel-info-list"); const isMobile = () => window.matchMedia("(max-width: 768px)").matches; const openPanel = () => { panel?.classList.add("open"); panel?.classList.remove("is-hidden-mobile"); }; const closePanel = () => { panel?.classList.remove("open"); if (isMobile()) { panel?.classList.add("is-hidden-mobile"); } }; const expandPanel = () => panel?.classList.add("expanded"); const collapsePanel = () => panel?.classList.remove("expanded"); let activeFilter = "all"; let activeLocationId = null; let eventsLoaded = false; const availableFilters = new Map(); const decodeHtml = (s) => { const el = document.createElement("textarea"); el.innerHTML = s || ""; return el.value; }; const escapeAttr = (s) => String(s || "") .replace(/&/g, "&") .replace(/"/g, """) .replace(//g, ">"); const escapeCssUrl = (s) => String(s || "") .replace(/"/g, '\\"') .replace(/'/g, "\'") .replace(/\n/g, "") .replace(/\r/g, ""); const normalizeSearchString = (s) => String(s || "") .toLowerCase() .normalize("NFD") .replace(/[\u0300-\u036f]/g, "") .replace(/[^a-z0-9\s]/g, " ") .replace(/\s+/g, " ") .trim(); const slugify = (s) => String(s || "") .toLowerCase() .normalize("NFD") .replace(/[\u0300-\u036f]/g, "") .replace(/[^a-z0-9]+/g, "-") .replace(/^-+|-+$/g, ""); const getFilterIcon = (slug, label = "") => { const key = `${slug} ${label}`.toLowerCase(); if (key.includes("music") || key.includes("musica") || key.includes("concerto")) return "music_note"; if (key.includes("art") || key.includes("arte") || key.includes("mostra") || key.includes("exhibition")) return "palette"; if (key.includes("talk") || key.includes("incontro") || key.includes("dialogo") || key.includes("conferenza")) return "mic"; if (key.includes("cinema") || key.includes("film")) return "movie"; if (key.includes("teatro") || key.includes("performance")) return "theater_comedy"; if (key.includes("letter") || key.includes("libri") || key.includes("book")) return "menu_book"; if (key.includes("workshop") || key.includes("laboratorio") || key.includes("laboratori")) return "construction"; return "apps"; }; const getEventTypeTerms = (ev) => { const embeddedTerms = ev?._embedded?.["wp:term"] || []; const out = []; embeddedTerms.forEach((group) => { if (!Array.isArray(group)) return; group.forEach((term) => { if (!term) return; const taxonomy = String(term.taxonomy || ""); const slug = String(term.slug || ""); const name = decodeHtml(term.name || ""); if ( taxonomy === "tipologie_evento" || taxonomy === "tipologia-evento" || taxonomy === "tipologia_evento" ) { out.push({ slug: slug || slugify(name), name }); } }); }); return out; }; const PANEL_CHIP_LABELS = { music: "Music", musica: "Music", concerto: "Music", concerti: "Music", live: "Music", talk: "Talk", talks: "Talk", conferenza: "Talk", conferenze: "Talk", incontro: "Talk", incontri: "Talk", mostra: "Mostra", mostre: "Mostra", exhibition: "Mostra", exhibitions: "Mostra", workshop: "Workshop", laboratorio: "Workshop", laboratori: "Workshop", cinema: "Cinema", film: "Cinema", teatro: "Teatro", performance: "Performance", libro: "Libri", libri: "Libri", letteratura: "Libri" }; const getPanelChipLabel = (term) => { const slug = String(term?.slug || "").toLowerCase(); const name = String(term?.name || "").toLowerCase(); return ( PANEL_CHIP_LABELS[slug] || PANEL_CHIP_LABELS[name] || decodeHtml(term?.name || "Evento") ); }; const getLocationEventTypes = (events = []) => { const mapTerms = new Map(); events.forEach((ev) => { const terms = getEventTypeTerms(ev); terms.forEach((term) => { const key = term.slug || slugify(term.name); if (!mapTerms.has(key)) { mapTerms.set(key, { slug: key, label: getPanelChipLabel(term) }); } }); }); return Array.from(mapTerms.values()); }; const renderPanelChips = (events = []) => { if (!panelChips) return; const chips = getLocationEventTypes(events); if (!chips.length) { panelChips.style.display = "none"; panelChips.innerHTML = ""; return; } panelChips.style.display = "flex"; panelChips.innerHTML = chips.map((chip) => ` ${escapeAttr(chip.label)} `).join(""); }; const itemMatchesFilter = (item) => { if (activeFilter === "all") return true; if (item.type === "location") { return Array.isArray(item.filterSlugs) && item.filterSlugs.includes(activeFilter); } if (item.type === "event") { return Array.isArray(item.filterSlugs) && item.filterSlugs.includes(activeFilter); } return true; }; const renderFilterChips = () => { if (!searchFilters) return; const filters = [ { slug: "all", label: "ALL", icon: "link" }, ...Array.from(availableFilters.values()).sort((a, b) => a.label.localeCompare(b.label, "it", { sensitivity: "base" }) ) ]; searchFilters.innerHTML = filters.map((filter) => ` `).join(""); }; const setActiveFilter = (slug) => { activeFilter = slug || "all"; renderFilterChips(); updateSearch(); }; const INFO_SERVIZI_MAP = { merch: { title: "Merchandising", icon: "shopping_bag" }, food: { title: "Food", icon: "fork_spoon" }, bar: { title: "Bar", icon: "local_bar" }, wc: { title: "WC", icon: "wc" }, park: { title: "Park", icon: "local_parking" }, abbigliamento_comodo: { title: "Dresscode", subtitle: "Comodo", icon: "apparel" }, abbigliamento_elegante: { title: "Dresscode", subtitle: "Elegante", icon: "apparel" }, abbigliamento_casual: { title: "Dresscode", subtitle: "Casual", icon: "apparel" } }; const normalizeInfoKey = (s) => String(s || "") .trim() .toLowerCase() .replace(/à/g, "a") .replace(/è|é/g, "e") .replace(/ì/g, "i") .replace(/ò/g, "o") .replace(/ù/g, "u") .replace(/[^a-z0-9]+/g, "_") .replace(/^_+|_+$/g, ""); const closeInfoMenu = () => { panel?.classList.remove("info-open"); infoMenu?.setAttribute("aria-hidden", "true"); infoToggle?.setAttribute("aria-expanded", "false"); }; const openInfoMenu = () => { if (!panel || !infoMenu || !infoToggle) return; const btnRect = infoToggle.getBoundingClientRect(); const panelRect = panel.getBoundingClientRect(); const top = (btnRect.bottom - panelRect.top) + 10; const right = Math.max(16, (panelRect.right - btnRect.right)); infoMenu.style.top = `${top}px`; infoMenu.style.right = `${right}px`; infoMenu.style.left = "auto"; panel.classList.add("info-open"); infoMenu.setAttribute("aria-hidden", "false"); infoToggle.setAttribute("aria-expanded", "true"); }; const toggleInfoMenu = () => { const isOpen = panel?.classList.contains("info-open"); if (isOpen) closeInfoMenu(); else openInfoMenu(); }; infoToggle?.addEventListener("click", (e) => { e.stopPropagation(); toggleInfoMenu(); }); document.addEventListener("click", (e) => { if (!panel?.classList.contains("info-open")) return; const t = e.target; const clickedToggle = infoToggle?.contains(t); const clickedMenu = infoMenu?.contains(t); if (!clickedToggle && !clickedMenu) closeInfoMenu(); }); document.addEventListener("keydown", (e) => { if (e.key === "Escape") { closeInfoMenu(); closeSearchResults(); } }); window.addEventListener("resize", () => { if (panel?.classList.contains("info-open")) openInfoMenu(); }); const renderInfoMenu = (rawValues) => { if (!infoList || !infoToggle) return; const arr = Array.isArray(rawValues) ? rawValues : (rawValues ? [rawValues] : []); const keys = arr.map(normalizeInfoKey); const items = keys.map((k) => INFO_SERVIZI_MAP[k]).filter(Boolean); if (!items.length) { infoToggle.classList.add("is-hidden"); infoList.innerHTML = ""; closeInfoMenu(); return; } infoToggle.classList.remove("is-hidden"); const maxTitle = items.map((it) => it.title || "").sort((a, b) => b.length - a.length)[0] || ""; infoMenu?.setAttribute("data-max-title", maxTitle); infoList.innerHTML = items.map((it) => { const title = decodeHtml(it.title || ""); const subtitle = decodeHtml(it.subtitle || ""); const iconName = decodeHtml(it.icon || "info"); return `
${title}
${subtitle ? `
${subtitle}
` : ``}
`; }).join(""); }; const formatAcfDate = (yyyymmdd) => { if (!yyyymmdd || String(yyyymmdd).length !== 8) return ""; const y = String(yyyymmdd).slice(0, 4); const m = String(yyyymmdd).slice(4, 6); const d = String(yyyymmdd).slice(6, 8); const months = ["GEN", "FEB", "MAR", "APR", "MAG", "GIU", "LUG", "AGO", "SET", "OTT", "NOV", "DIC"]; return `${d} ${months[parseInt(m, 10) - 1]} ${y}`; }; const formatTime = (hhmmss) => (hhmmss ? String(hhmmss).slice(0, 5) : ""); const getEventThumbUrl = (ev) => { const embeddedMedia = ev?._embedded?.["wp:featuredmedia"]?.[0]; if (embeddedMedia) { return ( embeddedMedia?.media_details?.sizes?.medium?.source_url || embeddedMedia?.media_details?.sizes?.medium_large?.source_url || embeddedMedia?.media_details?.sizes?.thumbnail?.source_url || embeddedMedia?.source_url || "" ); } if (typeof ev?.acf?.immagine_evento === "string") return ev.acf.immagine_evento; if (ev?.acf?.immagine_evento?.url) return ev.acf.immagine_evento.url; return ""; }; const getEventContextLabel = (ev) => { const embeddedTerms = ev?._embedded?.["wp:term"] || []; for (const group of embeddedTerms) { if (!Array.isArray(group)) continue; const found = group.find((term) => { const taxonomy = term?.taxonomy || ""; const slug = term?.slug || ""; return taxonomy === "contesto" || slug === "festival" || slug === "extra-festival"; }); if (found?.name) return decodeHtml(found.name); } if (ev?.acf?.contesto_label) { return decodeHtml(ev.acf.contesto_label); } return ""; }; const renderEventCard = (ev) => { const date = formatAcfDate(ev?.acf?.data); const time = formatTime(ev?.acf?.ora); const title = decodeHtml(ev?.title?.rendered || ""); const link = ev?.link || "#"; const linkTicket = ev?.acf?.link_biglietteria || "#"; const thumb = getEventThumbUrl(ev); const eventId = ev?.id ? String(ev.id) : ""; const contextLabel = getEventContextLabel(ev); return `
${date}${time ? `, ${time}` : ""}
${contextLabel ? `
${contextLabel}
` : ""}
${title}
`; }; const highlightEventCard = (eventId) => { if (!eventId || !panelEvents) return; const el = panelEvents.querySelector(`.panel-event[data-event-id="${CSS.escape(String(eventId))}"]`); if (!el) return; el.scrollIntoView({ behavior: "smooth", block: "nearest" }); el.classList.add("is-highlighted"); setTimeout(() => { el.classList.remove("is-highlighted"); }, 1800); }; let heroTimer = null; let heroIndex = 0; const stopHeroSlideshow = () => { if (heroTimer) clearInterval(heroTimer); heroTimer = null; heroIndex = 0; if (!panelHero) return; panelHero.classList.remove("is-a", "is-b"); panelHero.style.setProperty("--heroA", "none"); panelHero.style.setProperty("--heroB", "none"); panelHero.style.setProperty("--heroThumb", "none"); }; const preloadImages = (urls = []) => Promise.all( urls.map((url) => new Promise((resolve) => { const img = new Image(); img.onload = () => resolve(url); img.onerror = () => resolve(null); img.src = url; })) ).then((res) => res.filter(Boolean)); const extractGalleryItems = (gallery) => { if (!Array.isArray(gallery)) return []; return gallery.map((item) => { if (!item) return null; if (typeof item === "string") return item; if (typeof item === "number") return item; return item.url || item.source_url || item.sizes?.large || item.sizes?.medium_large || item.sizes?.full || null; }).filter(Boolean); }; const resolveMediaIdsToUrls = async (items = []) => { const ids = items.map((x) => { if (typeof x === "number") return x; const n = parseInt(String(x), 10); return Number.isFinite(n) ? n : null; }).filter(Boolean); if (!ids.length) return []; try { const res = await fetch(`/wp-json/wp/v2/media?include=${ids.join(",")}&per_page=100`); const media = await res.json(); return (media || []).map((m) => m?.source_url).filter(Boolean); } catch (e) { return []; } }; const startHeroSlideshow = async (gallery) => { if (!panelHero) return; stopHeroSlideshow(); let items = extractGalleryItems(gallery); const hasIds = items.some((x) => typeof x === "number" || /^\d+$/.test(String(x))); let urls = hasIds ? await resolveMediaIdsToUrls(items) : items.filter((x) => typeof x === "string"); urls = await preloadImages(urls); if (!urls.length) return; panelHero.style.setProperty("--heroThumb", `url("${escapeCssUrl(urls[0])}")`); if (isMobile()) { return; } if (urls.length === 1) { panelHero.style.setProperty("--heroA", `url("${escapeCssUrl(urls[0])}")`); panelHero.style.setProperty("--heroB", `url("${escapeCssUrl(urls[0])}")`); panelHero.classList.add("is-a"); return; } heroIndex = 1; panelHero.style.setProperty("--heroA", `url("${escapeCssUrl(urls[0])}")`); panelHero.style.setProperty("--heroB", `url("${escapeCssUrl(urls[1])}")`); panelHero.classList.add("is-a"); let showingA = true; heroTimer = setInterval(() => { heroIndex = (heroIndex + 1) % urls.length; const nextUrl = urls[heroIndex]; if (showingA) { panelHero.style.setProperty("--heroB", `url("${escapeCssUrl(nextUrl)}")`); panelHero.classList.remove("is-a"); panelHero.classList.add("is-b"); } else { panelHero.style.setProperty("--heroA", `url("${escapeCssUrl(nextUrl)}")`); panelHero.classList.remove("is-b"); panelHero.classList.add("is-a"); } showingA = !showingA; }, 3500); }; let sheetState = "hidden"; let currentY = 0; let dragging = false; let startY = 0; let startSheetY = 0; const getSnapPoints = () => { const headerOffset = 8; const availableHeight = window.innerHeight - headerOffset; return { hidden: availableHeight + 24, collapsed: Math.round(availableHeight * 0.84), peek: Math.round(availableHeight * 0.73), mid: Math.round(availableHeight * 0.52), expanded: 0 }; }; const applySheetY = (y, animate = true) => { if (!panel || !isMobile()) return; panel.style.transition = animate ? "transform 0.28s cubic-bezier(.4,0,.2,1)" : "none"; panel.style.transform = `translateY(${y}px)`; currentY = y; }; const snapTo = (state, animate = true) => { if (!isMobile() || !panel) return; const sp = getSnapPoints(); sheetState = state; if (state === "expanded") applySheetY(sp.expanded, animate); else if (state === "mid") applySheetY(sp.mid, animate); else if (state === "peek") applySheetY(sp.peek, animate); else if (state === "collapsed") applySheetY(sp.collapsed, animate); else applySheetY(sp.hidden, animate); if (state === "hidden") { panel.classList.add("is-hidden-mobile"); } else { panel.classList.remove("is-hidden-mobile"); panel.classList.add("open"); } }; const initSheet = () => { if (!panel) return; if (!isMobile()) { panel.style.transform = ""; panel.style.transition = ""; panel.classList.remove("is-hidden-mobile"); return; } snapTo("hidden", false); }; const getNearestState = (y) => { const sp = getSnapPoints(); const distances = [ { state: "expanded", value: Math.abs(y - sp.expanded) }, { state: "mid", value: Math.abs(y - sp.mid) }, { state: "peek", value: Math.abs(y - sp.peek) }, { state: "collapsed", value: Math.abs(y - sp.collapsed) }, { state: "hidden", value: Math.abs(y - sp.hidden) } ]; distances.sort((a, b) => a.value - b.value); return distances[0].state; }; const dragZone = panelHero; const dragZone2 = panelHandle; const onPointerDown = (e) => { if (!isMobile() || !panel) return; const t = e.target; if (infoToggle?.contains(t) || infoMenu?.contains(t)) return; dragging = true; startY = e.clientY; startSheetY = currentY; closeInfoMenu(); map.dragging.disable(); map.scrollWheelZoom.disable(); map.doubleClickZoom.disable(); map.touchZoom.disable(); try { e.target.setPointerCapture?.(e.pointerId); } catch (_) {} panel.style.transition = "none"; }; const onPointerMove = (e) => { if (!dragging || !isMobile()) return; const dy = e.clientY - startY; const sp = getSnapPoints(); const next = Math.min(Math.max(startSheetY + dy, sp.expanded), sp.hidden); applySheetY(next, false); }; const onPointerUp = () => { if (!dragging || !isMobile()) return; dragging = false; map.dragging.enable(); map.scrollWheelZoom.enable(); map.doubleClickZoom.enable(); map.touchZoom.enable(); const nextState = getNearestState(currentY); snapTo(nextState, true); }; [dragZone, dragZone2].forEach((el) => { if (!el) return; el.style.touchAction = "none"; el.addEventListener("pointerdown", onPointerDown); }); window.addEventListener("pointermove", onPointerMove); window.addEventListener("pointerup", onPointerUp); window.addEventListener("pointercancel", onPointerUp); panelHandle?.addEventListener("click", () => { if (!isMobile()) return; if (sheetState === "collapsed") snapTo("peek", true); else if (sheetState === "peek") snapTo("mid", true); else if (sheetState === "mid") snapTo("expanded", true); else if (sheetState === "expanded") snapTo("peek", true); else snapTo("peek", true); }); window.addEventListener("resize", () => { setTimeout(() => { map.invalidateSize(); }, 100); if (!isMobile()) { panel?.classList.remove("is-hidden-mobile"); panel.style.transform = ""; panel.style.transition = ""; return; } if (sheetState === "hidden") snapTo("hidden", false); else if (sheetState === "collapsed") snapTo("collapsed", false); else if (sheetState === "peek") snapTo("peek", false); else if (sheetState === "mid") snapTo("mid", false); else snapTo("expanded", false); if (panel?.classList.contains("info-open")) openInfoMenu(); }); initSheet(); function setPanelPlaceholder() { activeLocationId = null; stopHeroSlideshow(); closeInfoMenu(); collapsePanel(); if (panelTitle) panelTitle.textContent = "Seleziona una location"; if (panelSubtitle) panelSubtitle.textContent = "DROMOS FESTIVAL"; if (panelAddress) panelAddress.textContent = "Clicca su un marker per scoprire la programmazione."; if (panelHero) panelHero.style.setProperty("--heroThumb", "none"); if (panelChips) { panelChips.style.display = "none"; panelChips.innerHTML = ""; } if (panelEvents) panelEvents.innerHTML = ""; if (panelCtaWrap) panelCtaWrap.style.display = "none"; if (panelLink) panelLink.href = "#"; renderInfoMenu([]); if (btnGmaps) { btnGmaps.href = "#"; btnGmaps.setAttribute("aria-disabled", "true"); btnGmaps.style.pointerEvents = "none"; btnGmaps.style.opacity = "0.5"; } if (btnAmaps) { btnAmaps.href = "#"; btnAmaps.setAttribute("aria-disabled", "true"); btnAmaps.style.pointerEvents = "none"; btnAmaps.style.opacity = "0.5"; } if (isMobile()) { snapTo("hidden", true); closePanel(); } else { openPanel(); } } function setPanelLocation({ title, address, lat, lng, link, events = [], gallery = [], info_e_servizi = [], locationId = null }) { activeLocationId = locationId ? String(locationId) : null; expandPanel(); closeInfoMenu(); if (panelTitle) panelTitle.innerHTML = title || "Location"; if (panelSubtitle) panelSubtitle.textContent = `Dromos x ${title || "Location"}`; if (panelAddress) panelAddress.textContent = address || ""; renderPanelChips(events); if (panelEvents) { if (!events.length) { panelEvents.innerHTML = eventsLoaded ? `
Nessun evento in programma.
` : `
Caricamento eventi...
`; } else { const sorted = [...events].sort((a, b) => { const ad = (a?.acf?.data || "") + (a?.acf?.ora || ""); const bd = (b?.acf?.data || "") + (b?.acf?.ora || ""); return ad.localeCompare(bd); }); panelEvents.innerHTML = sorted.map(renderEventCard).join(""); } } if (panelCtaWrap) panelCtaWrap.style.display = "flex"; if (panelLink) panelLink.href = link || "#"; const q = `${lat},${lng}`; if (btnGmaps) { btnGmaps.href = `https://www.google.com/maps/search/?api=1&query=${encodeURIComponent(q)}`; btnGmaps.removeAttribute("aria-disabled"); btnGmaps.style.pointerEvents = ""; btnGmaps.style.opacity = ""; } if (btnAmaps) { btnAmaps.href = `https://maps.apple.com/?q=${encodeURIComponent(q)}`; btnAmaps.removeAttribute("aria-disabled"); btnAmaps.style.pointerEvents = ""; btnAmaps.style.opacity = ""; } startHeroSlideshow(gallery); renderInfoMenu(info_e_servizi); openPanel(); if (isMobile()) { snapTo("peek", true); } } panelClose?.addEventListener("click", () => { stopHeroSlideshow(); closeInfoMenu(); collapsePanel(); if (isMobile()) { snapTo("hidden", true); setTimeout(() => { setPanelPlaceholder(); }, 220); } else { setTimeout(() => { setPanelPlaceholder(); }, 220); } }); setPanelPlaceholder(); console.time("DROMOS LOCATIONS"); const locRes = await fetch("/wp-json/wp/v2/locations?per_page=100"); const locations = await locRes.json(); console.timeEnd("DROMOS LOCATIONS"); let eventi = []; const eventsByLocationId = new Map(); const bounds = []; const markersByLocationId = new Map(); const locationDataById = new Map(); const searchableItems = []; const setMarkerVisible = (marker, visible) => { if (!marker) return; marker.setOpacity(visible ? 1 : 0); const el = marker.getElement?.(); if (el) { el.style.pointerEvents = visible ? "" : "none"; } }; const showAllMarkers = () => { markersByLocationId.forEach((marker) => setMarkerVisible(marker, true)); }; const closeSearchResults = () => { if (searchResults) { searchResults.innerHTML = ""; searchResults.classList.remove("is-open"); } }; const renderSearchResults = (results) => { if (!searchResults) return; if (!results.length) { searchResults.innerHTML = `
Nessun risultato
`; searchResults.classList.add("is-open"); return; } searchResults.innerHTML = results.map((item) => { const label = escapeAttr(item.label || ""); const sub = escapeAttr(item.subLabel || ""); const type = escapeAttr(item.type || ""); return ` `; }).join(""); searchResults.classList.add("is-open"); }; const openLocationFromSearch = ({ locationId, eventId = null }) => { const locData = locationDataById.get(String(locationId)); if (!locData) return; const { lat, lng } = locData; const targetZoom = Math.max(map.getZoom(), 13); map.flyTo([lat, lng], targetZoom, { duration: 0.7 }); setPanelLocation(locData); searchInput?.blur(); if (eventId) { setTimeout(() => highlightEventCard(eventId), 250); } closeSearchResults(); }; const updateSearch = () => { const q = normalizeSearchString(searchInput?.value || ""); const matches = searchableItems.filter((item) => { const matchesText = !q || item.searchText.includes(q); const matchesFilter = itemMatchesFilter(item); return matchesText && matchesFilter; }); const uniqueLocationIds = [...new Set(matches.map((item) => item.locationId))]; if (!q && activeFilter === "all") { showAllMarkers(); closeSearchResults(); return; } markersByLocationId.forEach((marker, locId) => { setMarkerVisible(marker, uniqueLocationIds.includes(String(locId))); }); if (!q) { closeSearchResults(); return; } const results = matches.slice(0, 8); renderSearchResults(results); }; locations.forEach((loc) => { const pos = loc?.acf?.posizione; if (!pos || !pos.lat || !pos.lng) return; const lat = parseFloat(pos.lat); const lng = parseFloat(pos.lng); const title = decodeHtml(loc?.title?.rendered || loc.slug); const link = loc?.link || "#"; const address = loc?.acf?.indirizzo || pos.address || ""; const locId = String(loc?.id); const gallery = loc?.acf?.galleria || []; const info_e_servizi = loc?.acf?.info_e_servizi || []; const locationEvents = eventsByLocationId.get(locId) || []; const marker = L.marker([lat, lng], { icon: markerIcon }).addTo(map); marker.bindTooltip(title, { permanent: false, direction: "top", offset: [0, -12], opacity: 1, sticky: true, className: "dromos-location-tooltip" }); const locData = { title, address, lat, lng, link, events: locationEvents, gallery, info_e_servizi, marker, locationId: locId }; markersByLocationId.set(locId, marker); locationDataById.set(locId, locData); marker.on("click", () => { const targetZoom = Math.max(map.getZoom(), 13); map.flyTo([lat, lng], targetZoom, { duration: 0.7 }); setPanelLocation(locData); }); const locationFilterSlugs = [...new Set( locationEvents.flatMap((ev) => getEventTypeTerms(ev).map((term) => term.slug)) )]; searchableItems.push({ type: "location", locationId: locId, label: title, subLabel: address, filterSlugs: locationFilterSlugs, searchText: normalizeSearchString(`${title} ${address}`) }); bounds.push([lat, lng]); }); renderFilterChips(); if (bounds.length) { map.fitBounds(bounds, { padding: [60, 60] }); const getPanelOffsetX = () => { const w = window.innerWidth; if (w <= 768) return 0; if (w { map.panBy([getPanelOffsetX(), 0], { animate: false }); }, 0); } async function loadEventsForMap() { console.time("DROMOS EVENTS"); try { const evRes = await fetch("/wp-json/wp/v2/eventi?per_page=100&_embed"); eventi = await evRes.json(); console.timeEnd("DROMOS EVENTS"); eventsByLocationId.clear(); availableFilters.clear(); const locationSearchItems = searchableItems.filter((item) => item.type === "location"); searchableItems.splice(0, searchableItems.length, ...locationSearchItems); (eventi || []).forEach((ev) => { const locIds = ev?.acf?.location || []; if (!Array.isArray(locIds)) return; locIds.forEach((id) => { const key = String(id); if (!eventsByLocationId.has(key)) eventsByLocationId.set(key, []); eventsByLocationId.get(key).push(ev); }); }); locationDataById.forEach((locData, locId) => { const locationEvents = eventsByLocationId.get(String(locId)) || []; locData.events = locationEvents; const locationFilterSlugs = [...new Set( locationEvents.flatMap((ev) => getEventTypeTerms(ev).map((term) => term.slug)) )]; const locationItem = searchableItems.find( (item) => item.type === "location" && item.locationId === String(locId) ); if (locationItem) { locationItem.filterSlugs = locationFilterSlugs; } locationEvents.forEach((ev) => { const evTitle = decodeHtml(ev?.title?.rendered || ""); const evDate = formatAcfDate(ev?.acf?.data); const evTime = formatTime(ev?.acf?.ora); const artists = Array.isArray(ev?.acf?.artisti) ? ev.acf.artisti.join(" ") : ""; const contesto = getEventContextLabel(ev); const typeTerms = getEventTypeTerms(ev); typeTerms.forEach((term) => { if (!availableFilters.has(term.slug)) { availableFilters.set(term.slug, { slug: term.slug, label: term.name.toUpperCase(), icon: getFilterIcon(term.slug, term.name) }); } }); searchableItems.push({ type: "event", locationId: String(locId), eventId: String(ev?.id || ""), label: evTitle, subLabel: [locData.title, evDate, evTime, contesto].filter(Boolean).join(" • "), filterSlugs: typeTerms.map((t) => t.slug), filterLabels: typeTerms.map((t) => t.name), searchText: normalizeSearchString( `${evTitle} ${locData.title} ${locData.address} ${artists} ${evDate} ${evTime} ${contesto} ${typeTerms.map((t) => t.name).join(" ")}` ) }); }); }); eventsLoaded = true; renderFilterChips(); updateSearch(); if (activeLocationId) { const activeLocData = locationDataById.get(String(activeLocationId)); if (activeLocData) { setPanelLocation(activeLocData); } } } catch (error) { eventsLoaded = true; console.error("Errore caricamento eventi mappa:", error); if (activeLocationId) { const activeLocData = locationDataById.get(String(activeLocationId)); if (activeLocData) { setPanelLocation(activeLocData); } } } } loadEventsForMap(); searchInput?.addEventListener("input", updateSearch); searchInput?.addEventListener("focus", () => { if ((searchInput.value || "").trim()) updateSearch(); }); searchFilters?.addEventListener("click", (e) => { const btn = e.target.closest(".map-filter-chip"); if (!btn) return; setActiveFilter(btn.dataset.filter || "all"); }); searchResults?.addEventListener("click", (e) => { const btn = e.target.closest(".map-search-result"); if (!btn) return; openLocationFromSearch({ locationId: btn.dataset.locationId, eventId: btn.dataset.eventId || null }); }); document.addEventListener("click", (e) => { const t = e.target; const clickedInput = searchInput?.contains?.(t) || t === searchInput; const clickedResults = searchResults?.contains?.(t); const clickedFilters = searchFilters?.contains?.(t); if (!clickedInput && !clickedResults && !clickedFilters) { closeSearchResults(); } }); searchInput?.addEventListener("keydown", (e) => { if (e.key === "Enter") { e.preventDefault(); const first = searchResults?.querySelector(".map-search-result[data-location-id]"); if (first) { openLocationFromSearch({ locationId: first.dataset.locationId, eventId: first.dataset.eventId || null }); } } }); } function maybeInitMap() { setConsentState(); if (hasMapConsent()) { initMap(); } } maybeInitMap(); });