# Hero 4

> Typography monument — per-word reveal headline with registry-focused production copy.

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

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

## Install

```bash
npx shadcn@latest add https://nusaiba.dev/r/base-nova/hero-4.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`, `button`, `@magicui/grid-pattern`

## Files

### `registry/blocks/hero/4/hero-section.tsx`

```tsx
"use client";

import { ArrowRightIcon } from "lucide-react";
import { motion, useReducedMotion } from "motion/react";
import { ContentRail, SectionShell } from "@/components/layout-contract";
import { Button } from "@/components/ui/button";
import { GridPattern } from "@/components/ui/grid-pattern";
import { cn } from "@/lib/utils";

const spring = { type: "spring" as const, stiffness: 380, damping: 28 };

type HeadlineWord = {
	text: string;
	muted?: boolean;
	accent?: boolean;
};

const headlineLines: HeadlineWord[][] = [
	[{ text: "Preview" }, { text: "blocks" }, { text: "live." }],
	[
		{ text: "Install", muted: true },
		{ text: "with" },
		{ text: "shadcn.", accent: true },
	],
];

function RevealWord({
	word,
	index,
	reduceMotion,
}: {
	word: HeadlineWord;
	index: number;
	reduceMotion: boolean;
}) {
	return (
		<span className="inline-flex overflow-hidden pb-[0.08em]">
			<motion.span
				className={cn(
					word.muted && "text-muted-foreground",
					word.accent && "text-primary",
				)}
				initial={
					reduceMotion
						? false
						: { opacity: 0, y: "100%", filter: "blur(4px)" }
				}
				transition={{ type: "spring", duration: 0.35, bounce: 0, delay: reduceMotion ? 0 : 0.1 + index * 0.07 }}
				viewport={{ once: true, margin: "-5%" }}
				whileInView={reduceMotion ? undefined : { opacity: 1, y: 0, filter: "blur(0px)" }}
			>
				{word.text}
			</motion.span>
		</span>
	);
}

export function HeroWordRevealSection() {
	const reduceMotion = useReducedMotion();

	return (
		<SectionShell spacingMode="section" className="relative isolate flex min-h-[min(88vh,52rem)] flex-col justify-center overflow-hidden">
			<div aria-hidden className="pointer-events-none absolute inset-0 -z-10">
				<div className="absolute top-1/2 left-1/2 h-[min(100vw,720px)] w-[min(100vw,720px)] -translate-x-1/2 -translate-y-1/2 rounded-full bg-primary/[0.05] blur-3xl" />
				<GridPattern
					className="absolute inset-0 mask-[radial-gradient(ellipse_at_center,white,transparent_68%)] stroke-foreground/[0.05]"
					height={56}
					width={56}
				/>
			</div>

			<ContentRail>
				<motion.p
					className="text-pretty text-muted-foreground text-sm lowercase tracking-wide"
					initial={reduceMotion ? false : { opacity: 0, y: 8, filter: "blur(4px)" }}
					transition={spring}
					viewport={{ once: true }}
					whileInView={reduceMotion ? undefined : { opacity: 1, y: 0, filter: "blur(0px)" }}
				>
					open registry
				</motion.p>

				<h1 className="mt-6 max-w-5xl font-medium text-[clamp(2.25rem,6.5vw,4.75rem)] leading-[1.02] tracking-[-0.04em]">
					{headlineLines.map((line, lineIndex) => {
						let wordOffset = 0;
						for (let i = 0; i < lineIndex; i++) {
							wordOffset += headlineLines[i]!.length;
						}

						return (
							<span
								className="flex flex-wrap items-baseline gap-x-[0.28em]"
								key={line.map((word) => word.text).join("-")}
							>
								{line.map((word, wordIndex) => (
									<RevealWord
										index={wordOffset + wordIndex}
										key={`${word.text}-${wordIndex}`}
										reduceMotion={!!reduceMotion}
										word={word}
									/>
								))}
							</span>
						);
					})}
				</h1>

				<motion.p
					className="mt-8 max-w-md text-pretty text-muted-foreground text-sm leading-relaxed md:text-base"
					initial={reduceMotion ? false : { opacity: 0, y: 10 }}
					transition={{ ...spring, delay: 0.5 }}
					viewport={{ once: true }}
					whileInView={reduceMotion ? undefined : { opacity: 1, y: 0 }}
				>
					Browse motion-first sections, copy prompts, and add components to your
					project — without rebuilding the same layout twice.
				</motion.p>

				<motion.div
					className="mt-10 flex flex-wrap gap-3"
					initial={reduceMotion ? false : { opacity: 0, y: 10 }}
					transition={{ ...spring, delay: 0.58 }}
					viewport={{ once: true }}
					whileInView={reduceMotion ? undefined : { opacity: 1, y: 0 }}
				>
					<Button className="active:scale-[0.96] transition-transform">
						Browse blocks
						<ArrowRightIcon data-icon="inline-end" />
					</Button>
					<Button className="active:scale-[0.96] transition-transform" variant="outline">
						View pricing
					</Button>
				</motion.div>
			</ContentRail>
		</SectionShell>
	);
}
```
