type CompletionId = string type CompletionTimer = number type CompletionFrame = number export const COMPLETION_EXIT_DURATION = 320 export function shouldAnimateCompletionExit({ completing, showCompleted }: { completing: boolean; showCompleted: boolean }) { return completing && !showCompleted } export function completionExitDelay(reducedMotion: boolean) { return reducedMotion ? 0 : COMPLETION_EXIT_DURATION } export function prefersReducedMotion() { return window.matchMedia?.('(prefers-reduced-motion: reduce)').matches ?? false } export function waitForCompletionExit(reducedMotion = prefersReducedMotion()) { const delay = completionExitDelay(reducedMotion) return delay ? new Promise((resolve) => window.setTimeout(resolve, delay)) : Promise.resolve() } export function createCompletionPulse( activate: (id: CompletionId) => void, deactivate: (id: CompletionId) => void, duration = 420, ) { const timers = new Map() const frames = new Map() return (id: CompletionId) => { const previousTimer = timers.get(id) if (previousTimer !== undefined) window.clearTimeout(previousTimer) const previousFrame = frames.get(id) if (previousFrame !== undefined) window.cancelAnimationFrame(previousFrame) deactivate(id) const frame = window.requestAnimationFrame(() => { frames.delete(id) activate(id) timers.set(id, window.setTimeout(() => { deactivate(id) timers.delete(id) }, duration)) }) frames.set(id, frame) } }