Scroll Zoom Hero
Original · freeA hero whose backdrop scales and brightens while the headline parallaxes upward as you scroll.
Copy prompt gives Claude Code, Cursor or v0 a ready-to-paste prompt that recreates this exact component.
npx shadcn@latest add https://webinnoventix.com/r/scroll-zoom-hero.json"use client";
import { useRef } from "react";
import { motion, useScroll, useTransform, useSpring } from "motion/react";
export default function ScrollZoomHero() {
const containerRef = useRef<HTMLDivElement>(null);
const { scrollYProgress } = useScroll({
target: containerRef,
offset: ["start start", "end end"],
});
const smooth = useSpring(scrollYProgress, {
stiffness: 120,
damping: 30,
mass: 0.4,
});
// Backdrop scales up and brightens as you scroll.
const bgScale = useTransform(smooth, [0, 1], [1, 1.55]);
const bgBrightness = useTransform(smooth, [0, 1], [0.72, 1.18]);
const bgSaturate = useTransform(smooth, [0, 1], [1.05, 1.35]);
const bgFilter = useTransform(
[bgBrightness, bgSaturate],
([b, s]: number[]) => `brightness(${b}) saturate(${s})`,
);
// Secondary mesh layer counter-parallaxes for depth.
const meshScale = useTransform(smooth, [0, 1], [1.2, 0.9]);
const meshOpacity = useTransform(smooth, [0, 0.6, 1], [0.9, 0.6, 0.25]);
// Vignette lifts as the scene brightens.
const vignetteOpacity = useTransform(smooth, [0, 1], [0.85, 0.2]);
// Headline parallaxes up and fades.
const headlineY = useTransform(smooth, [0, 1], [0, -220]);
const headlineOpacity = useTransform(smooth, [0, 0.55, 0.8], [1, 1, 0]);
const headlineScale = useTransform(smooth, [0, 1], [1, 0.92]);
const headlineBlur = useTransform(smooth, [0, 0.7, 1], [0, 0, 8]);
const headlineFilter = useTransform(
headlineBlur,
(b: number) => `blur(${b}px)`,
);
const subY = useTransform(smooth, [0, 1], [0, -140]);
const subOpacity = useTransform(smooth, [0, 0.4, 0.65], [1, 1, 0]);
// Scroll hint fades almost immediately.
const hintOpacity = useTransform(smooth, [0, 0.12], [1, 0]);
// Thin progress bar.
const progressScaleX = smooth;
return (
<section
ref={containerRef}
className="relative w-full bg-slate-950 dark:bg-black"
>
<style>{`
@keyframes szh-hint-bob {
0%, 100% { transform: translateY(0); opacity: 0.9; }
50% { transform: translateY(7px); opacity: 0.4; }
}
@keyframes szh-drift {
0% { transform: translate3d(0, 0, 0) rotate(0deg); }
50% { transform: translate3d(2%, -2%, 0) rotate(8deg); }
100% { transform: translate3d(0, 0, 0) rotate(0deg); }
}
@keyframes szh-shimmer {
0% { background-position: -200% 0; }
100% { background-position: 200% 0; }
}
@media (prefers-reduced-motion: reduce) {
.szh-anim { animation: none !important; }
}
`}</style>
{/* Tall scroll driver */}
<div className="relative h-[200vh]">
{/* Sticky stage */}
<div className="sticky top-0 h-screen w-full overflow-hidden">
{/* Full-bleed scaling backdrop */}
<motion.div
aria-hidden
className="absolute inset-0 will-change-transform"
style={{ scale: bgScale, filter: bgFilter }}
>
{/* Base gradient */}
<div className="absolute inset-0 bg-[radial-gradient(120%_120%_at_50%_10%,#1e1b4b_0%,#0f172a_45%,#020617_100%)] dark:bg-[radial-gradient(120%_120%_at_50%_10%,#312e81_0%,#0b1020_45%,#000000_100%)]" />
{/* Aurora blobs */}
<div className="absolute left-[-10%] top-[-10%] h-[60vh] w-[60vh] rounded-full bg-[radial-gradient(circle,rgba(129,140,248,0.55)_0%,transparent_65%)] blur-3xl" />
<div className="absolute right-[-8%] top-[8%] h-[55vh] w-[55vh] rounded-full bg-[radial-gradient(circle,rgba(217,70,239,0.45)_0%,transparent_65%)] blur-3xl" />
<div className="absolute bottom-[-14%] left-[28%] h-[65vh] w-[65vh] rounded-full bg-[radial-gradient(circle,rgba(45,212,191,0.42)_0%,transparent_65%)] blur-3xl" />
</motion.div>
{/* Counter-parallax mesh grid */}
<motion.div
aria-hidden
className="absolute inset-0 will-change-transform"
style={{ scale: meshScale, opacity: meshOpacity }}
>
<div
className="szh-anim absolute inset-[-20%]"
style={{
backgroundImage:
"linear-gradient(rgba(255,255,255,0.10) 1px, transparent 1px), linear-gradient(90deg, rgba(255,255,255,0.10) 1px, transparent 1px)",
backgroundSize: "64px 64px",
maskImage:
"radial-gradient(120% 90% at 50% 30%, black 30%, transparent 75%)",
WebkitMaskImage:
"radial-gradient(120% 90% at 50% 30%, black 30%, transparent 75%)",
animation: "szh-drift 22s ease-in-out infinite",
}}
/>
</motion.div>
{/* Floating orbs for parallax texture */}
<div aria-hidden className="absolute inset-0">
<span
className="szh-anim absolute left-[18%] top-[24%] h-2.5 w-2.5 rounded-full bg-indigo-300/80 shadow-[0_0_24px_6px_rgba(165,180,252,0.55)]"
style={{ animation: "szh-drift 14s ease-in-out infinite" }}
/>
<span
className="szh-anim absolute right-[22%] top-[38%] h-1.5 w-1.5 rounded-full bg-fuchsia-300/80 shadow-[0_0_20px_5px_rgba(240,171,252,0.55)]"
style={{ animation: "szh-drift 18s ease-in-out infinite reverse" }}
/>
<span
className="szh-anim absolute left-[62%] top-[62%] h-2 w-2 rounded-full bg-emerald-300/80 shadow-[0_0_22px_5px_rgba(110,231,183,0.5)]"
style={{ animation: "szh-drift 16s ease-in-out infinite" }}
/>
</div>
{/* Vignette that lifts on brighten */}
<motion.div
aria-hidden
className="absolute inset-0 bg-[radial-gradient(120%_100%_at_50%_50%,transparent_35%,rgba(2,6,23,0.9)_100%)]"
style={{ opacity: vignetteOpacity }}
/>
{/* Foreground content */}
<div className="relative z-10 mx-auto flex h-full max-w-6xl flex-col items-center justify-center px-6 text-center">
<motion.div
style={{ y: subY, opacity: subOpacity }}
className="mb-6"
>
<span className="inline-flex items-center gap-2 rounded-full border border-white/15 bg-white/5 px-4 py-1.5 text-xs font-medium uppercase tracking-[0.2em] text-indigo-100 backdrop-blur-sm">
<span className="relative flex h-2 w-2">
<span className="absolute inline-flex h-full w-full animate-ping rounded-full bg-emerald-400 opacity-75" />
<span className="relative inline-flex h-2 w-2 rounded-full bg-emerald-400" />
</span>
Now shipping · 2026
</span>
</motion.div>
<motion.h1
style={{
y: headlineY,
opacity: headlineOpacity,
scale: headlineScale,
filter: headlineFilter,
}}
className="max-w-4xl text-balance text-5xl font-semibold leading-[0.95] tracking-tight text-white sm:text-7xl lg:text-8xl"
>
Build worlds that
<br />
<span
className="szh-anim bg-[linear-gradient(110deg,#a5b4fc_0%,#f0abfc_35%,#6ee7b7_70%,#a5b4fc_100%)] bg-[length:200%_auto] bg-clip-text text-transparent"
style={{ animation: "szh-shimmer 6s linear infinite" }}
>
pull you inward
</span>
</motion.h1>
<motion.p
style={{ y: subY, opacity: subOpacity }}
className="mt-7 max-w-xl text-base leading-relaxed text-slate-300 sm:text-lg"
>
A depth-driven interface that reacts to every pixel you scroll.
The scene scales, brightens, and breathes while your words drift
toward the horizon.
</motion.p>
<motion.div
style={{ opacity: subOpacity }}
className="mt-9 flex flex-col items-center gap-3 sm:flex-row"
>
<a
href="#"
className="group inline-flex items-center gap-2 rounded-full bg-white px-7 py-3 text-sm font-semibold text-slate-900 shadow-lg shadow-indigo-500/20 transition hover:bg-indigo-50"
>
Start exploring
<svg
aria-hidden
viewBox="0 0 24 24"
className="h-4 w-4 transition-transform group-hover:translate-x-0.5"
fill="none"
stroke="currentColor"
strokeWidth="2.2"
strokeLinecap="round"
strokeLinejoin="round"
>
<path d="M5 12h14M13 6l6 6-6 6" />
</svg>
</a>
<a
href="#"
className="inline-flex items-center gap-2 rounded-full border border-white/20 px-7 py-3 text-sm font-semibold text-white backdrop-blur-sm transition hover:bg-white/10"
>
<svg
aria-hidden
viewBox="0 0 24 24"
className="h-4 w-4"
fill="currentColor"
>
<path d="M8 5v14l11-7z" />
</svg>
Watch the reel
</a>
</motion.div>
</div>
{/* Scroll hint */}
<motion.div
style={{ opacity: hintOpacity }}
className="pointer-events-none absolute inset-x-0 bottom-8 z-10 flex flex-col items-center gap-2 text-slate-300"
>
<span className="text-[11px] font-medium uppercase tracking-[0.3em]">
Scroll to zoom
</span>
<div className="flex h-9 w-5 items-start justify-center rounded-full border border-white/25 p-1">
<span
className="szh-anim h-2 w-1 rounded-full bg-white/80"
style={{ animation: "szh-hint-bob 1.6s ease-in-out infinite" }}
/>
</div>
</motion.div>
{/* Progress bar */}
<motion.div
aria-hidden
style={{ scaleX: progressScaleX }}
className="absolute inset-x-0 bottom-0 z-20 h-1 origin-left bg-[linear-gradient(90deg,#818cf8,#e879f9,#34d399)]"
/>
</div>
</div>
</section>
);
}Dependencies
Licence
Built by Web Innoventix. Free for personal and commercial use, no attribution required.
Built by Web Innoventix
Need a custom interface, a full website, or SEO that gets you cited by AI? We design, build and rank it end to end.
Get a free quoteSimilar components
Browse all →
Scroll Progress Reveal
OriginalA section that reveals its content in sequence as you scroll, with a sticky scroll-progress bar showing how far through it you are.

Parallax Layers
OriginalA layered depth scene where background, midground and foreground drift at different speeds, driven by scroll position.

Pinned Scroll Steps
OriginalA pinned, sticky panel whose visual and copy change step by step as you scroll past it, a classic scrollytelling section.

Horizontal Scroll Gallery
OriginalVertical scrolling pans a horizontal rail of cards sideways while the section is pinned, then releases.

Scroll Reveal Timeline
OriginalA vertical timeline whose connecting line fills and whose milestones fade in as they enter the viewport.

Count-up Stats Band
OriginalA metrics band whose numbers count up from zero the moment it scrolls into view.

Clip-path Image Reveal
OriginalPanels that unmask with a clip-path wipe as they enter the viewport, revealing the media beneath.

Stacking Cards
OriginalA deck of cards that pin and scale as you scroll, each settling behind the next.

Scroll Text Highlight
OriginalA statement whose words light up one by one as you scroll through the section.

Spotlight Hero
OriginalA centred hero with a soft radial spotlight, badge and dual call-to-action.

Split Hero
OriginalA two-column hero pairing a headline and CTAs with a product mock and social proof.

Gradient Spotlight Hero
OriginalA minimal centred hero with a soft gradient-mesh backdrop, announcement pill and dual call-to-action buttons.

