# Blog 2

> Editorial blog spotlight — thumbnail rail, spring crossfade feature panel, auto-advance on pause-hover.

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

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

## Install

```bash
npx shadcn@latest add https://nusaiba.dev/r/base-nova/blog-2.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`, `@nusaiba/gallery-visuals`, `@nusaiba/decor-icon`, `@magicui/grid-pattern`

## Files

### `registry/blocks/blog/2/blog-section.tsx`

```tsx
"use client";

import Image from "next/image";
import Link from "next/link";
import { useEffect, useState } from "react";
import { ArrowRightIcon, ClockIcon } from "lucide-react";
import { AnimatePresence, motion } from "motion/react";
import { DecorIcon } from "@/components/decor-icon";
import { GridPattern } from "@/components/ui/grid-pattern";
import { cn } from "@/lib/utils";
import { SectionShell, ContentRail } from "@/components/layout-contract";

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

const posts = [
	{
		title: "How we cut release cycles in half",
		excerpt:
			"A practical playbook for smaller teams shipping on tight deadlines — from scoping rituals to async review loops that actually stick.",
		category: "Product",
		date: "Mar 12, 2026",
		readTime: "6 min read",
		image: "/images/visuals/hyperspeed.png",
		alt: "Neon light trails flowing through a dark space",
	},
	{
		title: "Designing onboarding that converts",
		excerpt:
			"Five interaction patterns we use to activate users in the first session, without overwhelming the UI or the team maintaining it.",
		category: "Design",
		date: "Mar 4, 2026",
		readTime: "5 min read",
		image: "/images/visuals/orb.png",
		alt: "Glowing cyan and violet orbital ring",
	},
	{
		title: "What SOC 2 taught our engineering team",
		excerpt:
			"Security controls that improved how we build software — not just checkbox compliance, but clearer ownership and safer defaults.",
		category: "Engineering",
		date: "Feb 22, 2026",
		readTime: "8 min read",
		image: "/images/visuals/color-bends.png",
		alt: "Purple and magenta color bends with film grain",
	},
	{
		title: "Motion tokens for marketing pages",
		excerpt:
			"How we tune spring easing, stagger delays, and whileInView thresholds so sections feel intentional — not like a generic animation pack.",
		category: "Craft",
		date: "Feb 8, 2026",
		readTime: "7 min read",
		image: "/images/visuals/ripple-grid.png",
		alt: "Distorted white grid glowing on black",
	},
];

export function BlogSpotlightSection() {
	const [active, setActive] = useState(0);
	const [paused, setPaused] = useState(false);
	const activePost = posts[active]!;

	useEffect(() => {
		if (paused) return;

		const timer = setInterval(() => {
			setActive((current) => (current + 1) % posts.length);
		}, 5600);

		return () => clearInterval(timer);
	}, [paused]);

	return (
		<SectionShell spacingMode="section" className="relative isolate overflow-hidden">
			<div aria-hidden="true" className="pointer-events-none absolute inset-0 -z-10">
				<div className="absolute top-1/2 left-1/2 h-[420px] w-[640px] -translate-x-1/2 -translate-y-1/2 rounded-full bg-primary/[0.06] blur-3xl" />
				<GridPattern
					className="absolute inset-0 mask-[radial-gradient(ellipse_at_center,white,transparent_72%)] stroke-foreground/[0.05]"
					height={48}
					width={48}
				/>
			</div>

			<ContentRail maxWidth="max-w-5xl" className="space-y-12">
				<motion.div
					className="mx-auto max-w-2xl text-center"
					initial={{ opacity: 0, y: 16 }}
					transition={spring}
					viewport={{ once: true }}
					whileInView={{ opacity: 1, y: 0 }}
				>
					<p className="font-medium text-primary text-xs uppercase tracking-[0.25em]">
						From the journal
					</p>
					<h2 className="mt-3 font-medium text-2xl tracking-tight md:text-4xl">
						Stories from teams shipping in public
					</h2>
					<p className="mt-4 text-muted-foreground text-sm md:text-base">
						Product craft, motion notes, and lessons from building sections that feel
						alive — not another generic blog grid.
					</p>
				</motion.div>

				<motion.div
					className="relative border border-border bg-border p-px"
					initial={{ opacity: 0, y: 20 }}
					onMouseEnter={() => setPaused(true)}
					onMouseLeave={() => setPaused(false)}
					transition={{ ...spring, delay: 0.08 }}
					viewport={{ once: true }}
					whileInView={{ opacity: 1, y: 0 }}
				>
					<DecorIcon position="top-left" />
					<DecorIcon position="top-right" />
					<DecorIcon position="bottom-left" />
					<DecorIcon position="bottom-right" />

					<div className="grid bg-background lg:grid-cols-[minmax(0,12.5rem)_minmax(0,1fr)]">
						<div className="grid grid-cols-2 gap-2 border-b border-border p-4 sm:grid-cols-4 lg:grid-cols-1 lg:gap-2.5 lg:border-r lg:border-b-0 lg:p-5">
							{posts.map((post, index) => {
								const isActive = index === active;

								return (
									<button
										aria-label={`Show article: ${post.title}`}
										aria-pressed={isActive}
										className={cn(
											"group rounded-[var(--radius-md)] border p-2 text-left transition-all",
											isActive
												? "border-foreground/20 bg-muted/50 shadow-sm"
												: "border-transparent opacity-60 hover:border-border hover:opacity-100",
										)}
										key={post.title}
										onClick={() => setActive(index)}
										type="button"
									>
										<div
											className={cn(
												"relative aspect-[4/3] overflow-hidden rounded-[var(--radius-sm)] border border-border/80 bg-muted/30",
												isActive && "ring-1 ring-foreground/10",
											)}
										>
											<Image
												alt={post.alt}
												className="object-cover transition-transform duration-500 group-hover:scale-[1.03]"
												fill
												sizes="120px"
												src={post.image}
											/>
										</div>
										<p className="mt-2 line-clamp-1 font-medium text-[11px]">
											{post.category}
										</p>
										<p className="mt-0.5 line-clamp-2 text-[10px] text-muted-foreground leading-snug">
											{post.title}
										</p>
									</button>
								);
							})}
						</div>

						<div className="relative min-h-[24rem] p-6 md:p-8 lg:min-h-[26rem]">
							<AnimatePresence mode="wait">
								<motion.article
									animate={{ opacity: 1, y: 0 }}
									className="flex h-full flex-col"
									exit={{ opacity: 0, y: -12 }}
									initial={{ opacity: 0, y: 16 }}
									key={activePost.title}
									transition={spring}
								>
									<div className="relative aspect-[16/10] overflow-hidden rounded-[var(--radius-md)] border border-border bg-muted/20">
										<motion.div
											animate={{ scale: 1 }}
											className="absolute inset-0"
											initial={{ scale: 1.04 }}
											transition={{ duration: 0.7, ease: "easeOut" }}
										>
											<Image
												alt={activePost.alt}
												className="object-cover"
												fill
												priority
												sizes="(max-width: 1024px) 100vw, 640px"
												src={activePost.image}
											/>
										</motion.div>
										<div className="absolute inset-0 bg-[radial-gradient(ellipse_at_30%_0%,oklch(1_0_0/0.08),transparent_55%)]" />
									</div>

									<div className="mt-5 flex flex-wrap items-center gap-x-3 gap-y-1 text-muted-foreground text-xs">
										<span className="font-medium text-foreground">
											{activePost.category}
										</span>
										<span aria-hidden>·</span>
										<span>{activePost.date}</span>
										<span aria-hidden>·</span>
										<span className="inline-flex items-center gap-1">
											<ClockIcon className="size-3" />
											{activePost.readTime}
										</span>
									</div>

									<h3 className="mt-3 text-balance font-medium text-xl tracking-tight md:text-2xl">
										<Link className="hover:underline" href="#">
											{activePost.title}
										</Link>
									</h3>

									<p className="mt-3 max-w-2xl text-muted-foreground text-sm leading-relaxed md:text-base">
										{activePost.excerpt}
									</p>

									<Link
										className="group mt-auto inline-flex items-center gap-1.5 pt-6 font-medium text-sm"
										href="#"
									>
										Read article
										<ArrowRightIcon className="size-3.5 transition-transform group-hover:translate-x-0.5" />
									</Link>
								</motion.article>
							</AnimatePresence>
						</div>
					</div>
				</motion.div>
			</ContentRail>
		</SectionShell>
	);
}
```
