"use client"; import { useId, useRef, type ElementType, type PointerEvent, type ReactNode } from "react"; import { motion, useReducedMotion } from "motion/react"; export type FeatureGridItem = { title: string; text: string; icon?: ReactNode; /** Makes the whole cell a link. */ href?: string; }; type FeatureGridProps = { items: FeatureGridItem[]; title?: string; /** Small line above the title. */ eyebrow?: string; subtitle?: string; /** Smallest cell width in pixels. Sets how many columns fit. */ minWidth?: number; /** Colour of the light and the icons. */ color?: string; /** Cell colour. Match it to the page behind. */ cell?: string; /** h2 by default. */ headingAs?: ElementType; /** Set padding and colours here. */ className?: string; }; /* FeatureGrid: a grid of features divided by hairlines. A single light follows the pointer and picks out the lines and cells near it. */ export function FeatureGrid({ items, title, eyebrow, subtitle, minWidth = 240, color = "#a78bfa", cell = "#0b0a14", headingAs: Heading = "h2", className = "bg-[#0b0a14] px-5 py-16 text-white", }: FeatureGridProps) { const reduce = useReducedMotion(); const id = useId(); const grid = useRef(null); const move = (e: PointerEvent) => { const box = grid.current; if (!box || e.pointerType !== "mouse") return; const rect = box.getBoundingClientRect(); box.style.setProperty("--fg-x", `${e.clientX - rect.left}px`); box.style.setProperty("--fg-y", `${e.clientY - rect.top}px`); box.style.setProperty("--fg-on", "1"); }; const leave = () => grid.current?.style.setProperty("--fg-on", "0"); // The cells are opaque, so the layer under them shows only in the 1px gaps. The layer over them tints the cells. const light = (radius: number, tint: string) => ({ opacity: "var(--fg-on, 0)", background: `radial-gradient(${radius}px circle at var(--fg-x, 50%) var(--fg-y, 50%), ${tint}, transparent 70%)`, }); return (
{title && (
{eyebrow &&

{eyebrow}

} {title} {subtitle &&

{subtitle}

}
)}
    {items.map((item, i) => { const body = ( <> {item.icon && ( {item.icon} )} {item.title} {item.text} ); return (
  • {item.href ? ( {body} ) : (
    {body}
    )}
  • ); })}
); }