document.addEventListener('DOMContentLoaded', function() { function waitForSplitType(callback) { if (typeof SplitType !== 'undefined') { callback(); } else { setTimeout(() => waitForSplitType(callback), 100); } } waitForSplitType(function() { gsap.registerPlugin(ScrollTrigger); ScrollTrigger.normalizeScroll({ normalizeScrollX: false }); gsap.config({ nullTargetWarn: false }); let _scrollTweens = []; let fadeUpWordSplit = new SplitType('[data-anim="fade-up-word"]:not([data-anim-container] [data-anim="fade-up-word"]), [data-anim="fade-up-word-p"]:not([data-anim-container] [data-anim="fade-up-word-p"])', { types: 'lines, words' }); let slideUpCharSplit = new SplitType('[data-anim="slide-up-char"]:not([data-anim-container] [data-anim="slide-up-char"]), [data-anim="slide-up-char-p"]:not([data-anim-container] [data-anim="slide-up-char-p"])', { types: 'lines, words, chars' }); let scrollFadeUpWordSplit = new SplitType('[data-anim="scroll-fade-up-word"], [data-anim="scroll-fade-up-word-p"]', { types: 'lines, words' }); let scrollSlideUpCharSplit = new SplitType('[data-anim="scroll-slide-up-char"], [data-anim="scroll-slide-up-char-p"]', { types: 'lines, words, chars' }); let fadeUpLineSplit = new SplitType('[data-anim="fade-up-line"]', { types: 'lines' }); let scrollFadeUpLineSplit = new SplitType('[data-anim="scroll-fade-up-line"]', { types: 'lines' }); document.querySelectorAll('[data-anim="fade-up-word"]').forEach(el => { const customDelay = el.getAttribute('data-anim-delay') ? parseFloat(el.getAttribute('data-anim-delay')) / 1000 : 0; gsap.set(el.querySelectorAll('.word'), { y: 50, autoAlpha: 0, force3D: true, willChange: 'transform, opacity' }); const tl = gsap.timeline({ delay: customDelay }); tl.to(el.querySelectorAll('.word'), { y: 0, autoAlpha: 1, duration: 1, ease: 'cubic-bezier(.27, 1, .48, 1)', stagger: 0.08, force3D: true, clearProps: 'willChange' }, 0); }); document.querySelectorAll('[data-anim="fade-up-word-p"]').forEach(el => { const customDelay = el.getAttribute('data-anim-delay') ? parseFloat(el.getAttribute('data-anim-delay')) / 1000 : 0; const finalDelay = 0.3 + customDelay; gsap.set(el.querySelectorAll('.word'), { y: 30, autoAlpha: 0, force3D: true, willChange: 'transform, opacity' }); const tl = gsap.timeline({ delay: finalDelay }); tl.to(el.querySelectorAll('.word'), { y: 0, autoAlpha: 1, duration: 0.8, ease: 'cubic-bezier(.27, 1, .48, 1)', stagger: 0.04, force3D: true, clearProps: 'willChange' }, 0); }); document.querySelectorAll('[data-anim="slide-up-char"]').forEach(el => { const customDelay = el.getAttribute('data-anim-delay') ? parseFloat(el.getAttribute('data-anim-delay')) / 1000 : 0; const finalDelay = 0.5 + customDelay; gsap.set(el.querySelectorAll('.char'), { y: '120%', autoAlpha: 0, force3D: true, willChange: 'transform, opacity' }); const tl = gsap.timeline({ delay: finalDelay }); tl.to(el.querySelectorAll('.char'), { y: '0%', autoAlpha: 1, duration: 0.8, ease: 'cubic-bezier(.34, 1, .64, 1)', stagger: 0.02, force3D: true, clearProps: 'willChange' }, 0); }); document.querySelectorAll('[data-anim="slide-up-char-p"]').forEach(el => { const customDelay = el.getAttribute('data-anim-delay') ? parseFloat(el.getAttribute('data-anim-delay')) / 1000 : 0; const finalDelay = 0.8 + customDelay; gsap.set(el.querySelectorAll('.char'), { y: '100%', autoAlpha: 0, force3D: true, willChange: 'transform, opacity' }); const tl = gsap.timeline({ delay: finalDelay }); tl.to(el.querySelectorAll('.char'), { y: '0%', autoAlpha: 1, duration: 0.6, ease: 'cubic-bezier(.34, 1, .64, 1)', stagger: 0.01, force3D: true, clearProps: 'willChange' }, 0); }); document.querySelectorAll('[data-anim="scroll-fade-up-word"]').forEach(el => { const _t = gsap.from(el.querySelectorAll('.word'), { y: 50, autoAlpha: 0, duration: 1, ease: 'cubic-bezier(.27, 1, .48, 1)', stagger: 0.08, force3D: true, onComplete: () => el.setAttribute('data-anim-done', 'true'), scrollTrigger: { trigger: el, start: 'top 80%', end: 'top 20%', markers: false, }, clearProps: 'all' }); _scrollTweens.push(_t); }); document.querySelectorAll('[data-anim="scroll-fade-up-word-p"]').forEach(el => { const _t = gsap.from(el.querySelectorAll('.word'), { y: 30, autoAlpha: 0, duration: 0.8, ease: 'cubic-bezier(.27, 1, .48, 1)', stagger: 0.04, delay: 0.3, force3D: true, onComplete: () => el.setAttribute('data-anim-done', 'true'), scrollTrigger: { trigger: el, start: 'top 80%', end: 'top 20%', markers: false, }, clearProps: 'all' }); _scrollTweens.push(_t); }); document.querySelectorAll('[data-anim="scroll-slide-up-char"]').forEach(el => { const _t = gsap.from(el.querySelectorAll('.char'), { y: '120%', autoAlpha: 0, duration: 0.8, ease: 'cubic-bezier(.34, 1, .64, 1)', stagger: 0.02, delay: 0.5, force3D: true, onComplete: () => el.setAttribute('data-anim-done', 'true'), scrollTrigger: { trigger: el, start: 'top 80%', end: 'top 20%', markers: false, }, clearProps: 'all' }); _scrollTweens.push(_t); }); document.querySelectorAll('[data-anim="scroll-slide-up-char-p"]').forEach(el => { const _t = gsap.from(el.querySelectorAll('.char'), { y: '100%', autoAlpha: 0, duration: 0.6, ease: 'cubic-bezier(.34, 1, .64, 1)', stagger: 0.01, delay: 0.8, force3D: true, onComplete: () => el.setAttribute('data-anim-done', 'true'), scrollTrigger: { trigger: el, start: 'top 80%', end: 'top 20%', markers: false, }, clearProps: 'all' }); _scrollTweens.push(_t); }); document.querySelectorAll('[data-anim="fade-up-line"]').forEach(el => { const customDelay = el.getAttribute('data-anim-delay') ? parseFloat(el.getAttribute('data-anim-delay')) / 1000 : 0; gsap.set(el.querySelectorAll('.line'), { y: 50, autoAlpha: 0, force3D: true, willChange: 'transform, opacity' }); const tl = gsap.timeline({ delay: customDelay }); tl.to(el.querySelectorAll('.line'), { y: 0, autoAlpha: 1, duration: 0.8, ease: 'cubic-bezier(.27, 1, .48, 1)', stagger: 0.12, force3D: true, clearProps: 'willChange' }, 0); }); document.querySelectorAll('[data-anim="scroll-fade-up-line"]').forEach(el => { const _t = gsap.from(el.querySelectorAll('.line'), { y: 50, autoAlpha: 0, duration: 0.8, ease: 'cubic-bezier(.27, 1, .48, 1)', stagger: 0.12, force3D: true, onComplete: () => el.setAttribute('data-anim-done', 'true'), scrollTrigger: { trigger: el, start: 'top 80%', end: 'top 20%', markers: false, }, clearProps: 'all' }); _scrollTweens.push(_t); }); document.querySelectorAll('[data-anim="slide-up"]:not([data-anim-container] [data-anim="slide-up"])').forEach(el => { const customDelay = el.getAttribute('data-anim-delay') ? parseFloat(el.getAttribute('data-anim-delay')) / 1000 : 0; gsap.from(el, { y: 60, autoAlpha: 0, duration: 0.8, ease: 'cubic-bezier(.27, 1, .48, 1)', delay: customDelay, force3D: true, scrollTrigger: { trigger: el, start: 'top 80%', markers: false, }, clearProps: 'all' }); }); document.querySelectorAll('[data-anim="fade-up"]:not([data-anim-container] [data-anim="fade-up"])').forEach(el => { const customDelay = el.getAttribute('data-anim-delay') ? parseFloat(el.getAttribute('data-anim-delay')) / 1000 : 0; gsap.from(el, { y: 30, autoAlpha: 0, duration: 0.8, ease: 'cubic-bezier(.27, 1, .48, 1)', delay: customDelay, force3D: true, scrollTrigger: { trigger: el, start: 'top 80%', markers: false, }, clearProps: 'all' }); }); document.querySelectorAll('[data-anim="scale-up"]:not([data-anim-container] [data-anim="scale-up"])').forEach(el => { const customDelay = el.getAttribute('data-anim-delay') ? parseFloat(el.getAttribute('data-anim-delay')) / 1000 : 0; gsap.from(el, { scale: 0.8, autoAlpha: 0, duration: 0.8, ease: 'cubic-bezier(.34, 1, .64, 1)', delay: customDelay, force3D: true, scrollTrigger: { trigger: el, start: 'top 80%', markers: false, }, clearProps: 'all' }); }); document.querySelectorAll('[data-anim="slide-left"]:not([data-anim-container] [data-anim="slide-left"])').forEach(el => { const customDelay = el.getAttribute('data-anim-delay') ? parseFloat(el.getAttribute('data-anim-delay')) / 1000 : 0; gsap.from(el, { x: -60, autoAlpha: 0, duration: 0.8, ease: 'cubic-bezier(.27, 1, .48, 1)', delay: customDelay, force3D: true, scrollTrigger: { trigger: el, start: 'top 80%', markers: false, }, clearProps: 'all' }); }); document.querySelectorAll('[data-anim="slide-right"]:not([data-anim-container] [data-anim="slide-right"])').forEach(el => { const customDelay = el.getAttribute('data-anim-delay') ? parseFloat(el.getAttribute('data-anim-delay')) / 1000 : 0; gsap.from(el, { x: 60, autoAlpha: 0, duration: 0.8, ease: 'cubic-bezier(.27, 1, .48, 1)', delay: customDelay, force3D: true, scrollTrigger: { trigger: el, start: 'top 80%', markers: false, }, clearProps: 'all' }); }); const loadContainers = document.querySelectorAll('[data-anim-container="load"]'); loadContainers.forEach(container => { const animatedElements = container.querySelectorAll('[data-anim]'); if (animatedElements.length === 0) return; animatedElements.forEach(el => { const animType = el.getAttribute('data-anim'); if (animType.includes('fade-up-word')) { new SplitType(el, { types: 'lines, words' }); } else if (animType.includes('slide-up-char')) { new SplitType(el, { types: 'lines, words, chars' }); } else if (animType.includes('fade-up-line')) { new SplitType(el, { types: 'lines' }); } }); const tl = gsap.timeline(); animatedElements.forEach((el, index) => { const animType = el.getAttribute('data-anim'); const customDelay = el.getAttribute('data-anim-delay'); const elementDelay = index * 0.4 + (customDelay ? parseFloat(customDelay) / 1000 : 0); if (animType === 'fade-up-word') { gsap.set(el.querySelectorAll('.word'), { y: 50, autoAlpha: 0, force3D: true, willChange: 'transform, opacity' }); tl.to(el.querySelectorAll('.word'), { y: 0, autoAlpha: 1, duration: 1, ease: 'cubic-bezier(.27, 1, .48, 1)', stagger: 0.08, force3D: true, clearProps: 'willChange' }, elementDelay); } else if (animType === 'fade-up-word-p') { gsap.set(el.querySelectorAll('.word'), { y: 30, autoAlpha: 0, force3D: true, willChange: 'transform, opacity' }); tl.to(el.querySelectorAll('.word'), { y: 0, autoAlpha: 1, duration: 0.8, ease: 'cubic-bezier(.27, 1, .48, 1)', stagger: 0.04, force3D: true, clearProps: 'willChange' }, elementDelay); } else if (animType === 'slide-up-char') { gsap.set(el.querySelectorAll('.char'), { y: '120%', autoAlpha: 0, force3D: true, willChange: 'transform, opacity' }); tl.to(el.querySelectorAll('.char'), { y: '0%', autoAlpha: 1, duration: 0.8, ease: 'cubic-bezier(.34, 1, .64, 1)', stagger: 0.02, force3D: true, clearProps: 'willChange' }, elementDelay); } else if (animType === 'slide-up-char-p') { gsap.set(el.querySelectorAll('.char'), { y: '100%', autoAlpha: 0, force3D: true, willChange: 'transform, opacity' }); tl.to(el.querySelectorAll('.char'), { y: '0%', autoAlpha: 1, duration: 0.6, ease: 'cubic-bezier(.34, 1, .64, 1)', stagger: 0.01, force3D: true, clearProps: 'willChange' }, elementDelay); } else if (animType === 'fade-up-line') { gsap.set(el.querySelectorAll('.line'), { y: 50, autoAlpha: 0, force3D: true, willChange: 'transform, opacity' }); tl.to(el.querySelectorAll('.line'), { y: 0, autoAlpha: 1, duration: 0.8, ease: 'cubic-bezier(.27, 1, .48, 1)', stagger: 0.12, force3D: true, clearProps: 'willChange' }, elementDelay); } else if (animType === 'fade-up') { gsap.set(el, { y: 30, autoAlpha: 0, force3D: true, willChange: 'transform, opacity' }); tl.to(el, { y: 0, autoAlpha: 1, duration: 0.8, ease: 'cubic-bezier(.27, 1, .48, 1)', force3D: true, clearProps: 'willChange' }, elementDelay); } else if (animType === 'slide-up') { gsap.set(el, { y: 60, autoAlpha: 0, force3D: true, willChange: 'transform, opacity' }); tl.to(el, { y: 0, autoAlpha: 1, duration: 0.8, ease: 'cubic-bezier(.27, 1, .48, 1)', force3D: true, clearProps: 'willChange' }, elementDelay); } else if (animType === 'scale-up') { gsap.set(el, { scale: 0.8, autoAlpha: 0, force3D: true, willChange: 'transform, opacity' }); tl.to(el, { scale: 1, autoAlpha: 1, duration: 0.8, ease: 'cubic-bezier(.34, 1, .64, 1)', force3D: true, clearProps: 'willChange' }, elementDelay); } else if (animType === 'slide-left') { gsap.set(el, { x: -60, autoAlpha: 0, force3D: true, willChange: 'transform, opacity' }); tl.to(el, { x: 0, autoAlpha: 1, duration: 0.8, ease: 'cubic-bezier(.27, 1, .48, 1)', force3D: true, clearProps: 'willChange' }, elementDelay); } else if (animType === 'slide-right') { gsap.set(el, { x: 60, autoAlpha: 0, force3D: true, willChange: 'transform, opacity' }); tl.to(el, { x: 0, autoAlpha: 1, duration: 0.8, ease: 'cubic-bezier(.27, 1, .48, 1)', force3D: true, clearProps: 'willChange' }, elementDelay); } }); }); // ---- RESIZE HANDLER ---- // Must be inside waitForSplitType so split vars + _scrollTweens are in scope let _splitResizeTimer; let _lastSplitWidth = window.innerWidth; function _handleSplitResize() { const newWidth = window.innerWidth; if (newWidth === _lastSplitWidth) return; _lastSplitWidth = newWidth; // Kill all tracked scroll text tweens + their ScrollTriggers _scrollTweens.forEach(t => { if (t && t.scrollTrigger) t.scrollTrigger.kill(); if (t) t.kill(); }); _scrollTweens = []; // Revert + re-split LOAD animations (already done — naturally visible after re-split) fadeUpWordSplit.revert(); fadeUpWordSplit = new SplitType('[data-anim="fade-up-word"]:not([data-anim-container] [data-anim="fade-up-word"]), [data-anim="fade-up-word-p"]:not([data-anim-container] [data-anim="fade-up-word-p"])', { types: 'lines, words' }); slideUpCharSplit.revert(); slideUpCharSplit = new SplitType('[data-anim="slide-up-char"]:not([data-anim-container] [data-anim="slide-up-char"]), [data-anim="slide-up-char-p"]:not([data-anim-container] [data-anim="slide-up-char-p"])', { types: 'lines, words, chars' }); fadeUpLineSplit.revert(); fadeUpLineSplit = new SplitType('[data-anim="fade-up-line"]', { types: 'lines' }); // Revert + re-split SCROLL animations, recreate tweens only for not-yet-triggered elements scrollFadeUpWordSplit.revert(); scrollFadeUpWordSplit = new SplitType('[data-anim="scroll-fade-up-word"], [data-anim="scroll-fade-up-word-p"]', { types: 'lines, words' }); document.querySelectorAll('[data-anim="scroll-fade-up-word"]').forEach(el => { if (el.hasAttribute('data-anim-done')) return; const _t = gsap.from(el.querySelectorAll('.word'), { y: 50, autoAlpha: 0, duration: 1, ease: 'cubic-bezier(.27, 1, .48, 1)', stagger: 0.08, force3D: true, onComplete: () => el.setAttribute('data-anim-done', 'true'), scrollTrigger: { trigger: el, start: 'top 80%', end: 'top 20%', markers: false }, clearProps: 'all' }); _scrollTweens.push(_t); }); document.querySelectorAll('[data-anim="scroll-fade-up-word-p"]').forEach(el => { if (el.hasAttribute('data-anim-done')) return; const _t = gsap.from(el.querySelectorAll('.word'), { y: 30, autoAlpha: 0, duration: 0.8, ease: 'cubic-bezier(.27, 1, .48, 1)', stagger: 0.04, delay: 0.3, force3D: true, onComplete: () => el.setAttribute('data-anim-done', 'true'), scrollTrigger: { trigger: el, start: 'top 80%', end: 'top 20%', markers: false }, clearProps: 'all' }); _scrollTweens.push(_t); }); scrollSlideUpCharSplit.revert(); scrollSlideUpCharSplit = new SplitType('[data-anim="scroll-slide-up-char"], [data-anim="scroll-slide-up-char-p"]', { types: 'lines, words, chars' }); document.querySelectorAll('[data-anim="scroll-slide-up-char"]').forEach(el => { if (el.hasAttribute('data-anim-done')) return; const _t = gsap.from(el.querySelectorAll('.char'), { y: '120%', autoAlpha: 0, duration: 0.8, ease: 'cubic-bezier(.34, 1, .64, 1)', stagger: 0.02, delay: 0.5, force3D: true, onComplete: () => el.setAttribute('data-anim-done', 'true'), scrollTrigger: { trigger: el, start: 'top 80%', end: 'top 20%', markers: false }, clearProps: 'all' }); _scrollTweens.push(_t); }); document.querySelectorAll('[data-anim="scroll-slide-up-char-p"]').forEach(el => { if (el.hasAttribute('data-anim-done')) return; const _t = gsap.from(el.querySelectorAll('.char'), { y: '100%', autoAlpha: 0, duration: 0.6, ease: 'cubic-bezier(.34, 1, .64, 1)', stagger: 0.01, delay: 0.8, force3D: true, onComplete: () => el.setAttribute('data-anim-done', 'true'), scrollTrigger: { trigger: el, start: 'top 80%', end: 'top 20%', markers: false }, clearProps: 'all' }); _scrollTweens.push(_t); }); scrollFadeUpLineSplit.revert(); scrollFadeUpLineSplit = new SplitType('[data-anim="scroll-fade-up-line"]', { types: 'lines' }); document.querySelectorAll('[data-anim="scroll-fade-up-line"]').forEach(el => { if (el.hasAttribute('data-anim-done')) return; const _t = gsap.from(el.querySelectorAll('.line'), { y: 50, autoAlpha: 0, duration: 0.8, ease: 'cubic-bezier(.27, 1, .48, 1)', stagger: 0.12, force3D: true, onComplete: () => el.setAttribute('data-anim-done', 'true'), scrollTrigger: { trigger: el, start: 'top 80%', end: 'top 20%', markers: false }, clearProps: 'all' }); _scrollTweens.push(_t); }); ScrollTrigger.refresh(); } window.addEventListener('resize', () => { clearTimeout(_splitResizeTimer); _splitResizeTimer = setTimeout(_handleSplitResize, 150); }); }); window.replayFadeUpWord = function() { gsap.fromTo('[data-anim="fade-up-word"] .word', { y: 50, autoAlpha: 0 }, { y: 0, autoAlpha: 1, duration: 1, ease: 'cubic-bezier(.27, 1, .48, 1)', stagger: 0.08, force3D: true }); gsap.fromTo('[data-anim="fade-up-word-p"] .word', { y: 30, autoAlpha: 0 }, { y: 0, autoAlpha: 1, duration: 0.8, ease: 'cubic-bezier(.27, 1, .48, 1)', stagger: 0.04, delay: 0.3, force3D: true }); }; window.replaySlideUpChar = function() { gsap.fromTo('[data-anim="slide-up-char"] .char', { y: '120%', autoAlpha: 0 }, { y: '0%', autoAlpha: 1, duration: 0.8, ease: 'cubic-bezier(.34, 1, .64, 1)', stagger: 0.02, force3D: true }); gsap.fromTo('[data-anim="slide-up-char-p"] .char', { y: '100%', autoAlpha: 0 }, { y: '0%', autoAlpha: 1, duration: 0.6, ease: 'cubic-bezier(.34, 1, .64, 1)', stagger: 0.01, delay: 0.3, force3D: true }); }; window.replayScrollFadeUpWord = function() { gsap.fromTo('[data-anim="scroll-fade-up-word"] .word', { y: 50, autoAlpha: 0 }, { y: 0, autoAlpha: 1, duration: 1, ease: 'cubic-bezier(.27, 1, .48, 1)', stagger: 0.08, force3D: true }); gsap.fromTo('[data-anim="scroll-fade-up-word-p"] .word', { y: 30, autoAlpha: 0 }, { y: 0, autoAlpha: 1, duration: 0.8, ease: 'cubic-bezier(.27, 1, .48, 1)', stagger: 0.04, delay: 0.3, force3D: true }); }; window.replayScrollSlideUpChar = function() { gsap.fromTo('[data-anim="scroll-slide-up-char"] .char', { y: '120%', autoAlpha: 0 }, { y: '0%', autoAlpha: 1, duration: 0.8, ease: 'cubic-bezier(.34, 1, .64, 1)', stagger: 0.02, force3D: true }); gsap.fromTo('[data-anim="scroll-slide-up-char-p"] .char', { y: '100%', autoAlpha: 0 }, { y: '0%', autoAlpha: 1, duration: 0.6, ease: 'cubic-bezier(.34, 1, .64, 1)', stagger: 0.01, delay: 0.3, force3D: true }); }; window.replayFadeUpLine = function() { gsap.fromTo('[data-anim="fade-up-line"] .line', { y: 50, autoAlpha: 0 }, { y: 0, autoAlpha: 1, duration: 0.8, ease: 'cubic-bezier(.27, 1, .48, 1)', stagger: 0.12, force3D: true }); }; window.replayScrollFadeUpLine = function() { gsap.fromTo('[data-anim="scroll-fade-up-line"] .line', { y: 50, autoAlpha: 0 }, { y: 0, autoAlpha: 1, duration: 0.8, ease: 'cubic-bezier(.27, 1, .48, 1)', stagger: 0.12, force3D: true }); }; const containers = document.querySelectorAll('[data-anim-container="scroll"]'); containers.forEach(container => { gsap.set(container, { autoAlpha: 0 }); }); const scrollContainerObserver = new IntersectionObserver((entries) => { entries.forEach(entry => { if (entry.isIntersecting) { const container = entry.target; const animatedElements = container.querySelectorAll('[data-anim]'); if (animatedElements.length === 0) return; animatedElements.forEach(el => { const animType = el.getAttribute('data-anim'); if (animType.includes('fade-up-word')) { new SplitType(el, { types: 'lines, words' }); } else if (animType.includes('slide-up-char')) { new SplitType(el, { types: 'lines, words, chars' }); } else if (animType.includes('fade-up-line')) { new SplitType(el, { types: 'lines' }); } }); const tl = gsap.timeline({ onStart: () => { gsap.set(container, { autoAlpha: 1 }); } }); animatedElements.forEach((el, index) => { const animType = el.getAttribute('data-anim'); const customDelay = el.getAttribute('data-anim-delay'); const elementDelay = index * 0.2 + (customDelay ? parseFloat(customDelay) / 1000 : 0); if (animType.includes('fade-up-word')) { tl.from(el.querySelectorAll('.word'), { y: 50, autoAlpha: 0, duration: 1, ease: 'cubic-bezier(.27, 1, .48, 1)', stagger: 0.08, force3D: true, clearProps: 'all' }, elementDelay); } else if (animType.includes('slide-up-char')) { tl.from(el.querySelectorAll('.char'), { y: '120%', autoAlpha: 0, duration: 0.8, ease: 'cubic-bezier(.34, 1, .64, 1)', stagger: 0.02, force3D: true, clearProps: 'all' }, elementDelay); } else if (animType.includes('fade-up-line')) { tl.from(el.querySelectorAll('.line'), { y: 50, autoAlpha: 0, duration: 0.8, ease: 'cubic-bezier(.27, 1, .48, 1)', stagger: 0.12, force3D: true, clearProps: 'all' }, elementDelay); } else if (animType === 'slide-up') { tl.from(el, { y: 60, autoAlpha: 0, duration: 0.8, ease: 'cubic-bezier(.27, 1, .48, 1)', force3D: true, clearProps: 'all' }, elementDelay); } else if (animType === 'fade-up') { tl.from(el, { y: 30, autoAlpha: 0, duration: 0.8, ease: 'cubic-bezier(.27, 1, .48, 1)', force3D: true, clearProps: 'all' }, elementDelay); } else if (animType === 'scale-up') { tl.from(el, { scale: 0.8, autoAlpha: 0, duration: 0.8, ease: 'cubic-bezier(.34, 1, .64, 1)', force3D: true, clearProps: 'all' }, elementDelay); } else if (animType === 'slide-left') { tl.from(el, { x: -60, autoAlpha: 0, duration: 0.8, ease: 'cubic-bezier(.27, 1, .48, 1)', force3D: true, clearProps: 'all' }, elementDelay); } else if (animType === 'slide-right') { tl.from(el, { x: 60, autoAlpha: 0, duration: 0.8, ease: 'cubic-bezier(.27, 1, .48, 1)', force3D: true, clearProps: 'all' }, elementDelay); } }); scrollContainerObserver.unobserve(container); } }); }, { threshold: 0.2, rootMargin: '0px 0px -20% 0px' }); containers.forEach(container => { scrollContainerObserver.observe(container); }); function initScrollDirectionMarquee() { const marqueeElements = document.querySelectorAll('[data-marquee-scroll]'); marqueeElements.forEach((element) => { const baseSpeed = parseFloat(element.getAttribute('data-speed')) || 1; let currentDirection = 1; const originalContent = element.innerHTML; element.innerHTML = originalContent + originalContent + originalContent + originalContent; gsap.delayedCall(0.1, () => { const contentWidth = element.scrollWidth / 4; let xPos = 0; const setX = gsap.quickSetter(element, 'x', 'px'); const wrapX = gsap.utils.wrap(-contentWidth, 0); const tick = () => { xPos -= baseSpeed * currentDirection; xPos = wrapX(xPos); setX(xPos); }; gsap.ticker.add(tick); let lastScrollY = window.pageYOffset; window.addEventListener('scroll', () => { const currentScrollY = window.pageYOffset; const scrollDirection = currentScrollY > lastScrollY ? 1 : -1; if (scrollDirection !== currentDirection) { currentDirection = scrollDirection; } lastScrollY = currentScrollY; }); }); }); } initScrollDirectionMarquee(); function initTiltScaleParallax() { document.querySelectorAll('[data-tilt-scale]').forEach((element) => { const container = element.closest('[data-tilt-scale-container]') || element.parentElement; const tiltDirection = element.getAttribute('data-tilt-direction') || 'rotateY'; const tiltIntensity = parseFloat(element.getAttribute('data-tilt-intensity')) || 18; const scaleAmount = parseFloat(element.getAttribute('data-scale-amount')) || 1.2; const isTouchDevice = ('ontouchstart' in window) || (navigator.maxTouchPoints > 0); const scrubValue = parseFloat(element.getAttribute('data-scrub')) || (isTouchDevice ? 0.5 : 1.2); const animProps = { scale: scaleAmount, ease: 'none' }; if (tiltDirection === 'rotateX') { animProps.rotateX = tiltIntensity; } else if (tiltDirection === 'rotateY') { animProps.rotateY = tiltIntensity; } else if (tiltDirection === 'both') { animProps.rotateX = tiltIntensity * 0.6; animProps.rotateY = tiltIntensity * 0.8; } gsap.fromTo(element, { rotateX: tiltDirection === 'rotateX' || tiltDirection === 'both' ? -tiltIntensity * 0.5 : 0, rotateY: tiltDirection === 'rotateY' || tiltDirection === 'both' ? -tiltIntensity * 0.3 : 0, scale: 1, force3D: true }, { ...animProps, scrollTrigger: { trigger: container, start: 'top bottom', end: 'bottom top', scrub: scrubValue, invalidateOnRefresh: true, } }); }); } initTiltScaleParallax(); function initVideoScaleScroll() { document.querySelectorAll('[data-scale-scroll]').forEach((element) => { const startScale = parseFloat(element.getAttribute('data-start-scale')) || 0.7; const endScale = parseFloat(element.getAttribute('data-end-scale')) || 1; const startOpacity = parseFloat(element.getAttribute('data-start-opacity')) || 0.6; const endOpacity = parseFloat(element.getAttribute('data-end-opacity')) || 1; const scrubValue = parseFloat(element.getAttribute('data-scrub')) || 1.5; gsap.fromTo(element, { scale: startScale, opacity: startOpacity, force3D: true }, { scale: endScale, opacity: endOpacity, ease: 'power2.out', scrollTrigger: { trigger: element, start: 'top bottom-=100', end: 'center center', scrub: scrubValue, invalidateOnRefresh: true, } }); }); } initVideoScaleScroll(); }); (function () { var SWIPE_DISTANCE = 50; var SWIPE_VELOCITY = 0.3; var AXIS_LOCK_RATIO = 1.2; var hasPointerEvents = typeof window !== 'undefined' && typeof window.PointerEvent !== 'undefined'; var initialized = false; function initSwipeStack() { if (initialized) return; var stack = document.getElementById('reviewSwipeStackMobile'); if (!stack) return; var cards = Array.from(stack.querySelectorAll('.review-swipe-card-item')); if (!cards.length) return; initialized = true; var currentCard = 0; var isAnimating = false; var startX = 0; var startY = 0; var startTime = 0; var activeTouchId = null; var mouseDown = false; var totalCards = cards.length; var queuedStepDelta = 0; var navWrap = null; var prevBtn = null; var nextBtn = null; if (stack.nextElementSibling && stack.nextElementSibling.classList.contains('review-swipe-nav')) { navWrap = stack.nextElementSibling; } else { navWrap = document.createElement('div'); navWrap.className = 'review-swipe-nav'; navWrap.innerHTML = '' + ''; stack.insertAdjacentElement('afterend', navWrap); } prevBtn = navWrap.querySelector('.review-swipe-prev-btn'); nextBtn = navWrap.querySelector('.review-swipe-next-btn'); function updateNavState() { if (!prevBtn || !nextBtn) return; prevBtn.disabled = currentCard <= 0; nextBtn.disabled = currentCard >= totalCards - 1; } function navigateWithGuard(delta) { step(delta); } function bindNavButton(button, delta) { if (!button) return; function onActivate(event) { if (event && event.type === 'keydown') { var key = event.key || event.code; if (key !== 'Enter' && key !== ' ' && key !== 'Spacebar') return; } if (event) { event.preventDefault(); event.stopPropagation(); } navigateWithGuard(delta); } button.addEventListener('keydown', onActivate); if (hasPointerEvents) { button.addEventListener('pointerup', function (event) { if (event.pointerType === 'mouse' && event.button !== 0) return; onActivate(event); }); button.addEventListener('click', function (event) { // Keep keyboard-triggered click support on pointer-enabled browsers. if (event.detail !== 0) return; onActivate(event); }); return; } button.addEventListener('click', onActivate); } /* ── Dynamic sizing ── */ var STACK_PAD = 48; function recalcSizes() { /* Temporarily clear fixed heights so cards can shrink as well as grow */ cards.forEach(function (card) { card.style.height = ''; }); var cardHeight = cards.reduce(function (max, card) { return Math.max(max, card.scrollHeight); }, 0); if (cardHeight < 10) cardHeight = 220; /* fallback if not yet rendered */ cards.forEach(function (card) { card.style.height = cardHeight + 'px'; }); stack.style.height = (cardHeight + STACK_PAD) + 'px'; } recalcSizes(); /* Re-measure on resize (debounced 120ms) */ var resizeTimer; window.addEventListener('resize', function () { clearTimeout(resizeTimer); resizeTimer = setTimeout(recalcSizes, 120); }); stack.style.touchAction = 'pan-y'; function applyCard(card, y, rotate, opacity, animated, duration) { /* Clear CSS individual transform props that can conflict with transform shorthand */ card.style.translate = ''; card.style.rotate = ''; card.style.scale = ''; var dur = duration || '0.75s'; card.style.transition = animated ? 'transform ' + dur + ' cubic-bezier(0.16,1,0.3,1), opacity 0.6s ease' : 'none'; card.style.transform = 'translate3d(0,' + y + '%,0) rotateZ(' + rotate + 'deg)'; card.style.opacity = String(opacity); } function paintCards(animated) { cards.forEach(function (card, index) { var position = index - currentCard; var abs = Math.abs(position); var y = -50, rotate = 0, opacity = 1, z; if (position < 0) { y = -50 - (220 + abs * 26); rotate = abs * 15; opacity = 0.82; z = totalCards + 20 + abs; } else if (position === 0) { y = -50; rotate = 0; opacity = 1; z = totalCards + 10; } else { y = -50; rotate = position * 2; opacity = Math.max(0.8, 1 - position * 0.06); z = totalCards - position; } card.style.pointerEvents = position === 0 ? 'auto' : 'none'; card.style.zIndex = String(z); /* Exiting cards travel a large distance — use a longer duration so they feel as slow as the incoming card settling into place */ var dur = (animated && position < 0) ? '1.4s' : '0.75s'; applyCard(card, y, rotate, opacity, animated, dur); }); updateNavState(); } function step(delta) { var direction = delta < 0 ? -1 : 1; if (isAnimating) { queuedStepDelta = direction; return; } var next = Math.max(0, Math.min(currentCard + direction, totalCards - 1)); if (next === currentCard) { queuedStepDelta = 0; return; } currentCard = next; isAnimating = true; paintCards(true); setTimeout(function () { isAnimating = false; if (queuedStepDelta !== 0) { var pendingStep = queuedStepDelta; queuedStepDelta = 0; step(pendingStep); } }, 1400); /* match longest card duration */ } function startGesture(x, y) { startX = x; startY = y; startTime = performance.now(); } function endGesture(x, y) { var dx = x - startX, dy = y - startY; var ax = Math.abs(dx), ay = Math.abs(dy); if (ax < 10) return; if (ay > ax * AXIS_LOCK_RATIO) return; var elapsed = Math.max(1, performance.now() - startTime); var velocity = ax / elapsed; if (ax < SWIPE_DISTANCE && velocity < SWIPE_VELOCITY) return; step(dx < 0 ? 1 : -1); } /* ── Touch ── */ stack.addEventListener('touchstart', function (e) { if (activeTouchId !== null) return; var t = e.changedTouches[0]; activeTouchId = t.identifier; startGesture(t.clientX, t.clientY); }, { passive: true }); stack.addEventListener('touchend', function (e) { if (activeTouchId === null) return; for (var i = 0; i < e.changedTouches.length; i++) { var t = e.changedTouches[i]; if (t.identifier !== activeTouchId) continue; endGesture(t.clientX, t.clientY); activeTouchId = null; break; } }, { passive: true }); stack.addEventListener('touchcancel', function () { activeTouchId = null; }, { passive: true }); /* ── Mouse ── */ stack.addEventListener('mousedown', function (e) { if (e.button !== 0) return; mouseDown = true; startGesture(e.clientX, e.clientY); }); stack.addEventListener('mouseup', function (e) { if (!mouseDown) return; mouseDown = false; endGesture(e.clientX, e.clientY); }); stack.addEventListener('mouseleave', function () { mouseDown = false; }); bindNavButton(prevBtn, -1); bindNavButton(nextBtn, 1); paintCards(false); } if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', initSwipeStack); } else { initSwipeStack(); } window.initReviewSwipeStackMobile = function () { initSwipeStack(); }; if (!document.getElementById('reviewSwipeStackMobile')) { var reviewSwipeInitObserver = new MutationObserver(function () { if (initialized) { reviewSwipeInitObserver.disconnect(); return; } if (document.getElementById('reviewSwipeStackMobile')) { initSwipeStack(); if (initialized) reviewSwipeInitObserver.disconnect(); } }); reviewSwipeInitObserver.observe(document.documentElement, { childList: true, subtree: true }); setTimeout(function () { reviewSwipeInitObserver.disconnect(); }, 15000); } })();