"use client"; import { useEffect, useRef, useState } from "react"; /** * useScrollReveal - Hook to detect when elements come into view * Adds/removes 'in-view' class for CSS scroll animations */ export function useScrollReveal( threshold = 0.1, rootMargin = "-50px" ) { const ref = useRef(null); const [isInView, setIsInView] = useState(false); useEffect(() => { const element = ref.current; if (!element) return; const observer = new IntersectionObserver( ([entry]) => { if (entry.isIntersecting) { setIsInView(true); observer.unobserve(element); } else { setIsInView(false); } }, { threshold, rootMargin, } ); observer.observe(element); return () => { observer.disconnect(); }; }, [threshold, rootMargin]); return { ref, isInView }; } /** * useStaggerReveal - Hook for staggered child animations */ export function useStaggerReveal(count: number) { const ref = useRef(null); const [visibleCount, setVisibleCount] = useState(0); useEffect(() => { const element = ref.current; if (!element) return; const observer = new IntersectionObserver( ([entry]) => { if (entry.isIntersecting) { // Animate children in with stagger let current = 0; const interval = setInterval(() => { current++; setVisibleCount(current); if (current >= count) { clearInterval(interval); observer.unobserve(element); } }, 60); return () => clearInterval(interval); } }, { threshold: 0.1, rootMargin: "-50px" } ); observer.observe(element); return () => observer.disconnect(); }, [count]); return { ref, visibleCount }; } /** * useParallax - Hook for parallax scroll effects */ export function useParallax(speed: number = 0.5) { const [offset, setOffset] = useState(0); useEffect(() => { let ticking = false; const handleScroll = () => { if (!ticking) { requestAnimationFrame(() => { setOffset(window.scrollY * speed); ticking = false; }); ticking = true; } }; window.addEventListener("scroll", handleScroll, { passive: true }); return () => { window.removeEventListener("scroll", handleScroll); }; }, [speed]); return offset; } /** * useReducedMotion - Hook to check user's motion preference */ export function useReducedMotion(): boolean { const [prefersReduced, setPrefersReduced] = useState(() => { // Check synchronously during render to avoid flash if (typeof window !== "undefined") { return window.matchMedia("(prefers-reduced-motion: reduce)").matches; } return false; }); useEffect(() => { const mediaQuery = window.matchMedia("(prefers-reduced-motion: reduce)"); const handler = (e: MediaQueryListEvent) => setPrefersReduced(e.matches); mediaQuery.addEventListener("change", handler); return () => mediaQuery.removeEventListener("change", handler); }, []); return prefersReduced; } /** * useMousePosition - Hook to track mouse position */ export function useMousePosition() { const [position, setPosition] = useState({ x: 0, y: 0 }); useEffect(() => { const handleMouseMove = (e: MouseEvent) => { setPosition({ x: e.clientX, y: e.clientY }); }; window.addEventListener("mousemove", handleMouseMove); return () => { window.removeEventListener("mousemove", handleMouseMove); }; }, []); return position; }