/* One poll of /api/live every 2 s, pushed to every scene on the page (docs/plans/site-ui-3.md, the scenes lab, 6 Oct 2026). IgneumFeed.start({ url, window, onState, subs: [scene, ...] }); each scene has push(reply). The state words are the site's: connecting, live, stale (observer offline, last update N ago), failed (live feed unavailable). */ (function (root) { 'use strict'; function fmtAge(sec) { sec = Math.round(sec); return sec < 90 ? sec + ' s' : sec < 5400 ? Math.round(sec / 60) + ' min' : Math.round(sec / 3600) + ' h'; } function start(opts) { var subs = opts.subs || [], failures = 0, state = 'connecting', reason = 'connecting'; function set(s, r) { if (s === state && r === reason) return; state = s; reason = r; if (opts.onState) opts.onState(s, r); } function poll() { if (document.hidden) return; fetch((opts.url || '/api/live') + '?window=' + (opts.window || 120), { cache: 'no-store' }).then(function (r) { if (!r.ok) throw new Error('HTTP ' + r.status); return r.json(); }).then(function (d) { if (!d || !d.ok || !d.state || !d.blocks) throw new Error('bad reply'); failures = 0; if (d.state.stale) set('stale', 'observer offline, last update ' + (d.state.age_s !== null && d.state.age_s !== undefined ? fmtAge(d.state.age_s) + ' ago' : 'unknown')); else set('live', null); subs.forEach(function (s) { s.push(d); }); if (opts.onData) opts.onData(d); }).catch(function () { failures++; if (failures >= 2) set('failed', 'live feed unavailable'); }); } poll(); var t = setInterval(poll, 2000); document.addEventListener('visibilitychange', function () { if (!document.hidden) poll(); }); return { stop: function () { clearInterval(t); }, state: function () { return { state: state, reason: reason }; } }; } // shared helpers for the three scenes function cssVar(name, fallback) { try { var v = getComputedStyle(document.documentElement).getPropertyValue(name).trim(); return v || fallback; } catch (e) { return fallback; } } function theme() { return { ember: cssVar('--ember', '#F2541B'), molten: cssVar('--molten', '#FFB35C'), bone: cssVar('--bone', '#F4F1EC'), ash: cssVar('--ash', '#9A9A9E'), line: cssVar('--line', '#2A2A30'), row: cssVar('--row', '#111114'), ink2: cssVar('--ink-2', '#C9C7C2') }; } function rgba(hex, a) { var h = hex.replace('#', ''); if (h.length !== 6) return hex; return 'rgba(' + parseInt(h.slice(0, 2), 16) + ',' + parseInt(h.slice(2, 4), 16) + ',' + parseInt(h.slice(4, 6), 16) + ',' + a + ')'; } // the ledger's word for a block function word(b) { return b.locked ? 'locked' : b.proven ? 'proven' : (b.chain || b.color === 'blue') ? 'included' : b.color === 'red' ? 'excluded' : 'pending'; } function hash32(s) { var h = 2166136261; for (var i = 0; i < s.length; i++) { h ^= s.charCodeAt(i); h = Math.imul(h, 16777619); } return (h >>> 0) / 4294967296; } function sizer(canvas, ctx) { var dpr = Math.min(root.devicePixelRatio || 1, 2); return function () { var W = canvas.clientWidth, H = canvas.clientHeight; canvas.width = Math.round(W * dpr); canvas.height = Math.round(H * dpr); ctx.setTransform(dpr, 0, 0, dpr, 0, 0); return { W: W, H: H }; }; } function loop(draw, canvas) { var reduce = root.matchMedia && root.matchMedia('(prefers-reduced-motion: reduce)').matches, running = false, inView = true; function frame(t) { if (!running) return; draw(t); if (reduce || document.hidden || !inView) { running = false; return; } requestAnimationFrame(frame); } function kick() { if (!running) { running = true; requestAnimationFrame(frame); } } if ('IntersectionObserver' in root) new IntersectionObserver(function (es) { es.forEach(function (e) { inView = e.isIntersecting; if (inView) kick(); }); }, { threshold: 0.05 }).observe(canvas); document.addEventListener('visibilitychange', function () { if (!document.hidden) kick(); }); return { kick: kick, reduce: reduce }; } root.IgneumFeed = { start: start, theme: theme, rgba: rgba, word: word, hash32: hash32, sizer: sizer, loop: loop }; })(window);