SPB Git forge

spb/rent-ka

Public
8commits 1branches 0releases
7.4 MBsize
maindefault branch
19 days agolast push
Python 68.8% TypeScript 18.6% CSS 8.7% JavaScript 3.3% HTML 0.6%
1.7 KB · 42 lines typescript
Raw Blame History
1// -----------------------------------------------------------------------------2// Rent-Ka — Rental listings aggregator (Canada, outside Québec)3// Author: Simon-Pierre Boucher — contact@spboucher.ai4// live.ts: shared "live data" hooks (home)5// -----------------------------------------------------------------------------6import { useEffect, useState } from "react";78/** Animated hero counter (~0.9 s, cubic easing) — the feel of a real engine9    indexing. Respects prefers-reduced-motion (direct value). */10export function useCountUp(target: number | null | undefined, ms = 900): string | null {11  const [v, setV] = useState<number | null>(null);12  useEffect(() => {13    if (target == null) return;14    if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) { setV(target); return; }15    let raf = 0;16    const t0 = performance.now();17    const step = (t: number) => {18      const p = Math.min(1, (t - t0) / ms);19      setV(Math.round(target * (1 - Math.pow(1 - p, 3))));20      if (p < 1) raf = requestAnimationFrame(step);21    };22    raf = requestAnimationFrame(step);23    return () => cancelAnimationFrame(raf);24  }, [target, ms]);25  return v == null ? null : v.toLocaleString("en-CA");26}2728/** Live "X s ago" — re-rendered every second while the ts exists. */29export function useAgo(ts: number | null): string | null {30  const [, tick] = useState(0);31  useEffect(() => {32    if (ts == null) return;33    const id = setInterval(() => tick((x) => x + 1), 1000);34    return () => clearInterval(id);35  }, [ts]);36  if (ts == null) return null;37  const s = Math.max(0, Math.floor(Date.now() / 1000 - ts));38  if (s < 90) return `${s} s ago`;39  if (s < 5400) return `${Math.round(s / 60)} min ago`;40  return `${Math.round(s / 3600)} h ago`;41}42