// 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;
}