Features

Features 01 — Bento Grid

Asymmetric bento grid of five feature cards with icon chips, a CSS-only product mock in the featured tile, hover glow accents and staggered reveals.

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

Code

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

"use client"

import * as React from "react"
import { motion } from "motion/react"
import {
  Blocks,
  Globe,
  LineChart,
  ShieldCheck,
  Zap,
  type LucideIcon,
} from "lucide-react"

import { fadeUp, stagger, viewport } from "@/lib/motion"
import { cn } from "@/lib/utils"

export type Feature = {
  icon: LucideIcon
  title: string
  description: string
}

export type Features01Props = {
  eyebrow?: string
  title?: React.ReactNode
  description?: string
  /**
   * Five features fill the bento grid. The first one renders as the
   * featured tile (spans two columns) with a visual inside.
   */
  features?: Feature[]
  /** Replace the CSS-only mock inside the featured tile. */
  featuredVisual?: React.ReactNode
  /** Heading element — use "h1" when this is the page's main heading. */
  as?: "h1" | "h2"
  className?: string
}

const defaultFeatures: Feature[] = [
  {
    icon: LineChart,
    title: "Insights in real time",
    description:
      "Watch signups, activation and revenue move the moment they happen. Live dashboards stream every event with sub-second latency — no refresh, no nightly sync.",
  },
  {
    icon: Zap,
    title: "Instant setup",
    description:
      "Drop in one snippet and Lumen starts learning immediately. Autocapture means your first chart is ready before your coffee is.",
  },
  {
    icon: ShieldCheck,
    title: "Private by default",
    description:
      "SOC 2 Type II, GDPR-ready pipelines and field-level encryption. Your customers' data never trains anyone else's models.",
  },
  {
    icon: Globe,
    title: "Fast from anywhere",
    description:
      "Queries run on an edge network spanning 30+ regions, so dashboards feel local whether your team is in Berlin or Bogotá.",
  },
  {
    icon: Blocks,
    title: "Composable by design",
    description:
      "Every chart, cohort and funnel is an API away. Embed insights in your product or pipe them into the tools you already use.",
  },
]

function FeaturedMock() {
  return (
    <div className="relative mt-8 -mr-6 -mb-6 overflow-hidden rounded-tl-2xl border-t border-l bg-background sm:-mr-8 sm:-mb-8">
      <div className="flex items-center gap-1.5 border-b px-4 py-2.5">
        <span className="size-2 rounded-full bg-muted-foreground/25" />
        <span className="size-2 rounded-full bg-muted-foreground/25" />
        <span className="size-2 rounded-full bg-muted-foreground/25" />
        <div className="ml-3 h-4 w-32 rounded bg-muted" />
        <div className="ml-auto h-4 w-16 rounded-full bg-primary/15" />
      </div>
      <div className="grid grid-cols-3 gap-3 p-4">
        {[0, 1, 2].map((i) => (
          <div key={i} className="rounded-lg border bg-card p-3">
            <div className="h-2.5 w-1/2 rounded bg-muted" />
            <div
              className={cn(
                "mt-2.5 h-4 rounded",
                i === 0 ? "w-3/4 bg-primary/40" : "w-2/3 bg-muted"
              )}
            />
          </div>
        ))}
      </div>
      <div className="flex h-24 items-end gap-1.5 px-4">
        {[35, 55, 40, 70, 50, 85, 60, 95, 75, 100, 65, 90, 80, 70].map(
          (h, i) => (
            <div
              key={i}
              style={{ height: `${h}%` }}
              className={cn(
                "flex-1 rounded-t-sm",
                i === 9 ? "bg-primary" : "bg-primary/40"
              )}
            />
          )
        )}
      </div>
    </div>
  )
}

export function Features01({
  eyebrow = "Features",
  title = "Everything you need to see clearly",
  description = "Lumen turns raw product events into answers your whole team can act on — without a data team on standby.",
  features = defaultFeatures,
  featuredVisual,
  as: Heading = "h2",
  className,
}: Features01Props) {
  const [featured, ...rest] = features

  return (
    <section className={cn("relative bg-background px-6 py-24 md:py-32", className)}>
      <div className="mx-auto max-w-6xl">
        <motion.div
          initial="hidden"
          whileInView="visible"
          viewport={viewport}
          variants={stagger()}
          className="max-w-2xl"
        >
          <motion.p
            variants={fadeUp}
            className="text-sm font-medium tracking-widest text-primary uppercase"
          >
            {eyebrow}
          </motion.p>
          <motion.div variants={fadeUp}>
            <Heading className="mt-4 text-balance font-heading text-4xl font-semibold tracking-tight md:text-5xl">
              {title}
            </Heading>
          </motion.div>
          <motion.p
            variants={fadeUp}
            className="mt-5 text-lg text-pretty text-muted-foreground"
          >
            {description}
          </motion.p>
        </motion.div>

        <motion.div
          initial="hidden"
          whileInView="visible"
          viewport={viewport}
          variants={stagger(0.1)}
          className="mt-14 grid gap-4 md:grid-cols-2 lg:grid-cols-3"
        >
          {featured && (
            <motion.div
              variants={fadeUp}
              className="group relative flex flex-col overflow-hidden rounded-2xl border bg-card p-6 transition-colors duration-300 hover:border-primary/40 sm:p-8 md:col-span-2"
            >
              <div
                aria-hidden
                className="absolute -top-16 left-1/4 h-32 w-2/3 rounded-full bg-primary/15 opacity-0 blur-3xl transition-opacity duration-500 group-hover:opacity-100"
              />
              <div className="relative">
                <div className="flex size-10 items-center justify-center rounded-lg bg-primary/10 text-primary ring-1 ring-primary/15">
                  <featured.icon className="size-5" />
                </div>
                <h3 className="mt-5 font-heading text-xl font-semibold tracking-tight">
                  {featured.title}
                </h3>
                <p className="mt-2 max-w-xl text-sm leading-relaxed text-muted-foreground">
                  {featured.description}
                </p>
              </div>
              {featuredVisual ?? <FeaturedMock />}
            </motion.div>
          )}

          {rest.map((feature) => (
            <motion.div
              key={feature.title}
              variants={fadeUp}
              className="group relative overflow-hidden rounded-2xl border bg-card p-6 transition-colors duration-300 hover:border-primary/40"
            >
              <div
                aria-hidden
                className="absolute -top-12 left-1/2 h-24 w-3/4 -translate-x-1/2 rounded-full bg-primary/15 opacity-0 blur-3xl transition-opacity duration-500 group-hover:opacity-100"
              />
              <div className="relative">
                <div className="flex size-10 items-center justify-center rounded-lg bg-primary/10 text-primary ring-1 ring-primary/15">
                  <feature.icon className="size-5" />
                </div>
                <h3 className="mt-5 font-heading text-lg font-semibold tracking-tight">
                  {feature.title}
                </h3>
                <p className="mt-2 text-sm leading-relaxed text-muted-foreground">
                  {feature.description}
                </p>
              </div>
            </motion.div>
          ))}
        </motion.div>
      </div>
    </section>
  )
}