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();