To view the map, you must accept marketing cookies.
Use the site's consent management button to update your preferences.
search
Select a location
DROMOS FESTIVAL
Active information and services
*service information may change up to one day before the event date
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 `
`;
}).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 `
`;
};
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
? `
${iconName}
${title}
${subtitle ? `${subtitle}
` : ``}
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();
});