// Small helpers shared by the web player's modules. export const $ = (s, r = document) => r.querySelector(s); export const $$ = (s, r = document) => [...r.querySelectorAll(s)]; export const esc = s => String(s ?? "").replace(/[&<>"']/g, c => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" })[c]); export const fmtTime = s => { s = Math.max(0, Math.floor(s || 0)); return `${Math.floor(s / 60)}:${String(s % 60).padStart(2, "0")}`; }; export const fmtBytes = n => n >= 1e9 ? (n / 1e9).toFixed(1) + " GB" : (n / 1e6).toFixed(n >= 1e8 ? 0 : 1) + " MB"; export const plural = (n, w) => `${n.toLocaleString()} ${w}${n === 1 ? "" : "s"}`; export const shuffled = a => { a = a.slice(); for (let i = a.length - 1; i > 0; i--) { const j = Math.floor(Math.random() * (i + 1)); [a[i], a[j]] = [a[j], a[i]]; } return a; }; export const clone = o => JSON.parse(JSON.stringify(o ?? null)); // crypto.randomUUID only exists on HTTPS pages, and the player is usually opened over plain HTTP. export const uuid = () => crypto.randomUUID ? crypto.randomUUID() : Date.now().toString(36) + Math.random().toString(36).slice(2); export function load(k) { try { return JSON.parse(localStorage.getItem(k)); } catch { return null; } } export function store(k, v) { try { localStorage.setItem(k, JSON.stringify(v)); } catch {} } // Lets the player tell the UI what changed without importing it. const bus = {}; export const on = (ev, fn) => { (bus[ev] ||= []).push(fn); }; export const emit = (ev, ...args) => { for (const fn of bus[ev] || []) fn(...args); }; export function toast(msg, ms = 2400) { let t = $("#toast"); if (!t) { t = document.createElement("div"); t.id = "toast"; document.body.append(t); } t.textContent = msg; t.classList.add("show"); clearTimeout(t._h); t._h = setTimeout(() => t.classList.remove("show"), ms); } export const ICON = { lock: '', play: '', pause: '', prev: '', next: '', shuffle: '', repeat: '', repeat1: '', mixer: '', queue: '', lyrics: '', more: '', download: '', busy: '', };