// ui.js — small DOM helpers (text only via append/textContent, never innerHTML) export const $ = (sel, root = document) => root.querySelector(sel); const SVG_NS = 'http://www.w3.org/2000/svg'; export const el = (tag, attrs = {}, ...kids) => fill(document.createElement(tag), attrs, kids); export const svg = (tag, attrs = {}, ...kids) => fill(document.createElementNS(SVG_NS, tag), attrs, kids); function fill(e, attrs, kids) { for (const [k, v] of Object.entries(attrs)) { if (typeof v === 'function' && k.startsWith('on')) e.addEventListener(k.slice(2), v); else if (v !== undefined && v !== null && v !== false) e.setAttribute(k, v === true ? '' : String(v)); } for (const k of kids.flat()) if (k !== undefined && k !== null && k !== false) e.append(k); return e; } let toastTimer; export function toast(msg, kind = 'error') { const t = $('#toast'); t.textContent = msg; t.className = `show ${kind}`; clearTimeout(toastTimer); toastTimer = setTimeout(() => { t.className = ''; }, 5000); } export async function api(url, { method = 'GET', body } = {}) { const r = await fetch(url, { method, headers: body === undefined ? {} : { 'Content-Type': 'application/json' }, body: body === undefined ? undefined : JSON.stringify(body) }); if (r.status === 204) return null; const data = await r.json().catch(() => ({})); if (!r.ok) throw new Error(data.error || `${r.status} ${r.statusText}`); return data; }