renderer pack EMBER 02 supplied by the project lead, 7 Oct 2026: src/live-dag.js and src/proof-core.js adopted byte for byte (sha256 582f2e73... and c2a094a6...), reviewed as third-party code (one same-origin fetch of /api/live when poll is on, no eval, no CDN, no font download, tokens read from :root in both themes, checkpoints placed only on an exact blue score, proof never implies inclusion, a lock only when reported), its tests/verify.py run on this Mac (73 of 73) and the module mounted against the real observer feed (state live, only /api/live requested, zero records promoted). Home: the pack's full mode in the one-screen fold with the legend row and the newest block's caption, fed by the page's own reader. /live: the pack in the graph slot, the four miner stats (blocks in window, your blocks, proven, latest checkpoint), the All blocks / Selected chain / Your blocks tabs, Pause and Follow, the window stepper, the inspector column with IgneumProof's shard scene, the proof shards list, inclusion, miner key, blue score, DAA, parents, header time, checkpoint, finality, and the three-beat Motion, with meaning block; the observatory's six tiles, identities, event stream, block activity, light client and table stay. site.css gains --included and --excluded. The standalone demo is the pack's standalone.html; the 10-second recording is from this branch's /live. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
72 lines
8 KiB
JavaScript
72 lines
8 KiB
JavaScript
/* Igneum proof detail. A data-driven companion, not a mining-progress estimate.
|
|
* One orbital tile per displayed shard (up to 12). No time-based status promotion.
|
|
* IgneumProof.mount(canvas).setBlock(observerBlock); destroy() on unmount.
|
|
*/
|
|
(function(root){
|
|
'use strict';
|
|
var mounted=new WeakMap(),TAU=Math.PI*2;
|
|
function mount(canvas,opts){
|
|
opts=opts||{};if(mounted.has(canvas))mounted.get(canvas).destroy();
|
|
var ctx=canvas.getContext('2d');if(!ctx)throw new Error('A 2D canvas context is required.');
|
|
var W=0,H=0,dpr=1,block=null,raf=0,disposed=false,paused=false,inView=true,lastPaint=0,frames=0,force=true;
|
|
var mq=matchMedia('(prefers-reduced-motion: reduce)'),reduce=mq.matches,motion=true,col={},ro,io,mo;
|
|
var phase=0,lastT=0;
|
|
function alpha(c,a){if(/^#[a-f\d]{6}$/i.test(c))return 'rgba('+parseInt(c.slice(1,3),16)+','+parseInt(c.slice(3,5),16)+','+parseInt(c.slice(5,7),16)+','+a+')';return c;}
|
|
function theme(){var s=getComputedStyle(document.documentElement);function c(k,f){return s.getPropertyValue(k).trim()||f;}col={ember:c('--ember','#F2541B'),gold:c('--molten','#FFB35C'),bone:c('--bone','#F4F1EC'),ash:c('--ash','#9A9A9E'),line:c('--line','#2A2A30'),surface:c('--graphite','#16161A'),bg:c('--row','#111114')};kick();}
|
|
function size(){var nw=canvas.clientWidth,nh=canvas.clientHeight,nd=Math.min(devicePixelRatio||1,2);if(W===nw&&H===nh&&dpr===nd&&canvas.width===Math.round(nw*nd)){kick();return;}W=nw;H=nh;dpr=nd;canvas.width=W*dpr;canvas.height=H*dpr;ctx.setTransform(dpr,0,0,dpr,0,0);kick();}
|
|
function poly(points,fill,stroke){ctx.beginPath();points.forEach(function(p,i){if(i===0)ctx.moveTo(p[0],p[1]);else ctx.lineTo(p[0],p[1]);});ctx.closePath();if(fill){ctx.fillStyle=fill;ctx.fill();}if(stroke){ctx.strokeStyle=stroke;ctx.stroke();}}
|
|
function iso(x,y,w,h,depth,fill,stroke){
|
|
poly([[x-w,y],[x,y+h],[x,y+h+depth],[x-w,y+depth]],alpha(fill,.17),alpha(stroke,.4));
|
|
poly([[x,y+h],[x+w,y],[x+w,y+depth],[x,y+h+depth]],alpha(fill,.07),alpha(stroke,.32));
|
|
poly([[x,y-h],[x+w,y],[x,y+h],[x-w,y]],col.bg,stroke);
|
|
poly([[x,y-h],[x+w,y],[x,y+h],[x-w,y]],alpha(fill,.15),null);
|
|
}
|
|
function line(a,b,c,w){ctx.strokeStyle=c;ctx.lineWidth=w||1;ctx.beginPath();ctx.moveTo(a[0],a[1]);ctx.lineTo(b[0],b[1]);ctx.stroke();}
|
|
function draw(t){
|
|
if(!W||!H||disposed)return;frames++;
|
|
if(lastT&&!reduce&&motion&&!paused)phase+=Math.min(t-lastT,70)/1000;lastT=t;
|
|
ctx.setTransform(dpr,0,0,dpr,0,0);ctx.clearRect(0,0,W,H);
|
|
var x=W*.5,y=H*.49,scale=Math.min(W/320,H/240),cw=43*scale,ch=23*scale;
|
|
var shards=block?block.shards||[]:[],active=shards.some(function(s){return s.state==='proving';});
|
|
var glow=ctx.createRadialGradient(x,y,0,x,y,W*.43);glow.addColorStop(0,alpha(col.ember,.10));glow.addColorStop(1,alpha(col.ember,0));ctx.fillStyle=glow;ctx.fillRect(0,0,W,H);
|
|
// A stationary coordinate grid, not fabricated hashrate or work counters.
|
|
for(var k=-3;k<=3;k++){line([x-130*scale,y+k*18*scale-46*scale],[x+130*scale,y+k*18*scale+46*scale],alpha(col.line,.45));line([x-130*scale,y+k*18*scale+46*scale],[x+130*scale,y+k*18*scale-46*scale],alpha(col.line,.45));}
|
|
ctx.strokeStyle=alpha(col.line,.7);ctx.lineWidth=1;ctx.setLineDash([2,6]);ctx.beginPath();ctx.ellipse(x,y+10*scale,106*scale,59*scale,0,0,TAU);ctx.stroke();ctx.setLineDash([]);
|
|
var count=Math.min(12,shards.length);
|
|
for(var i=0;i<count;i++){
|
|
var angle=-Math.PI/2+i/count*TAU,px=x+Math.cos(angle)*108*scale,py=y+Math.sin(angle)*57*scale;
|
|
var state=shards[i].state,ink=state==='proving'?col.ember:state==='paid'?col.gold:state==='verified'?col.bone:col.ash;
|
|
var midx=(px+x)/2,midy=(py+y)/2;
|
|
ctx.strokeStyle=alpha(ink,state==='planned'?.14:.38);ctx.lineWidth=1;ctx.beginPath();ctx.moveTo(px,py);ctx.quadraticCurveTo(midx,midy-12*scale,x,y);ctx.stroke();
|
|
if(state==='proving'&&!reduce&&motion&&!paused){var p=(phase*.48+i*.33)%1,xx=(1-p)*(1-p)*px+2*(1-p)*p*midx+p*p*x,yy=(1-p)*(1-p)*py+2*(1-p)*p*(midy-12*scale)+p*p*y;ctx.fillStyle=col.gold;ctx.shadowBlur=8;ctx.shadowColor=col.ember;ctx.beginPath();ctx.arc(xx,yy,1.8*scale,0,TAU);ctx.fill();ctx.shadowBlur=0;}
|
|
iso(px,py,13*scale,7*scale,5*scale,ink,alpha(ink,.72));
|
|
if(state==='verified'||state==='paid'){ctx.strokeStyle=ink;ctx.lineWidth=1.2;ctx.beginPath();ctx.moveTo(px-4*scale,py);ctx.lineTo(px-1*scale,py+2*scale);ctx.lineTo(px+4*scale,py-2*scale);ctx.stroke();}
|
|
else{ctx.fillStyle=alpha(ink,state==='proving'?.9:.4);ctx.fillRect(px-1.5*scale,py-1.5*scale,3*scale,3*scale);}
|
|
ctx.font='8px ui-monospace, monospace';ctx.textAlign='center';ctx.fillStyle=col.ash;ctx.fillText('S'+String(i+1).padStart(2,'0'),px,py+22*scale);
|
|
}
|
|
// Layered block core. Geometry does not imply three GPUs or three proofs.
|
|
var lift=active&&!reduce&&motion&&!paused?Math.sin(phase*1.7)*1.8*scale:0;
|
|
ctx.lineWidth=1;
|
|
iso(x,y+21*scale,cw,ch,10*scale,col.ember,alpha(col.ember,.34));
|
|
iso(x,y+6*scale,cw,ch,8*scale,col.ember,alpha(col.ember,.55));
|
|
iso(x,y-11*scale-lift,cw,ch,7*scale,col.ember,alpha(col.ember,.85));
|
|
var topY=y-11*scale-lift;
|
|
poly([[x,topY-13*scale],[x+25*scale,topY],[x,topY+13*scale],[x-25*scale,topY]],alpha(col.ember,block&&block.proven?.28:.06),alpha(col.gold,.46));
|
|
if(block&&block.proven){ctx.strokeStyle=col.gold;ctx.lineWidth=1.8;ctx.beginPath();ctx.moveTo(x-7*scale,topY);ctx.lineTo(x-2*scale,topY+4*scale);ctx.lineTo(x+8*scale,topY-4*scale);ctx.stroke();}
|
|
else{ctx.fillStyle=col.ember;ctx.fillRect(x-2*scale,topY-2*scale,4*scale,4*scale);}
|
|
if(block&&block.locked){ctx.strokeStyle=col.gold;ctx.lineWidth=1.2;ctx.beginPath();ctx.ellipse(x,y+12*scale,67*scale,38*scale,0,0,TAU);ctx.stroke();ctx.font='8px ui-monospace, monospace';ctx.fillStyle=col.gold;ctx.textAlign='center';ctx.fillText('LOCKED CHECKPOINT',x,H-12);}
|
|
else if(!block){ctx.font='10px ui-monospace, monospace';ctx.textAlign='center';ctx.fillStyle=col.ash;ctx.fillText('SELECT A BLOCK',x,H-12);}
|
|
else if(shards.length>12){ctx.font='9px ui-monospace, monospace';ctx.textAlign='center';ctx.fillStyle=col.ash;ctx.fillText('+ '+(shards.length-12)+' more shards in the list',x,H-12);}
|
|
}
|
|
function continuous(){return !disposed&&!document.hidden&&inView&&!reduce&&motion&&!paused&&block&&(block.shards||[]).some(function(s){return s.state==='proving';});}
|
|
function frame(t){raf=0;if(disposed||document.hidden||!inView)return;if(force||t-lastPaint>=1000/30){draw(t);lastPaint=t;force=false;}if(continuous())raf=requestAnimationFrame(frame);}
|
|
function kick(){force=true;if(!disposed&&!document.hidden&&inView&&!raf)raf=requestAnimationFrame(frame);}
|
|
function stop(){if(raf)cancelAnimationFrame(raf);raf=0;lastT=0;}
|
|
function visibility(){if(document.hidden)stop();else kick();}
|
|
function reduction(e){reduce=e.matches;stop();kick();}
|
|
var api={setBlock:function(b){block=b?{hash:b.hash,proven:b.proven===true,locked:b.locked===true,shards:(b.shards||[]).map(function(s){return {state:s.state};})}:null;canvas.setAttribute('aria-label',block?'Block proof detail: '+block.shards.map(function(s,i){return 'shard '+(i+1)+' '+s.state;}).join(', '):'Select a block to inspect its proof shards');kick();},setPaused:function(value){paused=!!value;stop();kick();},setMotion:function(value){motion=!!value;stop();kick();},getStats:function(){return {frames:frames,reducedMotion:reduce||!motion};},destroy:function(){if(disposed)return;disposed=true;stop();ro.disconnect();if(io)io.disconnect();mo.disconnect();root.removeEventListener('resize',size);document.removeEventListener('visibilitychange',visibility);mq.removeEventListener('change',reduction);mounted.delete(canvas);}};
|
|
ro=new ResizeObserver(size);ro.observe(canvas);if('IntersectionObserver'in root){io=new IntersectionObserver(function(es){inView=es[0].isIntersecting;if(inView)kick();else stop();});io.observe(canvas);}mo=new MutationObserver(theme);mo.observe(document.documentElement,{attributes:true,attributeFilter:['data-theme','style','class']});
|
|
root.addEventListener('resize',size);document.addEventListener('visibilitychange',visibility);mq.addEventListener('change',reduction);mounted.set(canvas,api);theme();size();return api;
|
|
}
|
|
root.IgneumProof={mount:mount,version:'2.0.0'};
|
|
})(window);
|