"use client"; // Adapted from the Magic UI dock interaction (MIT). See THIRD_PARTY_NOTICES.md. import { type ComponentProps, useEffect, useRef } from "react"; type DockProps = ComponentProps<"nav"> & { /** Maximum icon scale. CSS --dock-magnification is also supported. */ magnification?: number; /** Pointer influence radius in CSS pixels. */ distance?: number; direction?: "top" | "middle" | "bottom"; }; export function Dock({ className = "", "aria-label": label = "Quick navigation", magnification, distance = 150, direction = "bottom", onPointerEnter, onPointerMove, onPointerLeave, onFocus, onBlur, ref: forwardedRef, ...props }: DockProps) { const root = useRef(null); const frame = useRef(0); const geometry = useRef< { node: HTMLAnchorElement; center: number; width: number }[] >([]); function reset() { cancelAnimationFrame(frame.current); frame.current = 0; root.current ?.querySelectorAll(".ynr-dock-item") .forEach((item) => { item.style.removeProperty("--dock-item-growth"); item.style.removeProperty("--dock-item-scale"); }); geometry.current = []; } function measure() { geometry.current = Array.from( root.current?.querySelectorAll(".ynr-dock-item") ?? [], (node) => { const rect = node.getBoundingClientRect(); return { node, center: rect.left + rect.width / 2, width: rect.width }; }, ); } function magnify(clientX: number) { if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) return; if (!geometry.current.length) measure(); cancelAnimationFrame(frame.current); frame.current = requestAnimationFrame(() => { frame.current = 0; const cssScale = Number.parseFloat( getComputedStyle(root.current!).getPropertyValue( "--dock-magnification", ), ); const maximum = Math.max( 1, Math.min( 2, Number.isFinite(magnification) ? magnification! : Number.isFinite(cssScale) ? cssScale : 1.5, ), ); const radius = Math.max(1, Number.isFinite(distance) ? distance : 150); // Cached resting centers avoid layout reads while icons expand around the pointer. for (const { node, center, width } of geometry.current) { const influence = Math.max(0, 1 - Math.abs(clientX - center) / radius); const scale = 1 + (maximum - 1) * influence * influence; node.style.setProperty( "--dock-item-growth", `${width * (scale - 1)}px`, ); node.style.setProperty("--dock-item-scale", String(scale)); } }); } useEffect(() => { const reduced = window.matchMedia("(prefers-reduced-motion: reduce)"); reduced.addEventListener("change", reset); window.addEventListener("resize", reset); return () => { cancelAnimationFrame(frame.current); reduced.removeEventListener("change", reset); window.removeEventListener("resize", reset); }; }, []); return (