# Features 5

> 2×3 interactive capability grid with auto-cycle pulse focus — hover overrides cycle, label expands on active cell (logo-cloud-3 DNA).

A shadcn/ui block from the [@nusaiba](https://nusaiba.dev) registry, listed on [shadcn.exchange](https://shadcn.exchange/r/nusaiba/features-5).

- Page, with a live preview: https://shadcn.exchange/r/nusaiba/features-5
- Screenshot: https://abzjhwgwpdpz7ua5.public.blob.vercel-storage.com/shots/nusaiba/features-5-light-a79adf0c0a19b074.png
- Registry item: https://nusaiba.dev/r/base-nova/features-5.json
- Categories: features
- Listed: 2026-10-13, source last changed 2026-10-13

## Install

```bash
npx shadcn@latest add https://nusaiba.dev/r/base-nova/features-5.json
```

Run it in a project that has shadcn/ui set up. It adds the files below and installs what they depend on.

## Dependencies

- npm packages: `motion`
- Registry items: `@nusaiba/foundation`

## Files

### `registry/blocks/features/5/features-pulse-section.tsx`

```tsx
"use client";

import { useCallback, useEffect, useState } from "react";
import type { LucideIcon } from "lucide-react";
import {
	ClipboardCopyIcon,
	EyeIcon,
	KeyRoundIcon,
	SparklesIcon,
	SquareTerminalIcon,
	UnlockIcon,
} from "lucide-react";
import { motion, useInView, useReducedMotion } from "motion/react";
import { motionStagger, motionViewport } from "@/lib/motion-tokens";
import { cn } from "@/lib/utils";
import { SectionShell, ContentRail } from "@/components/layout-contract";

const CYCLE_MS = 3400;

type Capability = {
	id: string;
	title: string;
	short: string;
	description: string;
	icon: LucideIcon;
};

const capabilities: Capability[] = [
	{
		id: "motion",
		title: "Motion docs",
		short: "Motion",
		description: "40 live patterns linked to blocks — spring tokens, triggers, and isolated demos.",
		icon: SparklesIcon,
	},
	{
		id: "cli",
		title: "CLI install",
		short: "CLI",
		description: "npx shadcn add @nusaiba/block-name copies source into your repo with your tokens.",
		icon: SquareTerminalIcon,
	},
	{
		id: "pro",
		title: "Pro blocks",
		short: "Pro",
		description: "Depth sections for pricing, integrations, and motion-heavy layouts beyond the free catalog.",
		icon: UnlockIcon,
	},
	{
		id: "prompt",
		title: "Copy prompt",
		short: "Prompt",
		description: "Every block page ships an AI-ready install prompt when you prefer that workflow.",
		icon: ClipboardCopyIcon,
	},
	{
		id: "preview",
		title: "Preview live",
		short: "Preview",
		description: "Inspect motion and layout on the site before anything lands in your codebase.",
		icon: EyeIcon,
	},
	{
		id: "token",
		title: "Token auth",
		short: "Token",
		description: "Private registry token for Pro installs — no subscription lock-in on solo use.",
		icon: KeyRoundIcon,
	},
];

function PulseFeatureCell({
	capability,
	isActive,
	hasFocus,
	onActivate,
	onDeactivate,
	reduceMotion,
	cellVariants,
}: {
	capability: Capability;
	isActive: boolean;
	hasFocus: boolean;
	onActivate: () => void;
	onDeactivate: () => void;
	reduceMotion: boolean;
	cellVariants: {
		hidden: { opacity: number; scale?: number; filter?: string };
		visible: { opacity: number; scale?: number; filter?: string };
	};
}) {
	const Icon = capability.icon;
	const dimmed = hasFocus ? !isActive : false;
	const pulsing = isActive && !hasFocus && !reduceMotion;

	return (
		<motion.button
			aria-label={capability.title}
			aria-pressed={isActive}
			className={cn(
				"group relative flex min-h-[8.5rem] w-full flex-col items-start justify-between rounded-[var(--radius-md)] border border-border bg-background p-4 text-left transition-[background-color,border-color,transform,box-shadow] active:scale-[0.96] md:min-h-[9rem] md:p-5",
				"after:absolute after:top-1/2 after:left-1/2 after:size-11 after:-translate-1/2",
				isActive &&
					"border-foreground/10 bg-gradient-to-b from-primary/[0.05] to-background shadow-[0_1px_2px_rgba(0,0,0,0.04)] dark:shadow-[0_1px_2px_rgba(0,0,0,0.2)]",
			)}
			onBlur={onDeactivate}
			onFocus={onActivate}
			onMouseEnter={onActivate}
			onMouseLeave={onDeactivate}
			type="button"
			variants={cellVariants}
			whileHover={reduceMotion ? undefined : { y: -2 }}
		>
			<motion.div
				animate={
					reduceMotion
						? undefined
						: dimmed
							? { opacity: 0.42, scale: 0.98, filter: "blur(1px)" }
							: pulsing
								? { opacity: 1, scale: [1, 1.02, 1], filter: "blur(0px)" }
								: { opacity: isActive ? 1 : 0.62, scale: 1, filter: "blur(0px)" }
				}
				className="flex w-full flex-col gap-3"
				transition={
					pulsing
						? { duration: 2.4, repeat: Number.POSITIVE_INFINITY, ease: "easeInOut" }
						: { type: "spring", duration: 0.32, bounce: 0 }
				}
			>
				<div className="flex items-center gap-2.5">
					<span
						className={cn(
							"inline-flex size-9 items-center justify-center rounded-[var(--radius-sm)] border border-border bg-muted/30",
							isActive && "border-foreground/10 bg-primary/[0.08] text-primary",
						)}
					>
						<Icon className="size-4" strokeWidth={2} />
					</span>
					<div className="min-w-0">
						<p
							className={cn(
								"font-medium text-sm transition-[opacity,transform]",
								isActive ? "text-foreground" : "text-foreground/75",
							)}
						>
							{isActive ? capability.title : capability.short}
						</p>
						{!isActive ? (
							<p className="truncate font-mono text-[10px] text-muted-foreground uppercase tracking-[0.12em]">
								{capability.title}
							</p>
						) : null}
					</div>
				</div>

				<motion.p
					animate={
						reduceMotion
							? undefined
							: isActive
								? { opacity: 1, height: "auto", filter: "blur(0px)" }
								: { opacity: 0, height: 0, filter: "blur(4px)" }
					}
					className="overflow-hidden text-pretty text-muted-foreground text-sm leading-relaxed"
					transition={{ type: "spring", duration: 0.3, bounce: 0 }}
				>
					{capability.description}
				</motion.p>
			</motion.div>
		</motion.button>
	);
}

export function FeaturesPulseGridSection() {
	const reduceMotion = useReducedMotion();
	const [rootNode, setRootNode] = useState<HTMLElement | null>(null);
	const isInView = useInView({ current: rootNode }, { ...motionViewport, amount: 0.3 });
	const [active, setActive] = useState(0);
	const [cycleActive, setCycleActive] = useState(false);
	const [hoverFocus, setHoverFocus] = useState<string | null>(null);

	useEffect(() => {
		if (!isInView || reduceMotion) return;
		setCycleActive(true);
	}, [isInView, reduceMotion]);

	useEffect(() => {
		if (!cycleActive || reduceMotion || hoverFocus) return;
		const timer = setInterval(() => {
			setActive((current) => (current + 1) % capabilities.length);
		}, CYCLE_MS);
		return () => clearInterval(timer);
	}, [cycleActive, hoverFocus, reduceMotion]);

	const selectCapability = useCallback((id: string, index: number) => {
		setHoverFocus(id);
		setActive(index);
		setCycleActive(false);
	}, []);

	const clearFocus = useCallback((id: string) => {
		setHoverFocus((current) => (current === id ? null : current));
		setCycleActive(true);
	}, []);

	const cellVariants = reduceMotion
		? { hidden: { opacity: 1 }, visible: { opacity: 1 } }
		: {
				hidden: { opacity: 0, scale: 0.97, filter: "blur(4px)" },
				visible: { opacity: 1, scale: 1, filter: "blur(0px)" },
			};

	return (
		<SectionShell spacingMode="section" className="bg-background" ref={setRootNode}>
			<ContentRail maxWidth="max-w-4xl" className="space-y-8 md:space-y-10">
				<div className="max-w-xl space-y-2 text-left">
					<p className="font-medium text-primary text-xs uppercase tracking-[0.22em]">
						Capabilities
					</p>
					<h2 className="text-balance font-medium text-2xl tracking-tight md:text-4xl">
						What you get with the registry
					</h2>
					<p className="text-pretty text-muted-foreground text-sm md:text-base">
						Six core workflows — one cell in focus at a time. Hover to override the
						auto-cycle pulse.
					</p>
				</div>

				<motion.ul
					className="grid gap-3 sm:grid-cols-2 md:gap-4"
					initial="hidden"
					variants={{
						hidden: {},
						visible: {
							transition: {
								staggerChildren: motionStagger.item,
								delayChildren: 0.04,
							},
						},
					}}
					animate={isInView ? "visible" : "hidden"}
				>
					{capabilities.map((capability, index) => (
						<motion.li key={capability.id} variants={cellVariants}>
							<PulseFeatureCell
								capability={capability}
								cellVariants={cellVariants}
								hasFocus={hoverFocus !== null}
								isActive={index === active}
								onActivate={() => selectCapability(capability.id, index)}
								onDeactivate={() => clearFocus(capability.id)}
								reduceMotion={!!reduceMotion}
							/>
						</motion.li>
					))}
				</motion.ul>
			</ContentRail>
		</SectionShell>
	);
}
```
