/* soccerarena.ai — shared visual atoms. */
function toSlug(name) {
return (name || '').toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '');
}
const SA = {
turf: '#0a1f0f',
turf2: '#0c2613',
surface: '#0e2a16',
surfaceHi: '#123420',
line: 'rgba(200,245,58,0.10)',
line2: 'rgba(255,255,255,0.07)',
white: '#f4f7f2',
dim: 'rgba(244,247,242,0.62)',
faint: 'rgba(244,247,242,0.38)',
lime: '#c8f53a',
limeDim: 'rgba(200,245,58,0.55)',
orange: '#ff9d3d',
orangeDim: 'rgba(255,157,61,0.16)',
mono: "'JetBrains Mono', ui-monospace, monospace",
sans: "'Archivo', system-ui, sans-serif",
};
function Sparkline({ data, w = 64, h = 22, color = SA.lime, strokeWidth = 1.5 }) {
const min = Math.min(...data), max = Math.max(...data);
const span = max - min || 1;
const pts = data.map((v, i) => {
const x = (i / (data.length - 1)) * (w - 2) + 1;
const y = h - 1 - ((v - min) / span) * (h - 2);
return `${x.toFixed(1)},${y.toFixed(1)}`;
});
const last = data[data.length - 1];
const lx = w - 1;
const ly = h - 1 - ((last - min) / span) * (h - 2);
return (
);
}
function FlipBadge({ label = 'FLIPPED', when, compact = false }) {
return (
↺
{label}{when ? ` ${when}` : ''}
);
}
function SplitBar({ split, height = 6 }) {
const total = split.home + split.draw + split.away || 1;
const seg = (n, color) => n > 0 ? (
) : null;
return (
{seg(split.home, SA.lime)}
{seg(split.draw, 'rgba(244,247,242,0.28)')}
{seg(split.away, SA.white)}
);
}
function ConfBar({ value, w = 54 }) {
return (
);
}
function Delta({ value }) {
const up = value >= 0;
return (
{up ? '▲' : '▼'} {Math.abs(value)}
);
}
function PitchMarkings() {
const s = SA.line;
return (
);
}
function LiveDot() {
return (
);
}
function ProviderMark({ code, size = 26 }) {
return (
{code}
);
}
function ShareBar({ m }) {
const pageUrl = window.location.pathname.includes('/models/')
? window.location.href
: window.location.href.replace(/[^/]*$/, '') + 'models/' + m.slug;
const text = m.name + ' predicts ' + m.pick.name + ' to win the 2026 World Cup';
const tweetUrl = 'https://twitter.com/intent/tweet?text=' + encodeURIComponent(text) + '&url=' + encodeURIComponent(pageUrl);
const waUrl = 'https://wa.me/?text=' + encodeURIComponent(text + ' \u2192 ' + pageUrl);
const btn = (href, label, fg, bg, border) => (
e.stopPropagation()}
style={{
display: 'inline-flex', alignItems: 'center', gap: 5,
padding: '5px 11px', textDecoration: 'none',
fontFamily: SA.mono, fontSize: 10, fontWeight: 500, letterSpacing: '0.04em',
color: fg, background: bg, border: `1px solid ${border}`,
whiteSpace: 'nowrap', lineHeight: 1,
}}>
{label}
);
return (
{btn(tweetUrl, '\u2958 Share', '#1d9bf0', 'rgba(29,155,240,0.1)', 'rgba(29,155,240,0.28)')}
{btn(waUrl, 'WhatsApp', '#25d366', 'rgba(37,211,102,0.08)', 'rgba(37,211,102,0.25)')}
);
}
// ── Dynamic relative-time widget ──────────────────────────────────────────────
// Updates every minute; never stale regardless of when the page was built.
const { useState: useStateR, useEffect: useEffectR } = React;
function computeRelativeTime(isoString) {
if (!isoString) return '';
const diff = new Date(isoString).getTime() - Date.now();
if (diff <= 0) return 'kicked off';
const totalMin = Math.round(diff / 60000);
if (totalMin < 60) return `${totalMin}m`;
const h = Math.floor(diff / 3600000);
const m = Math.floor((diff % 3600000) / 60000);
if (h < 24) return `${h}h ${m}m`;
const days = Math.floor(h / 24);
return `${days}d ${h % 24}h`;
}
function useRelativeTime(isoString) {
const [val, setVal] = useStateR(() => computeRelativeTime(isoString));
useEffectR(() => {
if (!isoString) return;
const id = setInterval(() => setVal(computeRelativeTime(isoString)), 60000);
return () => clearInterval(id);
}, [isoString]);
return val;
}
function RelativeTime({ kickoffAt, style }) {
const t = useRelativeTime(kickoffAt);
return {t};
}
function useIsMobile(breakpoint = 768) {
const [mobile, setMobile] = React.useState(() => typeof window !== 'undefined' && window.innerWidth < breakpoint);
React.useEffect(() => {
const handler = () => setMobile(window.innerWidth < breakpoint);
window.addEventListener('resize', handler);
return () => window.removeEventListener('resize', handler);
}, [breakpoint]);
return mobile;
}
Object.assign(window, {
SA, Sparkline, FlipBadge, SplitBar, ConfBar, Delta, PitchMarkings, LiveDot, ProviderMark, ShareBar,
useRelativeTime, RelativeTime, useIsMobile, toSlug,
});