@@ -582,10 +583,10 @@
cm.textContent=fmtN(s.miners_10m!==null&&s.miners_10m!==undefined?s.miners_10m:(d.miners||[]).length);
ch.textContent=fmtHash(s.hashes_per_second_estimate);
if(note&&!s.stale){var lag=pv&&pv.active&&pv.median_proof_lag_s?Math.round(pv.median_proof_lag_s):null;
- note.textContent='One node is read every 2 s. Chain block is the EVM block number. Shards proven is every shard paid on the chain so far'+(pv&&pv.active?' ('+fmtN(pv.shards_paid_10m||0)+' in the last 10 minutes'+(lag?', landing a median '+lag+' s behind the tip, so the newest blocks on screen are still pending':'')+')':'')+'. Last lock is the latest checkpoint the finality rule locked. A card runs several vote keys. Hover or tap a block for its details; scroll or pinch to change the window.';}
+ note.textContent='One node is read every 2 s. Chain block is the EVM block number. Shards proven is every shard paid on the chain so far'+(pv&&pv.active?' ('+fmtN(pv.shards_paid_10m||0)+' in the last 10 minutes'+(lag?', landing a median '+lag+' s behind the tip, so the newest blocks on screen are still pending':'')+')':'')+'. Last lock is the latest checkpoint the finality rule locked. A card runs several vote keys. Hover or tap a block for its details; ctrl or cmd and scroll, a pinch, or a click into the scene then scroll, changes the window.';}
}
function mountDag(){var c=document.getElementById('dag');if(!c||!window.IgneumDag)return;
- IgneumDag.mount(c,{compact:window.innerWidth<700,window:90,onState:setWord,onData:onData,pauseButton:document.getElementById('dag-pause'),tooltip:document.getElementById('dag-tip')});}
+ IgneumDag.mount(c,{compact:window.innerWidth<700,window:90,onState:setWord,onData:onData,pauseButton:document.getElementById('dag-pause'),tooltip:document.getElementById('dag-tip'),chip:document.getElementById('dag-chip')});}
if(window.IgneumDag)mountDag();else document.addEventListener('DOMContentLoaded',function(){var s=document.querySelector('script[src="/live-dag.js"]');if(window.IgneumDag)mountDag();else if(s)s.addEventListener('load',mountDag);});
// hourly program ticker
diff --git a/site/live-dag.js b/site/live-dag.js
index 596a95228..6bf08e11d 100644
--- a/site/live-dag.js
+++ b/site/live-dag.js
@@ -4,10 +4,12 @@
are drawn; the edge between two selected-chain blocks is heavier and the chain glows as one path. Colour words are the
ledger's: pending, included (blue), excluded (red), proven (every shard verified or paid), locked (a checkpoint block).
Checkpoints are vertical bands at their blue score with the lock weight; a locked band is solid, a pending one dashed.
- The view scrolls at chain speed, new blocks arrive from the right with a brief glow; wheel and pinch zoom the window
- (30 to 300 s, the API's ?window=); a pause button; hover and tap show a block's details.
+ The view scrolls at chain speed, new blocks arrive from the right with a brief glow. The page's scroll passes through the
+ scene; zoom takes ctrl or cmd with the wheel, a pinch, or a click into the scene (engaged, released by Escape or a click
+ outside; opts.chip is the "scroll to zoom, Esc to release" element); window 30 to 300 s (the API's ?window=); a pause button;
+ hover and tap show a block's details.
- Usage: var dag = IgneumDag.mount(canvas, { compact, window, url, poll, mine, onState, onData, onWindow, pauseButton, tooltip })
+ Usage: var dag = IgneumDag.mount(canvas, { compact, window, url, poll, mine, onState, onData, onWindow, onEngage, pauseButton, tooltip, chip })
- url: the /api/live endpoint (default '/api/live'); poll: false stops the module's own 2 s fetch, the host then calls
dag.push(reply) with a reply of the same shape ({ok, state:{stale, age_s, height, ...}, blocks:[...], finality:{checkpoints:[...]}, proving})
whenever it has new data (every poll or every new block, either is fine; blocks already on screen are updated in place).
@@ -196,8 +198,20 @@
}
canvas.addEventListener('mousemove', function (ev) { hover = hit(ev); canvas.style.cursor = hover ? 'pointer' : 'default'; if (!pinned) showTip(hover, ev); kick(); });
canvas.addEventListener('mouseleave', function () { hover = null; if (!pinned) showTip(null); kick(); });
- canvas.addEventListener('click', function (ev) { var b = hit(ev); pinned = (pinned && b && pinned.hash === b.hash) ? null : b; showTip(pinned || hover); kick(); });
- canvas.addEventListener('wheel', function (ev) { if (!ev.ctrlKey && Math.abs(ev.deltaY) < 1) return; ev.preventDefault(); zoom(ev.deltaY > 0 ? 1.15 : 1 / 1.15); }, { passive: false });
+ canvas.addEventListener('click', function (ev) { engage(true); var b = hit(ev); pinned = (pinned && b && pinned.hash === b.hash) ? null : b; showTip(pinned || hover); kick(); });
+ // the wheel is never captured by default (6 Oct 2026, the project lead: scrolling past the scene zoomed it): the page scrolls through the
+ // scene; zoom takes ctrl or cmd with the wheel, a pinch, or a click into the scene, which engages it (opts.chip shows
+ // "scroll to zoom, Esc to release"); Escape, a click outside or leaving the scene releases it
+ var engaged = false, chip = opts.chip || null;
+ function engage(on) { if (on === engaged) return; engaged = on; canvas.classList.toggle('engaged', on); if (chip) chip.hidden = !on; if (opts.onEngage) opts.onEngage(on); }
+ canvas.addEventListener('wheel', function (ev) {
+ var zoomKey = ev.ctrlKey || ev.metaKey;
+ if (!zoomKey && !engaged) return; // a plain wheel or a two-finger scroll passes through to the page
+ if (Math.abs(ev.deltaY) < 1) return;
+ ev.preventDefault(); zoom(ev.deltaY > 0 ? 1.15 : 1 / 1.15);
+ }, { passive: false });
+ document.addEventListener('keydown', function (ev) { if (ev.key === 'Escape' && engaged) engage(false); });
+ document.addEventListener('pointerdown', function (ev) { if (engaged && !canvas.contains(ev.target) && !(chip && chip.contains(ev.target))) engage(false); });
var pinch = null;
canvas.addEventListener('touchstart', function (ev) { if (ev.touches.length === 2) pinch = dist(ev.touches); }, { passive: true });
canvas.addEventListener('touchmove', function (ev) { if (ev.touches.length === 2 && pinch) { var d = dist(ev.touches); if (Math.abs(d - pinch) > 12) { zoom(d > pinch ? 1 / 1.1 : 1.1); pinch = d; } } }, { passive: true });
@@ -216,7 +230,7 @@
new MutationObserver(function () { readTheme(); kick(); }).observe(document.documentElement, { attributes: true, attributeFilter: ['data-theme'] });
readTheme(); size(); if (opts.poll !== false) { poll(); setInterval(poll, 2000); } kick();
- return { push: push, setPaused: setPaused, zoom: zoom, getWindow: function () { return windowS; }, getState: function () { return { state: state, reason: reason }; }, redraw: kick, words: words };
+ return { push: push, setPaused: setPaused, zoom: zoom, engage: engage, getWindow: function () { return windowS; }, getState: function () { return { state: state, reason: reason }; }, redraw: kick, words: words };
}
root.IgneumDag = { mount: mount };
})(window);
diff --git a/site/live.html b/site/live.html
index 8a7873687..7de59505b 100644
--- a/site/live.html
+++ b/site/live.html
@@ -175,7 +175,7 @@ main{padding-bottom:var(--sec)}
connecting to the devnet observer
on screen 0chain 0identities 0last lock pendingproven pending