PageSourceSearch

https://www.dirs21.de/wp-content/themes/dirs21/js/global.js?ver=1.0.5.1790237686

js dirs21.de collected 2026-09-24 10:06:37 UTC 23,416 bytes, 654 lines download raw bytes

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.