| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| import { buildAppHtml } from './shell'; |
|
|
| const STYLES = ` |
| .fc { padding: 8px 0; border-bottom: 1px solid var(--border); } |
| .fc:last-child { border-bottom: none; } |
| .fc-head { display: flex; align-items: baseline; justify-content: space-between; gap: 10px; } |
| .fc-title { font-size: 13px; color: var(--fg); min-width: 0; } |
| .fc-prob { font-variant-numeric: tabular-nums; font-weight: 700; font-size: 13px; white-space: nowrap; } |
| .fc-meta { margin-top: 3px; display: flex; gap: 6px; flex-wrap: wrap; } |
| .chip { font-size: 10px; text-transform: uppercase; letter-spacing: 0.05em; color: var(--muted); |
| border: 1px solid var(--border); border-radius: 999px; padding: 1px 7px; } |
| `; |
|
|
| const BODY = ` |
| <div class="head"> |
| <div class="title">Forecasts</div> |
| <div class="badge">WorldMonitor Forecasts</div> |
| </div> |
| <div class="empty" id="empty">Waiting for forecasts…</div> |
| <div id="card" style="display:none"> |
| <div id="list"></div> |
| <div class="foot" id="foot"></div> |
| </div> |
| `; |
|
|
| const RENDER = ` |
| if (!data || typeof data !== "object") return; |
| var d = data.data && typeof data.data === "object" ? data.data : data; |
| q("empty").style.display = "none"; |
| q("card").style.display = "block"; |
| |
| var node = d.predictions && typeof d.predictions === "object" ? d.predictions : null; |
| var preds = listState(node && node.predictions).items; |
| var host = q("list"); |
| host.textContent = ""; |
| for (var i = 0; i < preds.length && i < 12; i++) { |
| var p = preds[i]; |
| if (!p || typeof p !== "object") continue; |
| var fc = el("div", "fc"); |
| var head = el("div", "fc-head"); |
| head.appendChild(el("span", "fc-title", collapseWs(p.title) || "Forecast")); |
| var pr = num(p.probability); |
| var pct = pr == null ? null : (pr <= 1 ? pr * 100 : pr); |
| head.appendChild(el("span", "fc-prob", pct == null ? "—" : Math.round(pct) + "%")); |
| fc.appendChild(head); |
| var meta = el("div", "fc-meta"); |
| var dom = collapseWs(p.domain); |
| if (dom) meta.appendChild(el("span", "chip", dom)); |
| var reg = collapseWs(p.region); |
| if (reg) meta.appendChild(el("span", "chip", reg)); |
| if (meta.childNodes.length) fc.appendChild(meta); |
| var bar = probabilityBar(pct); |
| if (bar) fc.appendChild(bar); |
| host.appendChild(fc); |
| } |
| if (!host.childNodes.length) host.appendChild(el("div", "empty", "No forecasts available.")); |
| |
| q("foot").textContent = data.cached_at |
| ? "Snapshot: " + collapseWs(data.cached_at) + (data.stale ? " (stale)" : "") |
| : ""; |
| `; |
|
|
| export const FORECASTS_APP_HTML = buildAppHtml({ |
| title: 'Forecasts — WorldMonitor', |
| appName: 'worldmonitor-forecasts', |
| styles: STYLES, |
| body: BODY, |
| renderBody: RENDER, |
| }); |
|
|