Homepage: hero dots go live as miners, one shared /api/live poll, live stats strip
The hero canvas now has a live path fed by the same 2 s fetch as the chain scene (one poller, three subscribers: hero, stats strip, chain scene). When the observer is fresh each dot is one miner seen in the last ten minutes (at most 24, no id drawn), it flashes ember when that miner finds a sampled block, and faint lines reach the miners of the block's parents for a moment. Dots join on a new miner and fade out when one leaves the ten-minute set. The drift, colours and pace are the simulation's; the simulation runs unchanged when the observer is off or stale. A live stats strip under the hero buttons (miners active, blocks / s, network hash rate, blocks on the devnet) shows only while the observer is fresh and hides again when it goes stale. Two tiles per row at phone width. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
parent
47b7aff21f
commit
08d45f82b9
1 changed files with 108 additions and 26 deletions
134
site/index.html
134
site/index.html
|
|
@ -92,6 +92,16 @@ p{margin:0}
|
|||
.hero-copy{display:flex;flex-direction:column;gap:24px}
|
||||
.hero p.lead{font-size:clamp(17px,2vw,20px);color:var(--ink-2);max-width:56ch}
|
||||
.cta{display:flex;flex-wrap:wrap;gap:12px}
|
||||
/* live stats strip: hidden until the observer reports fresh numbers */
|
||||
.livestrip{display:none;flex-direction:column;gap:10px}
|
||||
.livestrip.on{display:flex}
|
||||
.livestrip-head{display:flex;align-items:center;gap:8px;font-family:'IBM Plex Mono',monospace;font-size:12px;letter-spacing:.12em;text-transform:uppercase;color:var(--molten)}
|
||||
.livestrip-head a{color:var(--molten)}
|
||||
.livestrip-grid{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:10px}
|
||||
.livestrip .tile{padding:14px 16px;border-radius:12px;min-width:0}
|
||||
.livestrip .tile .v{font-size:18px;font-variant-numeric:tabular-nums;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
||||
.livestrip .tile .k{font-family:'IBM Plex Mono',monospace;font-size:11px;letter-spacing:.08em;text-transform:uppercase;margin-top:4px;line-height:1.35}
|
||||
@media (max-width:700px){.livestrip-grid{grid-template-columns:repeat(2,minmax(0,1fr))}}
|
||||
.verify{display:flex;flex-direction:column;gap:18px}
|
||||
.verify-head{display:flex;justify-content:space-between;align-items:center;gap:12px}
|
||||
.live{display:inline-flex;align-items:center;gap:8px;font-family:'IBM Plex Mono',monospace;font-size:12px;color:var(--molten)}
|
||||
|
|
@ -241,6 +251,15 @@ footer .wrap{padding-block:48px 32px}
|
|||
<a href="#mine" class="btn primary">Start mining</a>
|
||||
<a href="/litepaper" class="btn">Read the litepaper</a>
|
||||
</div>
|
||||
<div class="livestrip" id="livestrip" aria-live="off">
|
||||
<div class="livestrip-head"><span class="dot"></span><a href="/live">live devnet</a></div>
|
||||
<div class="livestrip-grid">
|
||||
<div class="tile"><div class="v" id="ls-miners">0</div><div class="k">miners active</div></div>
|
||||
<div class="tile"><div class="v" id="ls-bps">0.0</div><div class="k">blocks / s</div></div>
|
||||
<div class="tile"><div class="v" id="ls-hash">0.0 MH/s</div><div class="k">network hash rate</div></div>
|
||||
<div class="tile"><div class="v" id="ls-blocks">0</div><div class="k">blocks on the devnet</div></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card verify reveal">
|
||||
<div class="verify-head">
|
||||
|
|
@ -485,31 +504,99 @@ footer .wrap{padding-block:48px 32px}
|
|||
var bio=new IntersectionObserver(function(es){es.forEach(function(e){if(e.isIntersecting){bar.querySelectorAll('div').forEach(function(d){d.style.width=d.getAttribute('data-w');});bio.disconnect();}});},{threshold:.4});
|
||||
bio.observe(bar);
|
||||
|
||||
// hero background: a block graph that grows left to right, proves, and locks
|
||||
// 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.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;
|
||||
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;});}
|
||||
|
||||
// live stats strip under the hero buttons: shown only while the observer is fresh
|
||||
var strip=document.getElementById('livestrip');
|
||||
function lsSet(id,v){var el=document.getElementById(id);if(el)el.textContent=v;}
|
||||
function fmtHash(h){if(h===null||h===undefined||!(h>0))return 'pending';return h>=1e9?(h/1e9).toFixed(1)+' GH/s':(h/1e6).toFixed(1)+' MH/s';}
|
||||
live.sub(function(fresh,d){var on=live.on&&!!d;if(strip)strip.classList.toggle('on',on);if(!on)return;var s=d.state;
|
||||
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');});
|
||||
|
||||
// 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
|
||||
// miner finds a sampled block and a faint line joins it to the miners of the block's parents for a moment.
|
||||
var cv=document.getElementById('bg'),ctx=cv.getContext('2d'),nodes=[],W,H,dpr=Math.min(window.devicePixelRatio||1,2),lanes=5,count=0;
|
||||
function size(){W=cv.clientWidth;H=cv.clientHeight;cv.width=W*dpr;cv.height=H*dpr;ctx.setTransform(dpr,0,0,dpr,0,0);}
|
||||
size();window.addEventListener('resize',size);
|
||||
var spacing=function(){return W<700?74:92;};
|
||||
function spawn(x){count++;var lane=Math.floor(Math.random()*lanes);var y=H*(0.18+lane*(0.64/(lanes-1)))+(Math.random()*18-9);
|
||||
function laneY(l){return H*(0.18+l*(0.64/(lanes-1)));}
|
||||
function edge(a,b){ctx.beginPath();ctx.moveTo(a.x,a.y);var mx=(a.x+b.x)/2;ctx.bezierCurveTo(mx,a.y,mx,b.y,b.x,b.y);ctx.stroke();}
|
||||
function spawn(x){count++;var lane=Math.floor(Math.random()*lanes);var y=laneY(lane)+(Math.random()*18-9);
|
||||
var cands=nodes.slice(-7).filter(function(p){return p.x<x-30;});var parents=[];
|
||||
cands.sort(function(a,b){return b.x-a.x;});if(cands.length)parents.push(cands[0]);
|
||||
cands.slice(1).forEach(function(p){if(Math.random()<0.45&&parents.length<3)parents.push(p);});
|
||||
nodes.push({x:x,y:y,born:performance.now(),state:0,lock:count%10===0,parents:parents});if(nodes.length>60)nodes.shift();}
|
||||
function seed(){nodes=[];count=0;var sp=spacing();for(var i=0;i<Math.ceil(W/sp)+2;i++){spawn(W-i*sp);nodes[nodes.length-1].born=performance.now()-i*1000;}}
|
||||
var last=0,speed=0.22;
|
||||
// live state: hm = miner dots, links = parent lines, hq = sampled blocks waiting to flash, simA = simulation opacity
|
||||
var hm=[],links=[],hq=[],hLive=false,simA=1,hLast=0,MAXDOTS=24;
|
||||
function minerDot(id){for(var i=0;i<hm.length;i++)if(hm[i].id===id)return hm[i];return null;}
|
||||
function joinMiner(id){var m=minerDot(id);if(m){m.gone=false;return m;}
|
||||
var used={};hm.forEach(function(q){used[q.slot]=1;});var s=-1;for(var i=0;i<MAXDOTS;i++)if(!used[i]){s=i;break;}if(s<0)return null;
|
||||
m={id:id,slot:s,fx:0.06+0.88*((s*0.618034)%1),lane:s%lanes,jy:Math.random()*18-9,ph:Math.random()*Math.PI*2,a:0,gone:false,flash:0,x:0,y:0};hm.push(m);return m;}
|
||||
live.sub(function(fresh,d){
|
||||
if(reduce)return;
|
||||
var on=live.on&&!!d;
|
||||
if(on!==hLive){hLive=on;if(!on){hm.forEach(function(m){m.gone=true;});hq=[];}}
|
||||
if(!on)return;
|
||||
var keep={};d.miners.slice(0,MAXDOTS).forEach(function(m){if(!m.id)return;keep[m.id]=1;joinMiner(m.id);});
|
||||
hm.forEach(function(m){if(!keep[m.id])m.gone=true;}); // not seen in ten minutes: fade out
|
||||
fresh.forEach(function(b){hq.push(b);});if(hq.length>6)hq=hq.slice(-6);
|
||||
});
|
||||
function frame(t){
|
||||
if(t-last>1000){spawn(W+16);last=t;}
|
||||
ctx.clearRect(0,0,W,H);
|
||||
nodes.forEach(function(n){n.x-=speed;var age=t-n.born;if(age>3500)n.state=2;else if(age>1800)n.state=1;});
|
||||
// simulation: unchanged while the observer is off or stale, fades out behind the live dots when it is on
|
||||
simA=Math.max(0,Math.min(1,simA+(hLive?-0.02:0.02)));
|
||||
if(!hLive){if(!nodes.length)seed();if(t-last>1000){spawn(W+16);last=t;}}
|
||||
if(simA>0){
|
||||
ctx.globalAlpha=simA;
|
||||
nodes.forEach(function(n){n.x-=speed;var age=t-n.born;if(age>3500)n.state=2;else if(age>1800)n.state=1;});
|
||||
ctx.lineWidth=1;
|
||||
nodes.forEach(function(n){n.parents.forEach(function(p){if(nodes.indexOf(p)<0)return;var proven=n.state===2&&p.state===2;ctx.strokeStyle=proven?'rgba(242,84,27,0.35)':'rgba(154,154,158,0.18)';edge(n,p);});});
|
||||
nodes.forEach(function(n){var r=n.state===2?5:4;
|
||||
if(n.state===2&&n.lock){ctx.beginPath();ctx.arc(n.x,n.y,11,0,Math.PI*2);ctx.strokeStyle='rgba(242,84,27,0.9)';ctx.lineWidth=1.5;ctx.stroke();ctx.lineWidth=1;}
|
||||
if(n.state===2){var g=ctx.createRadialGradient(n.x,n.y,0,n.x,n.y,16);g.addColorStop(0,'rgba(242,84,27,0.35)');g.addColorStop(1,'rgba(242,84,27,0)');ctx.fillStyle=g;ctx.beginPath();ctx.arc(n.x,n.y,16,0,Math.PI*2);ctx.fill();}
|
||||
ctx.beginPath();ctx.arc(n.x,n.y,r,0,Math.PI*2);ctx.fillStyle=n.state===2?'#F2541B':(n.state===1?'#FFB35C':'#0C0C0E');ctx.fill();
|
||||
if(n.state===0){ctx.strokeStyle='rgba(154,154,158,0.8)';ctx.stroke();}});
|
||||
ctx.globalAlpha=1;
|
||||
} else if(nodes.length) nodes=[];
|
||||
// live: release one sampled block at the chain scene's pace; its miner flashes, lines reach its parents' miners
|
||||
if(hLive&&hq.length&&t-hLast>Math.max(60,1000/live.rate)){var b=hq.shift();hLast=t;var m=joinMiner(b.miner||'?');
|
||||
if(m){m.flash=1;var done={};(b.parents||[]).forEach(function(h){var pid=live.minerOf[h];if(!pid||pid===m.id||done[pid])return;done[pid]=1;var p=minerDot(pid);if(p&&!p.gone)links.push({a:m,b:p,born:t});});if(links.length>24)links=links.slice(-24);}}
|
||||
hm.forEach(function(m){m.a=Math.max(0,Math.min(1,m.a+(m.gone?-0.012:0.012)));
|
||||
m.fx-=speed/W;if(m.fx<-0.02){m.fx+=1.04;m.a=0;} // the same slow drift; a dot that leaves on the left fades back in on the right
|
||||
m.x=W*m.fx+Math.sin(t/9000+m.ph)*14;m.y=laneY(m.lane)+m.jy+Math.cos(t/12000+m.ph*1.7)*6;
|
||||
if(m.flash>0)m.flash=Math.max(0,m.flash-0.018);});
|
||||
hm=hm.filter(function(m){return !(m.gone&&m.a<=0);});
|
||||
links=links.filter(function(l){return t-l.born<1600&&hm.indexOf(l.a)>=0&&hm.indexOf(l.b)>=0;});
|
||||
ctx.lineWidth=1;
|
||||
nodes.forEach(function(n){n.parents.forEach(function(p){if(nodes.indexOf(p)<0)return;var proven=n.state===2&&p.state===2;ctx.strokeStyle=proven?'rgba(242,84,27,0.35)':'rgba(154,154,158,0.18)';ctx.beginPath();ctx.moveTo(n.x,n.y);var mx=(n.x+p.x)/2;ctx.bezierCurveTo(mx,n.y,mx,p.y,p.x,p.y);ctx.stroke();});});
|
||||
nodes.forEach(function(n){var r=n.state===2?5:4;
|
||||
if(n.state===2&&n.lock){ctx.beginPath();ctx.arc(n.x,n.y,11,0,Math.PI*2);ctx.strokeStyle='rgba(242,84,27,0.9)';ctx.lineWidth=1.5;ctx.stroke();ctx.lineWidth=1;}
|
||||
if(n.state===2){var g=ctx.createRadialGradient(n.x,n.y,0,n.x,n.y,16);g.addColorStop(0,'rgba(242,84,27,0.35)');g.addColorStop(1,'rgba(242,84,27,0)');ctx.fillStyle=g;ctx.beginPath();ctx.arc(n.x,n.y,16,0,Math.PI*2);ctx.fill();}
|
||||
ctx.beginPath();ctx.arc(n.x,n.y,r,0,Math.PI*2);ctx.fillStyle=n.state===2?'#F2541B':(n.state===1?'#FFB35C':'#0C0C0E');ctx.fill();
|
||||
if(n.state===0){ctx.strokeStyle='rgba(154,154,158,0.8)';ctx.stroke();}});
|
||||
links.forEach(function(l){if(Math.abs(l.a.x-l.b.x)>W*0.6)return;var p=(t-l.born)/1600;ctx.strokeStyle='rgba(242,84,27,'+(0.35*(1-p)*Math.min(l.a.a,l.b.a)).toFixed(3)+')';edge(l.a,l.b);});
|
||||
hm.forEach(function(m){if(m.a<=0)return;var f=m.flash;ctx.globalAlpha=m.a;var gr=16+8*f;
|
||||
var g=ctx.createRadialGradient(m.x,m.y,0,m.x,m.y,gr);g.addColorStop(0,'rgba(242,84,27,'+(0.2+0.35*f).toFixed(3)+')');g.addColorStop(1,'rgba(242,84,27,0)');ctx.fillStyle=g;ctx.beginPath();ctx.arc(m.x,m.y,gr,0,Math.PI*2);ctx.fill();
|
||||
ctx.beginPath();ctx.arc(m.x,m.y,4+2*f,0,Math.PI*2);ctx.fillStyle='#F2541B';ctx.fill();
|
||||
if(f>0){ctx.beginPath();ctx.arc(m.x,m.y,2+1.5*f,0,Math.PI*2);ctx.fillStyle='rgba(255,179,92,'+f.toFixed(3)+')';ctx.fill();}
|
||||
ctx.globalAlpha=1;});
|
||||
requestAnimationFrame(frame);
|
||||
}
|
||||
if(!reduce){var sp=spacing();for(var i=0;i<Math.ceil(W/sp)+2;i++){spawn(W-i*sp);nodes[nodes.length-1].born=performance.now()-i*1000;}requestAnimationFrame(frame);}
|
||||
if(!reduce){seed();requestAnimationFrame(frame);}
|
||||
|
||||
// chain scene: blocks stream in, miners fly in to prove shards, locks ripple
|
||||
(function(){
|
||||
|
|
@ -518,8 +605,8 @@ footer .wrap{padding-block:48px 32px}
|
|||
function size(){W=c.clientWidth;H=c.clientHeight;c.width=W*dpr;c.height=H*dpr;x.setTransform(dpr,0,0,dpr,0,0);S=W<600?18:26;GAP=W<600?S*2.5:S*3.1;speed=GAP/1000;k=Math.max(0.22,Math.min(1,(W/speed)/22000));}
|
||||
size();window.addEventListener('resize',size);
|
||||
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) replace the random spawner
|
||||
var liveMode=false,liveQ=[],liveSeen={},liveLanes={},liveRate=1,byHash={};
|
||||
// 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={};
|
||||
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.x<px-S;}).sort(function(a2,b2){return b2.x-a2.x;});if(prev.length)b.parents.push(prev[0]);if(prev.length>1&&Math.random()<0.5)b.parents.push(prev[1]);}}
|
||||
else{var cands=blocks.slice(-8).filter(function(q){return q.x<px-S;}).sort(function(a2,b2){return b2.x-a2.x;});
|
||||
|
|
@ -528,10 +615,10 @@ footer .wrap{padding-block:48px 32px}
|
|||
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;
|
||||
function frame(t){
|
||||
if(liveMode){if(liveQ.length&&t-last>Math.max(60,1000/liveRate)){spawn(W+S,liveQ.shift());last=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;
|
||||
var sp=liveMode?speed*liveRate:speed;blocks.forEach(function(b){b.x-=sp*dt;var age=t-b.born;
|
||||
var sp=liveMode?speed*live.rate:speed;blocks.forEach(function(b){b.x-=sp*dt;var age=t-b.born;
|
||||
if(b.state==='mined'&&age>1200*k)b.state='proving';
|
||||
if(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);}}
|
||||
|
|
@ -563,21 +650,16 @@ footer .wrap{padding-block:48px 32px}
|
|||
var seedN=Math.ceil(W/GAP)+1,pv=Math.min(5,Math.max(2,Math.floor(seedN*0.45)));for(var i=0;i<seedN;i++){spawn(W-i*GAP);var b=blocks[blocks.length-1];b.born=performance.now()-i*1000;if(i>pv){b.state='proven';b.shards=[1,1,1,1];b.claimed=[true,true,true,true];proven++;if(b.cp){b.locked=true;locked++;}}else if(i>1){b.state='proving';b.claimed=[true,true,false,false];b.shards=[1,1,0,0];}}
|
||||
cb.textContent=n;cp.textContent=proven;cl.textContent=locked;
|
||||
requestAnimationFrame(frame);
|
||||
// live path: one fetch on load; if the observer updated within 30 s, the label links to /live and real blocks feed the scene
|
||||
// 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:'';
|
||||
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?'<span class="dot" style="margin-right:8px"></span><a href="/live" style="color:var(--molten)">live devnet</a>':labelHTML;}
|
||||
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){setLive(false);return false;}
|
||||
setLive(true);liveRate=1.3; /* the scene keeps a calm pace; faster chains are sampled, the counter stays true */
|
||||
var fresh=d.blocks.filter(function(b){return !liveSeen[b.hash];});if(first)fresh=fresh.slice(-12);
|
||||
d.blocks.forEach(function(b){liveSeen[b.hash]=1;});
|
||||
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;}
|
||||
fresh.forEach(function(b){liveQ.push({hash:b.hash,parents:b.parents,lane:laneFor(b.miner||'?')});});
|
||||
if(liveQ.length>6)liveQ=liveQ.slice(-6);return true;
|
||||
}).catch(function(){setLive(false);return false;});}
|
||||
pullLive(true).then(function(on){if(on)setInterval(function(){pullLive(false);},2000);});
|
||||
live.sub(function(fresh){setLive(live.on);if(!live.on)return;
|
||||
fresh.forEach(function(b){liveQ.push({hash:b.hash,parents:b.parents||[],lane:laneFor(b.miner||'?')});});
|
||||
if(liveQ.length>6)liveQ=liveQ.slice(-6);});
|
||||
})();
|
||||
// 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);});
|
||||
|
||||
// hourly program ticker
|
||||
var ops=['rotl','rotr','xor','add','sub','mul','mulhi','mad','or'];
|
||||
|
|
|
|||
Loading…
Reference in a new issue