/* App.jsx — main composition + GSAP scroll choreography + tweaks */ const { useEffect, useState, useRef } = React; // Default tweak values — wrapped in EDITMODE markers so host can persist edits const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{ "animationIntensity": 1.0, "showCharacterChips": true, "comicSansOnPepeX": true, "showVideos": true }/*EDITMODE-END*/; function App() { const [t, setTweak] = useTweaks(TWEAK_DEFAULTS); const [active, setActive] = useState('top'); // Scrollspy useEffect(() => { const ids = ['idea', 'origins', 'timeline', 'fund', 'collectibles', 'people', 'characters', 'collabs']; const onScroll = () => { let current = 'top'; for (const id of ids) { const el = document.getElementById(id); if (!el) continue; const top = el.getBoundingClientRect().top; if (top < 160) current = id; } setActive(current); }; window.addEventListener('scroll', onScroll, { passive: true }); onScroll(); return () => window.removeEventListener('scroll', onScroll); }, []); // GSAP scroll choreography useEffect(() => { if (!window.gsap) return; const gsap = window.gsap; const ScrollTrigger = window.ScrollTrigger; if (ScrollTrigger) gsap.registerPlugin(ScrollTrigger); const intensity = t.animationIntensity || 1; const triggers = []; // Hero clouds — parallax X+Y at different speeds document.querySelectorAll('.hero-cloud').forEach((c) => { const speed = parseFloat(c.dataset.speed || '0.4'); const tr = gsap.to(c, { yPercent: -150 * speed * intensity, xPercent: 70 * speed * intensity, ease: 'none', scrollTrigger: { trigger: '.hero', start: 'top top', end: 'bottom top', scrub: true, } }); triggers.push(tr.scrollTrigger); }); // Wordmark inner — fade up on scroll in const wm = document.querySelector('[data-wordmark] .wordmark-inner'); if (wm) { const tr = gsap.fromTo(wm, { y: 60, opacity: 0 }, { y: 0, opacity: 1, duration: 1.4, ease: 'power3.out', scrollTrigger: { trigger: '[data-wordmark]', start: 'top 70%' }}); triggers.push(tr.scrollTrigger); } // Idea characters — enter from sides document.querySelectorAll('[data-idea-char]').forEach((el, i) => { const fromX = el.dataset.side === 'L' ? -220 : 220; const tr = gsap.fromTo(el, { x: fromX * intensity, opacity: 0, scale: 0.6 }, { x: 0, opacity: 1, scale: 1, duration: 1, ease: 'power3.out', delay: i * 0.08, scrollTrigger: { trigger: '.idea-sec', start: 'top 65%' } }); triggers.push(tr.scrollTrigger); }); // Origins works — reveal in document.querySelectorAll('.work').forEach((el, i) => { const tr = gsap.fromTo(el, { y: 30, opacity: 0 }, { y: 0, opacity: 1, duration: 0.7, delay: i * 0.05, ease: 'power3.out', scrollTrigger: { trigger: '.origins-works', start: 'top 80%' }}); triggers.push(tr.scrollTrigger); }); // Timeline thumbs in document.querySelectorAll('.tl-thumb').forEach((el, i) => { const tr = gsap.fromTo(el, { y: el.classList.contains('up') ? -96 : 96, opacity: 0 }, { y: el.classList.contains('up') ? -126 : 126, opacity: 1, duration: 1, delay: i * 0.12, ease: 'power3.out', scrollTrigger: { trigger: '.timeline-rail', start: 'top 70%' }}); triggers.push(tr.scrollTrigger); }); // ---- Bento scrub-zoom: pinned. center cell zooms to full screen, becomes "The Fund" header ---- const bento = document.querySelector('[data-bento]'); const center = document.querySelector('[data-bento-center]'); const pin = document.querySelector('[data-bento-pin]'); if (bento && center && pin) { const others = bento.querySelectorAll('.bento-cell:not(.cc)'); const tl = gsap.timeline({ scrollTrigger: { trigger: pin, start: 'top top', end: '+=150%', scrub: 1, pin: true, pinSpacing: true, anticipatePin: 1, } }); // First: hold, then zoom center while fading others tl.to(center, { scale: 6.5, duration: 1, ease: 'power2.inOut' }, 0) .to(others, { opacity: 0, duration: 0.45, stagger: 0.02 }, 0); triggers.push(tl.scrollTrigger); } // Horizontal type pin + scroll const horiz = document.querySelector('[data-horiz]'); const track = document.querySelector('[data-horiz-track]'); if (horiz && track) { const tr = gsap.to(track, { x: () => -(track.scrollWidth - window.innerWidth + 100), ease: 'none', scrollTrigger: { trigger: horiz, start: 'top top', end: () => '+=' + (track.scrollWidth - window.innerWidth + 100), scrub: 0.8, pin: true, invalidateOnRefresh: true, } }); triggers.push(tr.scrollTrigger); } // Squad cards stagger in document.querySelectorAll('[data-squad-card]').forEach((card, i) => { const tr = gsap.fromTo(card, { y: 60, opacity: 0 }, { y: 0, opacity: 1, duration: 0.7, delay: i * 0.1, ease: 'power3.out', scrollTrigger: { trigger: '.squad', start: 'top 75%' }}); triggers.push(tr.scrollTrigger); }); // Collabs final reveal const ci = document.querySelector('[data-collabs-inner]'); if (ci) { const tr = gsap.fromTo(ci, { y: 60, opacity: 0, scale: 0.96 }, { y: 0, opacity: 1, scale: 1, duration: 1.4, ease: 'power3.out', scrollTrigger: { trigger: '.collabs', start: 'top 70%' }}); triggers.push(tr.scrollTrigger); } return () => { triggers.forEach(tr => tr && tr.kill && tr.kill()); }; }, [t.animationIntensity]); // Character chips toggle useEffect(() => { const el = document.querySelector('.idea-chars'); if (el) el.style.display = t.showCharacterChips ? '' : 'none'; }, [t.showCharacterChips]); // Comic Sans toggle on Pepe X useEffect(() => { const el = document.querySelector('.char-pepex'); if (el) el.classList.toggle('no-comic', !t.comicSansOnPepeX); }, [t.comicSansOnPepeX]); // Video bg toggle (saves battery) useEffect(() => { document.querySelectorAll('section video').forEach(v => { if (t.showVideos) { v.play().catch(() => {}); v.style.display = ''; } else { v.pause(); v.style.display = 'none'; } }); }, [t.showVideos]); return (