/** * GMIIE Engine Transparency — ring scores, NIG, PCS calibration, engine breathing. * Mount: element with id gmiie-engine-viz (or data-gmiie-engine-viz). * Data: /api/gmiie/live/engine → digest.engine → /data/gmiie-engine-snapshot.json */ (function (global) { const RING_COLORS = { R1: '#5b9aff', R2: '#a78bfa', R3: '#34d399', R4: '#f87171', R5: '#fb923c', }; function injectStyles() { if (document.getElementById('gev-styles')) return; const s = document.createElement('style'); s.id = 'gev-styles'; s.textContent = ` .gev-wrap{margin:24px 0;padding:20px;border:1px solid var(--rule,#d4c9a8);background:var(--surface,#ece8dc);} .gev-hd{display:flex;align-items:flex-start;justify-content:space-between;flex-wrap:wrap;gap:12px;margin-bottom:16px;padding-bottom:10px;border-bottom:2px solid var(--ink,#1a1a1a);} .gev-title{font-family:var(--serif,Georgia,serif);font-size:22px;font-weight:700;color:var(--ink,#1a1a1a);} .gev-sub{font-family:var(--mono,'JetBrains Mono',monospace);font-size:9px;color:var(--ink3,#777);margin-top:4px;} .gev-source{font-family:var(--mono);font-size:8px;color:var(--gold,#b8953a);font-weight:700;letter-spacing:.08em;text-transform:uppercase;} .gev-pcs{display:flex;align-items:center;gap:12px;flex-wrap:wrap;padding:10px 14px;background:var(--gold-bg,rgba(184,149,58,.08));border:1px solid var(--gold-bd,rgba(184,149,58,.25));margin-bottom:16px;font-family:var(--mono);font-size:10px;} .gev-pcs strong{color:var(--gold);font-size:12px;} .gev-nig-row{display:grid;grid-template-columns:1fr 1fr;gap:16px;margin-bottom:16px;} @media(max-width:768px){.gev-nig-row{grid-template-columns:1fr;}} .gev-nig-box{padding:16px;border:1px solid var(--rule);background:var(--paper,#faf8f3);} .gev-nig-lbl{font-family:var(--mono);font-size:8px;font-weight:700;letter-spacing:.15em;text-transform:uppercase;color:var(--gold);margin-bottom:6px;} .gev-nig-score{font-family:var(--serif);font-size:42px;font-weight:900;color:var(--gold);line-height:1;display:flex;align-items:baseline;gap:8px;} .gev-trend{font-family:var(--mono);font-size:14px;font-weight:700;} .gev-trend.up{color:var(--green,#1a6b3a);}.gev-trend.down{color:var(--red,#c0392b);}.gev-trend.flat{color:var(--ink3);} .gev-nig-read{font-family:var(--mono);font-size:9px;font-weight:700;color:var(--green);margin-top:4px;} .gev-breathe{font-family:var(--mono);font-size:9px;color:var(--ink2);line-height:1.6;} .gev-rings{display:grid;grid-template-columns:repeat(auto-fill,minmax(200px,1fr));gap:10px;} .gev-ring{padding:12px;border:1px solid var(--rule);background:var(--paper);border-top:3px solid;} .gev-ring-hd{display:flex;justify-content:space-between;align-items:center;margin-bottom:6px;} .gev-ring-id{font-family:var(--mono);font-size:8px;font-weight:700;color:var(--ink3);} .gev-ring-score{font-family:var(--serif);font-size:22px;font-weight:900;} .gev-ring-name{font-size:12px;font-weight:700;margin-bottom:4px;} .gev-ring-bar{height:4px;background:var(--rule2);border-radius:2px;overflow:hidden;margin:6px 0;} .gev-ring-fill{height:100%;border-radius:2px;transition:width .5s;} .gev-ring-meta{font-family:var(--mono);font-size:8px;color:var(--ink3);line-height:1.5;} .gev-links{margin-top:14px;display:flex;gap:12px;flex-wrap:wrap;font-family:var(--mono);font-size:9px;} .gev-links a{color:var(--gold);font-weight:700;text-decoration:none;} .gev-links a:hover{text-decoration:underline;}`; document.head.appendChild(s); } function formatTs(iso) { if (!iso) return '—'; try { return new Intl.DateTimeFormat('en-US', { dateStyle: 'medium', timeStyle: 'short', timeZone: 'UTC', }).format(new Date(iso)) + ' UTC'; } catch { return iso; } } function trendArrow(trend) { if (trend === 'up') return { sym: '↑', cls: 'up', label: 'rising' }; if (trend === 'down') return { sym: '↓', cls: 'down', label: 'falling' }; return { sym: '→', cls: 'flat', label: 'stable' }; } function sourceLabel(data) { if (data.source === 'live-kv') return 'Live API · KV snapshot'; if (data.source === 'digest') return 'Live digest · engine field'; return 'Static seed · refresh via /api/gmiie/live/engine'; } async function fetchEngine() { const urls = ['/api/gmiie/live/engine', '/api/gmiie/live/digest', '/data/gmiie-engine-snapshot.json']; for (const url of urls) { try { const r = await fetch(url, { signal: AbortSignal.timeout(12000) }); if (!r.ok) continue; const data = await r.json(); if (url.includes('/engine') && data.rings) return data; if (url.includes('/digest') && data.engine?.rings) return { ...data.engine, source: 'digest' }; if (data.rings) return data; } catch (_) {} } return null; } function render(container, data) { injectStyles(); const nig = data.nig || {}; const pcs = data.pcs || {}; const rings = data.rings || []; const tr = trendArrow(nig.trend); const scoreStr = nig.score >= 0 ? '+' + nig.score : String(nig.score); container.innerHTML = '