const number = new Intl.NumberFormat("en-US"); /** 12.345 -> "12.345", 83.2 -> "1:23.200", 3723.5 -> "1:02:03.500". */ export function formatTime(seconds: number): string { const ms = Math.round(seconds * 1000); const h = Math.floor(ms / 3_600_000); const m = Math.floor((ms % 3_600_000) / 60_000); const s = Math.floor((ms % 60_000) / 1000); const rest = String(ms % 1000).padStart(3, "0"); if (h > 0) return `${h}:${String(m).padStart(2, "0")}:${String(s).padStart(2, "0")}.${rest}`; if (m > 0) return `${m}:${String(s).padStart(2, "0")}.${rest}`; return `${s}.${rest}`; } /** Signed difference, e.g. "+1.204" or "−0.500". */ export function formatDelta(seconds: number): string { const sign = seconds < 0 ? "−" : "+"; return sign + formatTime(Math.abs(seconds)); } export function formatNumber(n: number): string { return number.format(n); } const rtf = new Intl.RelativeTimeFormat("en", { numeric: "auto" }); const UNITS: [Intl.RelativeTimeFormatUnit, number][] = [ ["year", 31_536_000], ["month", 2_592_000], ["week", 604_800], ["day", 86_400], ["hour", 3_600], ["minute", 60], ]; /** Unix seconds -> "3 weeks ago". */ export function timeAgo(unixSeconds: number, now = Date.now() / 1000): string { // Never show "in 3 minutes" when a server clock is slightly ahead. const diff = Math.min(0, unixSeconds - now); for (const [unit, size] of UNITS) { if (Math.abs(diff) >= size) return rtf.format(Math.round(diff / size), unit); } return "just now"; } /** Unix seconds -> "27 Sep 2026, 14:03:09" in the viewer's own time zone. */ export function formatDate(unixSeconds: number): string { return new Date(unixSeconds * 1000).toLocaleString("en-GB", { day: "numeric", month: "short", year: "numeric", hour: "2-digit", minute: "2-digit", second: "2-digit", hour12: false, }); } export function serverLabel(tag: string): string { return tag.toUpperCase(); } /** Which bonus band a position is in, for the label next to the multiplier. */ export function bonusLabel(position: number, completions: number): string | null { if (completions < 100) return null; const x = (position - 1) / completions; if (x < 0.01) return "Top 1%"; if (x < 0.05) return "Top 5%"; return null; } /** How many players on a board get points (half from 200 completions on). */ export function pointedCount(completions: number): number { return completions < 200 ? completions : Math.floor(completions / 2); }