'use client' import { useEffect, useState } from 'react' const reducedMotion = () => window.matchMedia('(prefers-reduced-motion: reduce)').matches /** Scroll position of the page as 0..1. Updates at most once per frame; frozen when the user prefers reduced motion. */ export function useScrollProgress(): number { const [progress, setProgress] = useState(0) useEffect(() => { if (reducedMotion()) return let frame = 0 const update = () => { frame = 0 const max = document.documentElement.scrollHeight - window.innerHeight setProgress(max > 0 ? Math.min(1, Math.max(0, window.scrollY / max)) : 0) } const schedule = () => { if (!frame) frame = requestAnimationFrame(update) } update() window.addEventListener('scroll', schedule, { passive: true }) window.addEventListener('resize', schedule) return () => { window.removeEventListener('scroll', schedule) window.removeEventListener('resize', schedule) if (frame) cancelAnimationFrame(frame) } }, []) return progress } /** True once the page has scrolled past the very top; only changes state when that flips. */ export function useScrolled(threshold = 8): boolean { const [scrolled, setScrolled] = useState(false) useEffect(() => { const update = () => setScrolled(window.scrollY > threshold) update() window.addEventListener('scroll', update, { passive: true }) return () => window.removeEventListener('scroll', update) }, [threshold]) return scrolled }