1import { isNarrowViewport, motionEnabled } from './motion.js'; 2 3const SCROLL_START = 'top 88%'; 4const SCROLL_ENTER = 'top bottom'; 5 6function scrollReveal(gsap, targets, vars, trigger, start = SCROLL_START) { 7 const items = gsap.utils.toArray(targets); 8 if (!items.length || !trigger) return; 9 10 gsap.from(items, { 11 y: vars.y ?? 16, 12 duration: vars.duration, 13 stagger: vars.stagger ?? 0, 14 ease: vars.ease, 15 immediateRender: false, 16 scrollTrigger: { 17 trigger, 18 start, 19 once: true, 20 toggleActions: 'play none none none', 21 }, 22 }); 23} 24 25function animateHero(gsap, narrow, y, duration, ease) { 26 const heroInner = document.querySelector('.hero__inner'); 27 if (!heroInner) return; 28 29 const timeline = gsap.timeline({ defaults: { ease, duration: narrow ? duration * 0.85 : duration } }); 30 const logo = heroInner.querySelector('.hero__logo'); 31 const title = heroInner.querySelector('.hero__title'); 32 const desc = heroInner.querySelector('.hero__desc'); 33 const actions = heroInner.querySelector('.hero__actions'); 34 35 if (logo) { 36 timeline.from(logo, narrow ? { autoAlpha: 0, y: y * 0.6 } : { autoAlpha: 0, y, scale: 0.94 }); 37 } 38 if (title) timeline.from(title, { autoAlpha: 0, y: y * 0.85 }, '-=0.42'); 39 if (desc) timeline.from(desc, { autoAlpha: 0, y: y * 0.7 }, '-=0.44'); 40 if (actions) timeline.from(actions, { autoAlpha: 0, y: y * 0.5 }, '-=0.38'); 41} 42 43function animateSectionHeaders(gsap, y, duration, ease) { 44 gsap.utils.toArray('.section__header').forEach((header) => { 45 scrollReveal(gsap, header, { y, duration, ease }, header, SCROLL_ENTER); 46 }); 47} 48 49function animateCardGrid(gsap, narrow, y, duration, ease, stagger) { 50 const community = document.getElementById('community'); 51 const panel = community?.querySelector('.section__panel'); 52 if (!community || !panel) return; 53 54 scrollReveal( 55 gsap, 56 narrow ? panel : gsap.utils.toArray('.card-grid .card'), 57 { 58 y: narrow ? y * 0.45 : y * 0.55, 59 duration: narrow ? duration * 0.85 : duration, 60 stagger: narrow ? 0 : stagger, 61 ease, 62 }, 63 community, 64 SCROLL_ENTER 65 ); 66} 67 68function animateFeatureBlocks(gsap, narrow, y, duration, ease) { 69 const motion = { 70 y: y * (narrow ? 0.45 : 0.6), 71 duration: narrow ? duration * 0.85 : duration, 72 ease, 73 }; 74 75 gsap.utils.toArray('.spotlight, .showcase').forEach((block) => { 76 const section = block.closest('.section'); 77 scrollReveal(gsap, block, motion, section || block, SCROLL_ENTER); 78 }); 79 80 document.querySelectorAll('.pin-scene').forEach((pinScene) => { 81 const layers = pinScene.querySelectorAll('.pin-scene__copy, .pin-scene__stack'); 82 if (!layers.length) return; 83 84 scrollReveal( 85 gsap, 86 layers, 87 { ...motion, stagger: narrow ? 0 : 0.08 }, 88 pinScene.closest('.section') || pinScene, 89 SCROLL_ENTER 90 ); 91 }); 92} 93 94function animateJoinSection(gsap, narrow, y, duration, ease) { 95 const joinSection = document.getElementById('join'); 96 const joinLayout = document.querySelector('#join .join-layout'); 97 if (!joinSection || !joinLayout) return; 98 99 scrollReveal( 100 gsap, 101 joinLayout, 102 { y: y * (narrow ? 0.45 : 0.6), duration: narrow ? duration * 0.85 : duration, ease }, 103 joinSection, 104 SCROLL_ENTER 105 ); 106} 107 108function animateFaq(gsap, narrow, y, duration, ease) { 109 const faqSection = document.getElementById('faq'); 110 const faq = faqSection?.querySelector('.faq'); 111 if (!faqSection || !faq) return; 112 113 scrollReveal( 114 gsap, 115 narrow ? faq : gsap.utils.toArray('.faq__item'), 116 { 117 y: y * 0.4, 118 duration: duration * 0.85, 119 stagger: narrow ? 0 : 0.04, 120 ease, 121 }, 122 faqSection, 123 SCROLL_ENTER 124 ); 125} 126 127function animateHeader() { 128 const header = document.getElementById('site-header'); 129 if (!header) return; 130 131 let isScrolled = header.classList.contains('is-scrolled'); 132 133 const update = () => { 134 const next = window.scrollY > 24; 135 if (next === isScrolled) return; 136 isScrolled = next; 137 header.classList.toggle('is-scrolled', next); 138 }; 139 140 window.addEventListener('scroll', update, { passive: true }); 141 update(); 142} 143 144function animateSkyGlow(gsap, narrow) { 145 if (narrow) return; 146 147 const skyGlow = document.querySelector('.sky__glow'); 148 if (!skyGlow) return; 149 150 gsap.to(skyGlow, { 151 yPercent: 10, 152 ease: 'none', 153 scrollTrigger: { 154 trigger: document.documentElement, 155 start: 'top top', 156 end: 'bottom bottom', 157 scrub: 0.5, 158 }, 159 }); 160} 161 162function completePassedTriggers() { 163 const ScrollTrigger = window.ScrollTrigger; 164 if (!ScrollTrigger) return; 165 166 const scroll = ScrollTrigger.scroll(); 167
168 ScrollTrigger.getAll().forEach((trigger) => { 169 const animation = trigger.animation; 170 if (!animation || animation.progress() > 0) return; 171 if (trigger.start >= scroll) return; 172 173 animation.progress(1); 174 trigger.kill(false); 175 }); 176} 177 178function refreshScrollMotion(ScrollTrigger) { 179 ScrollTrigger.refresh(); 180 completePassedTriggers(); 181} 182 183export function initGsapMotion() { 184 const gsap = window.gsap; 185 const ScrollTrigger = window.ScrollTrigger; 186 187 if (!gsap || !ScrollTrigger || !motionEnabled()) { 188 return; 189 } 190 191 gsap.registerPlugin(ScrollTrigger); 192 ScrollTrigger.config({ 193 ignoreMobileResize: true, 194 autoRefreshEvents: 'visibilitychange,DOMContentLoaded,load', 195 }); 196 197 document.documentElement.classList.add('js-motion'); 198 199 const narrow = isNarrowViewport(); 200 const y = narrow ? 10 : 22; 201 const duration = narrow ? 0.38 : 0.6; 202 const stagger = narrow ? 0 : 0.06; 203 const ease = 'power2.out'; 204 205 animateHero(gsap, narrow, y, duration, ease); 206 animateSectionHeaders(gsap, y, duration, ease); 207 animateCardGrid(gsap, narrow, y, duration, ease, stagger); 208 animateFeatureBlocks(gsap, narrow, y, duration, ease); 209 animateJoinSection(gsap, narrow, y, duration, ease); 210 animateFaq(gsap, narrow, y, duration, ease); 211 animateHeader(); 212 animateSkyGlow(gsap, narrow); 213 214 refreshScrollMotion(ScrollTrigger); 215 216 window.addEventListener( 217 'orientationchange', 218 () => { 219 window.setTimeout(() => refreshScrollMotion(ScrollTrigger), 150); 220 }, 221 { passive: true } 222 ); 223}
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.