'use client' /** * Ticker — horizontal scrolling price tape under the navbar, the way a crypto * terminal shows a running market strip. Driven by the shared WebSocket price * feed (no new socket). Each asset cell flashes green/red on a price change and * shows the live last price. * * Behaviour notes: * - The strip is duplicated once and translated -50% in a CSS marquee so the * loop is seamless (`ticker-marquee` keyframe in globals.css). * - prefers-reduced-motion disables the scroll (CSS), so the tape becomes a * static, readable row instead of moving. * - The animation is paused on hover so a user can read a specific price. * - Assets with no tick yet render "—" rather than a stale zero. */ import { useRef, useState } from 'react' import { useWsSubscribe } from '@/lib/wsContext' // Assets the backend streams (see binance ASSET_MAP + hl_price_feed). Order is // the display order in the tape. const ASSETS = ['BTC', 'ETH', 'SOL', 'BNB', 'DOGE', 'LINK', 'AAVE', 'TRUMP', 'HYPE'] as const interface Cell { price: number | null dir: 'up' | 'down' | null } function fmtPrice(p: number): string { if (p >= 1000) return '$' + p.toLocaleString('en-US', { maximumFractionDigits: 0 }) if (p >= 1) return '$' + p.toLocaleString('en-US', { maximumFractionDigits: 2 }) return '$' + p.toLocaleString('en-US', { maximumFractionDigits: 4 }) } export default function Ticker() { const [cells, setCells] = useState>({}) // Per-asset flash-clear timers so a fast stream doesn't leak setTimeouts. const timers = useRef>>({}) useWsSubscribe('price', (msg) => { const m = msg as { asset?: string; price?: number } if (!m.asset || typeof m.price !== 'number') return const asset = m.asset.toUpperCase() if (!ASSETS.includes(asset as (typeof ASSETS)[number])) return setCells((prev) => { const old = prev[asset] const dir: Cell['dir'] = old?.price != null && m.price !== old.price ? (m.price! > old.price ? 'up' : 'down') : old?.dir ?? null return { ...prev, [asset]: { price: m.price!, dir } } }) // Clear the flash after 600ms. clearTimeout(timers.current[asset]) timers.current[asset] = setTimeout(() => { setCells((prev) => (prev[asset] ? { ...prev, [asset]: { ...prev[asset], dir: null } } : prev)) }, 600) }) const strip = ASSETS.map((a) => { const c = cells[a] return ( {a} {c?.price != null ? fmtPrice(c.price) : '—'} {c?.dir === 'up' ? '▲' : c?.dir === 'down' ? '▼' : ''} ) }) return (
{strip} {/* duplicate for seamless loop */} {strip}
) }