1/**2 * Shared timing contract for menu-like scenes. The renderers keep their own3 * settled geometry; this helper only describes the presentation phase around4 * it so a scene can capture one exit action and ignore later input.5 */6export const SCENE_MOTION_TIMING = Object.freeze({7 entry: 520,8 exit: 280,9 shutter: 140,10 stagger: 35,11});1213export function sceneMotionPhase({14 now = 0,15 openedAt = 0,16 exitStartedAt = null,17 reducedMotion = false,18 timing = SCENE_MOTION_TIMING,19} = {}) {20 if (reducedMotion) return { phase: 'settled', entryProgress: 1, exitProgress: 0, shutterProgress: 0 };21 if (Number.isFinite(exitStartedAt)) {22 const elapsed = Math.max(0, now - exitStartedAt);23 const exitProgress = Math.min(1, elapsed / timing.exit);24 return {25 phase: exitProgress >= 1 ? 'settled' : 'exiting',26 entryProgress: 1,27 exitProgress,28 shutterProgress: Math.min(1, elapsed / timing.shutter),29 };30 }31 const entryProgress = Math.min(1, Math.max(0, (now - openedAt) / timing.entry));32 return { phase: entryProgress >= 1 ? 'settled' : 'opening', entryProgress, exitProgress: 0, shutterProgress: 0 };33}3435export function sceneMotionExitFrame(now, exitStartedAt, timing = SCENE_MOTION_TIMING) {36 const elapsed = Math.max(0, now - exitStartedAt);37 return {38 progress: Math.min(1, elapsed / timing.exit),39 shutterProgress: Math.min(1, elapsed / timing.shutter),40 };41}42