igneum/tools/site-redesign/engine.js
igneum-josh 2fa3adf997 site-redesign: the full-site redesign on our content, 22 pages served
The package's design system, layouts and interactions on our tokens and self-hosted fonts (site/site.css section 10),
our facts, numbers, records and live services on every page. The home page: the GPU hero (site/hero-scene.js, continuous
while in view, labelled as drawn), the live DAG panel on scenes/feed.js and live-dag.js 2.0.2, three reasons, the
economics split, the download row with the OS marks, the ledger line. The miner page: the download layout, platform
tabs stamped from the live download index with sha256, the steps, the card table, the fee section (the one source of
/dev-fee), the FAQ. The wallet page in the package's wallet-hero layout, the window slot ready for the wallet redesign.
The secondary pages rendered by the build from one source each: /claims and /randomx from the litepaper's sections,
/dev-fee from the miner's fee section, /provenance from docs/provenance.md through the scrub, /journey from
journey.json; /benchmarks rewrites to /miners, /connect redirects to /metamask. The generated pages (bench, bench
table, ledger) restyled onto the page hero and the docs layout with a section filter. The footer's secondary links point
at the real pages. The copy pass on every served page (docs/plans/site-copy-pass.md). The package's data tests and the
hero loop test in the gate (tools/site-redesign). No sample data is served; the package's review chrome is not.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-07 11:30:18 +01:00

121 lines
20 KiB
JavaScript

/* Igneum site motion and data boundary. The demo generator is never used by real observer mode. */
(function(root){
'use strict';
const TAU=Math.PI*2,clamp=(v,a,b)=>Math.max(a,Math.min(b,v)),MINE='a17f4c82';
const clone=o=>JSON.parse(JSON.stringify(o));
function random(seed){let a=seed>>>0;return()=>{a+=0x6D2B79F5;let t=a;t=Math.imul(t^t>>>15,t|1);t^=t+Math.imul(t^t>>>7,t|61);return((t^t>>>14)>>>0)/4294967296;};}
function validateReply(raw){
if(!raw||raw.ok!==true||!raw.state||typeof raw.state!=='object'||!Array.isArray(raw.blocks)||raw.blocks.length>6000)throw new Error('Unexpected observer response. Expected the existing /api/live contract.');
if(raw.state.stale!=null&&typeof raw.state.stale!=='boolean')throw new Error('Invalid observer stale state.');
if(raw.state.age_s!=null&&(!Number.isFinite(raw.state.age_s)||raw.state.age_s<0))throw new Error('Invalid observer age.');
const seen=new Set();
for(const b of raw.blocks){
if(!b||typeof b.hash!=='string'||!/^[a-f0-9]{8,64}$/i.test(b.hash)||seen.has(b.hash))throw new Error('Invalid or repeated block hash in observer response.');
seen.add(b.hash);
for(const flag of ['chain','locked','final','proven'])if(b[flag]!=null&&typeof b[flag]!=='boolean')throw new Error('Invalid block flag: '+flag);
if(b.color!=null&&!['blue','red','pending'].includes(b.color))throw new Error('Invalid inclusion colour.');
for(const score of ['daa','blue_score'])if(b[score]!=null&&(!Number.isFinite(b[score])||b[score]<0))throw new Error('Invalid block score.');
if(typeof b.ts!=='number'||!Number.isFinite(b.ts)||b.ts<1e12||b.ts>Date.now()+60000)throw new Error('Invalid block time in observer response.');
if(!Array.isArray(b.parents)||b.parents.length>128||b.parents.some(p=>typeof p!=='string'||!/^[a-f0-9]{8,64}$/i.test(p)||p===b.hash))throw new Error('Invalid parent links in observer response.');
if(b.miner!=null&&(typeof b.miner!=='string'||b.miner.length>128))throw new Error('Invalid miner identity.');
if(b.shards!=null&&(!Array.isArray(b.shards)||b.shards.length>256||b.shards.some(s=>!s||!['planned','proving','verified','paid'].includes(s.state))))throw new Error('Invalid proof shard state.');
}
if(raw.finality?.checkpoints!=null&&(!Array.isArray(raw.finality.checkpoints)||raw.finality.checkpoints.length>500))throw new Error('Invalid checkpoint list.');
for(const c of raw.finality?.checkpoints||[]){if(!c||!['locked','pending'].includes(c.state)||!Number.isFinite(c.blue_score)||c.blue_score<0)throw new Error('Invalid checkpoint record.');if(c.fraction_total!=null&&(!Number.isFinite(c.fraction_total)||c.fraction_total<0||c.fraction_total>1))throw new Error('Invalid checkpoint fraction.');}
return clone(raw);
}
class Demo{
constructor(now=Date.now(),seed=174){this.r=random(seed);this.blocks=[];this.cps=[];this.seq=247810;this.head=null;this.sides=[];this.events=[];this.keys=[MINE,'6b41e008','dd09a7f2','347ca21b','5de0c719','c378af06'];for(let t=now-145000;t<now-2000;t+=1850)this.create(t,false,false);this.update(now);this.events=[{type:'proof',title:'Sample proof shards verified',at:now-4000},{type:'mine',title:'Sample block joined the graph',at:now-8000},{type:'lock',title:'Sample checkpoint locked',at:now-13000}];}
hash(){return Math.floor(this.r()*0xffffffff).toString(16).padStart(8,'0')+Math.floor(this.r()*0xffffffff).toString(16).padStart(8,'0');}
create(now=Date.now(),own=false,announce=true){
this.seq++;let red=!own&&this.seq%19===0,chain=own||(!red&&this.seq%4!==0);
const b={hash:this.hash(),ts:now,blue_score:red?null:this.seq,daa:830000+this.seq,miner:own?MINE:this.keys[Math.floor(this.r()*this.keys.length)],parents:this.head?[this.head,...this.sides.slice(-1)]:[],chain,color:red?'red':'blue',locked:false,final:false,proven:false,shards:[{state:'planned'},{state:'planned'},{state:'planned'}],manual:0};
this.blocks.push(b);if(chain){this.head=b.hash;this.sides=[];}else if(!red)this.sides.push(b.hash);
if(chain&&this.seq%17===0)this.cps.push({index:Math.floor(this.seq/17),blue_score:b.blue_score,state:'pending',fraction_total:0,at:now,hash:b.hash});
if(announce)this.event('mine',own?'Your sample block arrived':'A sample block arrived',now);return b;
}
event(type,title,at=Date.now()){this.events.unshift({type,title,at});this.events=this.events.slice(0,5);}
update(now=Date.now()){
const states=['planned','proving','verified','paid'];
for(const b of this.blocks){b.shards.forEach((s,i)=>{const a=(now-b.ts)/1000-i*2.6;const n=b.color==='red'?0:Math.max(b.manual,a<3?0:a<10?1:a<22?2:3);s.state=states[n];});b.proven=b.shards.length>0&&b.shards.every(s=>s.state==='verified'||s.state==='paid');}
// Explicit simulation fixtures only. Real mode never calls this method.
for(const cp of this.cps){if(cp.forced||now-cp.at>17000){cp.state='locked';cp.fraction_total=.71;}else{cp.state='pending';cp.fraction_total=Math.min(.65,(now-cp.at)/17000*.65);}}
const locked=this.cps.filter(c=>c.state==='locked'),max=locked.reduce((n,c)=>Math.max(n,c.blue_score),0),hashes=new Set(locked.map(c=>c.hash));
for(const b of this.blocks){b.locked=hashes.has(b.hash);b.final=b.color==='blue'&&b.blue_score<=max;}
this.blocks=this.blocks.filter(b=>b.ts>now-320000);this.cps=this.cps.slice(-12);
}
trigger(kind){let target=null,now=Date.now();if(kind==='mine'){target=this.create(now,true);}
if(kind==='proof'){target=[...this.blocks].reverse().find(b=>b.miner===MINE&&!b.proven)||this.create(now,true,false);target.manual=2;this.event('proof','Your sample proof completed');}
if(kind==='lock'){target=[...this.blocks].reverse().find(b=>b.chain&&now-b.ts>3500);if(target){this.cps.push({index:Math.floor(this.seq/17)+1,blue_score:target.blue_score,state:'locked',fraction_total:.71,at:now,hash:target.hash,forced:true});this.event('lock','A sample checkpoint locked');}}
this.update();return target;
}
snapshot(now=Date.now()){this.update(now);return{ok:true,state:{stale:false,age_s:0,height:this.seq,now},blocks:this.blocks.map(({manual,...b})=>clone(b)),finality:{checkpoints:this.cps.map(({at,hash,forced,...c})=>clone(c))},proving:{},_demo:true};}
}
class Feed{
constructor(config){this.config=config;this.mode='demo';this.windowSec=120;this.demo=new Demo();this.reply=this.demo.snapshot();this.status={state:'demo',message:'Design demo / simulated blocks'};this.listeners=new Set();this.timer=0;this.pollTimer=0;this.controller=null;this.generation=0;this.destroyed=false;this.failures=0;this.lastReceipt=0;this.paused=false;this.visibility=()=>{if(!document.hidden&&this.mode==='observer')this.poll(this.generation);};document.addEventListener('visibilitychange',this.visibility);}
subscribe(fn){this.listeners.add(fn);fn(this.reply,this.status);this.start();return()=>{this.listeners.delete(fn);if(!this.listeners.size)this.stopTimers();};}
emit(){for(const fn of this.listeners){try{fn(this.reply,this.status);}catch(e){console.error('Feed subscriber failed',e);}}}
start(){if(this.timer||!this.listeners.size)return;this.timer=setInterval(()=>{if(document.hidden)return;if(this.mode==='demo'){let n=Date.now(),last=this.demo.blocks[this.demo.blocks.length-1];if(!last||n-last.ts>1850)this.demo.create(n);this.reply=this.demo.snapshot();this.emit();}else if(this.lastReceipt&&Date.now()-this.lastReceipt>12000&&this.status.state==='live'){this.status={state:'stale',message:'Observer connection is stale. Last received data is held.'};this.emit();}},1000);if(this.mode==='observer')this.poll(this.generation);}
stopTimers(){clearInterval(this.timer);this.timer=0;clearTimeout(this.pollTimer);this.pollTimer=0;if(this.controller){this.controller.abort();this.controller=null;}}
setMode(mode){if(!['demo','observer'].includes(mode))return;this.generation++;this.stopTimers();this.mode=mode;this.lastReceipt=0;this.failures=0;this.reply=null;this.status={state:'connecting',message:'Connecting to the read-only observer...'};if(mode==='demo'){this.demo=new Demo();this.reply=this.demo.snapshot();this.status={state:'demo',message:'Design demo / simulated blocks'};}this.emit();this.start();}
setWindow(seconds){const next=Math.round(clamp(Number(seconds)||120,30,300));if(next===this.windowSec)return;this.windowSec=next;if(this.mode==='observer')this.poll(this.generation);}
trigger(kind){if(this.mode!=='demo')return null;const b=this.demo.trigger(kind);this.reply=this.demo.snapshot();this.emit();return b;}
async poll(g){if(this.controller||!this.listeners.size||this.mode!=='observer'||document.hidden||g!==this.generation)return;clearTimeout(this.pollTimer);const controller=new AbortController();this.controller=controller;const timeout=setTimeout(()=>controller.abort(),8000);
try{const url=new URL(this.config.observerUrl,location.href);if(url.protocol!=='https:'&&!['localhost','127.0.0.1'].includes(url.hostname))throw new Error('The observer URL must use HTTPS.');if(url.username||url.password)throw new Error('Credentials must not be placed in the observer URL.');url.searchParams.set('window',String(this.windowSec));
const r=await fetch(url,{method:'GET',cache:'no-store',credentials:'omit',mode:'cors',redirect:'error',headers:{Accept:'application/json'},signal:controller.signal});if(!r.ok)throw new Error('Observer returned HTTP '+r.status+'.');if(Number(r.headers.get('content-length')||0)>2e6)throw new Error('Observer reply is too large.');const text=await r.text();if(text.length>2e6)throw new Error('Observer reply is too large.');const reply=validateReply(JSON.parse(text));if(g!==this.generation)return;this.lastReceipt=Date.now();this.failures=0;this.reply=reply;const stale=reply.state.stale===true||(typeof reply.state.age_s==='number'&&reply.state.age_s>30);this.status={state:stale?'stale':'live',message:stale?'Observer says its data is stale. No synthetic activity is substituted.':'Read-only observer connected. Data is reported by one observer, not independently verified.'};this.emit();
}catch(e){if(g!==this.generation)return;this.failures++;this.status={state:'failed',message:(e.name==='AbortError'?'Observer request timed out.':e.message)+' No demo fallback is used. Check connectivity and CORS.'};this.emit();}
finally{clearTimeout(timeout);if(this.controller===controller)this.controller=null;if(g===this.generation&&this.mode==='observer'&&this.listeners.size)this.pollTimer=setTimeout(()=>this.poll(g),Math.min(16000,Math.max(2000,this.config.observerIntervalMs||2000)*2**Math.min(this.failures,3)));}
}
destroy(){this.destroyed=true;this.generation++;this.stopTimers();this.listeners.clear();document.removeEventListener('visibilitychange',this.visibility);}
}
class HardwareScene{
constructor(canvas){this.canvas=canvas;this.ctx=canvas.getContext('2d');this.stage=0;this.lastStage=-1;this.motion=true;this.disposed=false;this.inView=true;this.raf=0;this.stageAt=performance.now();this.last=0;this.w=0;this.h=0;this.reduced=matchMedia('(prefers-reduced-motion: reduce)');this.resize=()=>{this.w=canvas.clientWidth;this.h=canvas.clientHeight;this.dpr=Math.min(devicePixelRatio||1,2);canvas.width=Math.round(this.w*this.dpr);canvas.height=Math.round(this.h*this.dpr);this.paint(performance.now());};this.ro=new ResizeObserver(this.resize);this.ro.observe(canvas);this.io=new IntersectionObserver(e=>{this.inView=e[0].isIntersecting;if(this.inView)this.kick();else this.stop();});this.io.observe(canvas);this.mo=new MutationObserver(()=>this.paint(performance.now()));this.mo.observe(document.documentElement,{attributes:true,attributeFilter:['data-theme']});this.vis=()=>document.hidden?this.stop():this.kick();document.addEventListener('visibilitychange',this.vis);this.reduction=()=>{this.stop();this.paint(performance.now());this.kick();};this.reduced.addEventListener('change',this.reduction);this.resize();this.kick();}
setStage(n){this.lastStage=this.stage;this.stage=n;this.stageAt=performance.now();this.kick();this.paint(this.stageAt);}
setMotion(v){this.motion=v;this.stop();this.paint(performance.now());if(v)this.kick();}
stop(){cancelAnimationFrame(this.raf);this.raf=0;}
kick(){if(this.raf||this.disposed||document.hidden||!this.inView||this.reduced.matches||!this.motion)return;const frame=t=>{this.raf=0;if(this.disposed||document.hidden||!this.inView)return;if(t-this.last>32){this.paint(t);this.last=t;}if(t-this.stageAt<1000)this.raf=requestAnimationFrame(frame);};this.raf=requestAnimationFrame(frame);}
paint(t){if(!this.w||!this.h||this.disposed)return;const c=this.ctx,w=this.w,h=this.h,d=this.dpr;c.setTransform(d,0,0,d,0,0);c.clearRect(0,0,w,h);const light=document.documentElement.dataset.theme==='light',cs=getComputedStyle(document.documentElement),ember=cs.getPropertyValue('--ember').trim(),gold=cs.getPropertyValue('--molten').trim(),bone=cs.getPropertyValue('--bone').trim(),ash=cs.getPropertyValue('--ash').trim(),line=cs.getPropertyValue('--line').trim();const s=Math.min(w/590,h/490),cx=w*.51,cy=h*.52,age=clamp((t-this.stageAt)/950,0,1),moving=this.motion&&!this.reduced.matches,ease=moving?1-Math.pow(1-age,3):1,pulse=moving?Math.sin(age*Math.PI):0;const p=(x,y,z=0)=>({x:cx+(x*.91-y*.86)*s,y:cy+(x*.42+y*.43-z)*s});
const poly=(ps,fill,stroke,lw=1)=>{c.beginPath();ps.forEach((a,i)=>i?c.lineTo(a.x,a.y):c.moveTo(a.x,a.y));c.closePath();if(fill){c.fillStyle=fill;c.fill();}if(stroke){c.strokeStyle=stroke;c.lineWidth=lw;c.stroke();}};
const stroke=(ps,col,lw=1)=>{c.beginPath();ps.forEach((a,i)=>i?c.lineTo(a.x,a.y):c.moveTo(a.x,a.y));c.strokeStyle=col;c.lineWidth=lw;c.stroke();};
const box=(x,y,z,ww,hh,dep,top,edge)=>{poly([p(x,y+hh,z),p(x+ww,y+hh,z),p(x+ww,y+hh,z-dep),p(x,y+hh,z-dep)],light?'#e8e2d9':'#0d0d10',edge);poly([p(x+ww,y,z),p(x+ww,y+hh,z),p(x+ww,y+hh,z-dep),p(x+ww,y,z-dep)],light?'#d6d0c9':'#17171b',edge);poly([p(x,y,z),p(x+ww,y,z),p(x+ww,y+hh,z),p(x,y+hh,z)],top,edge);};
// Quiet engineering grid under the hardware. It stays still between explained events.
for(let j=-6;j<=6;j++){stroke([p(-245,j*29,-60),p(245,j*29,-60)],light?'#d9d2c666':'#35313955',.7);stroke([p(j*40,-155,-60),p(j*40,155,-60)],light?'#d9d2c666':'#35313955',.7);}
let g=c.createRadialGradient(cx,cy+15*s,5,cx,cy+15*s,235*s);g.addColorStop(0,light?'#F2541B16':'#F2541B14');g.addColorStop(1,'#F2541B00');c.fillStyle=g;c.fillRect(0,0,w,h);
// PCB carrier and routed connections.
box(-223,-112,-40,446,224,8,light?'#ede8e0':'#101013',light?'#c8c1b6':'#383139');
const connections=[[-205,-93,-173,-93,-173,-68],[-210,77,-174,77,-174,49],[80,91,80,115,190,115],[184,-74,214,-74,214,-28],[-105,97,-105,125,-157,125],[144,71,201,71,201,107]];
for(let i=0;i<connections.length;i++){const a=connections[i],pts=[p(a[0],a[1],-38),p(a[2],a[3],-38),p(a[4],a[5],-38)];stroke(pts,i%2?light?'#af795a77':'#a5613655':light?'#be59208c':'#f2541b66',1.1);const q=pts[2];c.fillStyle=i%2?gold:ember;c.fillRect(q.x-2,q.y-2,4,4);if(pulse>.02){const u=age,seg=u<.5?0:1,v=(u%0.5)*2,pt={x:pts[seg].x+(pts[seg+1].x-pts[seg].x)*v,y:pts[seg].y+(pts[seg+1].y-pts[seg].y)*v};c.beginPath();c.arc(pt.x,pt.y,2.2,0,TAU);c.fillStyle=gold;c.fill();}}
for(const [x,y] of [[-200,-85],[181,-85],[-200,89],[181,89]]){box(x,y,-36,16,10,3,light?'#e2dbd0':'#24232a',line);}
const lift=this.stage===1?15*ease:this.stage===2?8:0,top=18-lift;
// Finned aluminium backplate and GPU shroud. No brand mark is altered or illuminated.
box(-187,-61,top-18,374,122,17,light?'#b7b0a7':'#19191f',light?'#888279':'#444149');
for(let x=-180;x<180;x+=7)stroke([p(x,61,top-20),p(x,61,top-32)],light?'#6b655b':'#48434a',1);
box(-197,-67,top,394,134,17,light?'#d0c9bf':'#222127',light?'#898176':'#62504b');
stroke([p(-196,68,top-6),p(198,68,top-6)],ember,2.5);
const plane=(z,fn)=>{c.save();c.transform(.91*s,.42*s,-.86*s,.43*s,cx,cy-z*s);fn();c.restore();};
plane(top+1,()=>{
c.strokeStyle=light?'#827a71':'#555158';c.lineWidth=1;
for(let i=0;i<12;i++){const x=-178+i*31;c.beginPath();c.moveTo(x,-62);c.lineTo(x+24,-62);c.stroke();}
for(const fx of [-112,112]){c.beginPath();c.arc(fx,0,54,0,TAU);c.fillStyle=light?'#a29b91':'#101015';c.fill();c.strokeStyle=light?'#6c655b':'#4e464b';c.stroke();c.beginPath();c.arc(fx,0,48,0,TAU);c.strokeStyle=light?'#756d63':'#353039';c.stroke();const rot=this.stage===0&&moving?ease*.9:0;
for(let i=0;i<11;i++){c.save();c.translate(fx,0);c.rotate(i*TAU/11+rot);c.beginPath();c.moveTo(10,-4);c.bezierCurveTo(22,-21,39,-23,45,-8);c.quadraticCurveTo(25,-9,15,6);c.closePath();const g=c.createLinearGradient(10,0,45,-15);g.addColorStop(0,light?'#736c62':'#474149');g.addColorStop(1,light?'#c0b8ac':'#25212a');c.fillStyle=g;c.fill();c.strokeStyle=light?'#8d8478':'#51454a';c.lineWidth=.6;c.stroke();c.restore();}
c.beginPath();c.arc(fx,0,13,0,TAU);c.fillStyle=light?'#ddd4c8':'#2d272e';c.fill();c.strokeStyle=ember;c.lineWidth=1.4;c.stroke();c.beginPath();c.arc(fx,0,3,0,TAU);c.fillStyle=ember;c.fill();
}
c.fillStyle=light?'#a99d8e':'#17161b';c.fillRect(-40,-45,80,90);c.strokeStyle=light?'#9a8268':'#8c4b2d';c.strokeRect(-40,-45,80,90);c.fillStyle=ember;c.fillRect(-27,-30,54,60);c.fillStyle=light?'#f5e6d5':'#261610';c.fillRect(-23,-26,46,52);c.strokeStyle=gold;c.strokeRect(-19,-22,38,44);for(let a=-30;a<=30;a+=8){c.fillStyle=gold;c.fillRect(-44,a,4,2);c.fillRect(40,a,4,2);}c.fillStyle=gold;c.font='500 7px IBM Plex Mono,monospace';c.textAlign='center';c.fillText('IGNEUM',0,-4);c.font='6px IBM Plex Mono,monospace';c.fillText('COMPUTE',0,9);
c.font='500 7px IBM Plex Mono,monospace';c.textAlign='left';c.fillStyle=light?'#3f362b':'#bcaaa0';c.fillText('EMBER / GPU WORKER',-176,60);
});
for(const [x,y] of [[-184,-56],[183,-56],[-184,54],[183,54]])plane(top+1,()=>{c.beginPath();c.arc(x,y,2,0,TAU);c.fillStyle=light?'#6a6057':'#878087';c.fill();});
// The sample block lifts from compute; proof shards are explicit illustrative stages.
const z=this.stage===0?105:135,px=50,py=-23,blockS=this.stage===2?33:28;
for(let i=0;i<3;i++)box(px-blockS,py-blockS,z+i*9,blockS*2,blockS*2,6,light?'#efe4d7':'#221a19',i===2?ember:light?'#b2825d':'#8d4428');
plane(z+20,()=>{c.strokeStyle=gold;c.lineWidth=1;c.strokeRect(px-17,py-17,34,34);c.fillStyle=light?'#bc6535':'#f2541b33';c.fillRect(px-13,py-13,26,26);if(this.stage>0){c.strokeStyle=gold;c.lineWidth=2;c.beginPath();c.moveTo(px-7,py);c.lineTo(px-2,py+5);c.lineTo(px+9,py-7);c.stroke();}else{c.fillStyle=ember;c.fillRect(px-2,py-2,4,4);}});
const below=p(px,py,top+7),above=p(px,py,z-10);stroke([below,above],light?'#b7734580':'#f2541b60',1);c.setLineDash([3,5]);stroke([p(-112,0,top+8),p(-112,0,top+39),p(px,py,z-10)],light?'#b78b6866':'#d18e4750',.8);stroke([p(112,0,top+8),p(112,0,top+36),p(px,py,z-10)],light?'#b78b6866':'#d18e4750',.8);c.setLineDash([]);
if(this.stage>=1){for(let i=0;i<4;i++){const angle=i*TAU/4+.25,x=px+Math.cos(angle)*85,y=py+Math.sin(angle)*85,zz=z+12;stroke([p(x,y,zz),p(px,py,z+10)],light?'#b6774799':'#b8773e80',1);box(x-9,y-9,zz,18,18,5,this.stage===2?light?'#deba8e':'#75502c':light?'#ddab89':'#5a2d1d',gold);}}
if(this.stage===2){const z0=z-8,z1=z+50,v=42;for(const zz of [z0,z1]){poly([p(px-v,py-v,zz),p(px+v,py-v,zz),p(px+v,py+v,zz),p(px-v,py+v,zz)],null,gold,.9);}for(const [x,y] of [[-v,-v],[v,-v],[v,v],[-v,v]])stroke([p(px+x,py+y,z0),p(px+x,py+y,z1)],gold,.9);const a=p(px,py,z1+12);c.strokeStyle=gold;c.lineWidth=1.2;c.strokeRect(a.x-5*s,a.y,10*s,8*s);c.beginPath();c.arc(a.x,a.y,3.4*s,Math.PI,0);c.stroke();}
// Short, event-bound ripple. It rests after a single signal.
if(pulse>.02){const pos=p(px,py,z+18);c.save();c.translate(pos.x,pos.y);c.scale(1,.5);c.beginPath();c.arc(0,0,(50+age*65)*s,0,TAU);c.strokeStyle=light?'#b86b3555':'#ffb35c66';c.globalAlpha=(1-age)*.8;c.lineWidth=1;c.stroke();c.restore();}
const q=p(-203,90,-34);c.strokeStyle=line;c.lineWidth=1;c.beginPath();c.moveTo(q.x,q.y);c.lineTo(q.x-24*s,q.y+20*s);c.lineTo(q.x-52*s,q.y+20*s);c.stroke();
}
destroy(){this.disposed=true;this.stop();this.ro.disconnect();this.io.disconnect();this.mo.disconnect();document.removeEventListener('visibilitychange',this.vis);this.reduced.removeEventListener('change',this.reduction);}
}
root.IgneumSiteEngine={Demo,Feed,HardwareScene,validateReply,MINE};
})(typeof window!=='undefined'?window:globalThis);