// Mobile Navigation und alle Scrollanimationen aus TriHub Design Original. "use strict"; (() => { const menu = document.querySelector(".menu-toggle"), nav = document.querySelector("#mobile-nav"); menu.addEventListener("click", () => { const open = menu.getAttribute("aria-expanded") !== "true"; menu.setAttribute("aria-expanded", String(open)); menu.setAttribute("aria-label", open ? "Menü schließen" : "Menü öffnen"); nav.hidden = !open; }); nav.querySelectorAll("a").forEach((a) => a.addEventListener("click", () => { nav.hidden = true; menu.setAttribute("aria-expanded", "false"); menu.setAttribute("aria-label", "Menü öffnen"); }), ); // Progressive enhancement: content stays visible without JS or animation support. (() => { const reducedMotion = window.matchMedia("(prefers-reduced-motion: reduce)"); if (!("IntersectionObserver" in window) || !Element.prototype.animate) return; const running = new Map(); const played = new WeakSet(); const delays = new WeakMap(); const targets = [ ...document.querySelectorAll( ".hero-copy > *, .hero-visual, .section-heading, .benefit-grid > article, " + ".concept-note, .package-grid > article, .coaching-banner, .steps > li, .process-end, .detail-benefits > article, .detail-timeline > li, .price-panel", ), ]; document .querySelectorAll( ".hero-copy, .benefit-grid, .package-grid, .steps, .detail-benefits, .detail-timeline", ) .forEach((group) => { [...group.children].forEach((child, index) => delays.set(child, Math.min(index * 80, 240)), ); }); function reveal(element) { if (played.has(element)) return; played.add(element); if (reducedMotion.matches) return; const animation = element.animate( [ { opacity: 0, transform: "translateY(24px)" }, { opacity: 1, transform: "translateY(0)" }, ], { duration: 720, delay: delays.get(element) || 0, easing: "cubic-bezier(.22, 1, .36, 1)", fill: "backwards", }, ); running.set(element, animation); const cleanup = () => { if (running.get(element) === animation) running.delete(element); }; animation.onfinish = cleanup; animation.oncancel = cleanup; } const observer = new IntersectionObserver( (entries) => { entries.forEach((entry) => { if (!entry.isIntersecting) return; observer.unobserve(entry.target); // A direct anchor jump must not animate already-scrolled-past content. if (entry.boundingClientRect.bottom > 0) reveal(entry.target); }); }, { threshold: 0, rootMargin: "0px 0px -24px 0px" }, ); targets.forEach((element) => observer.observe(element)); // Keyboard users never wait for a focused control to become visible. document.addEventListener("focusin", (event) => { running.forEach((animation, element) => { if (element.contains(event.target)) animation.cancel(); }); }); reducedMotion.addEventListener("change", () => { if (reducedMotion.matches) running.forEach((animation) => animation.cancel()); }); })(); })();