1(function () { 2 'use strict'; 3 4 var COOKIE_MAX_AGE = 60 * 60 * 24 * 365; 5 var TEXTSIZE_COOKIE = 'g24_a11y_textsize'; 6 var CONTRAST_COOKIE = 'g24_a11y_contrast'; 7 8 /* ========== Consent module ========== */ 9 var g24Consent = (function () { 10 var KEY = 'g24_consent'; 11 12 function getRaw() { 13 var m = document.cookie.match('(^|;)\\s*' + KEY + '\\s*=\\s*([^;]*)'); 14 return m ? decodeURIComponent(m[2]) : null; 15 } 16 function setRaw(val) { 17 document.cookie = KEY + '=' + encodeURIComponent(val) 18 + '; max-age=' + COOKIE_MAX_AGE + '; path=/; SameSite=Lax'; 19 } 20 function get() { 21 try { var r = getRaw(); return r ? JSON.parse(r) : null; } catch (e) { return null; } 22 } 23 function set(thirdparty) { 24 setRaw(JSON.stringify({ necessary: true, thirdparty: !!thirdparty })); 25 } 26 function hasConsented() { return get() !== null; } 27 function hasThirdparty() { var c = get(); return c ? !!c.thirdparty : false; } 28 29 return { get: get, set: set, hasConsented: hasConsented, hasThirdparty: hasThirdparty }; 30 })(); 31 32 document.addEventListener('DOMContentLoaded', function () { 33 initCallOnLoadLink(); 34 repairBrokenAbsoluteMediaUrls(); 35 repairEmbeddedIframes(); 36 initWebcamImages(); 37 initMobileMenu(); 38 initMobileAccordions(); 39 initContentAccordions(); 40 initContextNavAccordions(); 41 initDesktopMenuToggles(); 42 initHeroSliders(); 43 initSearchModal(); 44 initA11yTools(); 45 initCookieBanner(); 46 gateExternalContent(); 47 initLeafletMap(); 48 initPhotoModal(); 49 }); 50 51 function initCallOnLoadLink() { 52 var link = document.querySelector('a.callonload[href]'); 53 if (!link) { return; } 54 55 var href = String(link.getAttribute('href') || '').trim(); 56 if (!href || href === '#') { return; } 57 58 // Prevent accidental self-navigation loops when alias points to current URL. 59 try { 60 var target = new URL(href, window.location.href); 61 if (target.href === window.location.href) { 62 return; 63 } 64 window.location.assign(target.href); 65 return; 66 } catch (e) { 67 // Fallback for malformed URLs: keep legacy behavior and try to navigate. 68 } 69 70 window.location.assign(href); 71 } 72 73 function repairBrokenAbsoluteMediaUrls() { 74 document.querySelectorAll('.wysiwyg-content [src], .wysiwyg-content [href]').forEach(function (el) { 75 ['src', 'href'].forEach(function (attr) { 76 if (!el.hasAttribute(attr)) { return; } 77 78 var value = el.getAttribute(attr) || ''; 79 var repaired = normalizeBrokenAbsoluteUrl(value); 80 if (repaired !== value) { 81 el.setAttribute(attr, repaired); 82 } 83 }); 84 }); 85 } 86 87 function normalizeBrokenAbsoluteUrl(value) { 88 return String(value || '') 89 .replace(/^(?:\.\.\/)+(?:admin\/)?(?=https?:\/\/)/i, '') 90 .replace(/^admin\/(?=https?:\/\/)/i, ''); 91 } 92 93 function repairEmbeddedIframes() { 94 document.querySelectorAll('.wysiwyg-content iframe, .page-article__content iframe').forEach(function (iframe) { 95 var sandbox = iframe.getAttribute('sandbox'); 96 if (sandbox !== null && sandbox.trim() === '') { 97 iframe.removeAttribute('sandbox'); 98 } 99 100 var src = iframe.getAttribute('src') || ''; 101 var normalizedSrc = normalizeEmbedUrl(src); 102 if (normalizedSrc !== src) { 103 iframe.setAttribute('src', normalizedSrc); 104 } 105 }); 106 } 107 108 function normalizeEmbedUrl(value) { 109 var url = String(value || ''); 110 if (url.indexOf('//') === 0) { 111 return window.location.protocol + url; 112 } 113 114 return url; 115 } 116 117 function initWebcamImages() { 118 var webcamImages = Array.prototype.slice.call(document.querySelectorAll('img.img-webcam[src]')); 119 if (!webcamImages.length) { return; } 120
121 webcamImages.forEach(function (img) { 122 if (!img.dataset.webcamBaseSrc) { 123 img.dataset.webcamBaseSrc = img.getAttribute('src') || ''; 124 } 125 }); 126 127 function refresh() { 128 var ts = Date.now(); 129 130 webcamImages.forEach(function (img) { 131 var baseSrc = img.dataset.webcamBaseSrc || img.getAttribute('src') || ''; 132 if (!baseSrc) { return; } 133 134 img.setAttribute('src', appendTimestamp(baseSrc, ts)); 135 }); 136 } 137 138 refresh(); 139 window.setInterval(refresh, 15000); 140 } 141 142 function appendTimestamp(src, ts) { 143 try { 144 var url = new URL(src, window.location.href); 145 url.searchParams.set('ts', String(ts)); 146 return url.toString(); 147 } catch (e) { 148 var cleanSrc = String(src || '').replace(/([?&])ts=\d+(&)?/g, function (match, prefix, suffix) { 149 return prefix === '?' && suffix ? '?' : (suffix ? prefix : ''); 150 }).replace(/[?&]$/, ''); 151 return cleanSrc + (cleanSrc.indexOf('?') === -1 ? '?' : '&') + 'ts=' + ts; 152 } 153 } 154 155 /* ========== Mobile menu ========== */ 156 function initMobileMenu() { 157 var openBtn = document.getElementById('mobile-btn'); 158 var closeBtn = document.getElementById('mobile-close'); 159 var panel = document.getElementById('mobile-menu'); 160 var overlay = document.getElementById('mobile-overlay'); 161 var lastTrigger = null; 162 163 if (!openBtn || !panel || !overlay) { return; } 164 165 function setState(open) { 166 panel.classList.toggle('is-open', open); 167 overlay.classList.toggle('is-open', open); 168 panel.setAttribute('aria-hidden', open ? 'false' : 'true'); 169 openBtn.setAttribute('aria-expanded', open ? 'true' : 'false'); 170 document.body.style.overflow = open ? 'hidden' : ''; 171 if (open) { 172 lastTrigger = document.activeElement; 173 if (closeBtn) { closeBtn.focus(); } 174 } else if (lastTrigger && typeof lastTrigger.focus === 'function') { 175 lastTrigger.focus(); 176 } 177 } 178 179 openBtn.addEventListener('click', function () { setState(true); }); 180 if (closeBtn) { closeBtn.addEventListener('click', function () { setState(false); }); } 181 overlay.addEventListener('click', function () { setState(false); }); 182 document.addEventListener('keydown', function (e) { 183 if (e.key === 'Escape') { setState(false); } 184 }); 185 } 186 187 /* ========== Content accordions (.accordion-trigger) ========== */ 188 function initContentAccordions() { 189 document.querySelectorAll('.accordion-trigger:not([data-acc-init])').forEach(function (trigger) { 190 var panel = trigger.closest('.accordion-item') 191 ? trigger.closest('.accordion-item').querySelector('.accordion-panel') 192 : null; 193 trigger.dataset.accInit = '1'; 194 trigger.addEventListener('click', function () { 195 var expanded = trigger.getAttribute('aria-expanded') === 'true'; 196 trigger.setAttribute('aria-expanded', expanded ? 'false' : 'true'); 197 if (panel) { panel.classList.toggle('is-open', !expanded); } 198 }); 199 }); 200 } 201 202 function initMobileAccordions() { 203 var triggers = document.querySelectorAll('.mobile-acc-trigger'); 204 triggers.forEach(function (trigger) { 205 trigger.addEventListener('click', function () { 206 var contentId = trigger.getAttribute('aria-controls'); 207 var content = contentId ? document.getElementById(contentId) : null; 208 var open = trigger.classList.toggle('is-open'); 209 trigger.setAttribute('aria-expanded', open ? 'true' : 'false'); 210 if (content) { content.classList.toggle('is-open', open); } 211 }); 212 }); 213 } 214 215 function initContextNavAccordions() { 216 var toggles = document.querySelectorAll('.context-nav__toggle:not([data-context-nav-init])');
217 toggles.forEach(function (toggle) { 218 var contentId = toggle.getAttribute('aria-controls'); 219 var content = contentId ? document.getElementById(contentId) : null; 220 var item = toggle.closest('.context-nav__item'); 221 222 if (!content || !item) { return; } 223 224 toggle.dataset.contextNavInit = '1'; 225 item.classList.toggle('is-expanded', !content.hidden); 226 item.classList.toggle('is-collapsed', content.hidden); 227 228 toggle.addEventListener('click', function () { 229 var open = toggle.getAttribute('aria-expanded') === 'true'; 230 toggle.setAttribute('aria-expanded', open ? 'false' : 'true'); 231 content.hidden = open; 232 item.classList.toggle('is-expanded', !open); 233 item.classList.toggle('is-collapsed', open); 234 }); 235 }); 236 } 237 238 function initDesktopMenuToggles() { 239 var items = document.querySelectorAll('.nav-item.has-children, .sub-item.has-children'); 240 items.forEach(function (item) { 241 var trigger = item.querySelector(':scope > .nav-top-link--toggle, :scope > .nav-link--toggle'); 242 if (!trigger) { return; } 243 function syncExpanded() { 244 trigger.setAttribute('aria-expanded', item.matches(':hover, :focus-within') ? 'true' : 'false'); 245 } 246 item.addEventListener('mouseenter', syncExpanded); 247 item.addEventListener('mouseleave', syncExpanded); 248 item.addEventListener('focusin', syncExpanded); 249 item.addEventListener('focusout', function () { 250 window.setTimeout(syncExpanded, 0); 251 }); 252 syncExpanded(); 253 }); 254 } 255 256 /* ========== Hero sliders ========== */ 257 function initHeroSliders() { 258 var sliders = document.querySelectorAll('[data-slider]'); 259 sliders.forEach(function (slider) { 260 var slides = slider.querySelectorAll('[data-slide]'); 261 var dots = slider.querySelectorAll('[data-slide-to]'); 262 if (slides.length < 2) { return; } 263 var current = 0; 264 var interval = null; 265 266 function render(idx) { 267 slides.forEach(function (slide, i) { 268 slide.classList.toggle('is-active', i === idx); 269 }); 270 dots.forEach(function (dot, i) { 271 dot.classList.toggle('is-active', i === idx); 272 }); 273 current = idx; 274 } 275 function start() { 276 clearInterval(interval); 277 interval = setInterval(function () { 278 render((current + 1) % slides.length); 279 }, 6000); 280 } 281 dots.forEach(function (dot) { 282 dot.addEventListener('click', function () { 283 var idx = parseInt(dot.getAttribute('data-slide-to') || '0', 10); 284 if (!isNaN(idx)) { render(idx); start(); } 285 }); 286 }); 287 slider.addEventListener('mouseenter', function () { clearInterval(interval); }); 288 slider.addEventListener('mouseleave', start); 289 render(0); 290 start(); 291 }); 292 } 293 294 /* ========== Search modal ========== */ 295 function initSearchModal() { 296 var modal = document.getElementById('search-modal'); 297 var btn = document.getElementById('search-btn'); 298 var closeBtn = document.getElementById('search-close'); 299 var input = document.getElementById('search-input'); 300 var results = document.getElementById('search-results'); 301 var lastTrigger = null; 302 303 if (!modal || !btn || !input || !results) { return; } 304 305 var entries = mergeSearchEntries(collectSearchEntries(), Array.isArray(window.G24_SEARCH_ENTRIES) ? window.G24_SEARCH_ENTRIES : []); 306 307 function renderResults(query) { 308 var q = normalizeSearchValue(query); 309 var filtered = q 310 ? entries.filter(function (e) { 311 var haystack = normalizeSearchValue((e.search || '') + ' ' + (e.label || '') + ' ' + (e.text || '') + ' ' + (e.group || '')); 312 return haystack.indexOf(q) !== -1; 313 }
313).slice(0, 30) 314 : entries.slice(0, 20); 315 if (!filtered.length) { 316 results.innerHTML = '<p class="search-empty">Keine Ergebnisse</p>'; 317 return; 318 } 319 var groups = {}; 320 filtered.forEach(function (e) { 321 if (!groups[e.group]) { groups[e.group] = []; } 322 groups[e.group].push(e); 323 }); 324 var html = ''; 325 Object.keys(groups).forEach(function (group) { 326 html += '<div class="search-group">'; 327 html += '<p class="search-group__label">' + escapeHtml(group) + '</p>'; 328 groups[group].forEach(function (entry) { 329 html += '<a href="' + escapeAttr(entry.url) + '">'; 330 html += '<span class="search-result__title">' + escapeHtml(entry.label) + '</span>'; 331 if (entry.text) { 332 html += '<span class="search-result__text">' + escapeHtml(entry.text) + '</span>'; 333 } 334 html += '</a>'; 335 }); 336 html += '</div>'; 337 }); 338 results.innerHTML = html; 339 } 340 341 function open() { 342 modal.classList.add('is-open'); 343 modal.setAttribute('aria-hidden', 'false'); 344 btn.setAttribute('aria-expanded', 'true'); 345 lastTrigger = document.activeElement; 346 setTimeout(function () { input.focus(); }, 50); 347 renderResults(''); 348 } 349 function close() { 350 modal.classList.remove('is-open'); 351 modal.setAttribute('aria-hidden', 'true'); 352 btn.setAttribute('aria-expanded', 'false'); 353 input.value = ''; 354 if (lastTrigger && typeof lastTrigger.focus === 'function') { 355 lastTrigger.focus(); 356 } 357 } 358 359 btn.addEventListener('click', open); 360 if (closeBtn) { closeBtn.addEventListener('click', close); } 361 modal.addEventListener('click', function (e) { if (e.target === modal) { close(); } }); 362 input.addEventListener('input', function (e) { renderResults(e.target.value); }); 363 document.addEventListener('keydown', function (e) { 364 if ((e.metaKey || e.ctrlKey) && e.key && e.key.toLowerCase() === 'k') { 365 e.preventDefault(); 366 modal.classList.contains('is-open') ? close() : open(); 367 } 368 if (e.key === 'Escape') { close(); } 369 }); 370 } 371 372 function collectSearchEntries() { 373 var entries = []; 374 var root = document.querySelector('.desktop-nav .nav-root'); 375 if (!root) { return entries; } 376 var topItems = root.querySelectorAll(':scope > .nav-item'); 377 topItems.forEach(function (top) { 378 var topLabel = textOf(top.querySelector(':scope > .nav-top-link')); 379 var topUrl = hrefOf(top.querySelector(':scope > a.nav-top-link')) || '#'; 380 entries.push({ label: topLabel, url: topUrl, group: 'Hauptnavigation' }); 381 top.querySelectorAll(':scope > .nav-dropdown .nav-submenu-inner > .sub-item').forEach(function (sub) { 382 var subLabel = textOf(sub.querySelector(':scope > .nav-link, :scope > .nav-link--toggle')); 383 var subUrl = hrefOf(sub.querySelector(':scope > a.nav-link')) || '#'; 384 entries.push({ label: topLabel + ' ⺠' + subLabel, url: subUrl, group: topLabel }); 385 sub.querySelectorAll(':scope > .nav-flyout .nav-submenu-inner > .sub-item > a.nav-link').forEach(function (leaf) { 386 entries.push({ label: topLabel + ' ⺠' + subLabel + ' ⺠' + leaf.textContent.trim(), url: leaf.getAttribute('href') || '#', group: topLabel }); 387 }); 388 }); 389 }); 390 return entries; 391 } 392 393 function mergeSearchEntries(navEntries, contentEntries) { 394 var seen = new Set(); 395 396 return navEntries.concat(contentEntries).map(function (entry) { 397 return { 398 label: entry && (entry.label || entry.title) ? String(entry.label || entry.title) : '', 399 text: entry && entry.text ? String(entry.text) : '', 400 search: entry && entry.search ? String(entry.search) : '', 401 group: entry && entry.group ? String(entry.group) : 'Weitere Inhalte', 402 url: entry && entry.url ? String(entry.url) : '' 403 }; 404 }).filter(function (entry) { 405 if (!entry.label || !entry.url) { 406 return false;
407 } 408 409 var key = [entry.group || '', entry.label || '', entry.url || ''].join('||'); 410 if (seen.has(key)) { 411 return false; 412 } 413 seen.add(key); 414 415 return true; 416 }); 417 } 418 419 function normalizeSearchValue(value) { 420 return String(value || '') 421 .toLowerCase() 422 .normalize('NFD') 423 .replace(/[\u0300-\u036f]/g, '') 424 .replace(/ä/g, 'a') 425 .replace(/ö/g, 'o') 426 .replace(/ü/g, 'u') 427 .replace(/Ã/g, 'ss') 428 .trim(); 429 } 430 431 function textOf(el) { return el ? (el.textContent || '').trim() : ''; } 432 function hrefOf(el) { return el ? el.getAttribute('href') : null; } 433 434 /* ========== A11y tools ========== */ 435 function initA11yTools() { 436 var textBtn = document.querySelector('[data-a11y-textsize]'); 437 var contrastBtn = document.querySelector('[data-a11y-contrast]'); 438 var root = document.documentElement; 439 var textSizeLevel = clampTextSizeLevel(readCookie(TEXTSIZE_COOKIE)); 440 var contrastEnabled = readCookie(CONTRAST_COOKIE) === '1'; 441 442 function applyTextSize(level) { 443 textSizeLevel = clampTextSizeLevel(level); 444 if (textSizeLevel > 0) { 445 root.setAttribute('data-a11y-textsize', String(textSizeLevel)); 446 } else { 447 root.removeAttribute('data-a11y-textsize'); 448 } 449 writeCookie(TEXTSIZE_COOKIE, String(textSizeLevel)); 450 if (textBtn) { 451 textBtn.setAttribute('aria-pressed', textSizeLevel > 0 ? 'true' : 'false'); 452 textBtn.setAttribute('aria-label', 'Schriftgrösse vergrössern, Stufe ' + textSizeLevel + ' von 3'); 453 } 454 } 455 456 function applyContrast(enabled) { 457 contrastEnabled = !!enabled; 458 document.body.classList.toggle('a11y-contrast', contrastEnabled); 459 writeCookie(CONTRAST_COOKIE, contrastEnabled ? '1' : '0'); 460 if (contrastBtn) { 461 contrastBtn.setAttribute('aria-pressed', contrastEnabled ? 'true' : 'false'); 462 } 463 } 464 465 applyTextSize(textSizeLevel); 466 applyContrast(contrastEnabled); 467 468 if (textBtn) { 469 textBtn.addEventListener('click', function () { 470 applyTextSize((textSizeLevel + 1) % 4); 471 }); 472 } 473 if (contrastBtn) { 474 contrastBtn.addEventListener('click', function () { 475 applyContrast(!contrastEnabled); 476 }); 477 } 478 } 479 480 /* ========== Shared consent helpers ========== */ 481 function showCookieBanner() { 482 var b = document.getElementById('cookie-banner'); 483 if (b) { b.hidden = false; } 484 } 485 function hideCookieBanner() { 486 var b = document.getElementById('cookie-banner'); 487 if (b) { b.hidden = true; } 488 } 489 function syncBannerToggle(checked) { 490 var t = document.getElementById('cookie-toggle-thirdparty'); 491 if (t) { t.checked = checked; } 492 } 493 /* Called when thirdparty consent is granted â activates all gated content */ 494 function onThirdpartyGranted() { 495 hideCookieBanner(); 496 syncBannerToggle(true); 497 gateExternalContent(); // re-run: will now activate since consent is true 498 initLeafletMap(); 499 } 500 501 /* ========== Cookie banner ========== */ 502 function initCookieBanner() { 503 var banner = document.getElementById('cookie-banner'); 504 if (!banner) { return; } 505 506 var toggle = document.getElementById('cookie-toggle-thirdparty'); 507 var btnNec = document.getElementById('cookie-btn-necessary'); 508 var btnSave = document.getElementById('cookie-btn-save'); 509 var btnAll = document.getElementById('cookie-btn-all'); 510 511 function showBanner() { 512 banner.hidden = false; 513 banner.focus(); 514 } 515 516 function applyConsent(thirdparty) { 517 g24Consent.set(thirdparty); 518 hideCookieBanner(); 519 syncBannerToggle(thirdparty); 520 requestHomepageCacheClear(); 521 gateExternalContent(); 522 if (thirdparty) { initLeafletMap(); } 523 } 524 525 // Sync toggle to stored consent state 526 var existing = g24Consent.get(); 527 if (toggle && existing) { toggle.checked = !!existing.thirdparty; } 528 529 // Auto-show only if admin enabled it and no consent given yet 530 var autoShow = banner.getAttribute('data-auto-show') === '1'; 531 if (autoShow && !g24Consent.hasConsented()) { showBanner(); } 532 533 if (btnNec) { btnNec.addEventListener('click', function () { applyConsent(false); }); } 534 if (btnSave) { btnSave.addEventListener('click', function () { applyConsent(toggle ? toggle.checked : false); }); }
535 if (btnAll) { btnAll.addEventListener('click', function () { applyConsent(true); }); } 536 537 // Re-open triggers (footer + datenschutz page) via event delegation 538 document.addEventListener('click', function (e) { 539 var t = e.target; 540 if (t && (t.id === 'cookie-reopen-btn' || t.id === 'datenschutz-cookie-reopen' || t.id === 'datenschutz-cookie-settings-btn')) { 541 e.preventDefault(); 542 // Sync toggle to current state before showing 543 var c = g24Consent.get(); 544 if (toggle && c) { toggle.checked = !!c.thirdparty; } 545 showBanner(); 546 banner.scrollIntoView({ behavior: 'smooth', block: 'end' }); 547 } 548 }); 549 } 550 551 function requestHomepageCacheClear() { 552 var url = '/_homepage/cache/clear'; 553 554 if (navigator.sendBeacon) { 555 try { 556 navigator.sendBeacon(url, new Blob([], { type: 'text/plain' })); 557 return; 558 } catch (e) { 559 // fall through to fetch 560 } 561 } 562 563 if (window.fetch) { 564 fetch(url, { 565 method: 'POST', 566 credentials: 'same-origin', 567 keepalive: true, 568 headers: { 569 'X-Requested-With': 'XMLHttpRequest' 570 } 571 }).catch(function () {}); 572 } 573 } 574 575 /* ========== External content gating (iframes in page content) ========== */ 576 function gateExternalContent() { 577 // 1. Activate already-gated iframes if consent now given 578 if (g24Consent.hasThirdparty()) { 579 document.querySelectorAll('iframe[data-orig-src]').forEach(function (f) { 580 f.src = normalizeEmbedUrl(f.getAttribute('data-orig-src')); 581 f.removeAttribute('data-orig-src'); 582 f.style.display = ''; 583 }); 584 document.querySelectorAll('.ext-consent-gate').forEach(function (g) { g.remove(); }); 585 return; 586 } 587 588 // 2. Find external iframes not yet gated 589 var areas = document.querySelectorAll('.wysiwyg-content, .page-article__content'); 590 areas.forEach(function (area) { 591 area.querySelectorAll('iframe:not([data-orig-src])').forEach(function (iframe) { 592 var src = normalizeEmbedUrl(iframe.getAttribute('src') || ''); 593 if (!src || src === 'about:blank') { return; } 594 if (src.charAt(0) === '/' && src.charAt(1) !== '/') { return; } 595 596 // Determine service label 597 var label = src.indexOf('youtube') >= 0 || src.indexOf('youtu.be') >= 0 ? 'YouTube' 598 : src.indexOf('vimeo') >= 0 ? 'Vimeo' 599 : src.indexOf('maps.google') >= 0 ? 'Google Maps' 600 : src.indexOf('soundcloud') >= 0 ? 'SoundCloud' 601 : 'Externer Inhalt'; 602 603 // Save src and blank the iframe 604 iframe.setAttribute('data-orig-src', src); 605 iframe.src = 'about:blank'; 606 iframe.style.display = 'none'; 607 608 // Build gate placeholder in place of the iframe 609 var gate = document.createElement('div'); 610 gate.className = 'ext-consent-gate'; 611 gate.innerHTML = 612 '<div class="ext-consent-gate__overlay">' 613 + '<svg class="ext-consent-gate__icon" width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="3" y="3" width="18" height="18" rx="2"/><path d="M3 9h18M9 21V9"/></svg>' 614 + '<p class="ext-consent-gate__label">' + escapeHtml(label) + '</p>' 615 + '<p class="ext-consent-gate__title">Inhalt nicht geladen</p>' 616 + '<p class="ext-consent-gate__text">Dieser Inhalt wird von <strong>' + escapeHtml(label) + '</strong> bereitgestellt. Um ihn anzuzeigen, müssen Drittanbieter-Dienste aktiviert werden.</p>' 617 + '<button type="button" class="ext-consent-gate__btn">Inhalt aktivieren</button>' 618 + '<a href="/datenschutz" class="ext-consent-gate__link">Datenschutz</a>' 619 + '</div>'; 620 iframe.parentNode.insertBefore(gate, iframe); 621 622 gate.querySelector('.ext-consent-gate__btn').addEventListener('click', function () { 623 g24Consent.set(true); 624 onThirdpartyGranted(); 625 }); 626 }); 627 }); 628 } 629 630 /* ========== Leaflet map (consent-aware) ========== */ 631 function initLeafletMap() { 632 var el = document.getElementById('map'); 633 if (!el) { return; } 634 635 var lat = parseFloat(el.getAttribute('data-lat') || ''); 636 var lng = parseFloat(el.getAttribute('data-lng') || ''); 637 var label = el.getAttribute('data-label') || ''; 638 if (isNaN(lat) || isNaN(lng)) { return; } 639 640 // Already initialized (Leaflet stamps _leaflet_id on the container) 641 if (el._leaflet_id) { return; } 642 643 // No consent â show overlay inside the map card (parent of canvas) 644 if (!g24Consent.hasThirdparty()) { 645 var card = el.closest('.map-card') || el.parentElement; 646 if (card && card.querySelector('.map-consent-overlay')) { return; }
647 var overlay = document.createElement('div'); 648 overlay.className = 'map-consent-overlay'; 649 overlay.innerHTML = 650 '<svg class="map-consent-overlay__icon" width="36" height="36" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">' 651 + '<path d="M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0 1 18 0z"/><circle cx="12" cy="10" r="3"/></svg>' 652 + '<p class="map-consent-overlay__title">Karte nicht geladen</p>' 653 + '<p class="map-consent-overlay__text">Diese Karte lädt Daten von <strong>OpenStreetMap</strong> (Drittanbieter). Aktivieren Sie den Dienst, um die Karte anzuzeigen.</p>' 654 + '<button type="button" class="map-consent-overlay__btn">Karte aktivieren</button>' 655 + '<a href="/datenschutz" class="map-consent-overlay__link">Datenschutz</a>'; 656 (card || el).appendChild(overlay); 657 overlay.querySelector('.map-consent-overlay__btn').addEventListener('click', function () { 658 g24Consent.set(true); 659 onThirdpartyGranted(); 660 }); 661 return; 662 } 663 664 // Consent given â wait for Leaflet library if still loading (defer) 665 if (typeof L === 'undefined') { 666 setTimeout(initLeafletMap, 200); 667 return; 668 } 669 670 // Remove consent overlay if present 671 var card = el.closest('.map-card') || el.parentElement; 672 var prev = card ? card.querySelector('.map-consent-overlay') : null; 673 if (prev) { prev.remove(); } 674 675 var map = L.map(el, { zoomControl: true, scrollWheelZoom: false }).setView([lat, lng], 16); 676 L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { 677 attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a>', 678 maxZoom: 19 679 }).addTo(map); 680 681 var accent = getComputedStyle(document.documentElement).getPropertyValue('--theme-accent').trim() || '#0f766e'; 682 var icon = L.divIcon({ 683 className: '', 684 html: '<div style="width:28px;height:28px;border-radius:50%;background:' + accent + ';border:3px solid white;box-shadow:0 4px 12px rgba(0,0,0,.3)"></div>', 685 iconSize: [28, 28], 686 iconAnchor: [14, 14] 687 }); 688 L.marker([lat, lng], { icon: icon }).addTo(map).bindPopup(label || ''); 689 } 690 691 /* ========== helpers ========== */ 692 function escapeHtml(s) { 693 return String(s || '').replace(/[&<>"']/g, function (c) { 694 return { '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[c]; 695 }); 696 } 697 /* ========== Photo modal ========== */ 698 function initPhotoModal() { 699 var modal = document.getElementById('photo-modal'); 700 if (!modal) { return; } 701 var img = document.getElementById('photo-modal-img'); 702 var caption = document.getElementById('photo-modal-caption'); 703 var counter = document.getElementById('photo-modal-counter'); 704 var closeBtn = modal.querySelector('.photo-modal__close'); 705 var backdrop = modal.querySelector('.photo-modal__backdrop'); 706 var prevBtn = modal.querySelector('.photo-modal__nav--prev'); 707 var nextBtn = modal.querySelector('.photo-modal__nav--next'); 708 var currentGroup = []; 709 var currentIndex = 0; 710 var lastTrigger = null; 711 712 function getPhotoName(wrapper, image) { 713 var card = wrapper ? wrapper.closest('.contact-card, .contact-inline') : null; 714 var nameEl = card ? card.querySelector('.contact-card__name, .contact-inline__name') : null; 715 var roleEl = card ? card.querySelector('.contact-card__role, .contact-inline__role') : null; 716 var name = nameEl ? nameEl.textContent.trim() : ''; 717 718 if (!name && image) { 719 name = (image.getAttribute('alt') || '').trim(); 720 } 721 722 if (!name && roleEl) { 723 name = roleEl.textContent.trim(); 724 } 725 726 return name; 727 } 728 729 function prepareContactPhotoTriggers() { 730 document.querySelectorAll('.contact-card__avatar, .contact-inline__avatar').forEach(function (wrapper) { 731 if (wrapper.hasAttribute('data-photo-modal')) { 732 return; 733 } 734 735 var image = wrapper.querySelector('img'); 736 var src = image ? (image.currentSrc || image.getAttribute('src') || '') : ''; 737 738 if (!src) { 739 return; 740 } 741 742 var name = getPhotoName(wrapper, image); 743 744 wrapper.dataset.photoModal = src; 745 if (name !== '') { 746 wrapper.dataset.photoName = name; 747 wrapper.setAttribute('aria-label', 'Foto von ' + name + ' vergröÃern'); 748 } 749 750 if (wrapper.classList.contains('contact-card__avatar')) { 751 wrapper.classList.add('contact-card__avatar--clickable'); 752 } 753 if (wrapper.classList.contains('contact-inline__avatar')) { 754 wrapper.classList.add('contact-inline__avatar--clickable'); 755 } 756 757 wrapper.setAttribute('role', 'button'); 758 wrapper.setAttribute('tabindex', '0'); 759 }); 760 } 761 762 function resolvePhotoGroup(trigger) { 763 var groupRoot = trigger ? trigger.closest('[data-photo-gallery]') : null; 764 if (!groupRoot) { 765 return [trigger]; 766 } 767 768 return Array.prototype.slice.call(groupRoot.querySelectorAll('[data-photo-modal]')); 769 } 770 771 function renderCurrentPhoto() { 772 var current = currentGroup[currentIndex]; 773 if (!current) { 774 return; 775 } 776 777 var name = current.dataset.photoName || ''; 778 var description = current.dataset.photoDescription || ''; 779 780 img.src = current.dataset.photoModal || ''; 781 img.alt = name; 782 caption.textContent = description !== '' ? (name !== '' ? name + ' - ' + description : description) : name; 783 784 if (counter) { 785 if (currentGroup.length > 1) { 786 counter.hidden = false;
787 counter.textContent = String(currentIndex + 1) + ' / ' + String(currentGroup.length); 788 } else { 789 counter.hidden = true; 790 counter.textContent = ''; 791 } 792 } 793 794 if (prevBtn) { 795 prevBtn.hidden = currentGroup.length <= 1; 796 } 797 if (nextBtn) { 798 nextBtn.hidden = currentGroup.length <= 1; 799 } 800 } 801 802 function openTrigger(trigger) { 803 currentGroup = resolvePhotoGroup(trigger).filter(function (item) { 804 return item && item.dataset && item.dataset.photoModal; 805 }); 806 807 if (currentGroup.length === 0) { 808 currentGroup = [trigger]; 809 } 810 811 currentIndex = Math.max(0, currentGroup.indexOf(trigger)); 812 lastTrigger = trigger; 813 renderCurrentPhoto(); 814 modal.hidden = false; 815 document.body.style.overflow = 'hidden'; 816 closeBtn.focus(); 817 } 818 819 function stepPhoto(direction) { 820 if (currentGroup.length <= 1) { 821 return; 822 } 823 824 currentIndex = (currentIndex + direction + currentGroup.length) % currentGroup.length; 825 renderCurrentPhoto(); 826 } 827 828 function closeModal() { 829 modal.hidden = true; 830 document.body.style.overflow = ''; 831 img.src = ''; 832 img.alt = ''; 833 caption.textContent = ''; 834 if (counter) { 835 counter.hidden = true; 836 counter.textContent = ''; 837 } 838 if (prevBtn) { prevBtn.hidden = true; } 839 if (nextBtn) { nextBtn.hidden = true; } 840 if (lastTrigger && typeof lastTrigger.focus === 'function') { 841 lastTrigger.focus(); 842 } 843 } 844 845 prepareContactPhotoTriggers(); 846 847 document.addEventListener('click', function (e) { 848 var trigger = e.target.closest('[data-photo-modal]'); 849 if (trigger) { 850 e.preventDefault(); 851 openTrigger(trigger); 852 } 853 }); 854 855 if (closeBtn) { closeBtn.addEventListener('click', closeModal); } 856 if (backdrop) { backdrop.addEventListener('click', closeModal); } 857 if (prevBtn) { 858 prevBtn.addEventListener('click', function () { stepPhoto(-1); }); 859 } 860 if (nextBtn) { 861 nextBtn.addEventListener('click', function () { stepPhoto(1); }); 862 } 863 document.addEventListener('keydown', function (e) { 864 var activeTrigger = document.activeElement; 865 if ((e.key === 'Enter' || e.key === ' ') && activeTrigger && activeTrigger.matches('[data-photo-modal][role="button"]')) { 866 e.preventDefault(); 867 openTrigger(activeTrigger); 868 return; 869 } 870 if (e.key === 'ArrowLeft' && !modal.hidden) { 871 e.preventDefault(); 872 stepPhoto(-1); 873 return; 874 } 875 if (e.key === 'ArrowRight' && !modal.hidden) { 876 e.preventDefault(); 877 stepPhoto(1); 878 return; 879 } 880 if (e.key === 'Escape' && !modal.hidden) { closeModal(); } 881 }); 882 } 883 884 function escapeAttr(s) { return escapeHtml(s); } 885 886 function clampTextSizeLevel(value) { 887 var parsed = parseInt(value || '0', 10); 888 if (isNaN(parsed) || parsed < 0) { return 0; } 889 if (parsed > 3) { return 3; } 890 return parsed; 891 } 892 893 function readCookie(name) { 894 var escapedName = name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); 895 var match = document.cookie.match(new RegExp('(?:^|; )' + escapedName + '=([^;]*)')); 896 return match ? decodeURIComponent(match[1]) : ''; 897 } 898 899 function writeCookie(name, value) { 900 document.cookie = name + '=' + encodeURIComponent(value) + '; path=/; max-age=' + COOKIE_MAX_AGE + '; SameSite=Lax'; 901 } 902 903})();
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.