| 1 | // Shared timing configuration for CSS and Web Animations API transitions. |
| 2 | // The curves mirror the desktop CSS motion tokens. |
| 3 | |
| 4 | /** 120ms — color/border hovers, tooltips. */ |
| 5 | export const DUR_FAST = 0.12; |
| 6 | |
| 7 | /** 180ms — popovers, menus, small enters. Matches CSS --dur-base. */ |
| 8 | export const DUR_BASE = 0.18; |
| 9 | |
| 10 | /** 340ms — drawers, modals, panel slides. Matches CSS --dur-slow. */ |
| 11 | export const DUR_SLOW = 0.34; |
| 12 | |
| 13 | /** Fast-out/decelerate curve used by expanding and entrance animations. */ |
| 14 | export const CSS_EASE_OUT = "cubic-bezier(0.2, 0.72, 0.2, 1)"; |
| 15 | |
| 16 | /** Accelerating curve used by short exit animations. */ |
| 17 | export const CSS_EASE_IN = "cubic-bezier(0.8, 0, 0.8, 0.28)"; |
| 18 | |
| 19 | /** Runs a short exit transition without letting cosmetic failures block work. */ |
| 20 | export function animateElementExit( |
| 21 | el: HTMLElement, |
| 22 | options: { opacity: number; y: number; duration: number; onComplete: () => void }, |
| 23 | ) { |
| 24 | let completed = false; |
| 25 | const complete = () => { |
| 26 | if (completed) return; |
| 27 | completed = true; |
| 28 | options.onComplete(); |
| 29 | }; |
| 30 | if (typeof el.animate !== "function") { |
| 31 | complete(); |
| 32 | return; |
| 33 | } |
| 34 | let animation: Animation; |
| 35 | try { |
| 36 | animation = el.animate( |
| 37 | [ |
| 38 | { opacity: 1, transform: "translateY(0)" }, |
| 39 | { opacity: options.opacity, transform: `translateY(${options.y}px)` }, |
| 40 | ], |
| 41 | { duration: options.duration * 1000, easing: CSS_EASE_IN }, |
| 42 | ); |
| 43 | } catch { |
| 44 | complete(); |
| 45 | return; |
| 46 | } |
| 47 | animation.onfinish = complete; |
| 48 | animation.oncancel = complete; |
| 49 | } |
| 50 | |
| 51 | /** Returns true when the user has requested reduced motion at the OS level. */ |
| 52 | export function prefersReducedMotion(): boolean { |
| 53 | if (typeof window === "undefined" || !window.matchMedia) return false; |
| 54 | return window.matchMedia("(prefers-reduced-motion: reduce)").matches; |
| 55 | } |
| 56 |