Hero One
It is a refined, minimal UI component built using Next.js and Framer Motion.
Live Preview
Ready to build
something great?
Interactive components designed for the next generation of web applications. Reliable, extensible, and fast.
No extra setup · Just copy-paste
Dependencies
npm install
framer-motionCode
"use client";
import { useRef, useEffect } from "react";
import { motion } from "framer-motion";
import Link from "next/link";
export default function HeroOne() {
const ref = useRef<HTMLDivElement>(null);
const canvasRef = useRef<HTMLCanvasElement>(null);
const CHARS = "something great?".split("");
useEffect(() => {
const canvas = canvasRef.current;
if (!canvas) return;
const ctx = canvas.getContext("2d");
if (!ctx) return;
const resize = () => {
canvas.width = canvas.offsetWidth;
canvas.height = canvas.offsetHeight;
};
resize();
window.addEventListener("resize", resize);
const particles: Array<{
x: number;
y: number;
vx: number;
vy: number;
size: number;
opacity: number;
hue: number;
}> = [];
for (let i = 0; i < 80; i++) {
particles.push({
x: Math.random() * canvas.width,
y: Math.random() * canvas.height,
vx: (Math.random() - 0.5) * 0.4,
vy: (Math.random() - 0.5) * 0.4,
size: Math.random() * 2 + 0.5,
opacity: Math.random() * 0.4 + 0.1,
hue: Math.random() > 0.7 ? 80 : Math.random() > 0.5 ? 195 : 320,
});
}
let animId: number;
const animate = () => {
ctx.clearRect(0, 0, canvas.width, canvas.height);
particles.forEach((p) => {
p.x += p.vx;
p.y += p.vy;
if (p.x < 0) p.x = canvas.width;
if (p.x > canvas.width) p.x = 0;
if (p.y < 0) p.y = canvas.height;
if (p.y > canvas.height) p.y = 0;
ctx.beginPath();
ctx.arc(p.x, p.y, p.size, 0, Math.PI * 2);
ctx.fillStyle = `hsla(${p.hue}, 100%, 60%, 1)`;
ctx.fill();
});
animId = requestAnimationFrame(animate);
};
animate();
return () => {
cancelAnimationFrame(animId);
window.removeEventListener("resize", resize);
};
}, []);
return (
<section ref={ref} className="relative w-full min-h-screen">
<motion.div className="relative overflow-hidden min-h-screen">
<div className="absolute inset-0 bg-gradient-to-br from-[#0d0d0d] via-[#000000] to-[#0b0b10]" />
<div className="absolute inset-0 bg-grid opacity-20" />
<canvas ref={canvasRef} className="absolute inset-0 w-full h-full" />
<div className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-96 h-96 rounded-full bg-white/20 blur-[150px]" />
<div className="absolute -top-20 -right-20 w-80 h-80 rounded-full bg-white/30 blur-[150px]" />
<div className="absolute -bottom-20 -left-20 w-80 h-80 rounded-full bg-white/30 blur-[150px]" />
<div
className="absolute inset-0 rounded-3xl"
style={{ boxShadow: "inset 0 0 0 1px rgba(255,255,255,0.08)" }}
/>
<div className="relative z-10 py-20 px-8 md:px-16 text-center mt-16">
<motion.h2
initial={{ opacity: 0, y: 30 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ delay: 0.1, duration: 0.8, ease: [0.22, 1, 0.36, 1] }}
className="text-[clamp(2.5rem,10vw,5.5rem)] font-bold leading-[0.9] text-white tracking-tight mb-6"
>
<motion.h2
initial={{ opacity: 0, y: -40 }}
whileInView={{ opacity: 1, y: 0 }}
>
Ready to build
</motion.h2>
<br />
{CHARS.map((char, i) => (
<motion.span
key={i}
initial={{ y: "20%", opacity: 0 }}
animate={{
y: ["-30%", "30%"],
opacity: [0.5, 1, 0.5],
}}
transition={{
duration: 0.9,
delay: i * 0.1,
repeat: Infinity,
repeatType: "reverse",
ease: "easeInOut",
}}
className="inline-block text-4xl md:text-7xl tracking-normal text-white"
>
{char === " " ? "\u00A0" : char}
</motion.span>
))}
</motion.h2>
<motion.p
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ delay: 0.25, duration: 0.7 }}
className="text-zinc-100 text-lg max-w-xl mx-auto mb-10"
>
Interactive components designed for the next generation of web
applications. Reliable, extensible, and fast.
</motion.p>
<motion.div
initial={{ opacity: 0, scale: 0.95 }}
whileInView={{ opacity: 1, scale: 1 }}
viewport={{ once: true }}
transition={{ delay: 0.4, type: "spring", stiffness: 200 }}
className="flex flex-col z-10 text-white sm:flex-row items-center justify-center gap-4"
>
<Link
href="/docs"
className="group relative inline-flex items-center justify-center gap-2 px-8 py-4 rounded-xl text-base font-semibold bg-white text-black transition-all duration-300 hover:bg-zinc-200 hover:shadow-[0_0_30px_rgba(255,255,255,0.3)] active:scale-95"
>
View Documentation
<svg
className="w-4 h-4 transition-transform duration-300 group-hover:translate-x-1"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
strokeWidth={2.5}
>
<path d="M5 12h14m-7-7l7 7-7 7" />
</svg>
</Link>
<Link
href="/components"
className="inline-flex items-center justify-center gap-2 px-8 py-4 rounded-xl text-base font-medium border border-white/10 bg-white/5 backdrop-blur-sm transition-all duration-300 hover:bg-white/10 hover:border-white/20 active:scale-95"
>
Get Started
</Link>
</motion.div>
<motion.p
initial={{ opacity: 0 }}
whileInView={{ opacity: 1 }}
viewport={{ once: true }}
transition={{ delay: 0.6 }}
className="mt-6 text-xs text-zinc-300"
>
No extra setup · Just copy-paste
</motion.p>
</div>
</motion.div>
</section>
);
}
Props
| Prop | Type | Default | Description |
|---|---|---|---|
| items | AccordionItem[] | data | An array of objects representing each accordion row, including the header title and body content. |