/* Igneum DAG / Ember 2.0 (the EMBER 02 renderer pack, 7 Oct 2026). THE SHARED SOURCE: scene/live-dag.js; site/live-dag.js and * app/igneum-app/ui/live-dag.js are byte-equal copies written by tools/scene/sync.mjs (the gate refuses a drifted copy). Edit here. * 2.0.3 (scene parity, 7 Oct 2026): (1) every push, size and theme change paints the current picture at once; only the motion * loop waits for a visible document and an intersecting canvas (a page that loads with document.hidden true, or whose embedder * never fires visibilitychange, showed a blank canvas while reporting live). (2) The narrow (phone) rule keys on the viewport * width, not the canvas width: a 640 px hero on a laptop is not a phone. The narrow options of 2.0.2 (narrow, narrowBreak 720, * narrowWindow 30, narrowLanes 4, narrowMinNode 11; on a phone the window shortens until the viewer sets one, four lanes, bigger * nodes, no hairlines but the selected chain, checkpoint labels as the percent) and the real-data presentation options of 2.0.1: * maxLanes (own key + top keys + others, default 7), minNode (node half-size floor, default 9.5), hairlineAlpha (non-chain * edges, default .12, drawn only within laneReach lanes, default 2, and only a merged block's first parent unless selected), * othersScale (the others lane's size and alpha, default .75), provenGlow (a glow and ring on proven blocks, default on). * Replacement for the supplied live-dag.js. No framework, network writes or synthetic records. * Existing mount/push options and public methods are retained; see README.md for additions. * Every node, parent edge, proof segment and checkpoint is derived from the observer reply. * Motion interpolates presentation ONLY. It never promotes proof, inclusion or finality. */ (function (root) { 'use strict'; var instances = new WeakMap(); var TAU = Math.PI * 2; function clamp(n, a, b) { return Math.max(a, Math.min(b, n)); } function finite(v) { return typeof v === 'number' && Number.isFinite(v); } function num(v) { return finite(v) ? v : null; } function fmt(v) { return v == null ? 'unknown' : Number(v).toLocaleString('en-GB'); } function rgba(c, a) { var s = String(c).trim(), h = s.replace('#', ''), rgb; if (/^#[0-9a-f]{3}$/i.test(s)) h = h.split('').map(function (x) { return x + x; }).join(''); if (/^[0-9a-f]{6}$/i.test(h)) return 'rgba(' + parseInt(h.slice(0,2),16) + ',' + parseInt(h.slice(2,4),16) + ',' + parseInt(h.slice(4,6),16) + ',' + a + ')'; rgb = s.match(/^rgba?\(\s*([\d.]+)[,\s]+([\d.]+)[,\s]+([\d.]+)/i); return rgb ? 'rgba(' + rgb[1] + ',' + rgb[2] + ',' + rgb[3] + ',' + a + ')' : s; } function rounded(ctx, x, y, w, h, r) { r = Math.max(0, Math.min(r, w/2, h/2)); ctx.beginPath(); ctx.moveTo(x+r,y); ctx.arcTo(x+w,y,x+w,y+h,r); ctx.arcTo(x+w,y+h,x,y+h,r); ctx.arcTo(x,y+h,x,y,r); ctx.arcTo(x,y,x+w,y,r); ctx.closePath(); } function cloneBlock(b) { return {hash:b.hash,ts:b.ts,blue_score:b.blue_score,blue:b.blue_score,daa:b.daa, parents:b.parents.slice(),chain:b.chain,color:b.color,miner:b.miner,locked:b.locked, final:b.final,proven:b.proven,shards:b.shards.map(function(s){return {state:s.state};})}; } function normalize(b) { if (!b || typeof b.hash !== 'string' || !b.hash.length || b.hash.length>256 || !finite(b.ts) || b.ts<0) return null; return {hash:b.hash,ts:b.ts,blue_score:num(b.blue_score),daa:num(b.daa), parents:Array.isArray(b.parents)?Array.from(new Set(b.parents.filter(function(p){return typeof p==='string' && p!==b.hash;}))).slice(0,128):[], chain:b.chain===true,color:['blue','red','pending'].includes(b.color)?b.color:'pending', miner:typeof b.miner==='string'?b.miner.slice(0,256):'unknown',locked:b.locked===true,final:b.final===true,proven:b.proven===true, shards:Array.isArray(b.shards)?b.shards.slice(0,256).map(function(s){return {state:s && ['planned','proving','verified','paid'].includes(s.state)?s.state:'unknown'};}):[]}; } function mount(canvas, opts) { opts=opts||{}; if (!canvas || typeof canvas.getContext!=='function') throw new TypeError('IgneumDag.mount requires a canvas element.'); if (instances.has(canvas)) instances.get(canvas).destroy(); var ctx=canvas.getContext('2d'); if(!ctx) throw new Error('A 2D canvas context is required.'); var doc=canvas.ownerDocument, compact=!!opts.compact; var windowS=clamp(finite(opts.window)?opts.window:(compact?90:120),30,300); var maxBlocks=clamp(finite(opts.maxBlocks)?opts.maxBlocks:6000,100,20000); var lag=finite(opts.lagMs)?clamp(opts.lagMs,0,10000):2500; var fps=clamp(finite(opts.fps)?opts.fps:30,10,60), minFrame=1000/fps; // narrow (phones, under narrowBreak px, or opts.narrow true/false): window narrowWindow s until the viewer sets one, at most // narrowLanes lanes, nodes no smaller than narrowMinNode, hairlines off except the selected chain and the selected block, // checkpoint labels as the percent alone. The owner's call of 7 Oct 2026: faster drift, more gap, on a phone. var narrow=false,narrowBreak=finite(opts.narrowBreak)?opts.narrowBreak:720,narrowWindow=clamp(finite(opts.narrowWindow)?opts.narrowWindow:30,30,300),narrowLanes=clamp(finite(opts.narrowLanes)?opts.narrowLanes:4,2,12),narrowMinNode=finite(opts.narrowMinNode)?opts.narrowMinNode:11,baseWindow=0,windowTouched=false; var maxLanes=clamp(finite(opts.maxLanes)?opts.maxLanes:7,2,12), minNode=finite(opts.minNode)?opts.minNode:9.5, hairAlpha=finite(opts.hairlineAlpha)?opts.hairlineAlpha:.12, laneReach=finite(opts.laneReach)?opts.laneReach:2, othersScale=finite(opts.othersScale)?opts.othersScale:.75, provenGlow=opts.provenGlow!==false; var mq=root.matchMedia?root.matchMedia('(prefers-reduced-motion: reduce)'):null; var reduced=mq?mq.matches:false, manualMotion=true; var mediaTheme=root.matchMedia?root.matchMedia('(prefers-color-scheme: dark)'):null; var col={}, W=0,H=0,dpr=1,padL=100,padR=22,padT=57,padB=37; var model=new Map(), view=new Map(), blocks=[], cps=[], latestCps=[], lanes=[], laneOf=new Map(); var state='connecting',reason='Waiting for observer',destroyed=false,paused=false,following=true,engaged=false; var fixedRight=0,pausedRight=0,staleRight=null,reducedRight=Date.now()-lag, lastGood=0, lastData=null; var selected=null,hover=null,filter='all',queuedCount=0,invalid=0,omitted=0; var visible=true,raf=0,lastPaint=-Infinity,frameCount=0,lastT=0,freezeT=0; var pollTimer=0,healthTimer=0,timeout=0,controller=null,inflight=false,pollAgain=false,failures=0; var remove=[],ro=null,io=null,mo=null,drag=null,pinch=null; var lastCheckpointStates=new Map(),checkpointBursts=new Map(); var initialAttributes={tabindex:canvas.getAttribute('tabindex'),role:canvas.getAttribute('role'),label:canvas.getAttribute('aria-label'),touch:canvas.style.touchAction,cursor:canvas.style.cursor}; var tip=opts.tooltip||null,chip=opts.chip||null; var mine=typeof opts.mine==='function'?opts.mine:opts.mine?function(b){return b.miner===opts.mine;}:function(){return false;}; function own(b){try{return !!mine(b);}catch(e){return false;}} function emit(name){if(destroyed || typeof opts[name]!=='function')return;var args=[].slice.call(arguments,1);try{opts[name].apply(null,args);}catch(e){console.error('IgneumDag '+name+' callback:',e);}} function on(target,name,handler,config){target.addEventListener(name,handler,config);remove.push(function(){target.removeEventListener(name,handler,config);});} function theme(){ var s=getComputedStyle(doc.documentElement); function c(n,f){return s.getPropertyValue(n).trim()||f;} col={ember:c('--ember','#F2541B'),emberHi:c('--ember-hi','#FF6A2B'),molten:c('--molten','#FFB35C'), bone:c('--bone','#F4F1EC'),ash:c('--ash','#9A9A9E'),line:c('--line','#2A2A30'),line2:c('--line-2','#3A3A42'), bg:c('--row',c('--obsidian','#111114')),blue:c('--included','#3B7DD8'),red:c('--excluded','#B0362B'), surface:c('--graphite','#16161A')}; paint();redraw(); } function rightTime(){ if(paused)return pausedRight; if(!following)return fixedRight; if(state!=='live' && staleRight!==null)return staleRight; return reduced||!manualMotion?reducedRight:Date.now()-lag; } function viewportW(){var w=root.innerWidth||(doc.documentElement?doc.documentElement.clientWidth:0);return w>0?w:W;} function xOf(ts,right){return padL+(W-padL-padR)*(1-(right-ts)/(windowS*1000));} function eventXY(ev){var r=canvas.getBoundingClientRect();return {x:(ev.clientX-r.left)*W/(r.width||1),y:(ev.clientY-r.top)*H/(r.height||1)};} function setState(s,r){ if(state===s && reason===r)return; if(s!=='live' && state==='live')staleRight=rightTime(); state=s;reason=r||null; if(s==='live')staleRight=null; emit('onState',s,reason);redraw(); } function layout(){ if(!baseWindow)baseWindow=windowS; var wasNarrow=narrow;narrow=opts.narrow===true||(opts.narrow!==false&&!compact&&W>0&&viewportW()=start && b.ts<=end){counts.set(b.miner,(counts.get(b.miner)||0)+1);if(own(b))mineKeys.add(b.miner);}}); var capacity=Math.min(narrow?narrowLanes:maxLanes,Math.max(2,Math.floor((H-padT-padB)/(compact?26:narrow?40:46)))),ids=Array.from(counts.keys()); ids.sort(function(a,b){return (mineKeys.has(b)?1:0)-(mineKeys.has(a)?1:0)||(counts.get(b)-counts.get(a))||a.localeCompare(b);}); var chosen=ids.slice(0,ids.length>capacity?capacity-1:capacity); // Preserve existing key order instead of re-ranking every poll. Always keep the user's lane visible. var keep=lanes.filter(function(id){return chosen.includes(id);}); chosen.forEach(function(id){if(!keep.includes(id))keep.push(id);}); keep.sort(function(a,b){return (mineKeys.has(b)?1:0)-(mineKeys.has(a)?1:0);}); lanes=keep;if(ids.length>chosen.length)lanes.push('__others__'); laneOf=new Map();lanes.forEach(function(id,i){laneOf.set(id,i);}); var now=performance.now(); blocks.forEach(function(b){var i=laneOf.has(b.miner)?laneOf.get(b.miner):Math.max(0,lanes.length-1); var y=padT+(i+.5)*(H-padT-padB)/Math.max(1,lanes.length); if(!finite(b.y)){b.y=y;b.fromY=y;b.targetY=y;} if(b.targetY!==y){b.fromY=b.y;b.targetY=y;b.laneAt=now;} }); } function size(){ if(destroyed)return; var nextW=canvas.clientWidth,nextH=canvas.clientHeight,nextDpr=Math.min(root.devicePixelRatio||1,2); if(W===nextW&&H===nextH&&dpr===nextDpr&&canvas.width===Math.round(nextW*nextDpr)){redraw();return;} W=nextW;H=nextH;dpr=nextDpr; canvas.width=Math.round(W*dpr);canvas.height=Math.round(H*dpr);ctx.setTransform(dpr,0,0,dpr,0,0);layout();paint();redraw(); } function syncView(){ var now=performance.now(),first=view.size===0, next=new Map(); model.forEach(function(b,hash){var old=view.get(hash),v=Object.assign({},b); v.born=old?old.born:first?now-3000:now;v.newArrival=old?old.newArrival:!first;v.visibleAt=old?old.visibleAt:null;v.changed=old && (old.proven!==b.proven||old.locked!==b.locked||old.color!==b.color||old.shards.map(function(s){return s.state;}).join(',')!==b.shards.map(function(s){return s.state;}).join(','))?now:old?old.changed:now-3000; v.y=old?old.y:NaN;v.fromY=old?old.fromY:NaN;v.targetY=old?old.targetY:NaN;v.laneAt=old?old.laneAt:now; next.set(hash,v); }); view=next;blocks=Array.from(view.values()).sort(function(a,b){return a.ts-b.ts||a.hash.localeCompare(b.hash);}); cps=latestCps.map(function(c){return Object.assign({},c);}); cps.forEach(function(c){var prev=lastCheckpointStates.get(c.index);if(prev==='pending'&&c.state==='locked')checkpointBursts.set(c.index,now);lastCheckpointStates.set(c.index,c.state);}); var cpKeys=new Set(cps.map(function(c){return c.index;}));lastCheckpointStates.forEach(function(_,key){if(!cpKeys.has(key)){lastCheckpointStates.delete(key);checkpointBursts.delete(key);}}); layout(); if(selected && !view.has(selected)){selected=null;emit('onSelect',null,'expired');} else if(selected)emit('onSelect',cloneBlock(view.get(selected)),'update'); if(hover&&!view.has(hover))hover=null; queuedCount=0; } function push(d){ if(destroyed)return false; if(!d||d.ok!==true||!d.state||typeof d.state!=='object'||!Array.isArray(d.blocks)){ setState('failed','Invalid observer reply; expected {ok, state, blocks}.');return false; } var now=Date.now(), incoming=[], bad=0; d.blocks.forEach(function(b){var n=normalize(b);if(n)incoming.push(n);else bad++;}); if(d.blocks.length && !incoming.length){setState('failed','Observer reply contains no valid block records.');return false;} invalid=bad;lastGood=now;reducedRight=now-lag;failures=0;lastData=d; incoming.forEach(function(b){if(!model.has(b.hash))queuedCount++;model.set(b.hash,b);}); var newest=0;model.forEach(function(b){newest=Math.max(newest,b.ts);}); // Bounded retention uses the newest observed header, not a local wall-clock guess. var cut=newest-340000;model.forEach(function(b,h){if(b.tsmaxBlocks){var ordered=Array.from(model.values()).sort(function(a,b){return b.ts-a.ts;});omitted=model.size-maxBlocks;model=new Map(ordered.slice(0,maxBlocks).map(function(b){return[b.hash,b];}));} latestCps=d.finality&&Array.isArray(d.finality.checkpoints)?d.finality.checkpoints.filter(function(c){return c&&finite(c.blue_score)&&['pending','locked'].includes(c.state);}).slice(-128).map(function(c){return {index:c.index,blue_score:c.blue_score,state:c.state,fraction_total:finite(c.fraction_total)&&c.fraction_total>=0&&c.fraction_total<=1?c.fraction_total:null};}):[]; if(d.state.stale){if(staleRight===null)staleRight=Math.min(now-lag,newest?newest+lag:now-lag);setState('stale','Observer stale'+(finite(d.state.age_s)?'; last update '+Math.round(d.state.age_s)+' s ago':''));} else setState('live',null); if(!paused)syncView(); emit('onData',d);emit('onStats',stats());if(!paused){/*paint-on-push*/paint();redraw();}return true; } function stats(){ var r=rightTime(),v=blocks.filter(function(b){return b.ts>=r-windowS*1000&&b.ts<=r;}); return {blocks:v.length,included:v.filter(function(b){return b.color==='blue';}).length,excluded:v.filter(function(b){return b.color==='red';}).length, selectedChain:v.filter(function(b){return b.chain;}).length,proven:v.filter(function(b){return b.proven;}).length, own:v.filter(own).length,minerKeys:new Set(v.map(function(b){return b.miner;})).size, lockedCheckpoints:cps.filter(function(c){return c.state==='locked';}).length,paused:paused,following:following,queued:paused?queuedCount:0, invalidRecords:invalid,omittedRecords:omitted,rendered:blocks.length,frames:frameCount,window:windowS,narrow:narrow,lanes:lanes.length,reducedMotion:reduced||!manualMotion,state:state}; } function poll(){ if(destroyed||opts.poll===false||doc.hidden)return; if(inflight){pollAgain=true;return;} clearTimeout(pollTimer);inflight=true;controller=new AbortController();var signal=controller.signal; timeout=setTimeout(function(){if(controller)controller.abort();},finite(opts.timeoutMs)?opts.timeoutMs:8000); var url; try{url=new URL(opts.url||'/api/live',doc.baseURI);if(!['https:','http:'].includes(url.protocol))throw new Error('Use an HTTP(S) observer URL.');url.searchParams.set('window',String(Math.round(windowS)));} catch(e){finishError(e);return;} fetch(url.href,{cache:'no-store',signal:signal,credentials:'same-origin'}).then(function(r){if(!r.ok)throw new Error('HTTP '+r.status);return r.json();}).then(function(d){if(destroyed)return;if(!push(d))throw new Error('Invalid observer reply');}).catch(finishError).finally(finish); function finishError(e){if(destroyed||doc.hidden)return;failures++;setState(failures>=2?'failed':'stale','Observer unavailable; retrying'+(e&&e.name==='AbortError'?' (timeout)':''));if(!url)finish();} function finish(){clearTimeout(timeout);timeout=0;controller=null;inflight=false;if(destroyed||opts.poll===false||doc.hidden)return;var again=pollAgain;pollAgain=false;pollTimer=setTimeout(poll,again?0:Math.min(30000,2000*Math.pow(2,Math.min(failures,4))));} } function passes(b){return filter==='all'||filter==='chain'&&b.chain||filter==='mine'&&own(b);} function curve(b,p){var mx=(b.x+p.x)/2;ctx.beginPath();ctx.moveTo(p.x,p.y);ctx.bezierCurveTo(mx,p.y,mx,b.y,b.x,b.y);} function pointOn(b,p,t){var u=1-t,mx=(b.x+p.x)/2;return {x:u*u*u*p.x+3*u*u*t*mx+3*u*t*t*mx+t*t*t*b.x,y:u*u*u*p.y+3*u*u*t*p.y+3*u*t*t*b.y+t*t*t*b.y};} function lockIcon(x,y,size,color){ctx.strokeStyle=color;ctx.lineWidth=1.25;rounded(ctx,x-size*.42,y,size*.84,size*.65,1.5);ctx.stroke();ctx.beginPath();ctx.arc(x,y,size*.25,Math.PI,0);ctx.stroke();} function draw(t){ if(destroyed||!W||!H)return; frameCount++;var rt=rightTime(),left=rt-windowS*1000,animate=!reduced&&manualMotion&&!paused&&state==='live';var clock=paused?freezeT:t; ctx.setTransform(dpr,0,0,dpr,0,0);ctx.clearRect(0,0,W,H);ctx.fillStyle=col.bg;ctx.fillRect(0,0,W,H); var plotW=W-padL-padR, plotH=H-padT-padB,lh=plotH/Math.max(1,lanes.length); // Sparse time ticks; true horizontal coordinate is header time, not DAA spacing. ctx.lineWidth=1;ctx.setLineDash([2,7]); for(var g=0;g<=6;g++){var gx=padL+plotW*g/6;ctx.strokeStyle=rgba(col.line,.52);ctx.beginPath();ctx.moveTo(gx,padT-10);ctx.lineTo(gx,H-padB);ctx.stroke();} ctx.setLineDash([]); lanes.forEach(function(lane,i){var y=padT+(i+.5)*lh, ownLane=blocks.some(function(b){return b.miner===lane&&own(b);}); if(ownLane){var grad=ctx.createLinearGradient(padL,0,W,0);grad.addColorStop(0,rgba(col.molten,.085));grad.addColorStop(1,rgba(col.molten,.005));ctx.fillStyle=grad;rounded(ctx,padL-5,y-lh*.4,plotW+5,lh*.8,7);ctx.fill();} ctx.strokeStyle=rgba(ownLane?col.molten:col.line,ownLane?.20:.65);ctx.beginPath();ctx.moveTo(padL,y);ctx.lineTo(W-padR,y);ctx.stroke(); if(padL>50){ctx.font='500 10px ui-monospace, SFMono-Regular, Consolas, monospace';ctx.textBaseline='middle';ctx.textAlign='left';ctx.fillStyle=ownLane?col.molten:col.ash; ctx.fillText(ownLane?'YOUR KEY':lane==='__others__'?'OTHERS':lane.slice(0,8),12,y-5); ctx.font='8px ui-monospace, monospace';ctx.fillStyle=rgba(col.ash,.78);ctx.fillText(ownLane?'YOUR BLOCKS':lane==='__others__'?'GROUPED KEYS':'MINER KEY',12,y+10); } }); var cpLabels=[]; cps.forEach(function(c){ // Exact score only. Never pin a checkpoint to an unrelated nearest block. var candidates=blocks.filter(function(b){return b.blue_score===c.blue_score;}); var ref=candidates.find(function(b){return b.locked;})||candidates.find(function(b){return b.chain;})||(candidates.length===1?candidates[0]:null); if(!ref||ref.tsrt)return; var x=xOf(ref.ts,rt),locked=c.state==='locked',burstAt=checkpointBursts.get(c.index),burst=animate&&burstAt?Math.max(0,1-(t-burstAt)/1400):0; var band=ctx.createLinearGradient(x-20,0,x+36,0);band.addColorStop(0,rgba(col.ember,0));band.addColorStop(.38,rgba(col.ember,locked?.09:.035));band.addColorStop(1,rgba(col.ember,0));ctx.fillStyle=band;ctx.fillRect(x-20,padT-12,56,plotH+12); ctx.strokeStyle=rgba(col.ember,locked?.6:.32);ctx.lineWidth=locked?1.5:1;ctx.setLineDash(locked?[]:[3,6]);ctx.beginPath();ctx.moveTo(x,padT-14);ctx.lineTo(x,H-padB);ctx.stroke();ctx.setLineDash([]); if(burst>0){ctx.strokeStyle=rgba(col.molten,burst*.5);ctx.lineWidth=1;ctx.strokeRect(x-(1-burst)*42,padT-12,(1-burst)*84,plotH+12);} if(!compact && (narrow || W>510))cpLabels.push({x:x,cp:c,locked:locked}); }); cpLabels.sort(function(a,b){return b.x-a.x;});var cpLast=Infinity; cpLabels.forEach(function(p){var pct=p.cp.fraction_total===null||p.cp.fraction_total===undefined?'':Math.round(p.cp.fraction_total*100)+'%',label=narrow?(pct||(p.locked?'LOCKED':'PENDING')):(p.locked?'LOCKED':'PENDING')+(pct?' '+pct:''),w=label.length*5.5+(narrow?14:22),x=clamp(p.x-w/2,padL,W-padR-w);if(x+w+8>cpLast)return;cpLast=x; ctx.fillStyle=col.bg;ctx.strokeStyle=rgba(col.ember,p.locked?.45:.23);rounded(ctx,x,17,w,22,4);ctx.fill();ctx.stroke();ctx.font='9px ui-monospace, monospace';ctx.fillStyle=p.locked?col.ember:col.ash;ctx.textAlign='center';ctx.textBaseline='middle';ctx.fillText(label,x+w/2,28); }); // Animate lane transitions without changing their underlying key identity. blocks.forEach(function(b){b.x=xOf(b.ts,rt);var progress=animate?clamp((clock-b.laneAt)/450,0,1):1;progress=1-Math.pow(1-progress,3);b.y=b.fromY+(b.targetY-b.fromY)*progress;b.on=b.ts>=left&&b.ts<=rt&&passes(b);if(b.on&&b.newArrival&&b.visibleAt===null){b.visibleAt=t;b.born=t;}}); ctx.save();ctx.beginPath();ctx.rect(padL-1,padT-15,plotW+2,plotH+25);ctx.clip(); blocks.forEach(function(b){if(!b.on)return;var bSel=selected&&b.hash===selected;b.parents.forEach(function(hash,pi){var p=view.get(hash);if(!p||!passes(p)||p.x>W-padR||p.x0)return;var li=laneOf.has(b.miner)?laneOf.get(b.miner):lanes.length-1,lp=laneOf.has(p.miner)?laneOf.get(p.miner):lanes.length-1;if(Math.abs(li-lp)>laneReach)return;} if(path||selectedEdge){ctx.strokeStyle=rgba(path?col.ember:col.bone,path?.1:.10);ctx.lineWidth=path?8:4;curve(b,p);ctx.stroke();} ctx.strokeStyle=rgba(path?col.ember:selectedEdge?col.bone:b.color==='red'?col.red:col.ash,path?.68:selectedEdge?.7:hairAlpha);ctx.lineWidth=path?1.65:selectedEdge?1.4:1;curve(b,p);ctx.stroke(); var age=t-b.born;if(animate&&age>=0&&age<1400){var f=clamp(age/1150,0,1),pt=pointOn(b,p,f);ctx.shadowBlur=10;ctx.shadowColor=path?col.ember:col.blue;ctx.fillStyle=path?col.molten:col.blue;ctx.beginPath();ctx.arc(pt.x,pt.y,path?2.2:1.65,0,TAU);ctx.fill();ctx.shadowBlur=0;} });}); var S=compact?6.5:narrow?clamp(lh*.24,narrowMinNode,15):clamp(lh*.22,minNode,13); blocks.forEach(function(b){if(!b.on)return; var age=t-b.born,newness=animate?Math.max(clamp(1-age/1700,0,1),clamp(1-(t-b.changed)/900,0,1)*.65):0; var ink=b.color==='red'?col.red:b.chain?col.ember:b.color==='blue'?col.blue:col.ash; var isOwn=own(b),isSelected=b.hash===selected||b.hash===hover,inOthers=!laneOf.has(b.miner),sz=S*(1+(newness*.15))*(inOthers?othersScale:1); if(newness>0||isOwn||isSelected){var glow=ctx.createRadialGradient(b.x,b.y,0,b.x,b.y,sz*3.8);glow.addColorStop(0,rgba(isOwn?col.molten:ink,newness*.26+(isSelected?.15:isOwn?.06:0)));glow.addColorStop(1,rgba(ink,0));ctx.fillStyle=glow;ctx.fillRect(b.x-sz*4,b.y-sz*4,sz*8,sz*8);} ctx.globalAlpha=(b.color==='red'?.78:1)*(inOthers&&!isSelected&&!isOwn?othersScale:1); // a proven block glows and carries a ring, so the proof reads from across the room if(provenGlow&&b.proven){var pg=ctx.createRadialGradient(b.x,b.y,0,b.x,b.y,sz*3.2);pg.addColorStop(0,rgba(ink,.26));pg.addColorStop(1,rgba(ink,0));ctx.fillStyle=pg;ctx.fillRect(b.x-sz*3.4,b.y-sz*3.4,sz*6.8,sz*6.8);ctx.strokeStyle=rgba(ink,.6);ctx.lineWidth=1;ctx.beginPath();ctx.arc(b.x,b.y,sz+5,0,TAU);ctx.stroke();} // Inset tiles: inclusion is the outline; proof is the core, never the same status. ctx.fillStyle=col.bg;ctx.strokeStyle=rgba(ink,b.chain?.95:.8);ctx.lineWidth=b.chain?1.75:1.25;rounded(ctx,b.x-sz,b.y-sz,2*sz,2*sz,3);ctx.fill();ctx.stroke(); var tile=ctx.createLinearGradient(b.x,b.y-sz,b.x,b.y+sz);tile.addColorStop(0,rgba(ink,b.proven?.5:.16));tile.addColorStop(1,rgba(ink,b.proven?.22:.025));ctx.fillStyle=tile;rounded(ctx,b.x-sz+2,b.y-sz+2,2*sz-4,2*sz-4,2);ctx.fill(); if(b.proven){ctx.strokeStyle=rgba(b.color==='red'?col.red:col.bone,.9);ctx.lineWidth=1.3;ctx.beginPath();ctx.moveTo(b.x-3,b.y);ctx.lineTo(b.x-1,b.y+2);ctx.lineTo(b.x+3,b.y-2);ctx.stroke();} else if(b.color==='red'){ctx.strokeStyle=ink;ctx.lineWidth=1.1;ctx.beginPath();ctx.moveTo(b.x-2,b.y-2);ctx.lineTo(b.x+2,b.y+2);ctx.moveTo(b.x+2,b.y-2);ctx.lineTo(b.x-2,b.y+2);ctx.stroke();} else{ctx.fillStyle=rgba(ink,.65);ctx.fillRect(b.x-1.3,b.y-1.3,2.6,2.6);} if(!compact && b.shards.length){var total=b.shards.length,shown=Math.min(total,8),gap=1.6,barW=2*sz,seg=(barW-gap*(shown-1))/shown; for(var k=0;k50)ctx.fillText('HEADER TIME',12,H-13); if(visibleBlocks.length && W>600){var last=visibleBlocks[visibleBlocks.length-1];ctx.textAlign='right';ctx.fillText('DAA '+fmt(last.daa),W-padR,8);} } if(state==='live'&&following&&!paused){ctx.strokeStyle=rgba(col.ember,.3);ctx.beginPath();ctx.moveTo(W-padR,padT-9);ctx.lineTo(W-padR,H-padB);ctx.stroke();ctx.fillStyle=col.ember;ctx.beginPath();ctx.arc(W-padR,padT-12,2.3,0,TAU);ctx.fill();} if(!blocks.some(function(b){return b.on;})){ ctx.textAlign='center';ctx.fillStyle=col.ash;ctx.font='12px system-ui, sans-serif';ctx.fillText(state==='connecting'?'Waiting for observer data':filter==='mine'?'No blocks for your key in this window':filter==='chain'?'No selected-chain blocks in this window':'No blocks in this time window',padL+plotW/2,H/2); } var active=selected?view.get(selected):hover?view.get(hover):null;if(active&&active.on)showTip(active);else if(tip)tip.hidden=true; lastT=t; } function continuous(){return !destroyed&&!doc.hidden&&visible&&!paused&&!reduced&&manualMotion&&state==='live'&&blocks.length>0;} function frame(t){raf=0;if(destroyed||doc.hidden||!visible)return;if(t-lastPaint>=minFrame-.5){draw(t);lastPaint=t;}if(continuous())raf=requestAnimationFrame(frame);} // one synchronous frame of the current picture, whatever the document's visibility says (2.0.3) function paint(){if(destroyed||!W||!H)return;var t=performance.now();draw(t);lastPaint=t;} function redraw(){if(destroyed||raf||doc.hidden||!visible)return;lastPaint=-Infinity;raf=requestAnimationFrame(frame);} function stopFrame(){if(raf)cancelAnimationFrame(raf);raf=0;} function words(b){ if(!b)return {title:'No block selected',lines:[]}; var inclusion=b.color==='blue'?'included':b.color==='red'?'excluded':'pending'; var bits=[inclusion];if(b.chain)bits.push('selected chain');if(b.proven)bits.push('proven');if(b.locked)bits.push('locked checkpoint');if(b.final)bits.push('final'); return {title:b.hash+' '+bits.join(' / '),lines:[(own(b)?'Your block / ':'')+'miner key '+b.miner, 'Blue score '+fmt(b.blue_score===undefined?b.blue:b.blue_score)+' / DAA '+fmt(b.daa),b.parents.length+' parent'+(b.parents.length===1?'':'s'), 'Shards: '+(b.shards.length?b.shards.map(function(s){return s.state;}).join(', '):'no shard plan yet')]}; } function showTip(b){ if(!tip)return; if(!b){tip.hidden=true;return;} var w=words(b),key=w.title+'|'+w.lines.join('|'); if(tip.dataset.igneumKey!==key){tip.replaceChildren();var title=doc.createElement('b');title.textContent=w.title;tip.appendChild(title);w.lines.forEach(function(line){var p=doc.createElement('span');p.textContent=line;p.style.display='block';tip.appendChild(p);});tip.dataset.igneumKey=key;} tip.hidden=false;tip.style.pointerEvents='none';tip.style.position='absolute';tip.style.left='0px';tip.style.top='0px';tip.style.maxWidth=Math.max(160,W-24)+'px';tip.style.whiteSpace='normal';tip.style.overflowWrap='anywhere'; var host=tip.offsetParent||canvas.parentElement,cr=canvas.getBoundingClientRect(),pr=host.getBoundingClientRect(); var tw=tip.offsetWidth,th=tip.offsetHeight;var x=clamp(b.x+16,8,Math.max(8,W-tw-8)),y=b.y-th-18;if(y<8)y=Math.min(H-th-8,b.y+22); tip.style.transform='translate('+Math.round(cr.left-pr.left-host.clientLeft+x)+'px,'+Math.round(cr.top-pr.top-host.clientTop+Math.max(8,y))+'px)'; } function hit(ev){var p=eventXY(ev),best=null,dist=22*22;blocks.forEach(function(b){if(!b.on)return;var d=(b.x-p.x)**2+(b.y-p.y)**2;if(d4)drag.moved=true;if(drag.moved){panTo(drag.start-dx*windowS*1000/Math.max(1,W-padL-padR));canvas.style.cursor='grabbing';return;}} var b=hit(ev);hover=b?b.hash:null;canvas.style.cursor=b?'pointer':'grab';if(!selected)showTip(b);if(!continuous())redraw(); }); on(canvas,'pointerup',function(ev){if(ev.pointerType==='touch')return;if(!drag||drag.id!==ev.pointerId)return;var moved=drag.moved;drag=null;if(canvas.hasPointerCapture(ev.pointerId))canvas.releasePointerCapture(ev.pointerId);canvas.style.cursor='grab';if(!moved){var b=hit(ev);select(b&&selected!==b.hash?b.hash:null,'pointer');}}); on(canvas,'pointercancel',function(){drag=null;}); on(canvas,'pointerleave',function(){hover=null;if(!drag)engage(false);if(!selected)showTip(null);redraw();}); on(canvas,'wheel',function(ev){if(!ev.ctrlKey&&!ev.metaKey&&!engaged)return;if(Math.abs(ev.deltaY)<1)return;ev.preventDefault();zoom(ev.deltaY>0?1.15:1/1.15);},{passive:false}); var touchStart=null; function dist(touches){return Math.hypot(touches[0].clientX-touches[1].clientX,touches[0].clientY-touches[1].clientY);} on(canvas,'touchstart',function(ev){if(ev.touches.length===2){pinch={distance:dist(ev.touches),window:windowS};touchStart=null;}else if(ev.touches.length===1){touchStart={x:ev.touches[0].clientX,y:ev.touches[0].clientY};}},{passive:true}); on(canvas,'touchmove',function(ev){if(ev.touches.length===2&&pinch){ev.preventDefault();setWindow(pinch.window*pinch.distance/Math.max(1,dist(ev.touches)));}else if(touchStart&&ev.touches.length){if(Math.hypot(ev.touches[0].clientX-touchStart.x,ev.touches[0].clientY-touchStart.y)>8)touchStart=null;}},{passive:false}); on(canvas,'touchend',function(ev){if(pinch){if(ev.touches.length<2)pinch=null;touchStart=null;return;}if(touchStart&&ev.changedTouches.length){var b=hit(ev.changedTouches[0]);select(b&&selected!==b.hash?b.hash:null,'touch');touchStart=null;}},{passive:true}); on(canvas,'keydown',function(ev){ var key=ev.key;if(['ArrowLeft','ArrowRight','ArrowUp','ArrowDown','Home','End'].includes(key)){ ev.preventDefault();var list=blocks.filter(function(b){return b.on;}),i=list.findIndex(function(b){return b.hash===selected;}),next; if(!list.length)return;if(key==='Home')next=0;else if(key==='End')next=list.length-1;else next=i<0?list.length-1:clamp(i+(['ArrowRight','ArrowDown'].includes(key)?1:-1),0,list.length-1);select(list[next].hash,'keyboard'); }else if(key===' '){ev.preventDefault();setPaused(!paused);}else if(key==='+'||key==='='){ev.preventDefault();zoom(1/1.2);}else if(key==='-'){ev.preventDefault();zoom(1.2);}else if(key.toLowerCase()==='f'){ev.preventDefault();follow();} }); on(doc,'keydown',function(ev){if(ev.key==='Escape'&&(engaged||doc.activeElement===canvas)){engage(false);select(null,'escape');}}); on(doc,'pointerdown',function(ev){if(engaged&&ev.target!==canvas&&!(chip&&chip.contains(ev.target)))engage(false);}); if(opts.pauseButton){opts.pauseButton.setAttribute('aria-pressed','false');on(opts.pauseButton,'click',function(){setPaused(!paused);});} on(root,'resize',size); if('ResizeObserver'in root){ro=new ResizeObserver(size);ro.observe(canvas);} if('IntersectionObserver'in root){io=new IntersectionObserver(function(es){visible=es[0].isIntersecting;if(visible)redraw();else stopFrame();},{threshold:0});io.observe(canvas);} if('MutationObserver'in root){mo=new MutationObserver(theme);mo.observe(doc.documentElement,{attributes:true,attributeFilter:['data-theme','class','style']});} if(mq&&mq.addEventListener)on(mq,'change',function(e){reduced=e.matches;reducedRight=Date.now()-lag;stopFrame();redraw();}); if(mediaTheme&&mediaTheme.addEventListener)on(mediaTheme,'change',theme); on(doc,'visibilitychange',function(){if(doc.hidden){stopFrame();clearTimeout(pollTimer);if(controller)controller.abort();}else{poll();redraw();}}); healthTimer=setInterval(function(){if(destroyed||doc.hidden||!lastGood)return;if(state==='live'&&Date.now()-lastGood>(finite(opts.staleAfterMs)?opts.staleAfterMs:15000))setState('stale','No observer update for '+Math.round((Date.now()-lastGood)/1000)+' s');},1000); function destroy(){if(destroyed)return;destroyed=true;stopFrame();clearTimeout(pollTimer);clearTimeout(timeout);clearInterval(healthTimer);if(controller)controller.abort();remove.forEach(function(fn){fn();});if(ro)ro.disconnect();if(io)io.disconnect();if(mo)mo.disconnect();if(tip)tip.hidden=true;canvas.classList.remove('engaged'); [['tabindex',initialAttributes.tabindex],['role',initialAttributes.role],['aria-label',initialAttributes.label]].forEach(function(pair){if(pair[1]===null)canvas.removeAttribute(pair[0]);else canvas.setAttribute(pair[0],pair[1]);});canvas.style.touchAction=initialAttributes.touch;canvas.style.cursor=initialAttributes.cursor;instances.delete(canvas);model.clear();view.clear();blocks=[]; } var api={push:push,setPaused:setPaused,zoom:zoom,engage:engage,getWindow:function(){return windowS;},getState:function(){return {state:state,reason:reason};},redraw:redraw,words:words, destroy:destroy,follow:follow,setWindow:setWindow,setFilter:setFilter,setMotion:setMotion,select:select,getStats:stats, getViewRange:function(){return {start:rightTime()-windowS*1000,end:rightTime(),filter:filter};},getBlocks:function(){return blocks.map(cloneBlock);},getCheckpoints:function(){return cps.map(function(c){return Object.assign({},c);});}, getSnapshot:function(){return {ok:true,state:{stale:state!=='live'},blocks:blocks.map(cloneBlock),finality:{checkpoints:cps.map(function(c){return Object.assign({},c);})}};}}; instances.set(canvas,api);if(chip)chip.hidden=true;theme();size();emit('onState',state,reason);if(opts.poll!==false)poll();redraw();return api; } root.IgneumDag={mount:mount,version:'2.0.3'}; })(window);