// Shared utility components used across variations
(() => {
const { useState, useEffect, useMemo, useRef } = React;
// Sparkline / line chart
function LineChart({ data, width = 400, height = 120, lines, yPad = 8, showAxis = true, bg = 'transparent', grid = true }) {
const all = lines.flatMap(l => data.map(d => d[l.key]));
const min = Math.min(...all);
const max = Math.max(...all);
const range = max - min || 1;
const x = i => (i / (data.length - 1)) * (width - 30) + 25;
const y = v => height - yPad - ((v - min) / range) * (height - yPad * 2);
return (
);
}
// Small blinking dot
function BlinkDot({ color = '#22c55e', size = 6 }) {
return (
);
}
// Progress bar
function Bar({ value, max, color = 'currentColor', height = 4, bg = 'currentColor', bgOp = 0.1 }) {
const pct = Math.max(0, Math.min(1, value / max));
return (
);
}
// Number formatting
const fmt = {
usd: n => (n < 0 ? '-$' : '$') + Math.abs(n).toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 }),
pct: n => (n >= 0 ? '+' : '') + (n * 100).toFixed(2) + '%',
pct1: n => (n >= 0 ? '+' : '') + n.toFixed(2) + '%',
compact: n => n >= 1000 ? '$' + (n / 1000).toFixed(1) + 'k' : '$' + n.toFixed(0),
};
// Hook: ticker (updates "now" every second)
function useNow(interval = 1000) {
const [t, setT] = useState(new Date('2026-04-22T15:32:00'));
useEffect(() => {
const id = setInterval(() => setT(prev => new Date(prev.getTime() + 1000)), interval);
return () => clearInterval(id);
}, [interval]);
return t;
}
const clockStr = d => d.toTimeString().slice(0, 8);
const dateStr = d => d.toISOString().slice(0, 10);
Object.assign(window, { LineChart, BlinkDot, Bar, fmt, useNow, clockStr, dateStr });
})();