Hero

Hero 01 — Spotlight

Centered hero with cursor spotlight, gradient headline, dual CTAs and a CSS-only product mock.

$ npx shadcn@latest add @ludusvibe/hero-01
Open in v0

Code

Installed to components/sections/hero-01.tsx — it's yours to edit.

"use client"

import * as React from "react"
import { motion } from "motion/react"
import { ArrowRight } from "lucide-react"

import { Badge } from "@/components/ui/badge"
import { buttonVariants } from "@/components/ui/button"
import { Spotlight } from "@/components/ludus/spotlight"
import { fadeUp, scaleIn, stagger, viewport } from "@/lib/motion"
import { cn } from "@/lib/utils"

type Cta = { label: string; href: string }

export type Hero01Props = {
  eyebrow?: string
  title?: React.ReactNode
  description?: string
  primaryCta?: Cta
  secondaryCta?: Cta
  /** Heading element — use "h1" when this is the page's main heading. */
  as?: "h1" | "h2"
  className?: string
}

export function Hero01({
  eyebrow = "Now in public beta",
  title = (
    <>
      Ship landing pages{" "}
      <span className="bg-gradient-to-r from-primary via-primary/80 to-foreground bg-clip-text text-transparent">
        at the speed of thought
      </span>
    </>
  ),
  description = "Production-ready, animated sections that drop straight into your codebase and inherit your brand automatically. No design debt, no lock-in — you own the code.",
  primaryCta = { label: "Start building", href: "#" },
  secondaryCta = { label: "Browse sections", href: "#" },
  as: Heading = "h1",
  className,
}: Hero01Props) {
  return (
    <section className={cn("relative overflow-hidden bg-background", className)}>
      <div
        aria-hidden
        className="absolute inset-0 [background-image:linear-gradient(to_right,var(--border)_1px,transparent_1px),linear-gradient(to_bottom,var(--border)_1px,transparent_1px)] [background-size:64px_64px] [mask-image:radial-gradient(ellipse_60%_50%_at_50%_0%,black,transparent)]"
      />
      <div
        aria-hidden
        className="absolute -top-40 left-1/2 h-[480px] w-[720px] -translate-x-1/2 rounded-full bg-primary/20 blur-[140px]"
      />
      <Spotlight />

      <motion.div
        initial="hidden"
        whileInView="visible"
        viewport={viewport}
        variants={stagger()}
        className="relative mx-auto flex max-w-5xl flex-col items-center px-6 pt-28 pb-24 text-center md:pt-40"
      >
        <motion.div variants={fadeUp}>
          <Badge
            variant="outline"
            className="gap-2 rounded-full px-3 py-1 text-sm font-normal text-muted-foreground"
          >
            <span className="size-1.5 animate-pulse rounded-full bg-primary" />
            {eyebrow}
          </Badge>
        </motion.div>

        <motion.div variants={fadeUp}>
          <Heading className="mt-6 max-w-3xl text-balance font-heading text-5xl font-semibold tracking-tighter sm:text-6xl md:text-7xl">
            {title}
          </Heading>
        </motion.div>

        <motion.p
          variants={fadeUp}
          className="mt-6 max-w-2xl text-lg text-pretty text-muted-foreground"
        >
          {description}
        </motion.p>

        <motion.div
          variants={fadeUp}
          className="mt-10 flex flex-col items-center gap-3 sm:flex-row"
        >
          <a
            href={primaryCta.href}
            className={cn(buttonVariants({ size: "lg" }), "group")}
          >
            {primaryCta.label}
            <ArrowRight className="transition-transform group-hover:translate-x-0.5" />
          </a>
          <a
            href={secondaryCta.href}
            className={buttonVariants({ size: "lg", variant: "outline" })}
          >
            {secondaryCta.label}
          </a>
        </motion.div>

        {/* CSS-only product mock */}
        <motion.div variants={scaleIn} className="relative mt-20 w-full">
          <div
            aria-hidden
            className="absolute -inset-x-8 top-8 -bottom-8 rounded-[2rem] bg-primary/10 blur-2xl"
          />
          <div className="relative overflow-hidden rounded-2xl border bg-card shadow-2xl">
            <div className="flex items-center gap-1.5 border-b px-4 py-3">
              <span className="size-2.5 rounded-full bg-muted-foreground/25" />
              <span className="size-2.5 rounded-full bg-muted-foreground/25" />
              <span className="size-2.5 rounded-full bg-muted-foreground/25" />
              <div className="mx-auto h-5 w-64 max-w-[50%] rounded-md bg-muted" />
            </div>
            <div className="grid grid-cols-[180px_1fr] max-sm:grid-cols-1">
              <div className="hidden flex-col gap-2 border-r p-4 sm:flex">
                <div className="h-7 rounded-md bg-primary/15" />
                <div className="h-7 rounded-md bg-muted" />
                <div className="h-7 rounded-md bg-muted" />
                <div className="h-7 rounded-md bg-muted" />
                <div className="h-7 rounded-md bg-muted" />
              </div>
              <div className="flex flex-col gap-4 p-6">
                <div className="flex gap-4">
                  {[0, 1, 2].map((i) => (
                    <div key={i} className="h-20 flex-1 rounded-lg border bg-background p-3">
                      <div className="h-3 w-1/2 rounded bg-muted" />
                      <div className="mt-3 h-5 w-2/3 rounded bg-primary/30" />
                    </div>
                  ))}
                </div>
                <div className="flex h-40 items-end gap-2 rounded-lg border bg-background p-4">
                  {[40, 65, 45, 80, 55, 90, 70, 100, 60, 85, 75, 95].map((h, i) => (
                    <div
                      key={i}
                      style={{ height: `${h}%` }}
                      className="flex-1 rounded-t bg-primary/60"
                    />
                  ))}
                </div>
              </div>
            </div>
            <div
              aria-hidden
              className="pointer-events-none absolute inset-x-0 bottom-0 h-24 bg-gradient-to-t from-background to-transparent"
            />
          </div>
        </motion.div>
      </motion.div>
    </section>
  )
}