An Apple-product-page-style scroll-scrubbed hero for the fictional brand Apex: a five-second Formula 1 launch film that you scrub by scrolling, with a synced telemetry HUD, a marquee divider, a spec sheet, and a full polished standalone site around it. The <video> is pinned full-screen while scroll position drives video.currentTime via a single requestAnimationFrame loop — no animation library. Generated with Claude Fable 5.
- Next.js 16 (App Router, Turbopack) + React 19 + TypeScript 5
- Tailwind CSS v4 (
@tailwindcss/postcss, CSS-first@themetokens) next/font— Archivo (variable-width display) + Chivo Mono- No animation libraries: a single
requestAnimationFrameloop does the scrubbing
app/components/ScrollHero.tsx— the whole effect. The section is460vhtall with aposition: stickyinner stage, so it pins for ~4.6 viewport heights while the scroll runs. A rAF loop lerps acurrentvalue toward the scrolltarget(factor0.14) and writesvideo.currentTime = p * duration, so seeks stay smooth even with jumpy wheel/trackpad input. Duration is read from the file'sloadedmetadata, so swapping the video needs no code changes.- Telemetry (speed/gear) and the progress rail are mutated via refs, never React state, so the component never re-renders on scroll.
prefers-reduced-motiondisables the scrub (the video just holds frame 0).- A muted
play()+pause()on first interaction primes Safari/iOS, which won't decode frames for a video that has never played.
app/page.tsx— the standalone site around the hero (chapters, marquee, spec sheet, footer).app/globals.css— film-grain overlay, sunset fallback gradient (shown while the video loads or if it's missing), and all section styling.
npm install
npm run dev # http://localhost:3000npm run build # next build — compile + TypeScript + static generation
npm run lint # eslint (next/core-web-vitals + next/typescript)The brief was to generate the real shot with the Higgsfield connector
(Seedance 2.0, 1080p, 5s, F1 sunset). At build time the connector was down —
generate_video returns "User not found" and the balance/cost preflights return
"Something went wrong". It needs the connector to be re-authenticated, which
couldn't be done in this session.
So the committed public/hero.mp4 is an ffmpeg-generated sunset-gradient
stand-in, encoded to the real shot's target spec (1920×1080, 5.0s, 25 fps) with
every frame a keyframe so currentTime scrubbing is perfectly smooth.
Once Higgsfield is re-authenticated, generate the shot, download it, and re-encode
it so every frame is a keyframe — without -g 1, currentTime seeking snaps
between sparse keyframes and the scrub looks janky:
ffmpeg -i downloaded.mp4 -c:v libx264 -g 1 -crf 18 -pix_fmt yuv420p \
-movflags +faststart -an public/hero.mp4No code changes are needed — ScrollHero reads the duration from the file's
metadata, so the new clip just drops in.
Part of the Animations & loaders collection in the claude-directory — an open-source gallery of AI-generated UI built with Claude Fable 5. Browse the live gallery.
