# Cta 6

> Free vs Pro fork with center-stage focus shift — pricing-2 DNA for registry path choice.

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

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

## Install

```bash
npx shadcn@latest add https://nusaiba.dev/r/base-nova/cta-6.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`

## Files

### `registry/blocks/cta/6/cta-section.tsx`

```tsx
"use client";

import Link from "next/link";
import { useCallback, useEffect, useRef, useState } from "react";
import { ArrowRightIcon, CheckIcon } from "lucide-react";
import { motion, useInView, useReducedMotion } from "motion/react";
import { buttonVariants } from "@/components/ui/button";
import { motionStagger, motionViewport } from "@/lib/motion-tokens";
import { cn } from "@/lib/utils";
import { SectionShell } from "@/components/layout-contract";

const spring = { type: "spring" as const, duration: 0.3, bounce: 0 };
const CYCLE_MS = 3600;

const paths = [
	{
		id: "free",
		eyebrow: "Open registry",
		title: "@nusaiba",
		points: ["Free blocks", "Live preview", "Copy prompt"],
		cta: "Browse free",
		href: "/blocks",
		variant: "outline" as const,
	},
	{
		id: "pro",
		eyebrow: "Pro registry",
		title: "@nusaiba-pro",
		points: ["Full catalog", "Registry token", "Commercial license"],
		cta: "Get Pro access",
		href: "/pricing",
		variant: "default" as const,
		popular: true,
	},
] as const;

export function CtaDualPathSection() {
	const reduceMotion = useReducedMotion();
	const ref = useRef<HTMLElement>(null);
	const inView = useInView(ref, motionViewport);
	const [active, setActive] = useState<"free" | "pro">("pro");
	const [hasFocus, setHasFocus] = useState(false);

	const activate = useCallback((id: "free" | "pro") => {
		setActive(id);
		setHasFocus(true);
	}, []);

	const deactivate = useCallback(() => setHasFocus(false), []);

	useEffect(() => {
		if (reduceMotion || hasFocus || !inView) return;
		const timer = setInterval(() => {
			setActive((current) => (current === "pro" ? "free" : "pro"));
		}, CYCLE_MS);
		return () => clearInterval(timer);
	}, [hasFocus, inView, reduceMotion]);

	const itemVariants = reduceMotion
		? { hidden: { opacity: 1 }, visible: { opacity: 1 } }
		: {
				hidden: { opacity: 0, filter: "blur(4px)", y: 8 },
				visible: { opacity: 1, filter: "blur(0px)", y: 0, transition: spring },
			};

	return (
		<SectionShell spacingMode="section" ref={ref}>
			<motion.div
				animate={inView ? "visible" : "hidden"}
				className="mx-auto w-full max-w-4xl"
				initial="hidden"
				variants={{
					hidden: {},
					visible: { transition: { staggerChildren: motionStagger.item, delayChildren: 0.04 } },
				}}
			>
				<motion.div className="mx-auto max-w-xl text-center" variants={itemVariants}>
					<p className="font-medium text-primary text-xs uppercase tracking-[0.22em]">Choose</p>
					<h2 className="mt-3 text-balance font-medium text-3xl tracking-tight md:text-4xl">
						Pick your install path
					</h2>
					<p className="mt-4 text-pretty text-muted-foreground text-sm md:text-base">
						Start with open blocks or unlock the full motion catalog with a registry
						token.
					</p>
				</motion.div>

				<motion.div
					className="mt-10 grid gap-4 sm:grid-cols-2"
					variants={itemVariants}
				>
					{paths.map((path) => {
						const isActive = active === path.id;
						const dimmed = hasFocus ? !isActive : !isActive;

						return (
							<div
								className={cn(
									"relative rounded-2xl border p-6 transition-[border-color,box-shadow,transform,opacity,filter] md:p-8",
									"shadow-[0_1px_0_rgba(0,0,0,0.04)]",
									isActive
										? "border-foreground/15 bg-muted/20"
										: "border-border bg-background",
									dimmed && !reduceMotion && "opacity-70 blur-[0.5px]",
								)}
								key={path.id}
								onBlur={deactivate}
								onFocus={() => activate(path.id)}
								onMouseEnter={() => activate(path.id)}
								onMouseLeave={deactivate}
							>
								{"popular" in path && path.popular ? (
									<span className="absolute top-4 right-4 rounded-full border border-border bg-background px-2 py-0.5 font-medium text-[10px] text-muted-foreground uppercase tracking-wide">
										Popular
									</span>
								) : null}
								<p className="font-medium text-primary text-xs uppercase tracking-[0.18em]">
									{path.eyebrow}
								</p>
								<p className="mt-2 font-medium font-mono text-xl tracking-tight">
									{path.title}
								</p>
								<ul className="mt-5 space-y-2">
									{path.points.map((point) => (
										<li className="flex items-center gap-2 text-muted-foreground text-sm" key={point}>
											<CheckIcon className="size-3.5 shrink-0 text-foreground" />
											{point}
										</li>
									))}
								</ul>
								<Link
									className={cn(
										buttonVariants({ variant: path.variant }),
										"mt-6 w-full active:scale-[0.96] transition-transform",
									)}
									href={path.href}
								>
									{path.cta}
									<ArrowRightIcon data-icon="inline-end" />
								</Link>
							</div>
						);
					})}
				</motion.div>
			</motion.div>
		</SectionShell>
	);
}
```
