Video Bg Hero
Original · freehero over a looping gradient/canvas backdrop
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/herox-video-bg.json"use client";
import { useEffect, useRef, useState } from "react";
import { motion, useReducedMotion, type Variants } from "motion/react";
type PaletteKey = "aurora" | "ember" | "tidal";
type Blob = {
cx: number;
cy: number;
ax: number;
ay: number;
sx: number;
sy: number;
r: number;
ci: number;
ph: number;
};
const PALETTES: Record<PaletteKey, { label: string; colors: string[] }> = {
aurora: { label: "Aurora", colors: ["#6366f1", "#8b5cf6", "#22d3ee", "#10b981"] },
ember: { label: "Ember", colors: ["#f43f5e", "#f59e0b", "#a855f7", "#fb7185"] },
tidal: { label: "Tidal", colors: ["#0ea5e9", "#6366f1", "#14b8a6", "#38bdf8"] },
};
const PALETTE_ORDER: PaletteKey[] = ["aurora", "ember", "tidal"];
const BLOBS: Blob[] = [
{ cx: 0.22, cy: 0.28, ax: 0.1, ay: 0.09, sx: 0.34, sy: 0.28, r: 0.85, ci: 0, ph: 0 },
{ cx: 0.78, cy: 0.3, ax: 0.12, ay: 0.08, sx: 0.27, sy: 0.39, r: 0.95, ci: 1, ph: 1.2 },
{ cx: 0.6, cy: 0.74, ax: 0.14, ay: 0.1, sx: 0.31, sy: 0.24, r: 0.9, ci: 2, ph: 2.4 },
{ cx: 0.34, cy: 0.68, ax: 0.11, ay: 0.12, sx: 0.22, sy: 0.33, r: 0.8, ci: 3, ph: 3.6 },
{ cx: 0.5, cy: 0.46, ax: 0.16, ay: 0.1, sx: 0.19, sy: 0.29, r: 0.7, ci: 0, ph: 4.8 },
];
const EASE: [number, number, number, number] = [0.22, 1, 0.36, 1];
const STATS: { value: string; label: string }[] = [
{ value: "60fps", label: "on mid-range laptops" },
{ value: "11 KB", label: "gzipped, only React" },
{ value: "0", label: "video files to host" },
];
function withAlpha(hex: string, a: number): string {
const n = parseInt(hex.slice(1), 16);
const r = (n >> 16) & 255;
const g = (n >> 8) & 255;
const b = n & 255;
return `rgba(${r}, ${g}, ${b}, ${a})`;
}
const container: Variants = {
hidden: {},
show: { transition: { staggerChildren: 0.09, delayChildren: 0.12 } },
};
const item: Variants = {
hidden: { opacity: 0, y: 22 },
show: { opacity: 1, y: 0, transition: { duration: 0.65, ease: EASE } },
};
export default function HeroxVideoBg() {
const reduce = useReducedMotion() ?? false;
const canvasRef = useRef<HTMLCanvasElement | null>(null);
const [palette, setPalette] = useState<PaletteKey>("aurora");
const [playing, setPlaying] = useState(true);
const [isDark, setIsDark] = useState(false);
const running = playing && !reduce;
useEffect(() => {
const mql = window.matchMedia("(prefers-color-scheme: dark)");
const root = document.documentElement;
const compute = () => setIsDark(root.classList.contains("dark") || mql.matches);
compute();
mql.addEventListener("change", compute);
const obs = new MutationObserver(compute);
obs.observe(root, { attributes: true, attributeFilter: ["class"] });
return () => {
mql.removeEventListener("change", compute);
obs.disconnect();
};
}, []);
useEffect(() => {
const canvas = canvasRef.current;
if (!canvas) return;
const ctx = canvas.getContext("2d");
if (!ctx) return;
const dpr = Math.min(window.devicePixelRatio || 1, 2);
let width = 0;
let height = 0;
let raf = 0;
const animate = playing && !reduce;
const drawFrame = (t: number) => {
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
ctx.globalCompositeOperation = "source-over";
const bg = ctx.createLinearGradient(0, 0, width, height);
if (isDark) {
bg.addColorStop(0, "#070b18");
bg.addColorStop(1, "#02040a");
} else {
bg.addColorStop(0, "#f8fafc");
bg.addColorStop(1, "#eef2ff");
}
ctx.fillStyle = bg;
ctx.fillRect(0, 0, width, height);
ctx.globalCompositeOperation = isDark ? "lighter" : "source-over";
const colors = PALETTES[palette].colors;
const alpha = isDark ? 0.55 : 0.5;
const base = Math.min(width, height);
for (const b of BLOBS) {
const x = (b.cx + Math.sin(t * b.sx + b.ph) * b.ax) * width;
const y = (b.cy + Math.cos(t * b.sy + b.ph) * b.ay) * height;
const r = b.r * base;
const color = colors[b.ci] ?? "#6366f1";
const g = ctx.createRadialGradient(x, y, 0, x, y, r);
g.addColorStop(0, withAlpha(color, alpha));
g.addColorStop(0.55, withAlpha(color, alpha * 0.35));
g.addColorStop(1, withAlpha(color, 0));
ctx.fillStyle = g;
ctx.beginPath();
ctx.arc(x, y, r, 0, Math.PI * 2);
ctx.fill();
}
ctx.globalCompositeOperation = "source-over";
};
const resize = () => {
const rect = canvas.getBoundingClientRect();
width = rect.width;
height = rect.height;
canvas.width = Math.max(1, Math.floor(width * dpr));
canvas.height = Math.max(1, Math.floor(height * dpr));
if (!animate) drawFrame(6);
};
resize();
window.addEventListener("resize", resize);
const startT = performance.now();
const loop = (now: number) => {
drawFrame((now - startT) / 1000);
raf = requestAnimationFrame(loop);
};
if (animate) {
raf = requestAnimationFrame(loop);
} else {
drawFrame(6);
}
return () => {
window.removeEventListener("resize", resize);
if (raf) cancelAnimationFrame(raf);
};
}, [palette, isDark, playing, reduce]);
return (
<section className="relative flex w-full items-center overflow-hidden bg-white px-6 py-24 sm:py-32 dark:bg-slate-950 min-h-[92vh]">
<style>{`
@keyframes hxvb-shimmer { 0% { background-position: 200% 0; } 100% { background-position: -200% 0; } }
@keyframes hxvb-ring { 0% { transform: scale(1); opacity: .7; } 75%, 100% { transform: scale(2.4); opacity: 0; } }
@keyframes hxvb-float { 0%, 100% { transform: translateY(0) rotate(6deg); } 50% { transform: translateY(-12px) rotate(6deg); } }
@keyframes hxvb-pulse { 0%, 100% { opacity: .35; } 50% { opacity: .7; } }
.hxvb-badge-glow { animation: hxvb-shimmer 7s linear infinite; }
.hxvb-ring { animation: hxvb-ring 2.6s cubic-bezier(0,0,0.2,1) infinite; }
.hxvb-float { animation: hxvb-float 9s ease-in-out infinite; }
.hxvb-pulse { animation: hxvb-pulse 6s ease-in-out infinite; }
@media (prefers-reduced-motion: reduce) {
.hxvb-badge-glow, .hxvb-ring, .hxvb-float, .hxvb-pulse { animation: none !important; }
}
`}</style>
<canvas
ref={canvasRef}
aria-hidden="true"
className="pointer-events-none absolute inset-0 h-full w-full"
/>
{/* legibility scrim */}
<div
aria-hidden="true"
className="pointer-events-none absolute inset-0 bg-gradient-to-b from-white/50 via-transparent to-white/70 dark:from-slate-950/55 dark:via-slate-950/10 dark:to-slate-950/85"
/>
{/* soft center glow */}
<div
aria-hidden="true"
className="hxvb-pulse pointer-events-none absolute left-1/2 top-1/2 h-[36rem] w-[36rem] -translate-x-1/2 -translate-y-1/2 rounded-full bg-indigo-400/10 blur-3xl dark:bg-indigo-500/10"
/>
{/* floating decorative chip */}
<div
aria-hidden="true"
className="hxvb-float pointer-events-none absolute right-[8%] top-[16%] hidden h-16 w-16 rounded-2xl bg-gradient-to-br from-indigo-400/40 to-sky-400/30 ring-1 ring-white/30 backdrop-blur-sm md:block dark:from-indigo-500/30 dark:to-sky-500/20 dark:ring-white/10"
/>
<div className="relative mx-auto w-full max-w-4xl text-center">
<motion.div
variants={container}
initial={reduce ? false : "hidden"}
animate={reduce ? false : "show"}
className="flex flex-col items-center"
>
{/* eyebrow badge */}
<motion.span
variants={item}
className="relative inline-flex items-center gap-2 overflow-hidden rounded-full border border-slate-300/70 bg-white/70 px-3.5 py-1.5 text-xs font-medium tracking-wide text-slate-700 shadow-sm backdrop-blur dark:border-white/15 dark:bg-white/5 dark:text-slate-200"
>
<span
aria-hidden="true"
className="hxvb-badge-glow absolute inset-0"
style={{
background:
"linear-gradient(110deg, transparent 35%, rgba(255,255,255,0.45) 50%, transparent 65%)",
backgroundSize: "200% 100%",
}}
/>
<span className="relative flex h-2 w-2">
<span
aria-hidden="true"
className="hxvb-ring absolute inline-flex h-full w-full rounded-full bg-emerald-400"
/>
<span className="relative inline-flex h-2 w-2 rounded-full bg-emerald-500" />
</span>
<span className="relative">Fluxwave 3.0 · Live canvas backdrops</span>
</motion.span>
{/* headline */}
<motion.h1
variants={item}
className="mt-7 text-balance text-4xl font-semibold leading-[1.05] tracking-tight text-slate-900 sm:text-6xl dark:text-white"
>
Ship a hero that{" "}
<span className="bg-gradient-to-r from-indigo-500 via-violet-500 to-sky-500 bg-clip-text text-transparent dark:from-indigo-400 dark:via-violet-400 dark:to-sky-400">
moves like video
</span>
, loads like text.
</motion.h1>
{/* subhead */}
<motion.p
variants={item}
className="mt-6 max-w-2xl text-pretty text-base leading-relaxed text-slate-600 sm:text-lg dark:text-slate-300"
>
Fluxwave renders a smooth, GPU-light gradient backdrop straight onto a canvas — no
MP4s to host, no layout shift, no jank. One component, crisp in light and dark, on every
screen.
</motion.p>
{/* CTAs */}
<motion.div
variants={item}
className="mt-9 flex flex-col items-center gap-3 sm:flex-row"
>
<a
href="#start"
className="group inline-flex items-center justify-center gap-2 rounded-xl bg-slate-900 px-6 py-3 text-sm font-semibold text-white shadow-lg shadow-slate-900/20 transition-transform hover:-translate-y-0.5 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 focus-visible:ring-offset-2 focus-visible:ring-offset-white dark:bg-white dark:text-slate-900 dark:shadow-black/40 dark:focus-visible:ring-offset-slate-950"
>
Start free
<svg
aria-hidden="true"
viewBox="0 0 24 24"
className="h-4 w-4 transition-transform group-hover:translate-x-0.5"
fill="none"
stroke="currentColor"
strokeWidth={2}
strokeLinecap="round"
strokeLinejoin="round"
>
<path d="M5 12h14M13 6l6 6-6 6" />
</svg>
</a>
<a
href="#demo"
className="inline-flex items-center justify-center gap-2 rounded-xl border border-slate-300 bg-white/60 px-6 py-3 text-sm font-semibold text-slate-800 backdrop-blur transition-colors hover:bg-white focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 focus-visible:ring-offset-2 focus-visible:ring-offset-white dark:border-white/20 dark:bg-white/5 dark:text-white dark:hover:bg-white/10 dark:focus-visible:ring-offset-slate-950"
>
<svg aria-hidden="true" viewBox="0 0 24 24" className="h-4 w-4" fill="currentColor">
<path d="M8 5.14v13.72a1 1 0 0 0 1.5.86l11-6.86a1 1 0 0 0 0-1.72l-11-6.86A1 1 0 0 0 8 5.14Z" />
</svg>
See it live
</a>
</motion.div>
{/* trust row */}
<motion.div
variants={item}
className="mt-9 flex flex-col items-center gap-3 sm:flex-row sm:gap-5"
>
<div className="flex -space-x-2.5">
{[
"from-indigo-400 to-violet-500",
"from-sky-400 to-emerald-500",
"from-rose-400 to-amber-500",
"from-violet-400 to-sky-500",
].map((g, i) => (
<span
key={i}
className={`h-8 w-8 rounded-full bg-gradient-to-br ${g} ring-2 ring-white dark:ring-slate-950`}
/>
))}
</div>
<div className="flex items-center gap-2 text-sm text-slate-600 dark:text-slate-300">
<span className="flex" aria-hidden="true">
{[0, 1, 2, 3, 4].map((s) => (
<svg key={s} viewBox="0 0 24 24" className="h-4 w-4 text-amber-400" fill="currentColor">
<path d="m12 2 2.9 6.26L21.6 9.3l-4.8 4.68 1.13 6.6L12 17.5 6.07 20.6l1.13-6.6L2.4 9.3l6.7-1.04Z" />
</svg>
))}
</span>
<span>
<strong className="font-semibold text-slate-900 dark:text-white">4.9/5</strong> from
4,200+ product teams
</span>
</div>
</motion.div>
{/* stats */}
<motion.dl
variants={item}
className="mt-12 grid w-full max-w-lg grid-cols-3 gap-4 border-t border-slate-200/70 pt-8 dark:border-white/10"
>
{STATS.map((s) => (
<div key={s.label} className="text-center">
<dt className="text-2xl font-semibold tracking-tight text-slate-900 sm:text-3xl dark:text-white">
{s.value}
</dt>
<dd className="mt-1 text-xs text-slate-500 dark:text-slate-400">{s.label}</dd>
</div>
))}
</motion.dl>
{/* backdrop controls */}
<motion.div
variants={item}
className="mt-10 inline-flex flex-wrap items-center justify-center gap-3 rounded-2xl border border-slate-200/70 bg-white/60 px-3 py-2.5 shadow-sm backdrop-blur dark:border-white/10 dark:bg-white/5"
>
<span className="pl-1 text-xs font-medium uppercase tracking-wider text-slate-500 dark:text-slate-400">
Backdrop
</span>
<div
role="group"
aria-label="Backdrop palette"
className="flex items-center gap-1 rounded-xl bg-slate-100/80 p-1 dark:bg-white/5"
>
{PALETTE_ORDER.map((key) => {
const active = palette === key;
return (
<button
key={key}
type="button"
aria-pressed={active}
onClick={() => setPalette(key)}
className={`rounded-lg px-3 py-1.5 text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 ${
active
? "bg-slate-900 text-white shadow-sm dark:bg-white dark:text-slate-900"
: "text-slate-600 hover:text-slate-900 dark:text-slate-300 dark:hover:text-white"
}`}
>
{PALETTES[key].label}
</button>
);
})}
</div>
<button
type="button"
onClick={() => setPlaying((p) => !p)}
disabled={reduce}
aria-pressed={running}
aria-label={running ? "Pause backdrop animation" : "Play backdrop animation"}
className="inline-flex h-9 w-9 items-center justify-center rounded-lg border border-slate-300 bg-white text-slate-700 transition-colors hover:bg-slate-50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 disabled:cursor-not-allowed disabled:opacity-40 dark:border-white/15 dark:bg-white/5 dark:text-slate-200 dark:hover:bg-white/10"
>
{running ? (
<svg aria-hidden="true" viewBox="0 0 24 24" className="h-4 w-4" fill="currentColor">
<path d="M8 5h3v14H8zM13 5h3v14h-3z" />
</svg>
) : (
<svg aria-hidden="true" viewBox="0 0 24 24" className="h-4 w-4" fill="currentColor">
<path d="M8 5.14v13.72a1 1 0 0 0 1.5.86l11-6.86a1 1 0 0 0 0-1.72l-11-6.86A1 1 0 0 0 8 5.14Z" />
</svg>
)}
</button>
</motion.div>
</motion.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 →
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.

App Preview Hero
OriginalA centred hero that pairs headline copy with a realistic product dashboard mock built entirely from markup, complete with browser chrome and a floating notification card.

Waitlist Capture Hero
OriginalA dark, focused hero with an inline email capture form and avatar social proof, ready for pre-launch waitlists.

Image Backdrop Hero
OriginalA cinematic full-bleed hero with a layered image-style backdrop, overlaid left-aligned copy and a trusted-by logos strip.

Gradient Mesh Hero with Staggered Text Reveal
OriginalA full-screen hero pairing a drifting animated gradient-mesh backdrop with a word-by-word staggered blur-in headline and a logo marquee under the fold.

Typewriter Rotating Words Hero
OriginalA hero whose headline cycles through rotating words with a live typewriter caret over an animated grid backdrop, degrading to a gentle word swap when reduced motion is on.

Floating UI Cards Hero with Mouse Parallax
OriginalA hero with glassy floating UI cards that drift continuously and shift on mouse-parallax depth around a staggered centre headline.

Aurora Hero with Shimmer CTA and Logo Marquee
OriginalA dark hero with animated aurora light beams, a shimmering sweep across the primary CTA, and dual-direction logo marquees beneath the fold.

Gradient Conversion Hero
MITCentered, full-screen marketing hero with a colour-accent headline word, supporting copy, and dual primary/secondary CTA buttons. Dark-mode ready and dependency-free.

Split Showcase Hero
MITTwo-column hero on a dark background with a headline, paragraph, and two buttons beside a gradient media panel. Responsive column stack on mobile.

