import { tick } from 'svelte'; const prefersReducedMotion = () => typeof window !== 'undefined' && window.matchMedia('(prefers-reduced-motion: reduce)').matches; /** * Runs a day change inside a view transition, so the outgoing day is still on * screen while the incoming one fades in - a real cross-fade rather than the * old content vanishing and the new one fading up from nothing. * * Only the regions that actually change carry a `view-transition-name` (see * `.day-region-*` in routes/layout.css); everything else - the strip, the * header, the page chrome - is pinned by the `day-switch` class so it stays * completely still. */ export async function runDayTransition(update: () => void): Promise { if (prefersReducedMotion() || !document.startViewTransition) { update(); return; } const root = document.documentElement; root.classList.add('day-switch'); try { // Svelte applies the change on the next tick; the transition has to wait // for that before it snapshots the new state. await document.startViewTransition(async () => { update(); await tick(); }).finished; } catch { /* a superseded transition is fine - the DOM is already up to date */ } finally { root.classList.remove('day-switch'); } } /** * Fallback for browsers without view transitions: fade the block back in when * the value passed to it changes. Animates the existing node rather than * remounting it, so the canvas charts keep their zoom state. */ export function daySwap(node: HTMLElement, key: unknown) { let current = key; const play = () => { // view transitions handle it properly where they exist if (typeof document.startViewTransition === 'function' || prefersReducedMotion()) return; node.animate([{ opacity: 0.1 }, { opacity: 1 }], { duration: 460, easing: 'cubic-bezier(0.22, 0.61, 0.36, 1)' }); }; return { update(next: unknown) { if (next === current) return; current = next; play(); } }; }