// Shared hooks — number tween for stat counters + scoreClass / decisionClass helpers. function useTween(target, opts = {}) { // Eased ease-out-cubic count-up animation; mirrors the imperative // `tween()` function from the legacy landing.html so the counters in // HeroMetric / MetricCard / DecisionPie / TopRules feel identical. const { duration = 1400, format = (n) => Math.round(n).toLocaleString('en-US') } = opts; const [display, setDisplay] = React.useState(format(0)); React.useEffect(() => { let raf; const start = performance.now(); const tick = (t) => { const p = Math.min(1, (t - start) / duration); const eased = 1 - Math.pow(1 - p, 3); setDisplay(format(target * eased)); if (p < 1) raf = requestAnimationFrame(tick); }; raf = requestAnimationFrame(tick); return () => cancelAnimationFrame(raf); }, [target, duration]); return display; } function scoreClass(s) { return s <= 40 ? 'score-low' : s <= 70 ? 'score-mid' : 'score-hi'; } function decisionClass(d) { return d === 'APPROVE' ? 'b-approve' : d === 'REVIEW' ? 'b-review' : 'b-block'; } function decisionBadgeClass(d) { return d === 'APPROVE' ? 'dec-approve' : d === 'REVIEW' ? 'dec-review' : 'dec-block'; } function nowTs() { return new Date().toLocaleTimeString('en-GB', { hour: '2-digit', minute: '2-digit', second: '2-digit' }); } function rulesCount(score) { if (score <= 40) return Math.random() < 0.5 ? 0 : 1; if (score <= 70) return 2 + Math.floor(Math.random() * 2); return 3 + Math.floor(Math.random() * 3); } // Live transaction generator — used by the Recent evaluations panel. const TX_TEMPLATES = [ { id: 'tx-2026-05-06-9a17', client: 'acme-19104', amt: '250.50 USDT', score: 71, decision: 'BLOCK', ip: '8.8.8.8' }, { id: 'tx-2026-05-06-3c9d', client: 'acme-19104', amt: '12 540 USDT', score: 53, decision: 'REVIEW', ip: '185.156.72.10' }, { id: 'tx-2026-05-06-f1b2', client: 'orbit-18769',amt: '2 435.13 USDT', score: 1, decision: 'APPROVE', ip: '52.10.4.198' }, { id: 'tx-2026-05-06-7e44', client: 'orbit-18769',amt: '57.20 USDT', score: 4, decision: 'APPROVE', ip: '34.224.18.7' }, { id: 'tx-2026-05-06-0b21', client: 'lyra-20126', amt: '50 000 USDT', score: 49, decision: 'REVIEW', ip: '185.156.72.10' }, { id: 'tx-2026-05-06-bb98', client: 'lyra-20126', amt: '87.75 USDT', score: 0, decision: 'APPROVE', ip: '3.122.14.91' }, { id: 'tx-2026-05-06-5a02', client: 'apex-19104', amt: '1 480 USDT', score: 28, decision: 'APPROVE', ip: '4.71.182.4' }, { id: 'tx-2026-05-06-d7f3', client: 'apex-19104', amt: '3 472 USDT', score: 64, decision: 'REVIEW', ip: '5.255.232.201' }, ];