"use client"; import { useRef, type MouseEvent, type ReactNode } from "react"; import { motion, useMotionValue, useReducedMotion, useSpring, useTransform, type MotionValue, } from "motion/react"; export type DockSphereItem = { label: string; href: string; /** Icon inside the sphere (an svg works best). */ icon: ReactNode; /** Sphere colour. */ color?: string; }; type DockSphereProps = { items: DockSphereItem[]; /** Resting sphere size in pixels. */ size?: number; /** Size of the sphere right under the pointer, as a multiple of `size`. */ magnify?: number; /** Pixels from the pointer where spheres start to grow. */ reach?: number; /** Called on click. Call e.preventDefault() to stop the browser following the link. */ onSelect?: (item: DockSphereItem, e: MouseEvent) => void; /** Name read out by screen readers. */ label?: string; className?: string; }; /* DockSphere: a dock of glossy spheres. Spheres near the pointer swell, the rest stay small. */ export function DockSphere({ items, size = 44, magnify = 1.7, reach = 140, onSelect, label = "Dock", className = "rounded-full border border-white/10 bg-white/5 px-4 backdrop-blur-md", }: DockSphereProps) { const mouseX = useMotionValue(Infinity); return ( ); } function Sphere({ item, mouseX, size, magnify, reach, onSelect, }: { item: DockSphereItem; mouseX: MotionValue; size: number; magnify: number; reach: number; onSelect?: DockSphereProps["onSelect"]; }) { const reduce = useReducedMotion(); const ref = useRef(null); const distance = useTransform(mouseX, (x) => { const r = ref.current?.getBoundingClientRect(); if (!r) return reach; return x - (r.left + r.width / 2); }); const target = useTransform(distance, [-reach, 0, reach], [size, size * magnify, size]); const width = useSpring(target, { stiffness: 320, damping: 22, mass: 0.2 }); const c = item.color ?? "#a78bfa"; return ( onSelect?.(item, e)} onFocus={() => { const r = ref.current?.getBoundingClientRect(); if (r) mouseX.set(r.left + r.width / 2); }} onBlur={() => mouseX.set(Infinity)} style={{ width: reduce ? size : width, height: reduce ? size : width }} className="group relative grid shrink-0 place-items-center rounded-full outline-none focus-visible:ring-2 focus-visible:ring-white/80 focus-visible:ring-offset-2 focus-visible:ring-offset-black" > {item.icon} {item.label} ); }