/** A rectangle measured in viewport CSS pixels. */ export interface OriginRect { /** Distance from the viewport's top edge in CSS pixels. */ top: number; /** Distance from the viewport's left edge in CSS pixels. */ left: number; /** Rectangle width in CSS pixels. */ width: number; /** Rectangle height in CSS pixels. */ height: number; } /** Clamp a value to the inclusive unit range `0..1`. */ export function clamp01(value: number): number { return Math.max(0, Math.min(1, value)); } /** Clamp a value to the inclusive range `min..max`. */ export function clamp(value: number, min: number, max: number): number { return Math.max(min, Math.min(max, value)); } /** Measure an element's viewport bounds as a plain, serializable rectangle. */ export function measureRect(element: Element): OriginRect { const { top, left, width, height } = element.getBoundingClientRect(); return { top, left, width, height }; } /** Bounds of the browser window, or a unit rectangle outside a browser. */ export function windowRect(): OriginRect { const browser = typeof window !== "undefined"; return { top: 0, left: 0, width: browser ? window.innerWidth : 1, height: browser ? window.innerHeight : 1, }; } /** * Whether animation should resolve immediately. * * True outside a browser, when `requestAnimationFrame` is unavailable, or when * the user prefers reduced motion. */ export function prefersReducedMotion(): boolean { return ( typeof window === "undefined" || !window.requestAnimationFrame || Boolean(window.matchMedia?.("(prefers-reduced-motion: reduce)").matches) ); }