diff --git a/docs/plans/site-ui-3-shots/item0/index-1440-dark-clip.png b/docs/plans/site-ui-3-shots/item0/index-1440-dark-clip.png new file mode 100644 index 000000000..7685b15c8 Binary files /dev/null and b/docs/plans/site-ui-3-shots/item0/index-1440-dark-clip.png differ diff --git a/docs/plans/site-ui-3-shots/item0/index-1440-dark-fail-clip.png b/docs/plans/site-ui-3-shots/item0/index-1440-dark-fail-clip.png new file mode 100644 index 000000000..735c57d64 Binary files /dev/null and b/docs/plans/site-ui-3-shots/item0/index-1440-dark-fail-clip.png differ diff --git a/docs/plans/site-ui-3-shots/item0/index-1440-dark-fail.jpg b/docs/plans/site-ui-3-shots/item0/index-1440-dark-fail.jpg new file mode 100644 index 000000000..d3f6d0f35 Binary files /dev/null and b/docs/plans/site-ui-3-shots/item0/index-1440-dark-fail.jpg differ diff --git a/docs/plans/site-ui-3-shots/item0/index-1440-dark.jpg b/docs/plans/site-ui-3-shots/item0/index-1440-dark.jpg new file mode 100644 index 000000000..96e03e66e Binary files /dev/null and b/docs/plans/site-ui-3-shots/item0/index-1440-dark.jpg differ diff --git a/docs/plans/site-ui-3-shots/item0/index-390-dark-clip.png b/docs/plans/site-ui-3-shots/item0/index-390-dark-clip.png new file mode 100644 index 000000000..fb4300f80 Binary files /dev/null and b/docs/plans/site-ui-3-shots/item0/index-390-dark-clip.png differ diff --git a/docs/plans/site-ui-3-shots/item0/index-390-dark.jpg b/docs/plans/site-ui-3-shots/item0/index-390-dark.jpg new file mode 100644 index 000000000..b32bc0ceb Binary files /dev/null and b/docs/plans/site-ui-3-shots/item0/index-390-dark.jpg differ diff --git a/site/api/live.mjs b/site/api/live.mjs index 10b1221af..57012a989 100644 --- a/site/api/live.mjs +++ b/site/api/live.mjs @@ -1,5 +1,5 @@ // Igneum live devnet feed. GET /api/live returns what tools/observer wrote to Neon: -// {now, state, blocks (last 90 s, or ?window=N up to 300), miners (last 10 min), events (last 30), finality (checkpoints, newest lock), +// {now, state (incl. height = chain block number), blocks (last 90 s, or ?window=N up to 300), miners (last 10 min), events (last 30), finality (checkpoints, newest lock), // proving (activation and 10-minute counts; every chain block carries shards: [{i, state, prover, lag, payout}])}. // Five queries, all on indexed columns. Cached one second at the edge. // LIVE_TABLE_PREFIX (default empty) reads a test observer's tables (fintest_live_*, provtest_live_*) instead of the devnet's. @@ -43,7 +43,8 @@ export default async function handler(req, res) { const [head, blockRows, minerRows, checkpointRows, proofRows] = await Promise.all([ sql(`SELECT now() AS now, (SELECT row_to_json(s) FROM ${T}live_state s WHERE s.id = 1) AS state, - (SELECT json_agg(e) FROM (SELECT ts, kind, text FROM ${T}live_events ORDER BY ts DESC LIMIT 30) e) AS events`), + (SELECT json_agg(e) FROM (SELECT ts, kind, text FROM ${T}live_events ORDER BY ts DESC LIMIT 30) e) AS events, + (SELECT max(number) FROM ${T}live_blocks WHERE number IS NOT NULL AND received_at > now() - interval '10 minutes') AS height`), sql(`SELECT hash, blue_score, daa_score, timestamp_ms, parent_hashes, is_chain_block, vote_key_hash, received_at, color FROM ${T}live_blocks WHERE received_at > now() - ($1 || ' seconds')::interval @@ -78,6 +79,9 @@ export default async function handler(req, res) { age_s: age_s === null ? null : Math.round(age_s * 10) / 10, network: s ? s.network : null, node_version: s ? s.node_version : null, + // 6 Oct 2026: height is the chain block number (the EVM block number of the newest chain block the observer planned), + // the same field /api/stats reports; block_count counts every DAG block the node holds (pruned nodes hold fewer) + height: num(head[0].height), block_count: s ? num(s.block_count) : null, header_count: s ? num(s.header_count) : null, blue_score: s ? num(s.blue_score) : null, diff --git a/site/index.html b/site/index.html index 92221eb12..344848da9 100644 --- a/site/index.html +++ b/site/index.html @@ -204,6 +204,8 @@ section:not(.light)+section:not(.light){padding-top:0;scroll-margin-top:var(--sm .blk.new{animation:pop .4s ease} @keyframes pop{from{transform:scale(.4);opacity:0}to{transform:scale(1);opacity:1}} .legend{display:flex;flex-wrap:wrap;gap:20px;margin-top:24px;font-size:14px;color:var(--ink-2)} +.viz-note{margin:14px 0 0;font-size:13px;line-height:1.5;color:var(--ash);max-width:80ch} +.legend[hidden]{display:none} .legend span{display:inline-flex;align-items:center;gap:8px} .sw{width:14px;height:14px;border-radius:4px;display:inline-block} pre{margin:0;font-family:var(--f-mono);font-size:13px;line-height:1.6;color:var(--ink-2);white-space:pre-wrap;min-height:9.6em} @@ -372,7 +374,7 @@ pre{margin:0;font-family:var(--f-mono);font-size:13px;line-height:1.6;color:var(
0
vote keys active in 10 min (a card runs several)
0.0
blocks / s
0.0 MH/s
hash rate
-
0
devnet blocks
+
0
chain block
@@ -394,18 +396,26 @@ pre{margin:0;font-family:var(--f-mono);font-size:13px;line-height:1.6;color:var(

Watch the chain prove itself

Blocks every second, proven in shards, locked every 30 seconds. First live lock 4 Oct 2026: checkpoint 242, 77.4% of all weight, two hours after genesis.

-
+
simulated preview
-
blocks 0proven 0locked 0
+
blocks 0proven 0locked 0
-
+
mined shards being proven proven locked checkpoint, final
+ +
@@ -683,19 +693,25 @@ pre{margin:0;font-family:var(--f-mono);font-size:13px;line-height:1.6;color:var( // live observer: ONE fetch of /api/live every 2 s feeds the hero, the stats strip and the chain scene. // Subscribers get (fresh, d): the sampled new blocks since the last pull and the whole response, or ([], null) when off or stale. - var live={on:false,rate:1.3,seen:{},minerOf:{},subs:[]}; + // live.reason says why the feed is off, in plain words, for the scene's label: 'connecting', 'live feed unavailable' (the fetch + // failed or the function errored), or 'observer offline, last update N s ago' (the observer's row is older than 30 s). + var live={on:false,rate:1.3,seen:{},minerOf:{},subs:[],reason:'connecting'}; live.sub=function(fn){live.subs.push(fn);}; function emitLive(fresh,d){live.subs.forEach(function(fn){fn(fresh,d);});} - function pullLive(first){return fetch('/api/live',{cache:'no-store'}).then(function(r){return r.ok?r.json():null;}).then(function(d){ - if(!d||!d.ok||!d.state||d.state.stale||!d.blocks||!d.miners){live.on=false;emitLive([],null);return false;} - live.on=true; + 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 liveOff(reason){live.on=false;live.reason=reason;emitLive([],null);return false;} + function pullLive(first){return fetch('/api/live',{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)return liveOff('live feed unavailable'); + if(!d.state||!d.blocks||!d.miners)return liveOff('live feed unavailable'); + if(d.state.stale)return liveOff('observer offline'+(d.state.age_s!==null&&d.state.age_s!==undefined?', last update '+fmtAge(d.state.age_s)+' ago':'')); + live.on=true;live.reason=null; var fresh=d.blocks.filter(function(b){return !live.seen[b.hash];});if(first)fresh=fresh.slice(-12); // the response is a 90 s window, so both maps are rebuilt from it and stay bounded var seen={},minerOf={};d.blocks.forEach(function(b){seen[b.hash]=1;minerOf[b.hash]=b.miner||'?';});live.seen=seen;live.minerOf=minerOf; /* the scenes keep a calm pace; faster chains are sampled, the counters stay true */ if(fresh.length>3){var step=fresh.length/3,pick=[];for(var i=0;i<3;i++)pick.push(fresh[Math.floor(i*step)]);fresh=pick;} emitLive(fresh,d);return true; - }).catch(function(){live.on=false;emitLive([],null);return false;});} + }).catch(function(){return liveOff('live feed unavailable');});} // live stats strip under the hero buttons: shown only while the observer is fresh var strip=document.getElementById('livestrip'); @@ -705,7 +721,7 @@ pre{margin:0;font-family:var(--f-mono);font-size:13px;line-height:1.6;color:var( lsSet('ls-miners',s.miners_10m!==null&&s.miners_10m!==undefined?s.miners_10m:d.miners.length); lsSet('ls-bps',s.blocks_60s!==null&&s.blocks_60s!==undefined?(s.blocks_60s/60).toFixed(1):'pending'); lsSet('ls-hash',fmtHash(s.hashes_per_second_estimate)); - lsSet('ls-blocks',s.block_count!==null&&s.block_count!==undefined?Number(s.block_count).toLocaleString('en-GB'):'pending');}); + lsSet('ls-blocks',s.height!==null&&s.height!==undefined?Number(s.height).toLocaleString('en-GB'):'pending');}); // hero background: a block graph that grows left to right, proves, and locks. // Live: each dot is one miner seen in the last ten minutes (at most 24, no ids drawn); it flashes when that @@ -784,21 +800,30 @@ pre{margin:0;font-family:var(--f-mono);font-size:13px;line-height:1.6;color:var( function laneY(l){return H*0.14+l*(H*0.72/(lanes-1));} // live path state: when the devnet observer is fresh, real blocks (lb) from the shared poller replace the random spawner var liveMode=false,liveQ=[],liveLanes={},byHash={}; + // a live block takes the observer's words: color pending / blue (included) / red (excluded), shards planned / proving / verified / paid, + // locked = a locked checkpoint block, final = at or before the newest lock. Re-applied on every poll so a block on screen updates. + function applyLive(b,src){b.color=src.color||'pending';b.chain=!!src.chain;b.isFinal=!!src.final;var sh=src.shards||[]; + var done=function(x){return x.state==='verified'||x.state==='paid';}; + var allProven=sh.length>0&&sh.every(done),anyProving=sh.some(function(x){return x.state==='proving'||done(x);}); + if(allProven){if(b.state!=='proven'){b.state='proven';b.glow=1;}}else if(anyProving)b.state='proving';else b.state='mined'; + b.shards=[0,0,0,0];sh.slice(0,4).forEach(function(x,i){b.shards[i]=done(x)?1:0;}); + if(src.locked&&!b.locked){b.locked=true;ripples.push({x:b.x,y:b.y,r:S,a:1});ripples.push({x:b.x,y:b.y,r:S*0.5,a:1});}} function spawn(px,lb){n++;var lane=lb?lb.lane:Math.floor(Math.random()*lanes);var b={id:n,x:px,y:laneY(lane),born:performance.now(),shards:[0,0,0,0],claimed:[false,false,false,false],state:'mined',cp:n%6===0,parents:[],glow:0,flash:0,hash:lb?lb.hash:null}; - if(lb){byHash[lb.hash]=b;lb.parents.forEach(function(h){var q=byHash[h];if(q&&blocks.indexOf(q)>=0&&b.parents.length<4)b.parents.push(q);});if(!b.parents.length){var prev=blocks.slice(-6).filter(function(q){return q.x1&&Math.random()<0.5)b.parents.push(prev[1]);}} + if(lb){byHash[lb.hash]=b;if(lb.src)applyLive(b,lb.src);lb.parents.forEach(function(h){var q=byHash[h];if(q&&blocks.indexOf(q)>=0&&b.parents.length<4)b.parents.push(q);});if(!b.parents.length){var prev=blocks.slice(-6).filter(function(q){return q.x1&&Math.random()<0.5)b.parents.push(prev[1]);}} else{var cands=blocks.slice(-8).filter(function(q){return q.x80){var old=blocks.shift();if(old.hash)delete byHash[old.hash];}cb.textContent=n;} + blocks.push(b);if(blocks.length>80){var old=blocks.shift();if(old.hash)delete byHash[old.hash];}if(!liveMode)cb.textContent=n;} function rr(px,py,w,h,r){x.beginPath();x.moveTo(px+r,py);x.arcTo(px+w,py,px+w,py+h,r);x.arcTo(px+w,py+h,px,py+h,r);x.arcTo(px,py+h,px,py,r);x.arcTo(px,py,px+w,py,r);x.closePath();} - var last=0,lastShard=0; + var last=0,lastShard=0,prevT=0; function frame(t){ if(liveMode){if(liveQ.length&&t-last>Math.max(60,1000/live.rate)){spawn(W+S,liveQ.shift());last=t;}} else if(t-last>1000){spawn(W+S);last=t;} - var dt=16; + // real frame time, capped: a fixed 16 ms ran the scene at double speed on 120 Hz displays (found 6 Oct 2026) + var dt=prevT?Math.min(50,t-prevT):16;prevT=t; var sp=liveMode?speed*live.rate:speed;blocks.forEach(function(b){b.x-=sp*dt;var age=t-b.born; if(!liveMode&&b.state==='mined'&&age>1200*k)b.state='proving'; if(!liveMode&&b.state==='proving'){for(var i=0;i<4;i++){if(!b.claimed[i]&&Math.random()<0.012/k){b.claimed[i]=true;var fromTop=Math.random()<0.5;sparks.push({x:Math.random()*W,y:fromTop?-6:H+6,tx:b.x,ty:b.y,b:b,i:i,p:0});}} - if(b.shards.every(function(v){return v>=1;})){b.state='proven';b.glow=1;proven++;cp.textContent=proven;if(b.cp)setTimeout(function(){b.locked=true;locked++;cl.textContent=locked;ripples.push({x:b.x,y:b.y,r:S,a:1});ripples.push({x:b.x,y:b.y,r:S*0.5,a:1});},1800*k);}} + if(b.shards.every(function(v){return v>=1;})){b.state='proven';b.glow=1;proven++;if(!liveMode)cp.textContent=proven;if(b.cp)setTimeout(function(){b.locked=true;locked++;if(!liveMode)cl.textContent=locked;ripples.push({x:b.x,y:b.y,r:S,a:1});ripples.push({x:b.x,y:b.y,r:S*0.5,a:1});},1800*k);}} if(b.glow>0)b.glow-=0.02;}); sparks.forEach(function(sp){sp.p+=Math.min(0.08,0.035/k);sp.tx=sp.b.x;sp.ty=sp.b.y;if(sp.p>=1){sp.done=true;sp.b.shards[sp.i]=1;}}); sparks=sparks.filter(function(sp){return !sp.done;}); @@ -814,10 +839,11 @@ pre{margin:0;font-family:var(--f-mono);font-size:13px;line-height:1.6;color:var( // blocks blocks.forEach(function(b){var h=S/2; // proving v0: a faint second glint behind a real block once every shard of it is verified on the devnet (nothing before activation) - if(b.sealed){var g2=x.createRadialGradient(b.x,b.y,S*0.45,b.x,b.y,S*1.35);g2.addColorStop(0,'rgba(255,179,92,0.18)');g2.addColorStop(1,'rgba(255,179,92,0)');x.fillStyle=g2;x.beginPath();x.arc(b.x,b.y,S*1.35,0,Math.PI*2);x.fill();} if(b.glow>0){var g=x.createRadialGradient(b.x,b.y,0,b.x,b.y,S*1.6);g.addColorStop(0,'rgba(242,84,27,'+(0.45*b.glow)+')');g.addColorStop(1,'rgba(242,84,27,0)');x.fillStyle=g;x.beginPath();x.arc(b.x,b.y,S*1.6,0,Math.PI*2);x.fill();} if(b.state==='proven'){x.fillStyle='#F2541B';rr(b.x-h,b.y-h,S,S,S*0.24);x.fill();} - else{x.fillStyle='#0C0C0E';rr(b.x-h,b.y-h,S,S,S*0.24);x.fill();x.strokeStyle=b.state==='proving'?'rgba(255,179,92,0.9)':'rgba(58,58,66,1)';x.lineWidth=2;rr(b.x-h,b.y-h,S,S,S*0.24);x.stroke(); + else{var ex=b.color==='red';if(ex)x.globalAlpha=0.45;x.fillStyle='#0C0C0E';rr(b.x-h,b.y-h,S,S,S*0.24);x.fill(); + // live: included (blue or on the selected chain) takes an ember outline, excluded (red) is dimmed, pending stays grey + x.strokeStyle=b.state==='proving'?'rgba(255,179,92,0.9)':(liveMode&&!ex&&(b.color==='blue'||b.chain))?'rgba(242,84,27,0.75)':'rgba(58,58,66,1)';x.lineWidth=2;rr(b.x-h,b.y-h,S,S,S*0.24);x.stroke();if(ex)x.globalAlpha=1; if(b.state==='proving'){var q=S/2-3;for(var i=0;i<4;i++){if(b.shards[i]>=1){x.fillStyle='#FFB35C';var qx=b.x-h+3+(i%2)*q,qy=b.y-h+3+Math.floor(i/2)*q;rr(qx,qy,q-1,q-1,2);x.fill();}}}} if(b.locked){var pulse=0.8+0.2*Math.sin(t/350);x.strokeStyle='rgba(242,84,27,'+pulse+')';x.lineWidth=3;x.beginPath();x.arc(b.x,b.y,S*1.15,0,Math.PI*2);x.stroke();x.fillStyle='#F4F1EC';x.beginPath();x.arc(b.x,b.y,S*0.16,0,Math.PI*2);x.fill();} }); @@ -830,19 +856,38 @@ pre{margin:0;font-family:var(--f-mono);font-size:13px;line-height:1.6;color:var( cb.textContent=n;cp.textContent=proven;cl.textContent=locked; requestAnimationFrame(frame); // live path: the shared poller above feeds sampled real blocks; while fresh, the label links to /live - var label=document.getElementById('viz-label'),labelHTML=label?label.innerHTML:''; + var label=document.getElementById('viz-label'),note=document.getElementById('viz-note'),legSim=document.getElementById('legend-sim'),legLive=document.getElementById('legend-live'); + var kb=document.getElementById('k-blocks'),kp=document.getElementById('k-proven'),kl=document.getElementById('k-locked'); + var fmtN=function(v){return v===null||v===undefined?'pending':Number(v).toLocaleString('en-GB');}; function laneFor(m){if(!(m in liveLanes))liveLanes[m]=Object.keys(liveLanes).length%lanes;return liveLanes[m];} - function setLive(on){if(on===liveMode)return;liveMode=on;if(label)label.innerHTML=on?'live devnet':labelHTML;} + // three label states: live (the observer is fresh), off with the reason (the simulation runs and its counters count simulated blocks), connecting + function setLive(on){ + if(on!==liveMode){liveMode=on;if(!on){cb.textContent=n;cp.textContent=proven;cl.textContent=locked;}} + if(legSim)legSim.hidden=on;if(legLive)legLive.hidden=!on; + if(kb)kb.textContent=on?'chain block':'blocks';if(kp)kp.textContent=on?'shards proven':'proven';if(kl)kl.textContent=on?'last lock':'locked'; + if(!label)return; + if(on){label.innerHTML='live devnet';return;} + var r=live.reason||'live feed unavailable'; + label.innerHTML='simulated preview'+(r==='connecting'?'':' ยท '+r); + if(note){note.hidden=false;note.textContent=r==='connecting'?'Connecting to the devnet observer. Until it answers, this is a simulation and its counters count simulated blocks.':r.charAt(0).toUpperCase()+r.slice(1)+'. This is a simulation and its counters count simulated blocks. The live page shows the last stored state.';}} live.sub(function(fresh,d){setLive(live.on);if(!live.on)return; - // live counters come from the observer, never from the simulation: proven = blocks in the window every shard of which is verified, locked = locked checkpoints in the window - if(d&&d.blocks){var pv=d.proving&&d.proving.supported&&d.proving.active;cp.textContent=pv?String(d.blocks.filter(function(b){return b.proven;}).length):'not active';cl.textContent=String(d.blocks.filter(function(b){return b.locked;}).length);} - fresh.forEach(function(b){liveQ.push({hash:b.hash,parents:b.parents||[],lane:laneFor(b.miner||'?')});}); - if(liveQ.length>6)liveQ=liveQ.slice(-6); - // the glint above: only the per-block shards data, only while the proving layer is active - if(d&&d.proving&&d.proving.supported&&d.proving.active)d.blocks.forEach(function(b){if(b.proven){var q=byHash[b.hash];if(q)q.sealed=true;}});}); + // live counters come from the chain, never from the 90 s window: the chain block number, the shards proven and paid on the chain + // as the observer's node counts them (proofs land minutes behind the tip, so the blocks on screen are still pending), the latest locked checkpoint + var s=d.state,pv=d.proving,fn=d.finality; + cb.textContent=fmtN(s.height); + cp.textContent=!pv||!pv.supported?'no proving layer':!pv.active?'not active':fmtN(pv.paid_shards_total); + cl.textContent=!fn||!fn.supported?'no finality':!fn.active?'paused':fn.latest_locked_index!==null&&fn.latest_locked_index!==undefined?'#'+fmtN(fn.latest_locked_index):'none yet'; + if(note){var lag=pv&&pv.active&&pv.median_proof_lag_s?Math.round(pv.median_proof_lag_s):null; + note.hidden=false;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.';} + // blocks already on screen take their newest words (pending to included, a shard proven, a checkpoint locked) + d.blocks.forEach(function(src){var q=byHash[src.hash];if(q)applyLive(q,src);}); + fresh.forEach(function(b){liveQ.push({hash:b.hash,parents:b.parents||[],lane:laneFor(b.miner||'?'),src:b});}); + if(liveQ.length>6)liveQ=liveQ.slice(-6);}); + setLive(false); })(); - // one fetch on load; only if the observer is fresh does the 2 s poll start (it feeds the hero, the strip and the chain scene) - pullLive(true).then(function(on){if(on)setInterval(function(){pullLive(false);},2000);}); + // one fetch on load, then every 2 s while the feed is fresh and every 10 s while it is off, so a feed that comes back is picked up + function pollLive(on){setTimeout(function(){pullLive(false).then(pollLive);},on?2000:10000);} + pullLive(true).then(pollLive); // hourly program ticker var ops=['rotl','rotr','xor','add','sub','mul','mulhi','mad','or']; diff --git a/site/live.html b/site/live.html index 00e9c5f44..7ef7e272a 100644 --- a/site/live.html +++ b/site/live.html @@ -236,7 +236,7 @@ main{padding-bottom:var(--sec)}
blocks, finality, proving
-
on screen 0chain 0identities 0last lock noneproven n/a
+
on screen 0chain 0identities 0last lock noneproven pending
@@ -402,7 +402,10 @@ main{padding-bottom:var(--sec)} blocks.forEach(function(b){if(b.ts>vis){on++;if(b.chain){chain++;if(b.shards.length){planned++;if(b.proven)proven++;}}}}); $('c-screen').textContent=on;$('c-chain').textContent=chain;$('c-idents').textContent=idents; var f=fin;$('c-locked').textContent=f&&f.latest_locked_index?('#'+f.latest_locked_index+(f.checkpoints&&f.checkpoints.length?', '+lockAge(f):'')):(f&&f.supported?'none yet':'n/a'); - $('c-proven').textContent=prov&&prov.supported&&prov.active?(proven+'/'+planned):(prov&&prov.supported?'not active':'n/a'); + // 6 Oct 2026: proofs land a median of minutes behind the tip, so the on-screen window (proven+'/'+planned) read 0 for ever; the + // counter now reads the observer's 10-minute truth: chain blocks with every shard paid, of the chain blocks planned in 10 min + var pe=$('c-proven');if(prov&&prov.supported&&prov.active){pe.textContent=(prov.blocks_fully_proven_10m||0)+'/'+(prov.blocks_10m||0)+' in 10 min';pe.title=(prov.shards_paid_10m||0)+' shards paid in the last 10 minutes'+(prov.median_proof_lag_s?', landing a median '+Math.round(prov.median_proof_lag_s)+' s behind the tip':'')+'; '+(prov.paid_shards_total!==null&&prov.paid_shards_total!==undefined?Number(prov.paid_shards_total).toLocaleString('en-GB')+' paid on the chain so far':'');} + else{pe.textContent=prov&&prov.supported?'not active':'no proving layer';pe.title='';} if(!running&&!document.hidden)kick(); } function lockAge(f){var cps=f.checkpoints||[],lk=null;for(var i=cps.length-1;i>=0;i--)if(cps[i].state==='locked'){lk=cps[i];break;}