From 6bc408b9c15e50b57d4386f8c33de6475cf07edb Mon Sep 17 00:00:00 2001 From: igneum-labs <337424239+igneum-labs@users.noreply.github.com> Date: Tue, 6 Oct 2026 19:38:34 +0000 Subject: [PATCH] Live DAG: the page's scroll passes through the scene; zoom only with ctrl or cmd, a pinch, or a click into the scene the project lead, 6 October 2026: scrolling past the DAG zoomed it. The module's wheel listener took every vertical wheel. Now a plain wheel or a trackpad's two-finger scroll is never captured: the handler returns before preventDefault unless ctrl or cmd is held or the scene is engaged. A click into the scene engages it (the canvas takes an ember outline and a chip reads "scroll to zoom, Esc to release"); Escape, a click outside the scene or the chip releases it. Pinch zoom is unchanged. Same module on / and /live; both pages carry the chip and the note says how to zoom. opts.chip and opts.onEngage are new, optional; dag.engage(bool) is exposed. Proved headless on both pages: a plain wheel over the scene scrolled the page 300 px and left the window at 120 s; engaged, a wheel zoomed the window from 120 to 138 s with the default prevented; the chip showed on click and hid on Escape; no console errors. Link check 884 links 0 broken. Co-Authored-By: Claude Fable 5.1 --- site/index.html | 7 ++++--- site/live-dag.js | 26 ++++++++++++++++++++------ site/live.html | 6 +++--- site/site.css | 5 +++++ 4 files changed, 32 insertions(+), 12 deletions(-) diff --git a/site/index.html b/site/index.html index 25a833527..89614e7d4 100644 --- a/site/index.html +++ b/site/index.html @@ -260,6 +260,7 @@
+
@@ -273,7 +274,7 @@
The live page
-

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. Last lock is the latest checkpoint the finality rule locked. Hover or tap a block for its hash, miner, blue score, parents and proof state. Scroll or pinch to change the window.

+

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. Last lock is the latest checkpoint the finality rule locked. Hover or tap a block for its hash, miner, blue score, parents and proof state. Ctrl or cmd and scroll, a pinch, or a click into the scene then scroll, changes the window.

@@ -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
-
+
pending includedselected chain @@ -185,7 +185,7 @@ main{padding-bottom:var(--sec)} finality band, locked, with its weight finality band, pending
-

120 s of blocks on screen. One lane per miner, the busiest first; the rest share the last lane. Hover or tap a block for its hash, miner, blue score, parents and proof state. Scroll or pinch to change the window; the page's ?window= fixes it.

+

120 s of blocks on screen. One lane per miner, the busiest first; the rest share the last lane. Hover or tap a block for its hash, miner, blue score, parents and proof state. Ctrl or cmd and scroll, a pinch, or a click into the scene then scroll, changes the window; the page's ?window= fixes it.

@@ -293,7 +293,7 @@ main{padding-bottom:var(--sec)} var dagCanvas=$('dag'),dag=null,lastD=null; function windowWord(w){return w+' s of blocks on screen';} function mountDag(){if(dag||!dagCanvas||!window.IgneumDag)return; - dag=IgneumDag.mount(dagCanvas,{poll:false,window:fixedWindow||120,tooltip:$('dag-tip'),pauseButton:$('dag-pause'), + dag=IgneumDag.mount(dagCanvas,{poll:false,window:fixedWindow||120,tooltip:$('dag-tip'),chip:$('dag-chip'),pauseButton:$('dag-pause'), onWindow:function(w){$('c-window').textContent=windowWord(w);}, onState:function(st,reason){var el=$('dag-state');if(!el)return;el.className='state '+(st==='live'?'live':st==='failed'?'bad':'');el.innerHTML=''+(st==='live'?'live devnet':st==='connecting'?'connecting to the devnet observer':esc(reason||'live feed unavailable'));}}); $('c-window').textContent=windowWord(dag.getWindow());if(lastD)dag.push(lastD);} diff --git a/site/site.css b/site/site.css index e39ee5d34..3500f2c5e 100644 --- a/site/site.css +++ b/site/site.css @@ -210,6 +210,11 @@ input:focus,select:focus,textarea:focus{border-color:var(--line-2)} .legend span{display:inline-flex;align-items:center;gap:8px} .sw{width:12px;height:12px;border-radius:3px;display:inline-block;flex:none} +/* the engaged scene: a chip in the corner while the wheel zooms */ +.scene canvas.engaged{outline:2px solid var(--ember);outline-offset:-2px} +.chip-zoom{position:absolute;right:12px;top:12px;display:inline-flex;align-items:center;gap:8px;background:var(--graphite);border:1px solid var(--line-2);border-radius:var(--r-pill);padding:5px 12px;font:500 var(--t-sm)/1.4 var(--f-mono);color:var(--ink-2);pointer-events:none} +.chip-zoom[hidden]{display:none} + /* ---------- 8. print ---------- */ @media print{ :root{--obsidian:#fff;--graphite:#fff;--row:#f3f3f3;--line:#bbb;--line-2:#999;--bone:#000;--ink-2:#111;--ash:#444;--ember:#B8390C;--ember-text:#B8390C;--molten-text:#8F5810}