A creation from the community
Nine Apple Pinball
Made by ArkheOS
Launch up to nine illustrated apples into a playable orchard pinball table. Time the left and right flippers, bounce off bumpers, build your score, and reset or undo.
Make your version
Play this widget, then change its rules, look, or purpose with Widget Canvas in ChatGPT.
Copy the selected request. In ChatGPT, choose “Try in chat,” replace the example prompt, and paste it.
You are exploring the published defaults. Changes here stay in this browser and do not change the creator’s work.
Follow the remix chain
- Nine Spinning Apples
Made by ArkheOS - Nine Apple Pinball
Made by ArkheOS
View published code
HTML
<main class="pin"><header><p class="kicker">THE ORCHARD ARCADE</p><h1>Nine Apple Pinball</h1><div class="scores"><span>Score <strong id="score">0</strong></span><span id="count">1 apple · 0 in play</span></div></header><div class="table"><svg id="board" viewBox="0 0 360 520" role="img" aria-label="Illustrated orchard pinball table. Use the flipper buttons below."><defs><linearGradient id="wood" x2="0" y2="1"><stop stop-color="#587a62"/><stop offset="1" stop-color="#254b43"/></linearGradient><pattern id="grain" width="30" height="40" patternUnits="userSpaceOnUse"><path d="M4 0Q12 20 4 40M22 0Q15 20 22 40" fill="none" stroke="#fff5cd" opacity=".035"/></pattern></defs><rect x="8" y="8" width="344" height="504" rx="43" fill="#b88f59"/><rect x="16" y="16" width="328" height="496" rx="37" fill="url(#wood)" stroke="#e9ce91" stroke-width="2"/><rect x="16" y="16" width="328" height="496" rx="37" fill="url(#grain)"/><path d="M67 37H294L334 110V355L262 425M98 425L26 355V110L65 35" fill="none" stroke="#142f2d" stroke-width="11" stroke-linejoin="round"/><path d="M67 37H294L334 110V355L262 425M98 425L26 355V110L65 35" fill="none" stroke="#e1bf7b" stroke-width="5" stroke-linejoin="round"/><path d="M51 282Q77 318 111 336M309 282Q283 318 249 336" fill="none" stroke="#d8bd81" stroke-width="1.5" stroke-dasharray="4 5"/><path d="M180 93C159 72 146 75 143 59Q177 52 180 77Q189 50 217 59Q211 80 180 93" fill="#b8c08b" opacity=".55"/><text x="180" y="110" fill="#eed9a3" text-anchor="middle" font-family="Georgia,serif" font-size="11" letter-spacing="2">NINE LITTLE THOUGHTS</text><g id="bumpers"></g><path d="M141 318Q180 338 219 318" fill="none" stroke="#d9bd78" stroke-width="1.5"/><text x="180" y="363" text-anchor="middle" fill="#e4cc9a" font-family="Georgia,serif" font-size="18">Keep them in mind.</text><text x="180" y="384" text-anchor="middle" fill="#b4c3a6" font-family="system-ui" font-size="9">TIMED FLIPS BUILD 2×–4× BUMPER POINTS</text><rect x="147" y="480" width="66" height="24" rx="9" fill="#172f2d"/><text x="180" y="497" text-anchor="middle" fill="#d6bd87" font-family="system-ui" font-size="9" letter-spacing="2">DRAIN</text><g id="flippers"></g><g id="balls"></g></svg><div id="overlay">Tap Launch to send your first apple.</div></div><div class="flipper-controls"><button id="left" aria-label="Left flipper, A or left arrow" aria-pressed="false">LEFT <small>A / ←</small></button><button id="right" aria-label="Right flipper, D or right arrow" aria-pressed="false">RIGHT <small>D / →</small></button></div><div class="primary"><button id="launch">Launch apple</button><button id="add">+ Add apple</button><button id="pause" disabled>Pause</button></div><p id="status" role="status">Launch, then tap or hold the flippers as an apple approaches.</p><label class="speed" for="speed">Apple speed <output id="rate">1.0×</output></label><input id="speed" type="range" min=".5" max="1.8" step=".1" value="1"><footer><button id="undo" disabled>Undo</button><button id="reset">Reset</button><button id="audio" aria-pressed="false">Sound off</button><span id="saved">Saved</span></footer><p class="note" id="motion"></p><p class="credit">Remixed from <a href="https://widgets.arkheos.ai/community/widgets/5f3551f1-b277-4a51-99e8-032f640819ca" target="_blank" rel="noopener">Nine Spinning Apples · ArkheOS</a></p></main>
CSS
*{box-sizing:border-box}body{margin:0;background:#faf3e5;color:#3c382d;font-family:system-ui,sans-serif}.pin{width:100%;max-width:460px;margin:auto;padding:18px 16px 12px;border:1px solid #e2d2b6;border-radius:22px;background:#faf3e5}.kicker{margin:0 0 6px;font-size:9px;letter-spacing:2px;color:#786448;font-weight:700}h1{font:normal clamp(26px,7vw,34px)/1.15 Georgia,serif;margin:0 0 12px;letter-spacing:-1px}.scores{display:flex;align-items:center;justify-content:space-between;font-size:11px;color:#746246;margin-bottom:10px}.scores strong{font-size:23px;color:#3d5847;font-variant-numeric:tabular-nums;margin-left:5px}.table{position:relative;width:100%;border-radius:28px;overflow:hidden;background:#365b4b}#board{display:block;width:100%;height:auto}#overlay{position:absolute;top:53%;left:14%;right:14%;padding:12px;background:#fcf3d9ed;border:1px solid #d1b777;border-radius:12px;text-align:center;font:15px/1.4 Georgia,serif;color:#3b4b38;pointer-events:none}.flipper-controls{display:grid;grid-template-columns:1fr 1fr;gap:12px;margin:12px 0}.flipper-controls button{height:56px;background:#b74a37;color:#fff4d7;border:1px solid #8f362b;border-bottom:5px solid #853a2a;border-radius:13px;font-size:14px;font-weight:750;touch-action:none;user-select:none;-webkit-user-select:none}.flipper-controls small{font-size:10px;font-weight:400;margin-left:8px;color:#ffdbc0}.flipper-controls button[aria-pressed=true]{background:#db7551;border-bottom-width:1px;padding-top:4px}.primary{display:grid;grid-template-columns:1.25fr 1.15fr .8fr;gap:7px}button{cursor:pointer;font-family:inherit;touch-action:manipulation;-webkit-tap-highlight-color:transparent}.primary button,footer button{min-height:42px;border:1px solid #d3c1a0;border-radius:9px;background:#fff9ec;color:#55452f;font-size:12px;padding:5px 8px}#launch{background:#3d654f;color:#fff8e8;border-color:#3d654f;font-weight:650}button:disabled{opacity:.5;cursor:default}#status{min-height:34px;font-size:11px;line-height:1.5;margin:10px 0;color:#776349}.speed{display:flex;justify-content:space-between;font-size:12px;font-weight:650}.speed output{font-weight:400}input{width:100%;height:32px;margin:4px 0 6px;accent-color:#a44331}footer{display:flex;gap:7px;align-items:center;border-top:1px solid #e3d4b9;padding-top:9px}footer button{font-size:11px;min-height:38px}#saved{margin-left:auto;font-size:10px;color:#7e6b4f}.note,.credit{font-size:10px;line-height:1.5;color:#7b6b50;margin:8px 0 0}.note:empty{display:none}.credit a{color:#496754}button:focus-visible,input:focus-visible{outline:3px solid #c58b37;outline-offset:2px}@media(max-width:350px){.pin{padding:16px 12px 10px}.primary{gap:5px}.primary button{font-size:11px;padding:4px}.flipper-controls{gap:10px}h1{font-size:27px}}JavaScript
const Pinball=(()=>{
const walls=[[26,110,65,35],[65,35,295,35],[295,35,334,110],[26,110,26,355],[334,110,334,355],[26,355,98,425],[334,355,262,425]];
const bumpers=[{x:120,y:145,r:23},{x:240,y:145,r:23},{x:180,y:235,r:28}];
const clone=x=>JSON.parse(JSON.stringify(x)),clamp=(x,a,b)=>Math.max(a,Math.min(b,x));
function ball(i,live){return {x:305-i*5,y:310-i*17,vx:-100-i*13,vy:-480-i*9,angle:i*27,live,combo:0};}
function fresh(){return {count:1,speed:1,score:0,balls:[ball(0,false)],flips:[.35,.35],running:false};}
function segment(b,x,y,X,Y,r,ux=0,uy=0,kick=0){const dx=X-x,dy=Y-y,t=clamp(((b.x-x)*dx+(b.y-y)*dy)/(dx*dx+dy*dy),0,1),qx=x+t*dx,qy=y+t*dy;let nx=b.x-qx,ny=b.y-qy,d=Math.hypot(nx,ny),rad=10+r;if(d>=rad)return false;if(d<.0001){nx=0;ny=-1;d=1;}else{nx/=d;ny/=d;}b.x=qx+nx*(rad+.05);b.y=qy+ny*(rad+.05);const dot=(b.vx-ux*t)*nx+(b.vy-uy*t)*ny;if(dot<0){b.vx-=1.86*dot*nx;b.vy-=1.86*dot*ny;if(kick){b.vy-=kick*(.35+.65*t);b.vx+=(qx<180?1:-1)*kick*.35*t;}return true;}return false;}
function flipper(j,a){const side=j===0?1:-1,x=j===0?98:262,y=425;return {x,y,X:x+side*Math.cos(a)*65,Y:y+Math.sin(a)*65};}
function step(s,dt,held,emit=()=>{}){if(!s.running)return;const fs=[];for(let j=0;j<2;j++){const old=s.flips[j],target=held[j]?-.52:.35;s.flips[j]+=clamp(target-old,-7*dt,7*dt);const f=flipper(j,s.flips[j]),prev=flipper(j,old);f.ux=(f.X-prev.X)/dt;f.uy=(f.Y-prev.Y)/dt;f.kick=s.flips[j]<old?180:0;fs.push(f);}
for(let i=0;i<s.balls.length;i++){const b=s.balls[i];if(!b.live)continue;b.vy+=210*dt;b.x+=b.vx*dt;b.y+=b.vy*dt;b.angle=(b.angle+dt*(60+i*8)*(i%2?-1:1))%360;
for(const w of walls)segment(b,...w,3);
for(let j=0;j<bumpers.length;j++){const p=bumpers[j],dx=b.x-p.x,dy=b.y-p.y,d=Math.hypot(dx,dy),rad=p.r+10;if(d<rad){const nx=d?dx/d:0,ny=d?dy/d:-1;b.x=p.x+nx*(rad+.1);b.y=p.y+ny*(rad+.1);const dot=b.vx*nx+b.vy*ny;if(dot<0){b.vx-=2*dot*nx;b.vy-=2*dot*ny;b.vx+=nx*90;b.vy+=ny*90;s.score=Math.min(99999999,s.score+100*(1+b.combo));b.combo=0;emit('bumper',j);}}}
for(let j=0;j<2;j++){const f=fs[j];if(segment(b,f.x,f.y,f.X,f.Y,7,f.ux,f.uy,f.kick)&&f.kick){b.combo=Math.min(3,b.combo+1);s.score=Math.min(99999999,s.score+25);emit('flip',j);}}
const v=Math.hypot(b.vx,b.vy);if(v>690){b.vx*=690/v;b.vy*=690/v;}if(b.y>510||b.x<0||b.x>360){b.live=false;emit('drain',i);}}
for(let i=0;i<s.balls.length;i++)for(let j=i+1;j<s.balls.length;j++){const a=s.balls[i],b=s.balls[j];if(!a.live||!b.live)continue;const dx=b.x-a.x,dy=b.y-a.y,d=Math.hypot(dx,dy);if(d>0&&d<20){const nx=dx/d,ny=dy/d,over=(20-d)/2;a.x-=nx*over;a.y-=ny*over;b.x+=nx*over;b.y+=ny*over;const dot=(b.vx-a.vx)*nx+(b.vy-a.vy)*ny;if(dot<0){a.vx+=dot*nx;a.vy+=dot*ny;b.vx-=dot*nx;b.vy-=dot*ny;}}}
for(const b of s.balls){const v=Math.hypot(b.vx,b.vy);if(v>690){b.vx*=690/v;b.vy*=690/v;}}if(!s.balls.some(b=>b.live))s.running=false;
}
function valid(s){return s&&Number.isInteger(s.count)&&s.count>=1&&s.count<=9&&Number.isFinite(s.speed)&&s.speed>=.5&&s.speed<=1.8&&Number.isInteger(s.score)&&s.score>=0&&s.score<=99999999&&typeof s.running==='boolean'&&Array.isArray(s.flips)&&s.flips.length===2&&s.flips.every(x=>Number.isFinite(x)&&x>=-.53&&x<=.36)&&Array.isArray(s.balls)&&s.balls.length===s.count&&s.balls.every(b=>b&&['x','y','vx','vy','angle'].every(k=>Number.isFinite(b[k]))&&b.x>=-30&&b.x<=390&&b.y>=-30&&b.y<=540&&Math.hypot(b.vx,b.vy)<=750&&Math.abs(b.angle)<=360&&typeof b.live==='boolean'&&Number.isInteger(b.combo)&&b.combo>=0&&b.combo<=3);}
return {fresh,ball,step,flipper,walls,bumpers,valid,clone};
})();
(()=>{
'use strict';const colors=[['#e76142','#a92f29'],['#e8b440','#bd7026'],['#93ab54','#547340'],['#d74d55','#972c42'],['#ef8f48','#bb4d2d'],['#bfd073','#739447'],['#b55343','#763327'],['#f0bd70','#cd7b39'],['#c54137','#852e2c']];
function svg(i){const [a,b]=colors[i],uid='apple'+i;const bulge=i%3===0?3:i%3===1?-2:0;return '<svg aria-hidden="true" viewBox="0 0 100 100"><defs><linearGradient id="'+uid+'" x1="0" y1="0" x2="1" y2="1"><stop stop-color="'+a+'"/><stop offset="1" stop-color="'+b+'"/></linearGradient></defs><path d="M50 29 Q46 18 52 10" fill="none" stroke="#68492c" stroke-width="5" stroke-linecap="round"/><path d="M51 23 Q58 4 78 13 Q74 31 51 23Z" fill="'+(i%2?'#668344':'#426f45')+'" stroke="#365337" stroke-width="1.5"/><path d="M54 23 72 15" stroke="#d4dea1" stroke-width="1.4" fill="none"/><path d="M50 33 C'+(30+bulge)+' 18 12 33 15 57 C17 80 32 94 47 86 Q50 84 53 86 C69 94 85 77 86 55 C88 32 69 19 50 33Z" fill="url(#'+uid+')" stroke="'+b+'" stroke-width="1.5"/><path d="M24 49 Q23 36 36 33" fill="none" stroke="#fff4d2" opacity=".8" stroke-width="5" stroke-linecap="round"/><ellipse cx="24" cy="58" rx="2" ry="3" fill="#fff7db" opacity=".55"/><path d="M59 79 Q75 73 77 60" fill="none" stroke="'+b+'" stroke-width="4" opacity=".5" stroke-linecap="round"/><path d="M44 84 Q50 81 56 84" fill="none" stroke="'+b+'" stroke-width="2"/></svg>';}
const $=id=>document.getElementById(id),NS='http://www.w3.org/2000/svg',bridge=typeof WidgetCanvas==='undefined'?null:WidgetCanvas,ID='nine-apple-pinball-v1',parent='5f3551f1-b277-4a51-99e8-032f640819ca',reduce=matchMedia('(prefers-reduced-motion: reduce)');
let s=Pinball.fresh(),history=[],held=[false,false],paused=false,last=0,acc=0,saveTime=0,groups=[],paddles=[],bumperNodes=[],flash=[0,0,0],sound=false,audio=null,speedUndo=false;
function node(tag,attrs){const n=document.createElementNS(NS,tag);for(const k in attrs)n.setAttribute(k,attrs[k]);return n;}
Pinball.bumpers.forEach((p,i)=>{const g=node('g',{});g.append(node('circle',{cx:p.x,cy:p.y+4,r:p.r+5,fill:'#17372f'}),node('circle',{cx:p.x,cy:p.y,r:p.r+4,fill:'#d9bc77',stroke:'#f3dfac','stroke-width':2}),node('circle',{cx:p.x,cy:p.y,r:p.r-3,fill:i===2?'#bc5743':'#678569',stroke:'#355343','stroke-width':2}));const t=node('text',{x:p.x,y:p.y+4,'text-anchor':'middle',fill:'#fff0cc','font-family':'Georgia,serif','font-size':12});t.textContent='100';g.append(t);$('bumpers').append(g);bumperNodes.push(g);});
for(let j=0;j<2;j++){const p=node('path',{fill:'none',stroke:'#edc978','stroke-width':14,'stroke-linecap':'round'});$('flippers').append(p);paddles.push(p);$('flippers').append(node('circle',{cx:j===0?98:262,cy:425,r:5,fill:'#965035'}));}
function rebuild(){groups=[];$('balls').replaceChildren();s.balls.forEach((b,i)=>{const g=node('g',{});g.innerHTML='<ellipse cx="2" cy="10" rx="12" ry="5" fill="#132f2b" opacity=".35"/>'+svg(i).replace('<svg aria-hidden="true"','<svg x="-18" y="-20" width="36" height="36" aria-hidden="true"');$('balls').append(g);groups.push(g);});}
function ui(){const live=s.balls.filter(b=>b.live).length;$('score').textContent=s.score.toLocaleString();$('count').textContent=s.count+' apple'+(s.count>1?'s':'')+' · '+live+' in play';$('add').disabled=s.count===9;$('launch').textContent=live?'Retry drained':s.balls.some(b=>b.y>510)?'Retry apples':'Launch '+(s.count===1?'apple':'apples');$('launch').disabled=live===s.count;$('pause').disabled=!s.running;$('pause').textContent=paused?'Resume':'Pause';$('undo').disabled=!history.length;$('speed').value=s.speed;$('rate').textContent=s.speed.toFixed(1)+'×';$('overlay').hidden=s.running&&!paused;$('overlay').textContent=paused?'Paused. Tap Resume when ready.':s.balls.some(b=>b.y>510)?'Apples drained. Retry to launch them again.':'Tap Launch to send '+(s.count===1?'your first apple.':'all '+s.count+' apples.');$('motion').textContent=reduce.matches?'Reduced motion: no decorative spin or flashes. Play starts only when you launch; Pause is always available.':'';}
function paint(t=0){for(let i=0;i<groups.length;i++){const b=s.balls[i];groups[i].style.display=b.live||!s.running&&b.y<500?'':'none';groups[i].setAttribute('transform','translate('+b.x+' '+b.y+')');groups[i].querySelector('svg').style.transform=reduce.matches?'':'rotate('+b.angle+'deg)';groups[i].querySelector('svg').style.transformOrigin='50% 50%';}for(let j=0;j<2;j++){const f=Pinball.flipper(j,s.flips[j]);paddles[j].setAttribute('d','M'+f.x+' '+f.y+'L'+f.X+' '+f.Y);$(j?'right':'left').setAttribute('aria-pressed',String(held[j]));}for(let i=0;i<3;i++)bumperNodes[i].style.opacity=!reduce.matches&&flash[i]>t?'.65':'1';}
function remember(){history.push(Pinball.clone(s));history=history.slice(-8);}
function save(context=true){if(!bridge)return;try{Promise.resolve(bridge.setState({schema:1,workId:ID,current:Pinball.clone(s),undo:history})).catch(()=>{$('saved').textContent='Save unavailable';});if(context)Promise.resolve(bridge.setContext({workId:ID,title:'Nine Apple Pinball',parentPublicationId:parent,count:s.count,speed:s.speed,score:s.score,inPlay:s.balls.filter(b=>b.live).length})).catch(()=>{});}catch(e){$('saved').textContent='Save unavailable';}}
function restore(q){if(!q||q.workId!==ID||!Pinball.valid(q.current))return;s=Pinball.clone(q.current);history=Array.isArray(q.undo)?q.undo.filter(Pinball.valid).slice(-8).map(Pinball.clone):[];paused=s.running;held=[false,false];acc=0;last=0;rebuild();ui();paint();$('status').textContent=s.running?'Saved table restored and paused. Resume to continue.':'Saved table restored.';}
function beep(type){if(!sound||!audio)return;try{const o=audio.createOscillator(),g=audio.createGain();o.type='sine';o.frequency.value=type==='flip'?260:520;o.connect(g);g.connect(audio.destination);g.gain.setValueAtTime(.025,audio.currentTime);g.gain.exponentialRampToValueAtTime(.001,audio.currentTime+.065);o.start();o.stop(audio.currentTime+.07);}catch(e){}}
function emit(type,i){if(type==='bumper'){flash[i]=performance.now()+90;beep(type);}if(type==='flip')beep(type);}
$('launch').onclick=()=>{remember();for(let i=0;i<s.count;i++)if(!s.balls[i].live)s.balls[i]=Pinball.ball(i,true);s.running=true;paused=false;acc=0;last=0;rebuild();ui();paint();$('status').textContent='Launched! Time a rising flipper for +25 and a bumper multiplier.';save();};
$('add').onclick=()=>{if(s.count>=9)return;remember();const i=s.count++;s.balls.push(Pinball.ball(i,s.running));rebuild();ui();paint();$('status').textContent=s.count===9?'Nine apples! Keep the whole orchard ricocheting.':s.count+' apples. '+(s.running?'New apple launched.':'Launch them together.');save();};
$('pause').onclick=()=>{paused=!paused;held=[false,false];last=0;ui();paint();save();};
$('undo').onclick=()=>{if(!history.length)return;s=history.pop();paused=s.running;held=[false,false];acc=0;last=0;rebuild();ui();paint();$('status').textContent='Previous table restored. Resume if you are ready.';save();};
$('reset').onclick=()=>{remember();s=Pinball.fresh();paused=false;held=[false,false];acc=0;rebuild();ui();paint();$('status').textContent='One apple. Tap Launch. Undo restores your table.';save();};
$('speed').oninput=()=>{if(!speedUndo){remember();speedUndo=true;}s.speed=Number($('speed').value);ui();save();};$('speed').onchange=()=>{speedUndo=false;};
$('audio').onclick=()=>{sound=!sound;if(sound){try{audio=audio||new (window.AudioContext||window.webkitAudioContext)();audio.resume().catch(()=>{});}catch(e){sound=false;}}$('audio').textContent=sound?'Sound on':'Sound off';$('audio').setAttribute('aria-pressed',String(sound));};
function press(j){if(held[j])return;if(s.running&&!paused)remember();held[j]=true;paint();save(false);}
function release(j){held[j]=false;paint();}
for(let j=0;j<2;j++){const b=$(j?'right':'left');b.addEventListener('pointerdown',e=>{if(e.button!==0)return;e.preventDefault();b.setPointerCapture(e.pointerId);press(j);});b.addEventListener('pointerup',()=>release(j));b.addEventListener('pointercancel',()=>release(j));b.addEventListener('lostpointercapture',()=>release(j));b.addEventListener('keydown',e=>{if(e.key===' '||e.key==='Enter'){e.preventDefault();if(!e.repeat)press(j);}});b.addEventListener('keyup',e=>{if(e.key===' '||e.key==='Enter'){e.preventDefault();release(j);}});b.addEventListener('click',e=>{if(e.detail===0&&!held[j]){press(j);setTimeout(()=>release(j),130);}});}
document.addEventListener('keydown',e=>{if(e.target.matches('input'))return;const k=e.key.toLowerCase(),j=['a','arrowleft'].includes(k)?0:['d','arrowright'].includes(k)?1:-1;if(j>=0){e.preventDefault();if(!e.repeat)press(j);}if(k==='p'&&!e.repeat&&s.running)$('pause').click();});
document.addEventListener('keyup',e=>{const k=e.key.toLowerCase();if(['a','arrowleft'].includes(k))release(0);if(['d','arrowright'].includes(k))release(1);});
function halt(){held=[false,false];if(s.running)paused=true;last=0;ui();paint();save();}
window.addEventListener('blur',halt);document.addEventListener('visibilitychange',()=>{if(document.hidden)halt();});
window.addEventListener('widgetcanvas:restore',e=>restore(e.detail));reduce.addEventListener('change',()=>{if(reduce.matches)halt();ui();});
function loop(t){let dt=last?Math.min((t-last)/1000,.04):0;last=t;if(s.running&&!paused&&!document.hidden){acc+=dt*s.speed;let steps=0;while(acc>=1/240&&steps++<24){Pinball.step(s,1/240,held,emit);acc-=1/240;}if(!s.running){$('status').textContent='All apples drained. Retry, or Undo your last flip.';save();}ui();}paint(t);if(s.running&&t-saveTime>2000){save(false);saveTime=t;}requestAnimationFrame(loop);}
rebuild();ui();paint();if(bridge)try{bridge.setControlsPosition('top-right');bridge.setResetEnabled(false);bridge.setSharingEnabled(false);const q=bridge.getState();if(q&&typeof q.then==='function')q.then(restore).catch(()=>{});else restore(q);}catch(e){$('saved').textContent='Save unavailable';}requestAnimationFrame(loop);
})();