// Thin wrapper around fetch — JSON in, JSON out. Throws on non-2xx. async function api(method, url, body) { const opts = { method, headers: { "Content-Type": "application/json" }, credentials: "same-origin", }; if (body !== undefined) opts.body = JSON.stringify(body); const r = await fetch(url, opts); let payload = null; try { payload = await r.json(); } catch (_) { /* no body */ } if (!r.ok) { const msg = payload && payload.detail ? payload.detail : `Request failed (${r.status})`; throw new Error(msg); } return payload; } window.API = { get: (u) => api("GET", u), post: (u, b) => api("POST", u, b), put: (u, b) => api("PUT", u, b), del: (u) => api("DELETE", u), }; // Time/date helpers window.fmt = { time(t) { if (!t) return ""; const [h, m] = t.split(":").map(Number); const period = h >= 12 ? "PM" : "AM"; const h12 = h % 12 === 0 ? 12 : h % 12; return `${h12}:${String(m).padStart(2, "0")} ${period}`; }, date(d) { if (!d) return ""; const dt = new Date(d + "T00:00:00"); return dt.toLocaleDateString(undefined, { weekday: "long", year: "numeric", month: "long", day: "numeric" }); }, dateShort(d) { if (!d) return ""; const dt = new Date(d + "T00:00:00"); return dt.toLocaleDateString(undefined, { month: "short", day: "numeric", year: "numeric" }); }, today() { const d = new Date(); const y = d.getFullYear(); const m = String(d.getMonth() + 1).padStart(2, "0"); const day = String(d.getDate()).padStart(2, "0"); return `${y}-${m}-${day}`; }, tomorrow() { const d = new Date(); d.setDate(d.getDate() + 1); const y = d.getFullYear(); const m = String(d.getMonth() + 1).padStart(2, "0"); const day = String(d.getDate()).padStart(2, "0"); return `${y}-${m}-${day}`; }, };