A big heading sits behind a product image; where the image overlaps the words the text shows only its outline (or inverts, or ghosts) while the rest stays solid — the product literally masks the type. Swipeable, with a floating, pointer-reactive product and smooth slide transitions.
import MaskedTextCarousel from '@crazygl/hero-masked-text-carousel';
export default function Hero() {
return (
<MaskedTextCarousel
slideCount={2}
slide1Image="/sneaker-1.png"
slide1Heading="VELOCITY"
slide1Caption="01 — Aero"
slide2Image="/sneaker-2.png"
slide2Heading="MOMENTUM"
maskEffect="outline"
/>
);
}slideCount (1–5) and per-slide slideNImage (transparent PNG), slideNHeading, slideNCaption.maskEffect (outline / invert / ghost), strokeColor, strokeWidth.transition (fade/slide/zoom), duration, easing, imageFloat, pointerParallax, autoplay, interval.bgTop/bgBottom, textColor, accentColor, headingFont, headingWeight, showArrows, showDots, showCaption.npm install @crazygl/hero-masked-text-carouselThe component takes the same props you see in the live customizer on the right — every default ships poster-quality.
import MaskedTextCarousel from '@crazygl/hero-masked-text-carousel';
export default function Landing() {
return (
<MaskedTextCarousel />
);
}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 MaskedTextCarousel from '@crazygl/hero-masked-text-carousel';
export default function Page() {
return (
<section>
<MaskedTextCarousel
heading="Say hi."
subheading="Your new hero."
/>
<article>
<h2>Welcome</h2>
<p>Your content keeps its own voice below the hero.</p>
</article>
</section>
);
}