"use client"; import { useSyncExternalStore } from "react"; import { useReducedMotion } from "framer-motion"; /** * The Settings "reduce motion" toggle (default off). It mirrors the OS * preference: the scene stops moving, CSS transitions collapse, and * framer-motion runs in its calm mode via MotionPreference. One shared store, * so the toggle and every consumer update together. */ const KEY = "freezmo.reduce-motion.v1"; let cached: boolean | null = null; const listeners = new Set<() => void>(); function read(): boolean { try { return window.localStorage.getItem(KEY) === "1"; } catch { return false; } } function snapshot(): boolean { cached ??= read(); return cached; } export function applyReducedMotion(next: boolean) { cached = next; document.documentElement.classList.toggle("reduce-motion", next); try { window.localStorage.setItem(KEY, next ? "1" : "0"); } catch { /* private mode */ } listeners.forEach((listener) => listener()); } export function useReducedMotionPref() { const reduced = useSyncExternalStore( (listener) => { listeners.add(listener); return () => listeners.delete(listener); }, snapshot, () => false, ); return { reduced, setReduced: applyReducedMotion }; } /** * The OS preference *or* the in-app toggle. MotionConfig only strips transform * animations, so components that branch on `useReducedMotion` (sheets fading * from transparent, etc.) must read this one — otherwise a manual "reduce * motion" still faded a translucent sheet in, ghosting the page through it. */ export function useAppReducedMotion(): boolean { const system = useReducedMotion(); const { reduced } = useReducedMotionPref(); return Boolean(system) || reduced; }