// ----------------------------------------------------------------------------- // Lou-Ka — Agrégateur de logements à louer (province de Québec) // Auteur : Simon-Pierre Boucher — contact@spboucher.ai // live.ts : hooks « données vivantes » partagés (accueil, court terme) // ----------------------------------------------------------------------------- import { useEffect, useState } from "react"; /** Compteur animé du héro (~0,9 s, easing cubique) — l'impression d'un vrai moteur qui indexe. Respecte prefers-reduced-motion (valeur directe). */ export function useCountUp(target: number | null | undefined, ms = 900): string | null { const [v, setV] = useState(null); useEffect(() => { if (target == null) return; if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) { setV(target); return; } let raf = 0; const t0 = performance.now(); const step = (t: number) => { const p = Math.min(1, (t - t0) / ms); setV(Math.round(target * (1 - Math.pow(1 - p, 3)))); if (p < 1) raf = requestAnimationFrame(step); }; raf = requestAnimationFrame(step); return () => cancelAnimationFrame(raf); }, [target, ms]); return v == null ? null : v.toLocaleString("fr-CA"); } /** « il y a X s » vivant — re-rendu chaque seconde tant que le ts existe. */ export function useAgo(ts: number | null): string | null { const [, tick] = useState(0); useEffect(() => { if (ts == null) return; const id = setInterval(() => tick((x) => x + 1), 1000); return () => clearInterval(id); }, [ts]); if (ts == null) return null; const s = Math.max(0, Math.floor(Date.now() / 1000 - ts)); if (s < 90) return `il y a ${s} s`; if (s < 5400) return `il y a ${Math.round(s / 60)} min`; return `il y a ${Math.round(s / 3600)} h`; }