Community widgets

A creation from the community

Merge Lab

Made by ArkheOS

Play a tiny Snake × Pong mashup. Steer toward green orbs to grow and launch a ball, then catch it with the paddle to build your score. Restart and try to beat your best.

Make your version

Play this widget, then change its rules, look, or purpose with Widget Canvas in ChatGPT.

Open 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.

Original creation

  1. Merge Lab
    Made by ArkheOS

Remixes of this creation

View published code

HTML

<main id="lab"><header><div><span class="eyebrow">HYBRID EXPERIMENT / 01</span><h1>Merge Lab<span class="dot">✦</span></h1></div><button id="sound" aria-pressed="false">Sound off</button></header><div class="stats"><div>SCORE <strong id="score">0</strong></div><div>BEST <strong id="best">0</strong></div><div>LIVES <strong id="lives">● ● ●</strong></div></div><div class="label"><span><i class="mint"></i>01 SNAKE</span><small>orb = +10 + segment + ball</small></div><div class="scene"><canvas id="snake" width="360" height="220" aria-label="Snake chamber. Hold the turn buttons to steer."></canvas><div id="intro"><b>Two games. One experiment.</b><span>Hold a turn button to begin ↙</span></div></div><div class="turns"><button id="left" aria-label="Turn snake left">↶ <span>TURN LEFT</span></button><button id="right" aria-label="Turn snake right"><span>TURN RIGHT</span> ↷</button></div><div class="connector"><span>GROW</span><b>↓ ORB TRANSFER ↓</b><span>DEFLECT</span></div><div class="label"><span><i class="gold"></i>02 PONG</span><small>deflect = +25 · miss = −1 life</small></div><canvas id="pong" width="360" height="160" tabindex="0" aria-label="Pong chamber. Drag to move the paddle."></canvas><div id="strip" role="slider" tabindex="0" aria-label="Paddle position" aria-valuemin="0" aria-valuemax="100" aria-valuenow="50"><span>← DRAG PADDLE →</span><b id="thumb"></b></div><footer><span class="mark">ARKHEOS</span><span id="status" role="status">Steer → collect → catch</span><button id="restart">Restart ↻</button></footer><p class="keys">Keys: A/D turn · ←/→ paddle · Space pause</p></main>

CSS

*{box-sizing:border-box}body{margin:0;color:#e9f3f1;font-family:ui-sans-serif,system-ui,sans-serif;background:#0c141b}#lab{width:100%;max-width:460px;margin:auto;padding:16px;background:radial-gradient(ellipse at 10% 0%,#233b40,#111d27 60%);border:1px solid #36505c;border-radius:20px;overflow:hidden}header{display:flex;justify-content:space-between;align-items:center;gap:8px}.eyebrow{font-size:9px;letter-spacing:1.7px;color:#92aeb6}h1{font-size:29px;letter-spacing:-1px;margin:3px 0 13px}.dot{color:#bbf578;font-size:23px;margin-left:10px}button{font:inherit;cursor:pointer;touch-action:none;color:inherit;background:#213540;border:1px solid #42606c;border-radius:10px;min-height:44px}button:focus-visible,#strip:focus-visible{outline:2px solid #d7ff91;outline-offset:2px}#sound{font-size:10px;padding:0 10px;margin-top:-8px;white-space:nowrap}.stats{display:flex;border:1px solid #354d57;border-radius:10px;padding:10px 12px;justify-content:space-between;background:#0c1820;margin-bottom:16px}.stats div{font-size:9px;letter-spacing:1px;color:#93abb4}.stats strong{display:block;font-size:21px;letter-spacing:0;color:#f1f7ec;line-height:1.3}#lives{font-size:16px;color:#ffb98d;padding-top:4px}.label{display:flex;justify-content:space-between;align-items:center;margin:8px 0;font-size:10px;font-weight:800;letter-spacing:1px}.label small{font-size:9px;color:#9fb2bb;letter-spacing:0;font-weight:500}i{display:inline-block;width:6px;height:6px;border-radius:50%;margin-right:6px}.mint{background:#b9f480}.gold{background:#ffbd7b}canvas{display:block;width:100%;background:#0c171f;border:1px solid #314b58;border-radius:12px}#snake{aspect-ratio:360/220}.scene{position:relative}#intro{position:absolute;left:12px;right:12px;top:12px;display:flex;flex-direction:column;gap:4px;font-size:12px;pointer-events:none}#intro span{color:#b9f480;font-size:10px}.turns{display:flex;gap:10px;margin-top:9px}.turns button{flex:1;font-size:27px;color:#c6f795;min-height:52px;display:flex;align-items:center;justify-content:center;gap:10px;background:#203d36}.turns span{font-size:10px;font-weight:800;letter-spacing:.5px}.turns button:active,.turns button.held{background:#355d43}.connector{display:flex;justify-content:space-between;padding:11px 10px 4px;font-size:8px;letter-spacing:1px;color:#6a8c9a}.connector b{color:#aac3cb;font-weight:500}#pong{aspect-ratio:360/160;touch-action:none}#strip{position:relative;height:44px;background:#293b46;border:1px solid #4f6270;border-radius:10px;margin-top:8px;touch-action:none;cursor:ew-resize;overflow:hidden;text-align:center;user-select:none}#strip span{font-size:9px;letter-spacing:2px;line-height:42px;color:#b6c7ce}#thumb{position:absolute;left:50%;bottom:4px;width:28px;height:3px;border-radius:3px;background:#ffbf83;transform:translateX(-50%)}footer{display:flex;align-items:center;justify-content:space-between;gap:7px;margin-top:12px}.mark{font-size:10px;font-weight:1000;letter-spacing:1px;border:1px solid #8097a0;padding:4px 5px;color:#b2c6cc}#status{font-size:9px;color:#b9c9cb;max-width:130px}#restart{font-size:10px;padding:0 10px;min-height:44px}.keys{font-size:9px;color:#728e9c;text-align:center;margin:7px 0 0}@media(max-width:340px){#lab{padding:12px;border-radius:14px}.label small{font-size:8px}h1{font-size:27px}.stats{padding:9px}#status{max-width:95px;font-size:8px}.turns{gap:8px}.turns span{font-size:9px}.mark{font-size:8px}}

JavaScript

(()=>{
'use strict';
const $=id=>document.getElementById(id),a=$('snake').getContext('2d'),b=$('pong').getContext('2d'),bridge=typeof WidgetCanvas!=='undefined'?WidgetCanvas:null;
let score=0,best=0,sound=false,running=false,over=false,life=3,angle=0,head,body,orb,balls=[],paddle=180,turn=0,keys={},last=0,acc=0,cool=0,flash=0,clock=0,audio;
const clamp=(x,l,h)=>Math.max(l,Math.min(h,x));
function save(){if(bridge)try{Promise.resolve(bridge.setState({id:'merge-lab-v1',score,best,sound})).catch(()=>{});}catch(e){}}
function restore(q){if(q&&q.id==='merge-lab-v1'){score=Number.isFinite(q.score)?clamp(Math.floor(q.score),0,1e8):0;best=Number.isFinite(q.best)?Math.max(score,clamp(Math.floor(q.best),0,1e8)):score;sound=!!q.sound;hud();}}
function hud(){$('score').textContent=score;$('best').textContent=best;$('lives').textContent=Array.from({length:3},(_,i)=>i<life?'●':'○').join(' ');$('sound').textContent=sound?'Sound on':'Sound off';$('sound').setAttribute('aria-pressed',String(sound));}
function tone(f){if(!sound)return;try{audio=audio||new (window.AudioContext||window.webkitAudioContext)();audio.resume();const o=audio.createOscillator(),g=audio.createGain();o.type='sine';o.frequency.value=f;g.gain.setValueAtTime(.06,audio.currentTime);g.gain.exponentialRampToValueAtTime(.001,audio.currentTime+.13);o.connect(g);g.connect(audio.destination);o.start();o.stop(audio.currentTime+.14);}catch(e){}}
function add(n){score+=n;best=Math.max(best,score);hud();save();}
function resetSnake(){head={x:85,y:125};angle=0;body=Array.from({length:5},(_,i)=>({x:85-i*9,y:125}));orb={x:145,y:125};}
function restart(){score=0;life=3;over=false;running=false;balls=[];paddle=180;turn=0;keys={};cool=0;resetSnake();$('intro').style.display='flex';$('intro').innerHTML='<b>Two games. One experiment.</b><span>Hold a turn button to begin ↙</span>';$('status').textContent='Steer → collect → catch';hud();save();draw();}
function begin(){if(over)return;if(!running){running=true;$('intro').style.display='none';$('status').textContent='Collect green. Catch gold.';last=0;}}
function miss(message){if(over)return;life--;flash=.5;tone(130);hud();save();$('status').textContent=message;if(life<=0){over=true;running=false;$('intro').style.display='flex';$('intro').innerHTML='<b>Experiment complete · '+score+' points</b><span>Tap Restart for another run</span>';}}
function step(dt){clock+=dt;flash=Math.max(0,flash-dt);if(!running||over)return;cool=Math.max(0,cool-dt);angle+=((keys.d?1:0)-(keys.a?1:0)||turn)*2.8*dt;head.x+=Math.cos(angle)*68*dt;head.y+=Math.sin(angle)*68*dt;
// Lab walls wrap; the snake's body remains a hazard.
if(head.x<8)head.x=352;if(head.x>352)head.x=8;if(head.y<58)head.y=212;if(head.y>212)head.y=58;
acc+=dt;if(acc>=.075){acc-=.075;body.unshift({...head});body.pop();}
if(cool===0&&body.slice(12).some(p=>Math.hypot(head.x-p.x,head.y-p.y)<8)){miss('Snake collision · −1 life');resetSnake();cool=1.4;}
if(Math.hypot(head.x-orb.x,head.y-orb.y)<13){add(10);body.push({...body[body.length-1]});balls.push({x:orb.x,y:20,vx:(orb.x-180)*.36,vy:83+Math.min(score*.08,35)});tone(660);$('status').textContent='+10 · segment + ball!';let tries=0;do{orb={x:26+Math.random()*308,y:76+Math.random()*118};}while(++tries<30&&body.some(p=>Math.hypot(p.x-orb.x,p.y-orb.y)<26));}
paddle=clamp(paddle+((keys.ArrowRight?1:0)-(keys.ArrowLeft?1:0))*235*dt,37,323);
for(let i=balls.length-1;i>=0;i--){let p=balls[i],old=p.y;p.x+=p.vx*dt;p.y+=p.vy*dt;if(p.x<6){p.x=6;p.vx=Math.abs(p.vx);}if(p.x>354){p.x=354;p.vx=-Math.abs(p.vx);}if(p.vy>0&&old+5<=135&&p.y+5>=135&&Math.abs(p.x-paddle)<=39){p.y=130;p.vy=-Math.abs(p.vy)*1.25;p.vx=(p.x-paddle)*2;add(25);tone(880);$('status').textContent='+25 · clean deflection!';}if(p.y< -8){balls.splice(i,1);}else if(p.y>167){balls.splice(i,1);miss('Ball missed · −1 life');if(over)break;}}
$('thumb').style.left=(paddle/360*100)+'%';$('strip').setAttribute('aria-valuenow',String(Math.round(paddle/360*100)));
}
function circle(c,x,y,r,col){c.fillStyle=col;c.beginPath();c.arc(x,y,r,0,7);c.fill();}
function rr(c,x,y,w,h,r,col){c.fillStyle=col;c.beginPath();c.roundRect(x,y,w,h,r);c.fill();}
function grid(c,h){c.clearRect(0,0,360,h);c.strokeStyle='#1c303a';c.lineWidth=1;for(let x=18;x<360;x+=24)for(let y=18;y<h;y+=24){c.beginPath();c.moveTo(x-2,y);c.lineTo(x+2,y);c.moveTo(x,y-2);c.lineTo(x,y+2);c.stroke();}}
function draw(){grid(a,220);a.font='9px monospace';a.fillStyle='#607f8c';a.fillText('↔ WRAP WALLS  /  AVOID YOUR TAIL',12,20);
a.strokeStyle='#b9f480';a.lineWidth=1.5;a.beginPath();a.arc(orb.x,orb.y,13+Math.sin(clock*4)*2,0,7);a.stroke();circle(a,orb.x,orb.y,7,'#bef587');a.fillStyle='#102a22';a.fillRect(orb.x-3,orb.y-1,6,2);a.fillRect(orb.x-1,orb.y-3,2,6);
body.slice().reverse().forEach((p,i)=>circle(a,p.x,p.y,6,'hsl(143, '+(30+i/body.length*30)+'%, '+(25+i/body.length*30)+'%)'));
a.save();a.translate(head.x,head.y);a.rotate(angle);rr(a,-9,-8,18,16,6,cool>0?'#f4ca90':'#d1ff9a');circle(a,3,-4,1.6,'#163528');circle(a,3,4,1.6,'#163528');a.restore();
grid(b,160);b.font='9px monospace';b.fillStyle='#8a7965';b.fillText('ORB IN →',12,18);b.setLineDash([4,5]);b.strokeStyle='#553b32';b.beginPath();b.moveTo(0,154);b.lineTo(360,154);b.stroke();b.setLineDash([]);
if(!balls.length){b.fillStyle='#678591';b.textAlign='center';b.font='11px system-ui';b.fillText('Collect an orb above to launch a ball',180,69);b.textAlign='left';}
balls.forEach(p=>{b.strokeStyle='#d99e6650';b.lineWidth=3;b.beginPath();b.moveTo(p.x,p.y);b.lineTo(p.x-p.vx*.1,p.y-p.vy*.1);b.stroke();circle(b,p.x,p.y,5,'#ffd39a');});
rr(b,paddle-34,135,68,9,4,'#ffb879');rr(b,paddle-27,136,54,2,1,'#ffe6bb');if(flash>0){a.fillStyle=b.fillStyle='rgba(255,111,100,'+flash*.28+')';a.fillRect(0,0,360,220);b.fillRect(0,0,360,160);}
}
for(const [id,dir] of [['left',-1],['right',1]]){const el=$(id);el.addEventListener('pointerdown',e=>{e.preventDefault();el.setPointerCapture(e.pointerId);turn=dir;el.classList.add('held');begin();tone(320);});for(const event of ['pointerup','pointercancel','lostpointercapture'])el.addEventListener(event,()=>{if(turn===dir)turn=0;el.classList.remove('held');});}
for(const id of ['pong','strip']){const el=$(id);let active=null;function move(e){const r=el.getBoundingClientRect();paddle=clamp((e.clientX-r.left)/r.width*360,37,323);draw();}el.addEventListener('pointerdown',e=>{if(e.pointerType==='touch'&&e.clientX<48)return;e.preventDefault();active=e.pointerId;el.setPointerCapture(active);move(e);});el.addEventListener('pointermove',e=>{if(active===e.pointerId)move(e);});for(const ev of ['pointerup','pointercancel','lostpointercapture'])el.addEventListener(ev,()=>active=null);}
document.addEventListener('keydown',e=>{if(['a','d','ArrowLeft','ArrowRight',' '].includes(e.key)){e.preventDefault();if(e.key===' '&&!e.repeat){running=!running&&!over;$('status').textContent=running?'Collect green. Catch gold.':'Paused · Space resumes';}else if(e.key!==' '){keys[e.key]=true;begin();}}});document.addEventListener('keyup',e=>delete keys[e.key]);
$('restart').onclick=restart;$('sound').onclick=()=>{sound=!sound;tone(520);hud();save();};
window.addEventListener('blur',()=>{keys={};turn=0;running=false;$('status').textContent=over?'Experiment complete':'Paused · turn to resume';save();});
document.addEventListener('visibilitychange',()=>{last=0;if(document.hidden){running=false;turn=0;keys={};save();}});
resetSnake();if(bridge)try{restore(bridge.getState());bridge.setControlsPosition('top-right');bridge.setResetEnabled(false);bridge.setSharingEnabled(false);}catch(e){}
window.addEventListener('widgetcanvas:restore',e=>restore(e.detail));hud();draw();
function loop(t){let dt=last?Math.min((t-last)/1000,.04):0;last=t;let n=Math.ceil(dt/(1/120));for(let i=0;i<n;i++)step(dt/n);draw();requestAnimationFrame(loop);}requestAnimationFrame(loop);
})();