import './style.css'; import Lenis from 'lenis'; import { gsap } from 'gsap'; import { ScrollTrigger } from 'gsap/ScrollTrigger'; gsap.registerPlugin(ScrollTrigger); // 1. Smooth Scroll Setup (Lenis) const lenis = new Lenis({ lerp: 0.07, smoothWheel: true, }); lenis.on('scroll', ScrollTrigger.update); gsap.ticker.add((time) => { lenis.raf(time * 1000); }); gsap.ticker.lagSmoothing(0); // 2. Canvas & Frames Setup const canvas = document.getElementById('hero-lightpass') as HTMLCanvasElement; const context = canvas.getContext('2d'); const frameCount = 240; const currentFrame = (index: number) => { const paddedIndex = index.toString().padStart(3, '0'); return `/frames/ezgif-frame-${paddedIndex}.jpg`; }; const images: HTMLImageElement[] = []; const truckCycle = { frame: 1 }; for (let i = 1; i <= frameCount; i++) { const img = new Image(); img.src = currentFrame(i); images.push(img); } function renderFrame() { if (!context) return; const img = images[truckCycle.frame - 1]; if (!img || !img.complete) return; const canvasRatio = canvas.width / canvas.height; const imgRatio = img.width / img.height; let drawWidth = canvas.width; let drawHeight = canvas.height; let offsetX = 0; let offsetY = 0; if (imgRatio > canvasRatio) { drawWidth = canvas.height * imgRatio; offsetX = (canvas.width - drawWidth) / 2; } else { drawHeight = canvas.width / imgRatio; offsetY = (canvas.height - drawHeight) / 2; } context.clearRect(0, 0, canvas.width, canvas.height); context.drawImage(img, offsetX, offsetY, drawWidth, drawHeight); } function resizeCanvas() { canvas.width = window.innerWidth; canvas.height = window.innerHeight; renderFrame(); } window.addEventListener('resize', resizeCanvas); if (images.length > 0) { images[0].onload = () => { resizeCanvas(); gsap.to(truckCycle, { frame: frameCount, snap: 'frame', ease: 'none', scrollTrigger: { trigger: '#app', start: 'top top', end: 'bottom bottom', scrub: 1, onUpdate: renderFrame } }); }; } // 3. UI Animations (ScrollTrigger) // --- HERO SEQUENCE --- const heroTl = gsap.timeline({ scrollTrigger: { trigger: '#hero-scroll', start: 'top top', end: 'bottom bottom', scrub: 1, } }); heroTl .to('.ht-1', { opacity: 1, y: 0, duration: 2 }) .to('.ht-1', { opacity: 0, scale: 1.1, duration: 2 }, "+=1") .to('.ht-2', { opacity: 1, y: 0, duration: 2 }) .to('.ht-2', { opacity: 0, scale: 1.1, duration: 2 }, "+=1") .to('.ht-3', { opacity: 1, y: 0, duration: 2 }) .to('.hero-tags-reveal', { opacity: 1, y: 0, duration: 2 }, "<") .to('.ht-3', { opacity: 0, y: -50, duration: 2 }, "+=2") .to('.hero-tags-reveal', { opacity: 0, y: -50, duration: 2 }, "<"); gsap.to('.scroll-indicator', { opacity: 0, scrollTrigger: { trigger: '#hero-scroll', start: 'top top', end: '+=300', scrub: true, } }); // --- INFO Section --- gsap.from('.info-content', { opacity: 0, x: 100, filter: 'blur(15px)', scrollTrigger: { trigger: '#info', start: 'top 70%', end: 'center center', scrub: 1, } }); // --- DIFFERENTIALS Section --- gsap.from('.diffs-header', { opacity: 0, y: 50, scrollTrigger: { trigger: '#diffs', start: 'top 80%', end: 'center center', scrub: 1, } }); const featureCards = gsap.utils.toArray('.feature-card') as HTMLElement[]; gsap.from(featureCards, { opacity: 0, y: 100, rotationX: -15, transformOrigin: 'top center', filter: 'blur(15px)', stagger: 0.15, ease: "power3.out", scrollTrigger: { trigger: '#diffs', start: 'top 70%', end: 'center center', scrub: 1, } }); // --- FINAL CTA --- gsap.to('.cta-overlay', { opacity: 0.90, scrollTrigger: { trigger: '#final-cta', start: 'top bottom', end: 'center center', scrub: true, } }); gsap.from('.cta-content', { y: 100, opacity: 0, filter: 'blur(15px)', scrollTrigger: { trigger: '#final-cta', start: 'top center', end: 'center center', scrub: 1, } });