"use client"; import { useId, type CSSProperties, type ElementType, type ReactNode } from "react"; type HeroOrbitProps = { title: string; /** Small line above the title. */ eyebrow?: string; subtitle?: string; /** Buttons or links under the text. */ children?: ReactNode; /** Small logos, avatars or icons. They are spread over the 3 rings. Decoration: hidden from screen readers. */ orbiters?: ReactNode[]; /** Seconds for the inner ring to go round once. Outer rings are slower. */ duration?: number; /** Soft patch behind the text that keeps it readable. Match it to the background. */ veil?: 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 RINGS = [ { size: 50, min: 290, pace: 1, way: "normal" }, { size: 72, min: 420, pace: 1.6, way: "reverse" }, { size: 94, min: 550, pace: 2.3, way: "normal" }, ] as const; const PAUSE = "group-hover/orbit:[animation-play-state:paused] motion-reduce:[animation-play-state:paused]"; // Longhand, not the animation shorthand: inline it would reset play-state and the pause classes would lose. const spin = (seconds: number, way: "normal" | "reverse"): CSSProperties => ({ animationName: "hero-orbit-turn", animationDuration: `${seconds}s`, animationTimingFunction: "linear", animationIterationCount: "infinite", animationDirection: way, }); /* HeroOrbit: a centred hero with three rings around the text. Logos or avatars circle on them and stay upright. Hover pauses them. */ export function HeroOrbit({ title, eyebrow, subtitle, children, orbiters = [], duration = 28, veil = "#0b0a14", headingAs: Heading = "h1", className = "min-h-[36rem] bg-[#0b0a14] text-white", }: HeroOrbitProps) { const id = useId(); return (
{RINGS.map((ring, r) => { const onRing = orbiters.filter((_, i) => i % RINGS.length === r); const seconds = duration * ring.pace; const size = `max(${ring.size}cqw, ${ring.min}px)`; return (
{onRing.map((node, i) => { const angle = (360 / onRing.length) * i + r * 40; return (
{node}
); })}
); })}
{eyebrow &&

{eyebrow}

} {title} {subtitle &&

{subtitle}

} {children &&
{children}
}
); }