1function switchText(symbols1 = "", symbols2 = "", symbols3 = "", symbols4 = "") { 2 3 if (symbols1 != "" && symbols2 != "") { 4 5 let count1 = 0; 6 let count2 = 0; 7 8 const element1 = document.getElementById("textswitcher1"); 9 const element2 = document.getElementById("textswitcher2"); 10 11 const iteration1 = () => { 12 element1.innerHTML = symbols1[parseInt(count1 / 2, 10) % symbols1.length]; 13 element2.innerHTML = symbols2[parseInt(count2 / 2, 10) % symbols2.length]; 14 15 if (count1 % 2 !== 0) { 16 element1.classList.add("out"); 17 element2.classList.add("out"); 18 } else { 19 element1.classList.remove("out"); 20 element2.classList.remove("out"); 21 } 22 23 count1++; 24 count2++; 25 26 if (count1 === symbols1.length * 2) { 27 count1 = 0; 28 count2 = 0; 29 } 30 }; 31 let inthandle = setInterval(iteration1, 3000); 32 iteration1(); 33 34 } 35 36 if (symbols3 != "" && symbols4 != "") { 37 38 let count3 = 0; 39 let count4 = 0; 40 41 const element3 = document.getElementById("textswitcher3"); 42 const element4 = document.getElementById("textswitcher4"); 43 44 const iteration2 = () => { 45 element3.innerHTML = symbols3[parseInt(count3 / 2, 10) % symbols3.length]; 46 element4.innerHTML = symbols4[parseInt(count4 / 2, 10) % symbols4.length]; 47 48 if (count3 % 2 !== 0) { 49 element3.classList.add("out"); 50 element4.classList.add("out"); 51 } else { 52 element3.classList.remove("out"); 53 element4.classList.remove("out"); 54 } 55 56 count3++; 57 count4++; 58 59 if (count3 === symbols3.length * 2) { 60 count3 = 0; 61 count4 = 0; 62 } 63 }; 64 let inthandle = setInterval(iteration2, 3000); 65 iteration2(); 66 } 67 68 69} 70 71 72// Aendert das Scrollverhalten bei Klick auf das Burger-Icon, so dass bei responsiver Ansicht das Menu nicht die eigentliche Seite beim Scrollen beeinflusst. 73const element = document.getElementById("btn_navbartoggler"); 74element.addEventListener("click", toggleMobileMenuScrollBehaviour); 75 76function toggleMobileMenuScrollBehaviour() { 77 var x = document.getElementsByTagName("BODY")[0]; 78 79 if (x.style.overflow === 'hidden') { 80 x.style.overflow = "auto"; 81 } 82 else { 83 x.style.overflow = "hidden"; 84 } 85} 86 87 /* 88 MENUEBAR BEIM RUNTERSCROLLEN SCHWARZ EINFAERBEN 89 */ 90 91 /* 92 document.addEventListener("DOMContentLoaded", function () { 93 var scrollSpy = new bootstrap.ScrollSpy(document.body, { 94 target: "#main_nav", 95 offset: 200, 96 }); 97 98 window.addEventListener("scroll", function () { 99 if (window.scrollY > 250) { 100 document.getElementById("navigation").classList.add("bg-black"); 101 document.getElementById("navigation").classList.add("einfaden"); 102 } else { 103 document.getElementById("navigation").classList.remove("bg-black"); 104 document.body.style.paddingTop = "0"; 105 } 106 }); 107 }); 108 */ 109 // DOMContentLoaded end 110 111 112 113 /* 114 function changeContent(numberslide) { 115 116 117 118 if (numberslide == 0) { 119 120 console.log("GO: "+numberslide); 121 122 // Preview-Video entfernen 123 var video_preview0 = document.getElementById("video_preview0"); 124 video_preview0.remove(); 125 126 // Vimeo-Video sichtbar machen 127 var video_embedded0 = document.getElementById("fullVideo0"); 128 video_embedded0.classList.remove("hidden"); 129 } 130 131 if (numberslide == 1) { 132 // Preview-Video entfernen 133 var video_preview1 = document.getElementById("video_preview1"); 134 video_preview1.remove(); 135 136 // Vimeo-Video sichtbar machen 137 var video_embedded1 = document.getElementById("fullVideo1"); 138 video_embedded1.classList.remove("hidden"); 139 } 140 141 if (numberslide == 2) { 142 // Preview-Video entfernen 143 var video_preview2 = document.getElementById("video_preview"+numberslide); 144 video_preview2.remove(); 145 146 // Vimeo-Video sichtbar machen 147 var video_embedded2 = document.getElementById("fullVideo"+numberslide); 148 video_embedded2.classList.remove("hidden"); 149 } 150 151 if (numberslide == 3) { 152 // Preview-Video entfernen 153 var video_preview3 = document.getElementById("video_preview"+numberslide); 154 video_preview3.remove(); 155 156 // Vimeo-Video sichtbar machen 157 var video_embedded3 = document.getElementById("fullVideo"+numberslide); 158 video_embedded3.classList.remove("hidden"); 159 } 160 161 162 } 163 */ 164 165 166 167 168 169(function () { 170 var ua = navigator.userAgent || navigator.vendor || window.opera; 171 var isAndroid = /android/i.test(ua); 172 var isIOS = /iPhone|iPad|iPod/i.test(ua) || (/Macintosh/i.test(ua) && navigator.maxTouchPoints > 1); // iPadOS 173 var isMobileOrTablet = isAndroid || isIOS || /Mobile|Tablet/i.test(ua); 174 175 document.querySelectorAll('[data-desktop-href], [data-mobile-href], [data-android-mobile-href], [data-ios-mobile-href]') 176 .forEach(function (el) { 177 var href = el.getAttribute('href') || ''; 178 179 if (isMobileOrTablet) { 180 // Plattform-spezifische Mobile-Links haben Priorität 181 if (isAndroid && el.dataset.androidMobileHref) { 182 href = el.dataset.androidMobileHref; 183 } else if (isIOS && el.dataset.iosMobileHref) { 184 href = el.dataset.iosMobileHref; 185 } else if (el.dataset.mobileHref) { 186 href = el.dataset.mobileHref; 187 } else if (el.dataset.desktopHref) { 188 href = el.dataset.desktopHref; 189 } 190 } else { 191 href = el.dataset.desktopHref || el.dataset.mobileHref || href; 192 } 193 194 if (href) el.setAttribute('href', href); 195 196 // Web-Store-Links im neuen Tab öffnen; native Schemes nicht 197 if (/^https?:\/\//i.test(href)) { 198 el.setAttribute('target', '_blank'); 199 el.setAttribute('rel', 'noopener'); 200 } else { 201 el.removeAttribute('target'); 202 el.removeAttribute('rel'); 203 } 204 205 el.classList.remove('is-hidden'); 206 });
207})(); 208 209 210 211 212(function () { 213 /** 214 * Lottie-Visibility Controller (v1.2) 215 * Startet .lottie-Animationen erst bei tatsächlicher Sichtbarkeit â und 216 * sorgt dafür, dass Header-Lotties *sofort* loslaufen, selbst wenn der Player 217 * beim ersten Versuch noch nicht "ready" war. 218 * 219 * Unterstützt: <dotlottie-player>, <lottie-player> 220 */ 221 function initLottieVisibility(options) { 222 const opts = Object.assign({ 223 selector: 'dotlottie-player, lottie-player', 224 threshold: 0.25, 225 rootMargin: '0px 0px -10% 0px', 226 pauseWhenHidden: true, 227 restartOnShow: true 228 }, options || {}); 229 230 const players = new Set(); 231 let io; 232 233 // --- Utils -------------------------------------------------------------- 234 235 function isActuallyVisible(el) { 236 if (!el.isConnected) return false; 237 const rect = el.getBoundingClientRect(); 238 if (rect.width === 0 || rect.height === 0) return false; 239 for (let n = el; n; n = n.parentElement) { 240 const cs = window.getComputedStyle(n); 241 if (cs.display === 'none' || cs.visibility === 'hidden' || cs.opacity === '0') return false; 242 } 243 return true; 244 } 245 246 function play(el) { if (typeof el.play === 'function') el.play(); else el.dispatchEvent(new Event('play')); } 247 function pause(el) { if (typeof el.pause === 'function') el.pause(); else el.dispatchEvent(new Event('pause')); } 248 function seek0(el) { if (typeof el.seek === 'function') el.seek(0); } 249 250 /** 251 * Startet sicher: wartet auf ready/load und versucht kurzzeitig mehrfach. 252 * Verwendet in: 253 * - Initial-Start (Header sichtbar beim Laden) 254 * - IO-Callback (wird sichtbar) 255 */ 256 function ensurePlay(el, { restart = false } = {}) { 257 if (restart) seek0(el); 258 259 // 1) Sofort versuchen 260 try { play(el); } catch (_) {} 261 262 // 2) Falls noch nicht ready: auf Events warten + kurz nachversuchen 263 const once = { once: true, passive: true }; 264 const startNow = () => { try { play(el); } catch (_) {} }; 265 266 el.addEventListener('ready', startNow, once); 267 el.addEventListener('load', startNow, once); 268 269 // 3) Mini-Retry-Loop (max ~2s), falls Events nicht feuern 270 let tries = 0; 271 const maxTries = 20; 272 const timer = setInterval(() => { 273 tries++; 274 try { play(el); } catch (_) {} 275 if (tries >= maxTries || !el.isConnected) clearInterval(timer); 276 }, 100); 277 } 278 279 // --- Intersection Observer --------------------------------------------- 280 281 io = new IntersectionObserver((entries) => { 282 entries.forEach(entry => { 283 const el = entry.target; 284 const visibleEnough = entry.isIntersecting && entry.intersectionRatio >= opts.threshold; 285 286 if (visibleEnough && isActuallyVisible(el)) { 287 const doRestart = opts.restartOnShow || el.hasAttribute('data-restart-on-show'); 288 ensurePlay(el, { restart: doRestart }); 289 } else if (opts.pauseWhenHidden) { 290 pause(el); 291 } 292 }); 293 }, { threshold: [0, opts.threshold, 1], rootMargin: opts.rootMargin }); 294 295 // --- Mutation Observer (Tabs/Accordions/Dynamic Content) --------------- 296 297 const mo = new MutationObserver((mutations) => { 298 let needsScan = false; 299 for (const m of mutations) { 300 if (m.type === 'childList') { 301 if ((m.addedNodes && m.addedNodes.length) || (m.removedNodes && m.removedNodes.length)) needsScan = true; 302 } 303 if (m.type === 'attributes' && (m.attributeName === 'class' || m.attributeName === 'style' || m.attributeName === 'hidden' || m.attributeName === 'aria-hidden')) { 304 needsScan = true; 305 } 306 } 307 if (needsScan) { 308 rescan(); 309 observeAncestors(); 310 } 311 }); 312 313 // --- Initialer Scan ----------------------------------------------------- 314 315 function rescan() { 316 const vpH = window.innerHeight || document.documentElement.clientHeight; 317 const vpW = window.innerWidth || document.documentElement.clientWidth; 318
319 document.querySelectorAll(opts.selector).forEach(el => { 320 if (players.has(el)) return; 321 322 players.add(el); 323 324 const rect = el.getBoundingClientRect(); 325 const inViewport = rect.bottom > 0 && rect.right > 0 && rect.top < vpH && rect.left < vpW; 326 const actuallyVisible = isActuallyVisible(el); 327 328 if (inViewport && actuallyVisible) { 329 // Header / bereits sichtbare Player â sofort & sicher starten 330 // Autoplay NICHT entfernen 331 requestAnimationFrame(() => ensurePlay(el)); 332 } else { 333 // Unsichtbar beim Laden â wir übernehmen die Kontrolle 334 if (el.hasAttribute('autoplay')) el.removeAttribute('autoplay'); 335 pause(el); 336 } 337 338 io.observe(el); 339 }); 340 } 341 342 function observeAncestors() { 343 players.forEach(el => { 344 let parent = el.parentElement; 345 while (parent) { 346 try { 347 mo.observe(parent, { attributes: true, attributeFilter: ['class', 'style', 'hidden', 'aria-hidden'] }); 348 } catch (_) {} 349 parent = parent.parentElement; 350 } 351 }); 352 } 353 354 // Document-weit auf Strukturänderungen hören 355 mo.observe(document.documentElement, { childList: true, subtree: true }); 356 357 // Erstscan 358 rescan(); 359 observeAncestors(); 360 361 // Fallback: nach vollständigem Laden (Fonts/Bilder) nochmal scannen 362 window.addEventListener('load', () => { 363 rescan(); 364 // Header nochmals "anstupsen", falls sehr früh initialisiert wurde 365 players.forEach(el => { 366 const rect = el.getBoundingClientRect(); 367 const inViewport = 368 rect.bottom > 0 && rect.right > 0 && 369 rect.top < (window.innerHeight || document.documentElement.clientHeight) && 370 rect.left < (window.innerWidth || document.documentElement.clientWidth); 371 if (inViewport && isActuallyVisible(el)) ensurePlay(el); 372 }); 373 }); 374 375 // Bei Resize: Sichtbarkeit kann sich ändern 376 window.addEventListener('resize', () => { 377 players.forEach(el => { 378 const rect = el.getBoundingClientRect(); 379 const inViewport = 380 rect.bottom > 0 && rect.right > 0 && 381 rect.top < (window.innerHeight || document.documentElement.clientHeight) && 382 rect.left < (window.innerWidth || document.documentElement.clientWidth); 383 if (inViewport && isActuallyVisible(el)) { 384 ensurePlay(el); 385 } else if (opts.pauseWhenHidden) { 386 pause(el); 387 } 388 }); 389 }); 390 391 // Public API 392 return { 393 rescan, 394 destroy() { 395 io.disconnect(); 396 mo.disconnect(); 397 players.forEach(pause); 398 players.clear(); 399 } 400 }; 401 } 402 403 // Auto-Init nach DOM Ready 404 if (document.readyState === 'loading') { 405 document.addEventListener('DOMContentLoaded', () => initLottieVisibility()); 406 } else { 407 initLottieVisibility(); 408 } 409 410 // Optional global verfügbar machen 411 window.initLottieVisibility = initLottieVisibility; 412})(); 413 414 415 416 417 418 419(function(){ 420 const triggerRatio = 0.90; // Bildwechsel-Trigger (wie gehabt) 421 const BLEND_PX = 200; 422 const HALF_BLEND = BLEND_PX / 2; 423 424 // Snap-Tuning 425 const DESKTOP_MQ = '(min-width: 991px)'; // nur Desktop 426 const SNAP_COOLDOWN_MS = 350; // Schutz gegen Ping-Pong 427 const SNAP_TOLERANCE_PX = 2; // "schon ausgerichtet?" 428 const SNAP_EARLY_PX = 120; // früher snappen 429 const mm = window.matchMedia(DESKTOP_MQ); 430 431 // --- Globale Sperren/Signale für Anker & User-Input --- 432 function blockSnap(ms=1500){ 433 // Blockiere Snap bis Zeitablauf ODER bis der User wieder scrollt/toucht/tastet 434 window.__snapBlockedHard = true; 435 window.__snapBlockUntil = performance.now() + ms; 436 } 437 function isSnapBlocked(){ 438 const t = performance.now(); 439 if (window.__snapBlockedHard) return true; 440 return (window.__snapBlockUntil && t < window.__snapBlockUntil); 441 } 442 // Sobald der User wieder interagiert, heben wir die harte Blockade auf 443 function clearHardBlock(){ window.__snapBlockedHard = false; } 444 ['wheel','touchstart','keydown'].forEach(ev=>{ 445 window.addEventListener(ev, clearHardBlock, {passive:true}); 446 }); 447 448 // Lottie helpers (optional) 449 const getPlayer = el => el?.querySelector?.('lottie-player, dotlottie-player'); 450 const playItem = el => { try { getPlayer(el)?.play?.(); } catch(e){} }; 451 const pauseItem = el => { try { const p=getPlayer(el); p?.pause?.(); p?.seek?.(0); } catch(e){} }; 452 453 // Hilfsfunktion: ist die Sektion im Viewport (genug sichtbar)? 454 function sectionInView(el, minRatio = 0.15){ 455 const vh = window.innerHeight || document.documentElement.clientHeight; 456 const r = el.getBoundingClientRect(); 457 const visible = Math.max(0, Math.min(r.bottom, vh) - Math.max(r.top, 0)); 458 const ratio = r.height > 0 ? visible / r.height : 0; 459 return ratio >= minRatio; 460 } 461 462 function initOne(root){ 463 const stack = root.querySelector('.img-stack'); 464 const steps = Array.from(root.querySelectorAll('.swap-step')); 465 if (!stack || steps.length === 0) return; 466 467 // Slides (Bilder/Lottie) 468 let items = Array.from(stack.querySelectorAll('.stack-item')); 469 if (!items.length) items = Array.from(stack.querySelectorAll('figure.wp-block-image'));
470 if (!items.length) return; 471 472 const n = Math.min(items.length, steps.length); 473 const cards = items.slice(0, n); 474 const stepps = steps.slice(0, n); 475 476 // Start: erstes Bild voll sichtbar 477 cards.forEach(c => { c.style.opacity = 0; c.style.zIndex = 1; }); 478 cards[0].style.opacity = 1; cards[0].style.zIndex = 200; 479 480 let lastPlaying = 0; 481 playItem(cards[0]); 482 483 // Sticky-Offset aus CSS lesen (Fallback 250) 484 const stickyTop = parseFloat(getComputedStyle(root).getPropertyValue('--sticky-top')) || 250; 485 486 // Snap-Status 487 let lastScrollY = window.scrollY; 488 let snapping = false; 489 let snapCooldown = false; 490 let lastSnappedIndex = 0; 491 492 // Zentren der Steps 493 const centers = () => stepps.map(s => { 494 const r = s.getBoundingClientRect(); 495 return r.top + r.height / 2; 496 }); 497 498 function snapToIndex(idx){ 499 if (!mm.matches) return; 500 if (idx < 0 || idx >= stepps.length) return; 501 502 const rectTop = stepps[idx].getBoundingClientRect().top; 503 const delta = rectTop - stickyTop; 504 if (Math.abs(delta) <= SNAP_TOLERANCE_PX) return; // quasi schon ausgerichtet 505 506 snapping = true; 507 window.scrollTo({ top: window.scrollY + delta, behavior: 'smooth' }); 508 509 // Cooldown, damit wir nicht direkt wieder snappen 510 snapCooldown = true; 511 setTimeout(()=> { snapCooldown = false; snapping = false; }, SNAP_COOLDOWN_MS); 512 lastSnappedIndex = idx; 513 } 514 515 let ticking = false; 516 function update(){ 517 const vh = window.innerHeight || document.documentElement.clientHeight; 518 const triggerY = vh * triggerRatio; 519 const c = centers(); 520 521 const dirDown = window.scrollY > lastScrollY; 522 const dirUp = window.scrollY < lastScrollY; 523 524 // Segment [i, i+1] finden 525 let i = -1; 526 for (let k = 0; k < c.length - 1; k++){ 527 if (c[k] <= triggerY && triggerY < c[k+1]) { i = k; break; } 528 } 529 530 // Ränder: hart zeigen + ggf. snappen (nur wenn die Sektion sichtbar ist) 531 if (i === -1){ 532 const show = (triggerY < c[0]) ? 0 : n-1; 533 534 for (let k=0;k<n;k++){ 535 const on = (k===show); 536 cards[k].style.opacity = on?1:0; 537 cards[k].style.zIndex = on?200:1; 538 } 539 if (lastPlaying !== show){ pauseItem(cards[lastPlaying]); playItem(cards[show]); lastPlaying = show; } 540 541 if (mm.matches && !snapping && !snapCooldown && !isSnapBlocked() && sectionInView(root)){ 542 if ((dirDown || dirUp) && show !== lastSnappedIndex){ 543 snapToIndex(show); 544 } 545 } 546 return; 547 } 548 549 // Crossfade zwischen i und i+1 (wie gehabt) 550 const mid = (c[i] + c[i+1]) / 2; 551 const dist = triggerY - mid; // <0: näher i, >0: näher i+1 552 553 let a = 1, b = 0; 554 if (dist <= -HALF_BLEND){ a = 1; b = 0; } 555 else if (dist >= HALF_BLEND){ a = 0; b = 1; } 556 else { 557 const t = (dist + HALF_BLEND) / BLEND_PX; // 0..1 558 a = 1 - t; b = t; 559 } 560 561 for (let k=0;k<n;k++){ 562 let op = 0, z = 1; 563 if (k === i) { op = a; z = 100 + Math.round(a*100); } 564 if (k === i + 1) { op = b; z = 100 + Math.round(b*100); } 565 cards[k].style.opacity = op; 566 cards[k].style.zIndex = z; 567 } 568 569 const playIdx = (b > a) ? (i+1) : i; 570 if (playIdx !== lastPlaying){ pauseItem(cards[lastPlaying]); playItem(cards[playIdx]); lastPlaying = playIdx; } 571 572 // --- Snap-Logik in beide Richtungen --- 573 if (mm.matches && !snapping && !snapCooldown && !isSnapBlocked() && sectionInView(root)){ 574 if (dirDown && dist >= -SNAP_EARLY_PX){ 575 const targetIdx = i + 1; 576 if (targetIdx !== lastSnappedIndex) snapToIndex(targetIdx); 577 } else if (dirUp && dist <= SNAP_EARLY_PX){ 578 const targetIdx = i; 579 if (targetIdx !== lastSnappedIndex) snapToIndex(targetIdx); 580 } 581 } 582 } 583 584 function onScroll(){ 585 if (!ticking){ 586 ticking = true; 587 requestAnimationFrame(()=>{ 588 update(); 589 lastScrollY = window.scrollY; 590 ticking = false; 591 }); 592 } 593 } 594 595 // Initial mehrfach (Layout/Fonts) 596 update(); 597 setTimeout(update, 120); 598 setTimeout(update, 400); 599 600 window.addEventListener('scroll', onScroll, { passive: true });
601 window.addEventListener('resize', onScroll, { passive: true }); 602 new ResizeObserver(onScroll).observe(root); 603 } 604 605 function boot(){ document.querySelectorAll('.sticky-test').forEach(initOne); } 606 if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', boot); 607 else boot(); 608 609 // ====== Anker-Handling: Offset + Snap blocken, bis User wieder interagiert ====== 610 function getStickyTopPx(){ 611 const host = document.querySelector('.sticky-test'); 612 if (!host) return 250; 613 const v = getComputedStyle(host).getPropertyValue('--sticky-top'); 614 const n = parseFloat(v); 615 return Number.isFinite(n) ? n : 250; 616 } 617 function scrollToAnchorTarget(target){ 618 if (!target) return; 619 const stickyTop = getStickyTopPx(); 620 const topAbs = target.getBoundingClientRect().top + window.scrollY; 621 const y = Math.max(0, topAbs - stickyTop - 12); // kleine Luft 622 blockSnap(2000); // blockiere Snap länger â wird bei User-Input aufgehoben 623 window.scrollTo({ top: y, behavior: 'smooth' }); 624 } 625 626 // Klicks auf #anker abfangen (WP-Menüs, Inhaltsverzeichnis, etc.) 627 document.addEventListener('click', function(e){ 628 const a = e.target.closest('a[href^="#"]:not([href="#"])'); 629 if (!a) return; 630 const id = decodeURIComponent(a.getAttribute('href').slice(1)); 631 const target = document.getElementById(id) || document.querySelector(`[id="${CSS.escape(id)}"]`); 632 if (!target) return; 633 e.preventDefault(); 634 if (history.pushState) history.pushState(null, '', '#' + id); 635 else location.hash = '#' + id; 636 scrollToAnchorTarget(target); 637 }, { passive: false }); 638 639 // Beim Laden mit Hash korrekt versetzen 640 function handleInitialHash(){ 641 if (!location.hash) return; 642 const id = decodeURIComponent(location.hash.slice(1)); 643 const target = document.getElementById(id) || document.querySelector(`[id="${CSS.escape(id)}"]`); 644 if (!target) return; 645 // kurz warten, dann versetzt scrollen 646 setTimeout(()=> scrollToAnchorTarget(target), 60); 647 } 648 if (document.readyState === 'loading'){ 649 document.addEventListener('DOMContentLoaded', handleInitialHash); 650 } else { 651 handleInitialHash(); 652 } 653 654})();
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.