/* ESPILON C2 — Shared utilities */ function escapeHtml(str) { const div = document.createElement('div'); div.appendChild(document.createTextNode(str)); return div.innerHTML; } function formatDuration(seconds) { if (seconds == null || isNaN(seconds)) return '-'; seconds = Math.round(seconds); if (seconds < 60) return seconds + 's'; if (seconds < 3600) return Math.floor(seconds / 60) + 'm ' + (seconds % 60) + 's'; const h = Math.floor(seconds / 3600); const m = Math.floor((seconds % 3600) / 60); if (h < 24) return h + 'h ' + m + 'm'; const d = Math.floor(h / 24); return d + 'd ' + (h % 24) + 'h'; } function formatBytes(bytes) { if (bytes == null || isNaN(bytes)) return '-'; if (bytes < 1024) return bytes + ' B'; if (bytes < 1048576) return (bytes / 1024).toFixed(1) + ' KB'; return (bytes / 1048576).toFixed(1) + ' MB'; } function formatTimestamp(ts) { if (!ts) return '-'; const d = typeof ts === 'number' ? new Date(ts * 1000) : new Date(ts); const pad = n => String(n).padStart(2, '0'); return pad(d.getHours()) + ':' + pad(d.getMinutes()) + ':' + pad(d.getSeconds()); } function debounce(fn, ms) { let timer; return function(...args) { clearTimeout(timer); timer = setTimeout(() => fn.apply(this, args), ms); }; } function toast(msg, type) { const el = document.createElement('div'); el.className = 'toast' + (type ? ' toast-' + type : ''); el.textContent = msg; document.body.appendChild(el); setTimeout(() => el.remove(), 3000); }