# Cta 7

> Copy-prompt variant cycle with blur handoff — features-10 DNA for AI editor workflow.

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

- Page, with a live preview: https://shadcn.exchange/r/nusaiba/cta-7
- Screenshot: https://abzjhwgwpdpz7ua5.public.blob.vercel-storage.com/shots/nusaiba/cta-7-light-af19fb9a29143bd4.png
- Registry item: https://nusaiba.dev/r/base-nova/cta-7.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-7.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`, `button`

## Files

### `registry/blocks/cta/cta-data.ts`

```ts
export const ctaMarqueeBlocks = [
	{ name: "features-11", href: "/blocks/features/features-11" },
	{ name: "pricing-3", href: "/blocks/pricing/pricing-3" },
	{ name: "footer-5", href: "/blocks/footer/footer-5" },
	{ name: "hero-4", href: "/blocks/hero/hero-4" },
	{ name: "logo-cloud-16", href: "/blocks/logo-cloud/logo-cloud-16" },
	{ name: "cta-3", href: "/blocks/cta/cta-3" },
] as const;

export const ctaCliCommands = [
	{ block: "hero-4", command: "npx shadcn add @nusaiba/hero-4" },
	{ block: "pricing-2", command: "npx shadcn add @nusaiba/pricing-2" },
	{ block: "footer-5", command: "npx shadcn add @nusaiba/footer-5" },
] as const;

export const ctaPromptVariants = [
	{
		id: "hero-4",
		text: "Install hero-4 from @nusaiba — motion word reveal, preview live, copy into src/components with shadcn CLI.",
	},
	{
		id: "footer-5",
		text: "Add footer-5 bento grid footer from @nusaiba-pro — includes footer-data.ts and motion stagger on view.",
	},
	{
		id: "pricing-2",
		text: "Install pricing-2 center-stage toggle from @nusaiba — spring focus shift between tiers, registry-native copy.",
	},
] as const;

export const ctaDeckCards = [
	{
		title: "Hero Word Reveal",
		category: "hero-4",
		image: "/images/visuals/beams.png",
		alt: "Vertical light beams preview",
	},
	{
		title: "Pricing Center Stage",
		category: "pricing-2",
		image: "/images/visuals/plasma.png",
		alt: "Soft plasma smoke preview",
	},
	{
		title: "Footer Bento Grid",
		category: "footer-5",
		image: "/images/visuals/threads.png",
		alt: "Flowing threads preview",
	},
] as const;
```

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

```tsx
"use client";

import Link from "next/link";
import { useEffect, useRef, useState } from "react";
import { CheckIcon, ClipboardCopyIcon } from "lucide-react";
import { AnimatePresence, motion, useInView, useReducedMotion } from "motion/react";
import { Button, buttonVariants } from "@/components/ui/button";
import { motionStagger, motionViewport } from "@/lib/motion-tokens";
import { cn } from "@/lib/utils";
import { ctaPromptVariants } from "../cta-data";
import { SectionShell } from "@/components/layout-contract";

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

export function CtaPromptHandoffSection() {
	const reduceMotion = useReducedMotion();
	const ref = useRef<HTMLElement>(null);
	const inView = useInView(ref, motionViewport);
	const [activeIndex, setActiveIndex] = useState(0);
	const [paused, setPaused] = useState(false);
	const [copied, setCopied] = useState(false);
	const active = ctaPromptVariants[activeIndex]!;

	useEffect(() => {
		if (reduceMotion || paused || !inView) return;
		const timer = setInterval(() => {
			setActiveIndex((current) => (current + 1) % ctaPromptVariants.length);
		}, CYCLE_MS);
		return () => clearInterval(timer);
	}, [inView, paused, reduceMotion]);

	async function copyPrompt() {
		await navigator.clipboard.writeText(active.text);
		setCopied(true);
		window.setTimeout(() => setCopied(false), 1800);
	}

	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-2xl text-center"
				initial="hidden"
				variants={{
					hidden: {},
					visible: { transition: { staggerChildren: motionStagger.item, delayChildren: 0.04 } },
				}}
			>
				<motion.p
					className="font-medium text-primary text-xs uppercase tracking-[0.22em]"
					variants={itemVariants}
				>
					Copy prompt
				</motion.p>
				<motion.h2
					className="mt-3 text-balance font-medium text-3xl tracking-tight md:text-4xl"
					variants={itemVariants}
				>
					Hand off to your editor
				</motion.h2>
				<motion.p
					className="mx-auto mt-4 max-w-md text-pretty text-muted-foreground text-sm md:text-base"
					variants={itemVariants}
				>
					Each block page includes an install prompt — copy, paste, and let your AI
					assistant wire the files.
				</motion.p>

				<motion.div
					className="mt-8 overflow-hidden rounded-2xl border border-border bg-muted/30 p-1 text-left shadow-[0_4px_20px_rgba(0,0,0,0.04)]"
					onMouseEnter={() => setPaused(true)}
					onMouseLeave={() => setPaused(false)}
					variants={itemVariants}
				>
					<div className="rounded-xl bg-background px-4 py-4 md:px-5 md:py-5">
						<p className="font-medium text-[11px] text-muted-foreground uppercase tracking-[0.14em]">
							Prompt · {active.id}
						</p>
						<div className="relative mt-3 min-h-[4.5rem]">
							<AnimatePresence initial={false} mode="wait">
								<motion.p
									animate={{ opacity: 1, y: 0, filter: "blur(0px)" }}
									className="text-pretty text-foreground text-sm leading-relaxed"
									exit={{ opacity: 0, y: -4, filter: "blur(4px)", transition: { duration: 0.15 } }}
									initial={{ opacity: 0, y: 4, filter: "blur(4px)" }}
									key={active.id}
									transition={spring}
								>
									{active.text}
								</motion.p>
							</AnimatePresence>
						</div>
					</div>
				</motion.div>

				<motion.div
					className="mt-6 flex flex-col items-center justify-center gap-3 sm:flex-row"
					variants={itemVariants}
				>
					<Button
						className="active:scale-[0.96] transition-transform"
						onClick={copyPrompt}
						type="button"
					>
						<span className="relative inline-flex size-4 items-center justify-center">
							<AnimatePresence initial={false} mode="wait">
								<motion.span
									animate={{ opacity: 1, scale: 1, filter: "blur(0px)" }}
									className="absolute inset-0 flex items-center justify-center"
									exit={{ opacity: 0, scale: 0.25, filter: "blur(4px)" }}
									initial={{ opacity: 0, scale: 0.25, filter: "blur(4px)" }}
									key={copied ? "check" : "copy"}
									transition={spring}
								>
									{copied ? <CheckIcon className="size-4" /> : <ClipboardCopyIcon className="size-4" />}
								</motion.span>
							</AnimatePresence>
						</span>
						{copied ? "Copied" : "Copy prompt"}
					</Button>
					<Link
						className={cn(
							buttonVariants({ variant: "outline" }),
							"active:scale-[0.96] transition-transform",
						)}
						href="/blocks"
					>
						Browse blocks
					</Link>
				</motion.div>
			</motion.div>
		</SectionShell>
	);
}
```
