const CONSENT_PREFIX = "havelseiten:external:"; function storageGet(key) { try { return localStorage.getItem(key); } catch (error) { return null; } } function storageSet(key, value) { try { localStorage.setItem(key, value); } catch (error) { // If storage is unavailable, the current in-page load still works. } } function consentKey(button) { if (button.dataset.consentKey) { return `${CONSENT_PREFIX}${button.dataset.consentKey}`; } if (button.dataset.mapAddress) { return `${CONSENT_PREFIX}map:${button.dataset.mapAddress}`; } if (button.dataset.embedSrc) { return `${CONSENT_PREFIX}embed:${button.dataset.embedSrc}`; } return ""; } function mapEmbedUrl(lat, lon) { const latitude = Number(lat); const longitude = Number(lon); const bbox = [ longitude - 0.01, latitude - 0.006, longitude + 0.01, latitude + 0.006 ].join(","); return ( "https://www.openstreetmap.org/export/embed.html" + `?bbox=${encodeURIComponent(bbox)}` + "&layer=mapnik" + `&marker=${encodeURIComponent(`${latitude},${longitude}`)}` ); } async function geocodeAddress(address) { const url = ( "https://nominatim.openstreetmap.org/search" + `?format=json&limit=1&q=${encodeURIComponent(address)}` ); const response = await fetch(url, { headers: { Accept: "application/json" } }); const results = await response.json(); if (!results.length) { throw new Error("Adresse nicht gefunden"); } return results[0]; } async function loadEmbed(button, remember = true) { const iframe = document.createElement("iframe"); const key = consentKey(button); button.disabled = true; button.textContent = "Wird geladen..."; if (button.dataset.mapAddress) { const cachedMapUrl = storageGet(`${key}:src`); if (cachedMapUrl) { iframe.src = cachedMapUrl; } else { try { const place = await geocodeAddress(button.dataset.mapAddress); iframe.src = mapEmbedUrl(place.lat, place.lon); storageSet(`${key}:src`, iframe.src); } catch (error) { button.disabled = false; button.textContent = "Karte konnte nicht geladen werden"; return; } } } else { iframe.src = button.dataset.embedSrc; } iframe.className = button.dataset.embedClass || ""; iframe.title = button.dataset.embedTitle || "Externer Inhalt"; iframe.loading = "lazy"; iframe.allow = button.dataset.embedAllow || ""; iframe.allowFullscreen = true; if (remember && key) { storageSet(key, "1"); } button.closest(".external-placeholder, .location-placeholder").replaceWith(iframe); } function galleryItems() { return [...document.querySelectorAll(".gallery-grid a")].map((link) => ({ alt: link.querySelector("img")?.alt || "", height: link.dataset.pswpHeight, src: link.href, width: link.dataset.pswpWidth })); } function createLightbox() { const lightbox = document.createElement("div"); lightbox.className = "site-lightbox"; lightbox.hidden = true; lightbox.innerHTML = `
`; document.body.append(lightbox); return lightbox; } function initGallery() { const items = galleryItems(); if (!items.length) { return; } const lightbox = createLightbox(); const image = lightbox.querySelector("img"); const caption = lightbox.querySelector("figcaption"); let currentIndex = 0; let touchStartX = 0; function show(index) { currentIndex = (index + items.length) % items.length; const item = items[currentIndex]; image.src = item.src; image.alt = item.alt; caption.textContent = item.alt; caption.hidden = !item.alt; lightbox.hidden = false; document.body.classList.add("lightbox-open"); } function close() { lightbox.hidden = true; image.removeAttribute("src"); document.body.classList.remove("lightbox-open"); } function next() { show(currentIndex + 1); } function prev() { show(currentIndex - 1); } document.querySelectorAll(".gallery-grid a").forEach((link, index) => { link.addEventListener("click", (event) => { event.preventDefault(); show(index); }); }); lightbox.querySelector(".site-lightbox-close").addEventListener("click", close); lightbox.querySelector(".site-lightbox-next").addEventListener("click", next); lightbox.querySelector(".site-lightbox-prev").addEventListener("click", prev); lightbox.addEventListener("click", (event) => { if (event.target === lightbox) { close(); } }); lightbox.addEventListener("touchstart", (event) => { touchStartX = event.changedTouches[0].clientX; }, { passive: true }); lightbox.addEventListener("touchend", (event) => { const delta = event.changedTouches[0].clientX - touchStartX; if (Math.abs(delta) < 40) { return; } if (delta < 0) { next(); } else { prev(); } }, { passive: true }); document.addEventListener("keydown", (event) => { if (lightbox.hidden) { return; } if (event.key === "Escape") { close(); } else if (event.key === "ArrowRight") { next(); } else if (event.key === "ArrowLeft") { prev(); } }); } function initSubmenus() { const mobileQuery = window.matchMedia("(max-width: 800px)"); document.querySelectorAll(".dropdown").forEach((dropdown) => { const button = dropdown.querySelector(".dropdown-label"); if (!button) { return; } const startsOpen = ( document.body.classList.contains("mobile-submenus-open") && mobileQuery.matches ); dropdown.classList.toggle("is-open", startsOpen); button.setAttribute("aria-expanded", startsOpen ? "true" : "false"); button.addEventListener("click", (event) => { if (!mobileQuery.matches) { return; } event.preventDefault(); const isOpen = dropdown.classList.toggle("is-open"); button.setAttribute("aria-expanded", isOpen ? "true" : "false"); }); dropdown.addEventListener("mouseenter", () => { if (mobileQuery.matches) { return; } dropdown.classList.add("is-open"); button.setAttribute("aria-expanded", "true"); }); dropdown.addEventListener("mouseleave", () => { if (mobileQuery.matches) { return; } dropdown.classList.remove("is-open"); button.setAttribute("aria-expanded", "false"); }); dropdown.addEventListener("focusin", () => { if (mobileQuery.matches) { return; } dropdown.classList.add("is-open"); button.setAttribute("aria-expanded", "true"); }); dropdown.addEventListener("focusout", (event) => { if ( mobileQuery.matches || dropdown.contains(event.relatedTarget) ) { return; } dropdown.classList.remove("is-open"); button.setAttribute("aria-expanded", "false"); }); }); } document.querySelectorAll(".external-load-button").forEach((button) => { const key = consentKey(button); if (button.dataset.autoLoad === "true" || (key && storageGet(key) === "1")) { loadEmbed(button, false); } }); document.addEventListener("click", (event) => { const externalButton = event.target.closest(".external-load-button"); if (externalButton) { loadEmbed(externalButton); } const galleryButton = event.target.closest(".gallery-load-button"); if (galleryButton) { const section = galleryButton.closest(".gallery-section"); const grid = section?.querySelector(".gallery-grid"); if (grid) { grid.hidden = false; } galleryButton.closest(".gallery-placeholder")?.remove(); } }); initSubmenus(); initGallery();