This is everything the S2 static-section tickets need. The multi-scene orchestrator comes in MS-201.
Steps
- Motion spec (by Sat Sep 26): a 30-minute call or Slack thread with Cole and Lucy to walk through §3.5. Confirm or change each row, and decide the Speakers two-frame question (final layout vs. animation pair). Record the answers in the Slack thread; step 5 turns them into code.
- Install
gsap, @gsap/react, lenis in apps/main. Register ScrollTrigger once in scenes/gsap.ts (client-only).
- Smooth scroll provider
lib/scroll/SmoothScroll.tsx: Lenis driven by the GSAP ticker (gsap.ticker.add(t => lenis.raf(t*1000)), lenis.on('scroll', ScrollTrigger.update), gsap.ticker.lagSmoothing(0)). Export useLenis(). Only active at ≥ 640px and without reduced motion.
scenes/types.ts: SceneId (hero|about|values|speakers|testimonials|projects|sponsors|team|faq) and SceneAnimation (§3.4).
scenes/motionSpec.ts: a typed constant per scene with scrollLength (vh), exitColor, pinned and a short enter/build description as a JSDoc comment. This is the confirmed spec from step 1.
- Artboard units:
--ab-w/--ab-h/--u in globals.css (with tablet overrides) and the u(n) helper in lib/scroll/units.ts, with a JSDoc example.
scenes/SceneFrame.tsx: full-viewport root (h-[100svh] w-full overflow-hidden relative), a background slot that bleeds to the edges and a centered content slot sized to the artboard. It also sets id={sceneId} for anchors.
- Dev harness
app/dev/scenes/page.tsx (calls notFound() in production):
?scene=<id> renders one scene in a SceneFrame
?progress=0..1 builds that scene's build timeline paused and seeks it; ?enter=1 previews the enter transition from the previous scene's exit color
- a progress slider + a width toggle (1512 / 1000)
- a scene map
dev/scenes/sceneMap.ts that each scene ticket adds one line to
- Proof of concept: one placeholder scene pinned with ScrollTrigger on Lenis, scrubbing a box, to prove the stack works in Chrome, Safari and Firefox.
- Put the "how to write a scene" rules in the JSDoc on
SceneAnimation and at the top of SceneFrame.tsx: use data-anim hooks, animate only transform/opacity, read lengths and colors from motionSpec.ts, use the harness.
Acceptance criteria
This is everything the S2 static-section tickets need. The multi-scene orchestrator comes in MS-201.
Steps
gsap,@gsap/react,lenisinapps/main. Register ScrollTrigger once inscenes/gsap.ts(client-only).lib/scroll/SmoothScroll.tsx: Lenis driven by the GSAP ticker (gsap.ticker.add(t => lenis.raf(t*1000)),lenis.on('scroll', ScrollTrigger.update),gsap.ticker.lagSmoothing(0)). ExportuseLenis(). Only active at ≥ 640px and without reduced motion.scenes/types.ts:SceneId(hero|about|values|speakers|testimonials|projects|sponsors|team|faq) andSceneAnimation(§3.4).scenes/motionSpec.ts: a typed constant per scene withscrollLength(vh),exitColor,pinnedand a shortenter/builddescription as a JSDoc comment. This is the confirmed spec from step 1.--ab-w/--ab-h/--uinglobals.css(with tablet overrides) and theu(n)helper inlib/scroll/units.ts, with a JSDoc example.scenes/SceneFrame.tsx: full-viewport root (h-[100svh] w-full overflow-hidden relative), abackgroundslot that bleeds to the edges and a centeredcontentslot sized to the artboard. It also setsid={sceneId}for anchors.app/dev/scenes/page.tsx(callsnotFound()in production):?scene=<id>renders one scene in aSceneFrame?progress=0..1builds that scene'sbuildtimeline paused and seeks it;?enter=1previews theentertransition from the previous scene's exit colordev/scenes/sceneMap.tsthat each scene ticket adds one line toSceneAnimationand at the top ofSceneFrame.tsx: usedata-animhooks, animate only transform/opacity, read lengths and colors frommotionSpec.ts, use the harness.Acceptance criteria
motionSpec.tsreflects what Cole and Lucy agreed (link the Slack thread in the PR)/dev/scenes?scene=placeholder&progress=0.3shows a paused, scrubbable sceneSceneFramescales a 1512×982 test grid correctly at 1512×982, 1920×1080, 1280×720 and 1000×982 (screenshots in the PR)