A product demo broken into frames along a 3D film strip that curves through the hero, with sprocket holes, drifting light leaks and grain, and a CTA where the strip ends.
import FilmStripJourney from '@crazygl/hero-film-strip-journey';
export default function Page() {
return (
<FilmStripJourney
frameCount={5}
frame1="https://crazygl.com/samples/screenshot-dashboard-dark.avif"
ctaLabel="Play the demo"
onCTAClick="/demo"
/>
);
}frameCount (3–8) and frame1…frame8 media slots (image or MP4/WebM/MOV video, cover-fit). Videos load and loop in the gate.ctaLabel and onCTAClick (URL string or function); empty label hides it.stripCurvature, stripSpan, stripWidth, stripOffsetX/Y/Z, stripTilt place and shape the ribbon.driftSpeed (ambient reel pull), scrollRange (frames advanced per scroll), parallaxStrength.lightLeakStrength + leakColor, grainStrength, sprocketColor, accentColor, frameBrightness, vignetteStrength.bgTop, bgBottom.npm install @crazygl/hero-film-strip-journeyThe component takes the same props you see in the live customizer on the right — every default ships poster-quality.
import FilmStripProductJourney from '@crazygl/hero-film-strip-journey';
export default function Landing() {
return (
<FilmStripProductJourney />
);
}The wrapper renders static HTML on the server and only initialises the canvas after hydration, so search engines see your copy.
// app/page.tsx — works in SSR-first frameworks (Next, Remix, Astro, etc.)
'use client';
import FilmStripProductJourney from '@crazygl/hero-film-strip-journey';
export default function Page() {
return (
<section>
<FilmStripProductJourney
heading="Say hi."
subheading="Your new hero."
/>
<article>
<h2>Welcome</h2>
<p>Your content keeps its own voice below the hero.</p>
</article>
</section>
);
}