← HeroOpen in v0
Hero 02 — Split
Split hero with left-aligned copy, avatar-stack social proof and a floating, layered dashboard mock with an SVG sparkline.
$ npx shadcn@latest add @ludusvibe/hero-02Code
Installed to components/sections/hero-02.tsx — it's yours to edit.
"use client"
import * as React from "react"
import { motion, useReducedMotion, type Transition } from "motion/react"
import { ArrowRight, Bell } from "lucide-react"
import { Badge } from "@/components/ui/badge"
import { buttonVariants } from "@/components/ui/button"
import { EASE, fadeUp, scaleIn, stagger, transition, viewport } from "@/lib/motion"
import { cn } from "@/lib/utils"
type Cta = { label: string; href: string }
export type Hero02Props = {
eyebrow?: string
title?: React.ReactNode
description?: string
primaryCta?: Cta
secondaryCta?: Cta
/** Initials rendered as overlapping avatar circles in the social-proof row. */
avatars?: string[]
socialProof?: React.ReactNode
/** Heading element — use "h1" when this is the page's main heading. */
as?: "h1" | "h2"
className?: string
}
const avatarTints = [
"bg-primary/15 text-primary",
"bg-muted text-muted-foreground",
"bg-secondary text-secondary-foreground",
"bg-primary/10 text-primary",
"bg-muted text-muted-foreground",
]
/** Slow continuous bob for the mock cards — timing derived from the shared presets. */
const floatTransition: Transition = {
duration: (transition.duration ?? 1) * 6,
ease: EASE,
repeat: Infinity,
repeatType: "mirror",
}
export function Hero02({
eyebrow = "Lumen 2.0 — now generally available",
title = (
<>
Every metric that matters, on{" "}
<span className="text-primary">one calm dashboard</span>
</>
),
description = "Lumen pulls revenue, product and support signals into a single live view — so your team stops tab-hopping and starts deciding. Connect your stack in minutes, no SQL required.",
primaryCta = { label: "Start for free", href: "#" },
secondaryCta = { label: "Book a live demo", href: "#" },
avatars = ["AK", "MJ", "SD", "RT", "EL"],
socialProof = (
<>
Loved by <span className="font-semibold text-foreground">2,000+</span>{" "}
builders
</>
),
as: Heading = "h1",
className,
}: Hero02Props) {
const reducedMotion = useReducedMotion()
return (
<section className={cn("relative overflow-hidden bg-background", className)}>
<motion.div
initial="hidden"
whileInView="visible"
viewport={viewport}
variants={stagger()}
className="relative mx-auto grid max-w-6xl items-center gap-16 px-6 py-24 md:py-32 lg:grid-cols-2 lg:gap-12"
>
{/* Left: copy */}
<div className="flex flex-col items-start">
<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 rounded-full bg-primary" />
{eyebrow}
</Badge>
</motion.div>
<motion.div variants={fadeUp}>
<Heading className="mt-6 max-w-xl text-balance font-heading text-4xl font-semibold tracking-tighter sm:text-5xl md:text-6xl">
{title}
</Heading>
</motion.div>
<motion.p
variants={fadeUp}
className="mt-6 max-w-xl text-lg text-pretty text-muted-foreground"
>
{description}
</motion.p>
<motion.div
variants={fadeUp}
className="mt-9 flex flex-col 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>
<motion.div variants={fadeUp} className="mt-10 flex items-center gap-4">
<div aria-hidden className="flex -space-x-2">
{avatars.map((initials, i) => (
<span
key={`${initials}-${i}`}
className={cn(
"flex size-9 items-center justify-center rounded-full text-xs font-semibold ring-2 ring-background",
avatarTints[i % avatarTints.length]
)}
>
{initials}
</span>
))}
</div>
<p className="text-sm text-muted-foreground">{socialProof}</p>
</motion.div>
</div>
{/* Right: layered CSS-only app mock */}
<motion.div
variants={scaleIn}
aria-hidden
className="relative mx-auto w-full max-w-md"
>
<div className="absolute top-1/2 left-1/2 h-[340px] w-[340px] -translate-x-1/2 -translate-y-1/2 rounded-full bg-primary/20 blur-[110px] sm:h-[440px] sm:w-[440px]" />
{/* Offset back card */}
<div className="absolute inset-0 translate-x-5 translate-y-6 rotate-3 rounded-2xl border bg-card/50" />
{/* Main dashboard card */}
<motion.div
animate={reducedMotion ? undefined : { y: [-5, 5] }}
transition={floatTransition}
className="relative -rotate-1 rounded-2xl border bg-card p-5 shadow-2xl"
>
<div className="flex items-center justify-between">
<div className="space-y-2">
<div className="h-4 w-28 rounded bg-muted-foreground/30" />
<div className="h-2.5 w-20 rounded bg-muted" />
</div>
<div className="h-6 w-16 rounded-full bg-primary/15" />
</div>
{/* Sparkline panel */}
<div className="mt-5 rounded-lg border bg-background p-4">
<div className="flex items-center justify-between">
<div className="h-3 w-24 rounded bg-muted" />
<div className="h-5 w-16 rounded bg-primary/25" />
</div>
<div className="mt-3 text-primary">
<svg
viewBox="0 0 240 64"
preserveAspectRatio="none"
fill="none"
className="h-16 w-full"
>
<polygon
points="0,52 24,44 48,48 72,34 96,38 120,24 144,30 168,16 192,22 216,10 240,16 240,64 0,64"
fill="currentColor"
opacity="0.1"
/>
<polyline
points="0,52 24,44 48,48 72,34 96,38 120,24 144,30 168,16 192,22 216,10 240,16"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
vectorEffect="non-scaling-stroke"
/>
</svg>
</div>
</div>
{/* Skeleton rows */}
<div className="mt-4 space-y-3">
{[0, 1, 2].map((i) => (
<div key={i} className="flex items-center gap-3">
<span className="size-8 shrink-0 rounded-full bg-muted" />
<div className="flex-1 space-y-1.5">
<div className="h-2.5 w-2/3 rounded bg-muted" />
<div className="h-2 w-1/3 rounded bg-muted/60" />
</div>
<div className="h-3 w-10 rounded bg-primary/20" />
</div>
))}
</div>
</motion.div>
{/* Floating notification card */}
<motion.div
animate={reducedMotion ? undefined : { y: [4, -4] }}
transition={{
...floatTransition,
delay: transition.duration ?? 0,
}}
className="absolute -top-6 right-0 w-52 rotate-2 rounded-xl border bg-card p-3 shadow-xl sm:-right-4"
>
<div className="flex items-center gap-3">
<span className="flex size-8 shrink-0 items-center justify-center rounded-full bg-primary/15 text-primary">
<Bell className="size-4" />
</span>
<div className="flex-1 space-y-1.5">
<div className="h-2.5 w-3/4 rounded bg-muted-foreground/30" />
<div className="h-2 w-1/2 rounded bg-muted" />
</div>
</div>
</motion.div>
</motion.div>
</motion.div>
</section>
)
}