"use client"; import { Fragment, useId, type CSSProperties, type ElementType, type ReactNode } from "react"; import { motion, useReducedMotion } from "motion/react"; type HeroNovaProps = { title: string; /** Small line above the title. */ eyebrow?: string; subtitle?: string; /** Buttons or links under the text. */ children?: ReactNode; /** Colours of the burst, from core to rim. */ colors?: [string, string, string]; /** h1 by default. Use h2 if the page already has an h1. */ headingAs?: ElementType; /** Set the height, background and text colour here. */ className?: string; }; const EASE = [0.16, 1, 0.3, 1] as const; // A circle of n% of the section width, centred on its parent. Never too small on a phone. const disc = (n: number): CSSProperties => { const size = `max(${n}cqw, ${n * 7}px)`; return { width: size, height: size, left: `calc(${size} / -2)`, top: `calc(${size} / -2)` }; }; /* HeroNova: a centred hero. A star bursts behind the title, throws a shock ring, then settles into a slow glow while the words rise in. */ export function HeroNova({ title, eyebrow, subtitle, children, colors = ["#ffffff", "#a78bfa", "#22d3ee"], headingAs: Heading = "h1", className = "min-h-[36rem] bg-[#07060f] text-white", }: HeroNovaProps) { const reduce = useReducedMotion(); const id = useId(); const [core, mid, rim] = colors; const words = title.split(" "); const rise = (delay: number) => reduce ? { initial: { opacity: 0 }, animate: { opacity: 1 }, transition: { duration: 0.4 } } : { initial: { opacity: 0, y: 24 }, animate: { opacity: 1, y: 0 }, transition: { duration: 0.8, delay, ease: EASE } }; return (
{eyebrow && ( {eyebrow} )} {title} {words.map((word, i) => ( {word} {" "} ))} {subtitle && ( {subtitle} )} {children && ( {children} )}
); }