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 installframer-motion

Code

"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

PropTypeDefaultDescription
itemsAccordionItem[]dataAn array of objects representing each accordion row, including the header title and body content.