// ----------------------------------------------------------------------------- // Rent-Ka — Rental listings aggregator (Canada, outside Québec) // Author: Simon-Pierre Boucher — contact@spboucher.ai // live.ts: shared "live data" hooks (home) // ----------------------------------------------------------------------------- import { useEffect, useState } from "react"; /** Animated hero counter (~0.9 s, cubic easing) — the feel of a real engine indexing. Respects prefers-reduced-motion (direct value). */ 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("en-CA"); } /** Live "X s ago" — re-rendered every second while the ts exists. */ 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 `${s} s ago`; if (s < 5400) return `${Math.round(s / 60)} min ago`; return `${Math.round(s / 3600)} h ago`; }