Split layout with registry preview panel and light parallax — hero-1 asymmetry for install conversion.
@nusaiba
registry/blocks/cta/3/cta-section.tsx
"use client";
import Image from "next/image";
import Link from "next/link";
import { useRef } from "react";
import { ArrowRightIcon } from "lucide-react";
import { motion, useInView, useReducedMotion, useScroll, useTransform } from "motion/react";
import { buttonVariants } from "@/components/ui/button";
import { motionStagger, motionViewport } from "@/lib/motion-tokens";
import { cn } from "@/lib/utils";
import { SectionShell, ContentRail } from "@/components/layout-contract";
const spring = { type: "spring" as const, duration: 0.3, bounce: 0 };
export function CtaSplitVisualSection() {
const reduceMotion = useReducedMotion();
const ref = useRef<HTMLElement>(null);
const inView = useInView(ref, motionViewport);
const { scrollYProgress } = useScroll({
target: ref,
offset: ["start end", "end start"],
});
const imageY = useTransform(scrollYProgress, [0, 1], reduceMotion ? [0, 0] : [12, -12]);
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}>
<ContentRail maxWidth="max-w-5xl" className="grid items-center gap-10 lg:grid-cols-2 lg:gap-14">
<motion.div
animate={inView ? "visible" : "hidden"}
className="max-w-lg"
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}
>
Install
</motion.p>
<motion.h2
className="mt-3 text-balance font-medium text-3xl tracking-tight md:text-4xl"
variants={itemVariants}
>
Copy blocks into your repo
</motion.h2>
<motion.p
className="mt-4 text-pretty text-muted-foreground text-sm leading-relaxed md:text-base"
variants={itemVariants}
>
Preview live on Nusaiba, install with one CLI command, and keep every file in
your project.
</motion.p>
<motion.div
className="mt-8 flex flex-col gap-3 sm:flex-row"
variants={itemVariants}
>
<Link
className={cn(buttonVariants(), "active:scale-[0.96] transition-transform")}
href="/blocks"
>
Browse blocks
<ArrowRightIcon data-icon="inline-end" />
</Link>
<Link
className={cn(
buttonVariants({ variant: "outline" }),
"active:scale-[0.96] transition-transform",
)}
href="/pricing"
>
View pricing
</Link>
</motion.div>
</motion.div>
<motion.div
className="relative"
initial={reduceMotion ? false : { opacity: 0, filter: "blur(4px)", y: 12 }}
transition={spring}
viewport={motionViewport}
whileInView={reduceMotion ? undefined : { opacity: 1, filter: "blur(0px)", y: 0 }}
>
<div className="overflow-hidden rounded-2xl p-1 shadow-[0_8px_30px_rgba(0,0,0,0.06)]">
<motion.div className="relative aspect-[4/3] overflow-hidden rounded-xl" style={{ y: imageY }}>
<Image
alt="Registry preview with motion blocks"
className={cn(
"object-cover object-top",
"outline outline-1 -outline-offset-1 outline-black/10 dark:outline-white/10",
)}
fill
priority={false}
sizes="(max-width: 1024px) 100vw, 50vw"
src="/images/visuals/beams.png"
/>
</motion.div>
</div>
</motion.div>
</ContentRail>
</SectionShell>
);
}































