"use client"; import { useRef, type CSSProperties, type ElementType, type PointerEvent, type ReactNode } from "react"; import { motion, useInView, useReducedMotion } from "motion/react"; type CTARevealProps = { title: string; text?: string; /** Small line above the title. */ eyebrow?: string; /** Buttons or links. */ children?: ReactNode; /** Panel colour at one corner. */ from?: string; /** Panel colour at the other corner. */ to?: string; /** Colour of the light under the pointer and of the opening flash. */ glow?: string; /** Plays once. false replays each time it comes back on screen. */ once?: boolean; /** h2 by default. */ headingAs?: ElementType; /** Set padding here. The panel sits inside. */ className?: string; }; const EASE = [0.16, 1, 0.3, 1] as const; /* CTAReveal: a call to action that opens from a thin band of light when it comes on screen. The words rise in, then the buttons, and a soft light follows the pointer. */ export function CTAReveal({ title, text, eyebrow, children, from = "#4c1d95", to = "#0e7490", glow = "#67e8f9", once = true, headingAs: Heading = "h2", className = "bg-[#0b0a14] px-5 py-16", }: CTARevealProps) { const reduce = useReducedMotion(); const box = useRef(null); const seen = useInView(box, { once, amount: 0.3 }); const state = seen ? "open" : "shut"; const words = title.split(" "); const track = (e: PointerEvent) => { const box = e.currentTarget.getBoundingClientRect(); e.currentTarget.style.setProperty("--cr-x", `${e.clientX - box.left}px`); e.currentTarget.style.setProperty("--cr-y", `${e.clientY - box.top}px`); }; const rise = (delay: number) => ({ shut: reduce ? { opacity: 0 } : { opacity: 0, y: "0.6em" }, open: { opacity: 1, y: 0, transition: { duration: reduce ? 0.3 : 0.7, delay: reduce ? 0 : delay, ease: EASE } }, }); return (
{eyebrow && ( {eyebrow} )} {title} {words.map((word, i) => ( {word} {i < words.length - 1 && " "} ))} {text && ( {text} )} {children && ( {children} )}
); }