/* ─── HOOKS & UTILITIES ─── */ const { useState, useEffect, useRef, useCallback } = React; function useCountdown(target) { const [t, setT] = useState({ days: 0, hours: 0, mins: 0, secs: 0 }); useEffect(() => { const tick = () => { const diff = Math.max(0, target - Date.now()); setT({ days: Math.floor(diff / 86400000), hours: Math.floor((diff % 86400000) / 3600000), mins: Math.floor((diff % 3600000) / 60000), secs: Math.floor((diff % 60000) / 1000), }); }; tick(); const id = setInterval(tick, 1000); return () => clearInterval(id); }, [target]); return t; } function useInView(threshold = 0.12) { const ref = useRef(null); const [visible, setVisible] = useState(false); useEffect(() => { const el = ref.current; if (!el) return; // Respect reduced-motion users by showing immediately const reduce = window.matchMedia("(prefers-reduced-motion: reduce)").matches; if (reduce) { setVisible(true); return; } const obs = new IntersectionObserver(([e]) => { if (e.isIntersecting) setVisible(true); }, { threshold }); obs.observe(el); return () => obs.disconnect(); }, [threshold]); return [ref, visible]; } function useViewport() { const [w, setW] = useState(typeof window !== "undefined" ? window.innerWidth : 1200); useEffect(() => { const h = () => setW(window.innerWidth); window.addEventListener("resize", h); return () => window.removeEventListener("resize", h); }, []); return { w, isMobile: w < 720, isTablet: w < 980 }; } function useScrolled(threshold = 60) { const [scrolled, setScrolled] = useState(false); useEffect(() => { const h = () => setScrolled(window.scrollY > threshold); h(); window.addEventListener("scroll", h, { passive: true }); return () => window.removeEventListener("scroll", h); }, [threshold]); return scrolled; } window.LTHooks = { useCountdown, useInView, useViewport, useScrolled };