// Simulation: live ticker of "sold" events + animated number hook + formatters. const { useState, useEffect, useRef, useMemo, useCallback } = React; let _eid = 1; function makeSaleEvent(now) { const kind = pick(GIFT_LIST); const listing = makeListing(kind); return { id: _eid++, kind, name: listing.name, num: listing.num, ton: listing.ton, rub: listing.rub, tier: listing.tier, buyer: pick(SELLERS), seller: listing.seller, when: now || Date.now(), }; } function seedSales(n = 8) { const arr = []; const now = Date.now(); for (let i = 0; i < n; i++) arr.push(makeSaleEvent(now - i * 22000 - Math.random() * 8000)); return arr; } function useTicker(enabled = true, intervalMs = 3400) { const [events, setEvents] = useState(() => seedSales()); useEffect(() => { if (!enabled) return; const id = setInterval(() => { setEvents(prev => [makeSaleEvent(), ...prev].slice(0, 30)); }, intervalMs + Math.random() * 1500); return () => clearInterval(id); }, [enabled, intervalMs]); return events; } // Animated counter — eases value toward target over `duration` ms. function useAnimatedNumber(target, duration = 1200) { const [val, setVal] = useState(target); const fromRef = useRef(target); const startRef = useRef(0); const rafRef = useRef(0); useEffect(() => { fromRef.current = val; startRef.current = performance.now(); cancelAnimationFrame(rafRef.current); const tick = (t) => { const p = Math.min(1, (t - startRef.current) / duration); const ease = 1 - Math.pow(1 - p, 3); setVal(fromRef.current + (target - fromRef.current) * ease); if (p < 1) rafRef.current = requestAnimationFrame(tick); }; rafRef.current = requestAnimationFrame(tick); return () => cancelAnimationFrame(rafRef.current); }, [target, duration]); return val; } // Formatters function fmtRub(v) { return Math.round(v).toLocaleString('ru-RU') + ' ₽'; } function fmtRubShort(v) { if (v >= 1000000) return (v / 1000000).toFixed(1).replace('.0','') + ' млн ₽'; if (v >= 1000) return (v / 1000).toFixed(v >= 10000 ? 0 : 1).replace('.0','') + 'k ₽'; return Math.round(v) + ' ₽'; } function fmtTon(v) { return (Math.round(v * 100) / 100).toFixed(2) + ' TON'; } function fmtRel(t) { const s = Math.max(1, Math.round((Date.now() - t) / 1000)); if (s < 60) return s + ' с назад'; const m = Math.round(s / 60); if (m < 60) return m + ' м назад'; const h = Math.round(m / 60); if (h < 24) return h + ' ч назад'; return Math.round(h / 24) + ' д назад'; } window.useTicker = useTicker; window.useAnimatedNumber = useAnimatedNumber; window.makeSaleEvent = makeSaleEvent; window.fmtRub = fmtRub; window.fmtRubShort = fmtRubShort; window.fmtTon = fmtTon; window.fmtRel = fmtRel;