/* *************************** * FULLCALENDAR SETUP * *************************** */ const DEBUG_MODE = true; // ============================================================================ // KONSTANTEN // ============================================================================ const CACHE_VALIDITY_MS = 15 * 60 * 1000; // 15 Minuten const CACHE_STORAGE_MS = 30 * 60 * 1000; // 30 Minuten const CITY_TEXT_MAP = { muenchen: "Münchens ", hamburg: "Hamburgs ", koeln: "Kölns ", kueste: "der Küste ", potsdam: "Potsdams ", berlin: "Berlins ", bremen: "Bremens ", }; const DEFAULT_CITY = "hamburg"; const ENCODED_TOKEN = "TWFsdGVzZXJBcGZlbGt1Y2hlbjY2OEF1ZnRhdWVuMQ=="; // ============================================================================ // LOGGING UTILITIES // ============================================================================ const log = (...args) => { if (DEBUG_MODE) console.log(`[Kalender-Log]`, ...args); }; const warn = (...args) => { if (DEBUG_MODE) console.warn(`[Kalender-Warnung]`, ...args); }; const error = (...args) => { if (DEBUG_MODE) console.error(`[Kalender-ERROR]`, ...args); }; // ============================================================================ // ERROR HANDLING // ============================================================================ function handleCalendarError(err, context) { error(`Fehler in ${context}:`, err); } // ============================================================================ // CACHE MANAGEMENT // ============================================================================ const calendarCache = { data: null, timestamp: 0, isValid() { return this.data && Date.now() - this.timestamp < CACHE_VALIDITY_MS; }, set(data) { this.data = data; this.timestamp = Date.now(); try { localStorage.setItem( "schnack-events-cache", JSON.stringify({ data: data, timestamp: this.timestamp, }), ); } catch (e) { warn("LocalStorage nicht verfügbar:", e); } }, loadFromStorage() { try { const stored = localStorage.getItem("schnack-events-cache"); if (stored) { const parsed = JSON.parse(stored); if (Date.now() - parsed.timestamp < CACHE_STORAGE_MS) { return parsed.data; } } } catch (e) { warn("Fehler beim Laden aus LocalStorage:", e); } return null; }, }; // ============================================================================ // UTILITY FUNCTIONS // ============================================================================ function isValidUrl(string) { try { new URL(string); return true; } catch (_) { return false; } } function normalizeCityName(cityName) { if (typeof cityName !== "string" || !cityName) return ""; return cityName .toLowerCase() .trim() .replace(/ä/g, "ae") .replace(/ö/g, "oe") .replace(/ü/g, "ue") .replace(/ß/g, "ss"); } function getNormalizedCity(cityName) { return normalizeCityName(cityName) || DEFAULT_CITY; } // ============================================================================ // DOM ELEMENT CACHE // ============================================================================ let domElements = {}; function cacheDOMElements() { domElements = { heroCityname: document.querySelector("#heroCityname"), // hamburBtn: document.querySelector('#hamburg-nav-btn'), // potsdamBtn: document.querySelector('#potsdam-nav-btn'), calendarEl: document.getElementById("divCalendar"), citySelect: document.querySelector("#fs-ortsfilter-select-list"), loadingEl: document.querySelector(".calendar-loading"), }; } // ============================================================================ // UI UPDATE FUNCTIONS // ============================================================================ function updateHeroText(city) { if (!domElements.heroCityname) return; const text = CITY_TEXT_MAP[city] || CITY_TEXT_MAP[DEFAULT_CITY]; domElements.heroCityname.textContent = text; log("Hero-Text aktualisiert:", text); } /* function updateNavigationButtons(city) { // Hamburg-Button IMMER ausblenden, AUSSER Parameter ist Potsdam if (city === 'potsdam') { if (domElements.potsdamBtn) domElements.potsdamBtn.style.display = 'none'; if (domElements.hamburBtn) domElements.hamburBtn.style.display = ''; } else { if (domElements.hamburBtn) domElements.hamburBtn.style.display = 'none'; if (domElements.potsdamBtn) domElements.potsdamBtn.style.display = ''; } log('Navigation-Buttons aktualisiert für:', city); } */ function updateBrowserURL(city) { const newUrl = new URL(window.location); const currentUrlCity = normalizeCityName( newUrl.searchParams.get("city"), ); if (currentUrlCity !== city) { newUrl.searchParams.set("city", city); window.history.pushState({}, "", newUrl); log("URL aktualisiert:", newUrl.toString()); } } function updateUI(cityName) { const city = getNormalizedCity(cityName); log("UI-Update für Stadt:", city); updateHeroText(city); // updateNavigationButtons(city); updateBrowserURL(city); } // ============================================================================ // CALENDAR HELPER FUNCTIONS // ============================================================================ function clearPopup() { document.body.classList.remove("no-scroll"); const overlay = document.querySelector(".fc-overlay"); if (overlay) { overlay.remove(); } } function updateDropdownOptions(myEvents) { if (!domElements.citySelect) { warn("Select-Element nicht gefunden!"); return; } const availableCities = new Set(); myEvents.forEach((event) => { const city = event.extendedProps?.city?.toLowerCase().trim(); if (city) availableCities.add(city); }); log("Verfügbare Städte in Events:", Array.from(availableCities)); const options = domElements.citySelect.querySelectorAll("option"); options.forEach((option) => { const value = option.value.toLowerCase().trim(); if (availableCities.has(value)) { option.style.display = ""; log(`Option "${option.textContent}" wird angezeigt`); } else { option.style.display = "none"; log(`Option "${option.textContent}" wird versteckt`); } }); document .querySelectorAll(".fs-select_list-3 a.fs-select_link-3") .forEach((link) => { const linkText = link.textContent.toLowerCase().trim(); if (availableCities.has(linkText)) { link.style.display = ""; } else { link.style.display = "none"; } }); // Navbar-Dropdown filtern: Elemente mit ID-Präfix "nav-link-" werden anhand der verfügbaren Städte ein-/ausgeblendet document .querySelectorAll("#nav-city-select-list a[id^='nav-link-']") .forEach((link) => { const city = link.id .replace("nav-link-", "") .toLowerCase() .trim(); if (availableCities.has(city)) { link.style.display = ""; } else { link.style.display = "none"; } }); } function filterEventsByCity(allEvents, cachedData) { if (!domElements.citySelect) return []; const selectedCity = getNormalizedCity(domElements.citySelect.value); // Cache-Check if ( cachedData.city === selectedCity && cachedData.events && cachedData.totalEvents === allEvents.length ) { log( `Cache-Hit für ${selectedCity} (${cachedData.totalEvents} Events)`, ); return cachedData.events; } // Neue Filterung const filtered = allEvents.filter( (event) => normalizeCityName(event.extendedProps?.city) === selectedCity, ); // Cache aktualisieren cachedData.city = selectedCity; cachedData.events = filtered; cachedData.totalEvents = allEvents.length; log( `Filter-Resultat für "${selectedCity}": ${filtered.length}/${allEvents.length} Events`, ); return filtered; } function processEventsFromJson(items) { const myEvents = []; if (!Array.isArray(items)) { warn( "Die übergebenen Daten sind kein Array. Events können nicht verarbeitet werden.", ); return myEvents; } if (items.length > 0) { log("=== WEBFLOW API DEBUG ==="); log("Vollständige Struktur des ersten Items:"); log("Top-Level Felder:", Object.keys(items[0])); log("Erstes Item komplett:", items[0]); const webflowFields = Object.keys(items[0]).filter( (key) => key.includes("archived") || key.includes("draft") || key.includes("published") || key === "isArchived" || key === "isDraft", ); log("Gefundene Webflow Status-Felder:", webflowFields); if (items[0].fieldData) { log("fieldData Struktur:", Object.keys(items[0].fieldData)); log("fieldData komplett:", items[0].fieldData); } log("========================"); } items.forEach((item) => { // WEBFLOW DRAFT-FILTER if (item.isArchived === true) { log( `Event "${item.name || item.fieldData?.name}" wird übersprungen (isArchived: true)`, ); return; } if (item.isDraft === true) { log( `Event "${item.name || item.fieldData?.name}" wird übersprungen (isDraft: true)`, ); return; } if (item.fieldData) { if (item.fieldData.isArchived === true) { log( `Event "${item.name || item.fieldData.name}" wird übersprungen (fieldData.isArchived: true)`, ); return; } if (item.fieldData.isDraft === true) { log( `Event "${item.name || item.fieldData.name}" wird übersprungen (fieldData.isDraft: true)`, ); return; } } if (item.archived === true || item.draft === true) { log( `Event "${item.name || item.fieldData?.name}" wird übersprungen (archived/draft: true)`, ); return; } // Event-Daten extrahieren const eventTitle = item.name || item.fieldData?.name || "Unbekannt"; const startDateTimeStr = item.start || item.fieldData?.start; const endDateTimeStr = item.end || item.fieldData?.end; const eventUrl = item.url || item.fieldData?.url; const eventVenue = item.location || item.fieldData?.location; const eventCityRaw = item.city2 || item.fieldData?.city2; const eventClassName = item["class-name"] || item.fieldData?.["class-name"]; const eventImage = item.thumbnail || item.image || item.fieldData?.thumbnail || item.fieldData?.image || ""; const eventDescription = item.description || item.fieldData?.description; if (!startDateTimeStr) { warn( `Event "${eventTitle}" (ID: ${item.id}) wird übersprungen, da kein Startdatum vorhanden ist.`, ); return; } const eventCity = normalizeCityName(eventCityRaw); const start = new Date(startDateTimeStr); const end = endDateTimeStr ? new Date(endDateTimeStr) : new Date(start.getTime() + 60 * 60 * 1000); if (isNaN(start.getTime())) { warn( `Ungültiges Startdatum für Event "${eventTitle}": ${startDateTimeStr}`, ); return; } myEvents.push({ title: eventTitle, start: start, end: end, url: eventUrl, className: eventClassName, extendedProps: { venue: eventVenue, city: eventCity, image: eventImage, description: eventDescription, }, }); }); log( `Verarbeitung abgeschlossen. ${myEvents.length} Events wurden erstellt.`, ); return myEvents; } function adjustCalendarForMobile(view) { if (!view || !view.calendar) { warn( "adjustCalendarForMobile: view oder view.calendar ist nicht definiert", ); return; } const currentView = view.calendar; if (window.innerWidth <= 768) { currentView.setOption("eventDisplay", "block"); currentView.setOption("dayCellContent", function (info) { const eventsOnDay = currentView .getEvents() .filter( (event) => event.start.toDateString() === info.date.toDateString(), ); const eventCount = eventsOnDay.length; return { html: `
${info.dayNumberText}
`, didMount: function (arg) { const eventsContainer = arg.el .closest(".fc-daygrid-day") ?.querySelector(".fc-daygrid-day-events"); if (eventsContainer && eventCount > 0) { const existingCounter = eventsContainer.querySelector( ".fc-event-count-mobile", ); if (existingCounter) existingCounter.remove(); const eventCountSpan = document.createElement("span"); eventCountSpan.className = "fc-event fc-event-start fc-event-end fc-event-past fc-daygrid-event fc-daygrid-block-event fc-h-event fc-event-count-mobile"; eventCountSpan.style.fontSize = "0.8em"; eventCountSpan.style.color = "gray"; eventCountSpan.style.pointerEvents = "none"; eventCountSpan.textContent = `${eventCount} Event${eventCount > 1 ? "s" : ""}`; eventsContainer.insertBefore( eventCountSpan, eventsContainer.firstChild, ); } else if (eventsContainer && eventCount === 0) { const existingCounter = eventsContainer.querySelector( ".fc-event-count-mobile", ); if (existingCounter) existingCounter.remove(); } }, }; }); } else { currentView.setOption("eventDisplay", "list-item"); currentView.setOption("dayCellContent", null); document .querySelectorAll(".fc-event-count-mobile") .forEach((el) => el.remove()); } } async function loadAllEventsOptimized() { if (calendarCache.isValid()) { log("🚀 Using cached events - super fast!"); return calendarCache.data; } try { const startTime = performance.now(); log("🔄 Loading fresh events from optimized API..."); const response = await fetch( `https://vivenu-webflow-sync.netlify.app/.netlify/functions/get-cms-items-v2`, { method: "GET", headers: { "x-api-token": atob(ENCODED_TOKEN), Accept: "application/json", "Cache-Control": "max-age=900", }, cache: "default", }, ); if (!response.ok) { throw new Error( `API responded with status: ${response.status}`, ); } const data = await response.json(); const loadTime = Math.round(performance.now() - startTime); log( `⚡ Events loaded in ${loadTime}ms (${data.cached ? "server cached" : "fresh"})`, ); const processedEvents = processEventsFromJson(data.items || []); calendarCache.set(processedEvents); return processedEvents; } catch (error) { handleCalendarError(error, "Event-Loading"); const offlineData = calendarCache.loadFromStorage(); if (offlineData) { log("🔄 Using offline cache as fallback"); return offlineData; } throw error; } } function showMobilePopup(date, eventsOnDay) { const overlay = document.createElement("div"); overlay.classList.add("fc-overlay"); overlay.addEventListener("click", function (event) { if (event.target === this) { clearPopup(); } }); const formattedDate = date.toLocaleDateString("de-DE", { day: "2-digit", month: "2-digit", }); const popupEl = document.createElement("div"); popupEl.classList.add("fc-popup-content"); const eventHtml = eventsOnDay .map((event) => { const eventDate = event.start.toLocaleDateString("de-DE", { weekday: "long", year: "numeric", month: "long", day: "numeric", }); const eventTime = event.start.toLocaleTimeString("de-DE", { hour: "2-digit", minute: "2-digit", }); let imageHtml = ""; if (event.extendedProps && event.extendedProps.image) { imageHtml = `${event.title || `; } return `
${imageHtml}
${event.title || "Unbekanntes Event"}

${eventDate}, ${eventTime} ${event.extendedProps && event.extendedProps.venue ? `
Ort: ${event.extendedProps.venue}` : ""}

`; }) .join(""); popupEl.innerHTML = `

Shows am ${formattedDate}

${eventHtml}
`; overlay.appendChild(popupEl); document.body.appendChild(overlay); const closeButton = popupEl.querySelector(".fc-popup-icon"); if (closeButton) closeButton.addEventListener("click", clearPopup); document.body.classList.add("no-scroll"); log("Mobile popup opened"); } function fixEventDisplayOnResize(calendar) { setTimeout(() => { if (window.innerWidth <= 768) { document .querySelectorAll(".fc-daygrid-dot-event") .forEach((dotEvent) => { dotEvent.classList.remove("fc-daygrid-dot-event"); dotEvent.classList.add("fc-daygrid-block-event"); }); } else { document .querySelectorAll(".fc-daygrid-block-event") .forEach((blockEvent) => { blockEvent.classList.remove("fc-daygrid-block-event"); blockEvent.classList.add("fc-daygrid-dot-event"); }); } }, 100); } // Find the first event and navigate to its month function checkAndHandleEmptyMonth(calendar, filteredEvents) { if (!calendar || !filteredEvents || filteredEvents.length === 0) { return; // Keine Events oder fehlender Calendar } // Finde das früheste Event in der gefilterten Liste const earliestEvent = filteredEvents.reduce((earliest, event) => { return event.start < earliest.start ? event : earliest; }); if (!earliestEvent) { return; } // Prüfe, ob das früheste Event bereits im aktuellen Monat liegt const currentStart = calendar.view.activeStart; const currentEnd = calendar.view.activeEnd; const targetDate = new Date(earliestEvent.start); if (targetDate >= currentStart && targetDate < currentEnd) { log( `✅ Der Monat enthält bereits das erste Event (${earliestEvent.title}).`, ); return; } // Navigiere zum Monat des frühesten Events log( `📅 Springe zum Monat des ersten Events: ${earliestEvent.title} (${targetDate.toLocaleDateString("de-DE")})`, ); calendar.gotoDate(targetDate); } // ============================================================================ // CALENDAR INITIALIZATION // ============================================================================ function initializeCalendar(events, initialCity) { if (!domElements.calendarEl) { warn("Kalender-Element nicht gefunden"); return null; } const initialFilteredEvents = events.filter( (event) => normalizeCityName(event.extendedProps?.city) === initialCity, ); const calendar = new FullCalendar.Calendar(domElements.calendarEl, { locale: "de", initialView: "dayGridMonth", height: window.innerWidth <= 768 ? "70vh" : "80vh", events: initialFilteredEvents, eventDisplay: "list-item", headerToolbar: { left: "prev", center: "title", right: "next" }, validRange: function (nowDate) { let startDate = new Date(nowDate); let endDate = new Date(nowDate); startDate.setMonth(startDate.getMonth() - 0); startDate.setDate(1); endDate.setMonth(endDate.getMonth() + 3); endDate.setDate(1); endDate.setMonth(endDate.getMonth() + 1); endDate.setDate(0); return { start: startDate, end: endDate }; }, eventLimit: false, eventTimeFormat: { hour: "2-digit", minute: "2-digit", meridiem: false, omitZeroMinute: false, }, eventDidMount: function (info) { // Desktop: Neuer Tab if (window.innerWidth > 768) { info.el.style.cursor = "pointer"; info.el.addEventListener("click", function (e) { if (info.event.url && isValidUrl(info.event.url)) { const tempLink = document.createElement("a"); tempLink.href = info.event.url; tempLink.target = "_blank"; tempLink.rel = "noopener noreferrer"; tempLink.style.display = "none"; document.body.appendChild(tempLink); tempLink.click(); document.body.removeChild(tempLink); log( "Desktop Event opened in NEW TAB:", info.event.title, ); e.preventDefault(); e.stopPropagation(); } }); } // Responsive Event Display if (window.innerWidth <= 768) { info.el.classList.remove("fc-daygrid-dot-event"); info.el.classList.add("fc-daygrid-block-event"); } else { info.el.classList.remove("fc-daygrid-block-event"); info.el.classList.add("fc-daygrid-dot-event"); } }, dateClick: function (info) { if (window.innerWidth <= 768) { const clickedDate = info.date; const eventsOnDay = info.view.calendar .getEvents() .filter( (event) => event.start.toDateString() === clickedDate.toDateString(), ); if (eventsOnDay.length > 0) { showMobilePopup(clickedDate, eventsOnDay); log( "Mobile day clicked:", clickedDate, eventsOnDay.length, "events", ); } } }, viewDidMount: function (arg) { adjustCalendarForMobile(arg); }, datesSet: function () { log("Kalenderansicht geändert."); }, windowResize: function (arg) { adjustCalendarForMobile(arg); fixEventDisplayOnResize(calendar); }, }); calendar.render(); log("✅ Kalender successfully initialized"); // Prüfung auf leeren Monat nach dem ersten Rendern checkAndHandleEmptyMonth(calendar, initialFilteredEvents); return calendar; } // ============================================================================ // EVENT LISTENERS // ============================================================================ function setupCityFilterListener( calendar, allEvents, cachedFilteredEvents, ) { if (!domElements.citySelect) { warn("City-Select Element nicht gefunden"); return () => {}; // Return empty cleanup function } const handleChange = function () { const selectedCity = this.value; updateUI(selectedCity); domElements.calendarEl.style.transition = "opacity 150ms ease-in-out"; domElements.calendarEl.style.opacity = "0.6"; setTimeout(() => { const filteredEvents = filterEventsByCity( allEvents, cachedFilteredEvents, ); calendar.getEventSources().forEach((source) => source.remove()); calendar.addEventSource(filteredEvents); domElements.calendarEl.style.opacity = "1"; setTimeout(() => fixEventDisplayOnResize(calendar), 100); // Prüfe auch nach Dropdown-Wechsel auf leeren Monat setTimeout( () => checkAndHandleEmptyMonth(calendar, filteredEvents), 100, ); }, 100); }; domElements.citySelect.addEventListener("change", handleChange); log("City-Filter Listener registriert"); // Return cleanup function return () => { domElements.citySelect.removeEventListener("change", handleChange); }; } function setupResizeHandler(calendar) { let resizeTimeout; const handleResize = () => { clearTimeout(resizeTimeout); resizeTimeout = setTimeout(() => { if (calendar) { adjustCalendarForMobile(calendar.view); fixEventDisplayOnResize(calendar); } }, 150); }; window.addEventListener("resize", handleResize); log("Resize Handler registriert"); // Return cleanup function return () => { window.removeEventListener("resize", handleResize); }; } // ============================================================================ // INITIAL SETUP // ============================================================================ function performInitialSetup() { if (!domElements.citySelect) { warn("Select-Element nicht gefunden für Initial-Setup"); return; } const urlParams = new URLSearchParams(window.location.search); const requestedCity = getNormalizedCity(urlParams.get("city")); const options = domElements.citySelect.querySelectorAll("option"); let selectedOption = Array.from(options).find( (opt) => normalizeCityName(opt.value) === requestedCity, ); if (!selectedOption) { selectedOption = Array.from(options).find( (opt) => normalizeCityName(opt.value) === DEFAULT_CITY, ); } if (!selectedOption) { warn("Keine passende Select-Option gefunden"); return; } domElements.citySelect.value = selectedOption.value; updateUI(selectedOption.value); // Beide Events für Finsweet Custom Select notwendig domElements.citySelect.dispatchEvent( new Event("change", { bubbles: true }), ); domElements.citySelect.dispatchEvent( new Event("input", { bubbles: true }), ); // Finsweet Label-Fix const toggleLabel = domElements.citySelect.parentElement?.querySelector( '[fs-selectcustom-element="label"]', ); if (toggleLabel) { toggleLabel.textContent = selectedOption.textContent; } log("Initial-Setup abgeschlossen für:", selectedOption.value); } // ============================================================================ // DEBUG TOOLS // ============================================================================ function setupDebugTools(myEvents, calendar, cachedFilteredEvents) { window.calendarDebug = { getTotalEvents: () => myEvents.length, getFilteredCount: () => { const selectedCity = domElements.citySelect?.value ?.toLowerCase() .trim(); if (!selectedCity || selectedCity === "alle") { return myEvents.length; } return myEvents.filter( (event) => event.extendedProps.city.toLowerCase().trim() === selectedCity, ).length; }, getCacheInfo: () => ({ browserCache: calendarCache, filterCache: cachedFilteredEvents, }), clearCache: () => { calendarCache.data = null; calendarCache.timestamp = 0; localStorage.removeItem("schnack-events-cache"); console.log("Cache cleared"); }, forceRefresh: () => { calendarDebug.clearCache(); location.reload(); }, fixEventDisplay: () => fixEventDisplayOnResize(calendar), showCmsFields: () => { console.log("=== CMS FIELDS DEBUG ==="); if (myEvents.length > 0) { fetch( `https://vivenu-webflow-sync.netlify.app/.netlify/functions/get-cms-items-v2`, { headers: { "x-api-token": atob(ENCODED_TOKEN), }, }, ) .then((r) => r.json()) .then((data) => { if (data.items && data.items[0]) { console.log( "Alle verfügbaren CMS-Felder:", Object.keys(data.items[0]), ); console.log( "Erstes Item komplett:", data.items[0], ); const draftFields = Object.keys( data.items[0], ).filter( (key) => key.toLowerCase().includes("draft") || key .toLowerCase() .includes("archived") || key .toLowerCase() .includes("published") || key.toLowerCase().includes("status"), ); console.log( "Mögliche Draft/Status Felder:", draftFields, ); const statusAnalysis = {}; data.items.forEach((item) => { draftFields.forEach((field) => { if (!statusAnalysis[field]) statusAnalysis[field] = []; statusAnalysis[field].push(item[field]); }); }); console.log( "Status-Feld Analyse:", statusAnalysis, ); } }) .catch((error) => { console.error( "Fehler beim Laden der CMS-Daten:", error, ); }); } }, testEventClick: (eventTitle) => { const event = myEvents.find((e) => e.title.includes(eventTitle), ); if (event) { console.log("Event gefunden:", event); console.log("URL:", event.url); if (event.url) { window.open(event.url, "_blank"); } else { console.log("Keine URL vorhanden!"); } } else { console.log("Event nicht gefunden. Verfügbare Events:"); myEvents.forEach((e) => console.log("- " + e.title)); } }, }; console.log("🚀 Optimized FullCalendar successfully initialized!"); log("=== DEBUG COMMANDS ==="); log("calendarDebug.showCmsFields() - Zeigt alle CMS Felder"); log('calendarDebug.testEventClick("EventTitle") - Testet Event Klick'); log("calendarDebug.getTotalEvents() - Anzahl Events"); log("calendarDebug.forceRefresh() - Cache leeren & neu laden"); } // ============================================================================ // BACKGROUND REFRESH // ============================================================================ function setupBackgroundRefresh(myEvents, calendar) { setInterval(() => { if (!calendarCache.isValid()) { log("🔄 Background refresh..."); loadAllEventsOptimized() .then((events) => { if (events.length !== myEvents.length) { log("📈 New events available, updating calendar"); myEvents.length = 0; myEvents.push(...events); updateDropdownOptions(myEvents); if (domElements.citySelect) { $(domElements.citySelect).trigger("change"); } } }) .catch((e) => warn("Background refresh failed:", e)); } }, 900000); // 15 Minuten } // ============================================================================ // MAIN INITIALIZATION // ============================================================================ $(window).on("load", async function () { log("=== Kalender-Initialisierung gestartet ==="); cacheDOMElements(); if (!domElements.calendarEl) { error('Element mit ID "divCalendar" nicht gefunden!'); return; } // Loading Spinner if (domElements.loadingEl) { domElements.loadingEl.style.display = "flex"; domElements.loadingEl.style.opacity = 1; } let myEvents = []; let calendar = null; const cachedFilteredEvents = { city: null, events: null, totalEvents: 0, }; let cleanupFunctions = []; // 1. Events laden try { const startTime = performance.now(); myEvents = await loadAllEventsOptimized(); const totalTime = Math.round(performance.now() - startTime); log( `✅ Total loading time: ${totalTime}ms for ${myEvents.length} events`, ); if (myEvents.length === 0) { domElements.calendarEl.innerHTML = '

Keine Events verfügbar.

'; log("Keine Events geladen - Abbruch"); return; } } catch (err) { handleCalendarError(err, "Event-Loading"); domElements.calendarEl.innerHTML = '

Fehler beim Laden der Events.

'; return; } // Loading Spinner ausblenden if (domElements.loadingEl) { domElements.loadingEl.style.opacity = "0"; domElements.loadingEl.style.transition = "opacity 300ms ease-out"; setTimeout( () => (domElements.loadingEl.style.display = "none"), 300, ); } // 2. Dropdown aktualisieren updateDropdownOptions(myEvents); // 3. Initiale Stadt ermitteln const urlParams = new URLSearchParams(window.location.search); const initialCity = getNormalizedCity(urlParams.get("city")); log("Initiale Stadt:", initialCity); // 4. Kalender initialisieren try { calendar = initializeCalendar(myEvents, initialCity); if (!calendar) { throw new Error("Kalender-Initialisierung fehlgeschlagen"); } } catch (err) { handleCalendarError(err, "Kalender-Initialisierung"); domElements.calendarEl.innerHTML = '

Kalender konnte nicht geladen werden.

'; return; } // 5. Event-Listener registrieren const cleanupFilter = setupCityFilterListener( calendar, myEvents, cachedFilteredEvents, ); const cleanupResize = setupResizeHandler(calendar); cleanupFunctions.push(cleanupFilter, cleanupResize); // 6. Initiales Setup setTimeout(() => { performInitialSetup(); log("=== Kalender-Initialisierung abgeschlossen ==="); }, 200); // 7. Background Refresh setupBackgroundRefresh(myEvents, calendar); // 8. Debug Tools setupDebugTools(myEvents, calendar, cachedFilteredEvents); // Cleanup bei beforeunload window.addEventListener("beforeunload", () => { cleanupFunctions.forEach((cleanup) => cleanup()); domElements = {}; log("Cleanup durchgeführt"); }); });