export function fmtDuration(sec: number): string { if (!isFinite(sec) || sec < 0) sec = 0; const s = Math.floor(sec % 60); const m = Math.floor((sec / 60) % 60); const h = Math.floor(sec / 3600); const ss = String(s).padStart(2, '0'); return h > 0 ? `${h}:${String(m).padStart(2, '0')}:${ss}` : `${m}:${ss}`; } // m:ss.t, for editing export function fmtTimecode(sec: number): string { if (!isFinite(sec) || sec < 0) sec = 0; const tenths = Math.floor((sec * 10) % 10); return `${fmtDuration(sec)}.${tenths}`; } export function fmtDate(iso: string): string { const d = new Date(iso); return d.toLocaleDateString(undefined, { year: 'numeric', month: 'short', day: 'numeric' }); } export function fmtDateTime(iso: string): string { const d = new Date(iso); return d.toLocaleString(undefined, { month: 'short', day: 'numeric', hour: 'numeric', minute: '2-digit' }); } export function fmtBytes(n: number): string { if (n < 1024) return `${n} B`; if (n < 1024 ** 2) return `${(n / 1024).toFixed(0)} KB`; if (n < 1024 ** 3) return `${(n / 1024 ** 2).toFixed(1)} MB`; return `${(n / 1024 ** 3).toFixed(2)} GB`; } export function decodePeaks(data: string): Uint8Array { const bin = atob(data); const out = new Uint8Array(bin.length); for (let i = 0; i < bin.length; i++) out[i] = bin.charCodeAt(i); return out; } export function loadPref(key: string, fallback: T): T { try { const s = localStorage.getItem(key); return s ? { ...fallback, ...JSON.parse(s) } : fallback; } catch { return fallback; } } export function savePref(key: string, value: unknown) { try { localStorage.setItem(key, JSON.stringify(value)); } catch { // storage unavailable } }