/** Initialize GSAP on pages with Turbo frames */ import gsap from 'gsap'; import ScrollTrigger from 'gsap/ScrollTrigger'; import { shouldAnimate, punchIn, punchOut } from './gsap-animations.js'; // Register GSAP plugins gsap.registerPlugin(ScrollTrigger); // Register GSDevTools in development only (esbuild inlines ELEVENTY_ENV at build time) if (process.env.ELEVENTY_ENV === 'development') { try { // Dynamic import wrapped in try-catch; GSDevTools package must be installed import('gsap/GSDevTools').then(module => { gsap.registerPlugin(module.default); }).catch(() => { // Silently continue if GSDevTools is not available }); } catch (e) { // Fallback for any import errors } } // Store active contexts for cleanup const activeContexts = new Map(); /** * Initialize GSAP animations for all containers */ function initGsapAnimations() { // Check if reduced motion is preferred if (!shouldAnimate()) { // Skip animations if user prefers reduced motion console.log('GSAP animations disabled: prefers-reduced-motion'); return; } // Find all GSAP animation containers const containers = document.querySelectorAll('[data-gsap-scroll-anim]'); containers.forEach(container => { try { // Parse configuration from data attribute const config = JSON.parse(container.dataset.gsapScrollAnim); // Extract configuration with defaults const { animationType, scrollStart = 'top 80%', scrollEnd = 'bottom 20%', scrub = true, pin = true, markers = false } = config; // Find all animated items within container const items = container.querySelectorAll('.gsap-item'); if (items.length === 0) return; // Create GSAP context for this container const ctx = gsap.context(() => { let timeline; if (animationType === 'punchIn' || animationType === 'punchOut') { const targetElements = container.querySelectorAll('.gsap-image'); const anim = animationType === 'punchIn' ? punchIn(targetElements, config) : punchOut(targetElements, config); // Create timeline with ScrollTrigger timeline = gsap.timeline({ scrollTrigger: { trigger: container, start: scrollStart, end: scrollEnd, scrub: scrub ? 1 : false, markers: markers, pin: pin, toggleActions: scrub ? undefined : 'play reverse play reverse', onEnter: () => container.dataset.gsapActive = 'true', onLeave: () => container.dataset.gsapActive = 'false', onEnterBack: () => container.dataset.gsapActive = 'true', onLeaveBack: () => container.dataset.gsapActive = 'false' } }); // Apply animation if (anim.from && anim.to) { timeline.fromTo(targetElements, anim.from, anim.to); } else if (anim.from) { timeline.from(targetElements, anim.from); } else if (anim.to) { timeline.to(targetElements, anim.to); } } else { console.warn('Unsupported animation type. Only zoomIn/zoomOut are available.', config); return; } }, container); // Store context for cleanup activeContexts.set(container, ctx); } catch (error) { console.error('Error initializing GSAP animation:', error, container); } }); console.log(`Initialized ${activeContexts.size} GSAP scroll animations`); } /** * Cleanup all active animations */ function cleanupGsapAnimations() { activeContexts.forEach((ctx, container) => { ctx.revert(); // Reverts all GSAP changes made in this context container.removeAttribute('data-gsap-active'); }); activeContexts.clear(); // Kill all ScrollTriggers ScrollTrigger.getAll().forEach(trigger => trigger.kill()); } /** * Refresh ScrollTrigger calculations * Useful when content height changes */ function refreshScrollTrigger() { ScrollTrigger.refresh(); } // Initialize on page load document.addEventListener('DOMContentLoaded', initGsapAnimations); // Turbo Drive compatibility if (window.Turbo) { // Clean up before navigation document.addEventListener('turbo:before-render', cleanupGsapAnimations); // Re-initialize after navigation document.addEventListener('turbo:render', initGsapAnimations); document.addEventListener('turbo:load', () => { // Slight delay to ensure DOM is ready setTimeout(initGsapAnimations, 100); }); // Refresh on Turbo frame load document.addEventListener('turbo:frame-load', refreshScrollTrigger); } // Handle window resize let resizeTimeout; window.addEventListener('resize', () => { clearTimeout(resizeTimeout); resizeTimeout = setTimeout(() => { refreshScrollTrigger(); }, 250); }); // Export for manual control if needed export { initGsapAnimations, cleanupGsapAnimations, refreshScrollTrigger };