"use client"; import { useEffect, useRef, useState, useCallback } from "react"; interface UseScrollAnimationOptions { threshold?: number; rootMargin?: string; triggerOnce?: boolean; } export function useScrollAnimation( options: UseScrollAnimationOptions = {}, ) { const { threshold = 0.1, rootMargin = "0px", triggerOnce = true } = options; const ref = useRef(null); const [isVisible, setIsVisible] = useState(false); const [hasTriggered, setHasTriggered] = useState(false); useEffect(() => { if (typeof window === "undefined") return; // extra safety const element = ref.current; if (!element) return; const observer = new IntersectionObserver( ([entry]) => { if (entry.isIntersecting) { setIsVisible(true); if (triggerOnce) { setHasTriggered(true); observer.unobserve(element); } } else if (!triggerOnce) { setIsVisible(false); } }, { threshold, rootMargin }, ); observer.observe(element); return () => observer.disconnect(); }, [threshold, rootMargin, triggerOnce]); const reset = useCallback(() => { setIsVisible(false); setHasTriggered(false); }, []); return { ref, isVisible: triggerOnce ? isVisible || hasTriggered : isVisible, reset, }; } export function useCountUp( end: number, duration: number = 2000, start: number = 0, ) { const [count, setCount] = useState(start); const [isAnimating, setIsAnimating] = useState(false); const countRef = useRef(start); const startAnimation = useCallback(() => { if (isAnimating) return; setIsAnimating(true); const startTime = Date.now(); const range = end - start; const animate = () => { const elapsed = Date.now() - startTime; const progress = Math.min(elapsed / duration, 1); const easeOut = 1 - Math.pow(1 - progress, 3); const currentCount = Math.floor(start + range * easeOut); countRef.current = currentCount; setCount(currentCount); if (progress < 1) { requestAnimationFrame(animate); } else { setCount(end); setIsAnimating(false); } }; requestAnimationFrame(animate); }, [end, duration, start, isAnimating]); const reset = useCallback(() => { setCount(start); setIsAnimating(false); }, [start]); return { count, startAnimation, reset, isAnimating }; } export function useParallax(speed: number = 0.5) { const ref = useRef(null); const [offset, setOffset] = useState(0); useEffect(() => { if (typeof window === "undefined") return; const handleScroll = () => { if (!ref.current) return; const rect = ref.current.getBoundingClientRect(); const scrolled = window.scrollY; const elementTop = rect.top + scrolled; const relativeScroll = scrolled - elementTop + window.innerHeight; setOffset(relativeScroll * speed * 0.1); }; window.addEventListener("scroll", handleScroll, { passive: true }); handleScroll(); return () => { window.removeEventListener("scroll", handleScroll); }; }, [speed]); return { ref, offset }; }