ouroboros / source /src /Motion.tsx
REXWind's picture
Publish the Ouroboros scientific showcase to CladeTeam
03677d0 verified
Raw History Blame Contribute Delete
2.14 kB
import {createContext, useContext, useEffect, useState} from 'react';
import type {ReactNode} from 'react';
const MotionContext = createContext({enabled:true, reduced:false, paused:false, toggle:()=>{}});
/** A single preference governs ambient movement, tours and archived playback. */
export function MotionProvider({children}:{children:ReactNode}) {
const [hidden,setHidden]=useState(document.hidden);
const [reduced,setReduced]=useState(()=>window.matchMedia('(prefers-reduced-motion: reduce)').matches);
const [paused,setPaused]=useState(()=>{try{return sessionStorage.getItem('ouroboros-motion-paused')==='true';}catch{return false;}});
useEffect(()=>{
const visibility=()=>setHidden(document.hidden);
document.addEventListener('visibilitychange',visibility);
return()=>document.removeEventListener('visibilitychange',visibility);
},[]);
useEffect(()=>{
const query=window.matchMedia('(prefers-reduced-motion: reduce)');
const update=()=>setReduced(query.matches);
query.addEventListener('change',update);
return()=>query.removeEventListener('change',update);
},[]);
useEffect(()=>{
document.documentElement.dataset.motion=paused||reduced||hidden?'paused':'running';
try{sessionStorage.setItem('ouroboros-motion-paused',String(paused));}catch{/* Preferences still work when storage is unavailable. */}
},[paused,reduced,hidden]);
return <MotionContext.Provider value={{enabled:!paused&&!reduced&&!hidden,reduced,paused,toggle:()=>setPaused(value=>!value)}}>{children}</MotionContext.Provider>;
}
export function useMotion(){return useContext(MotionContext);}
export function MotionControl(){
const {enabled,reduced,toggle}=useMotion();
return <button className="motion-control" type="button" onClick={toggle} disabled={reduced} aria-label={reduced?'Animations off: reduced motion preference':enabled?'Pause page animations':'Resume page animations'} aria-pressed={!enabled} title={reduced?'Following your reduced motion preference':enabled?'Pause page animations':'Resume page animations'}><span aria-hidden="true">{enabled?'Ⅱ':'▷'}</span><span>{enabled?'Motion on':'Motion off'}</span></button>;
}