Gorillaz
GPT 6 Luna · full

Reviews & generation details
Reviews
No human review yet. Unreviewed does not mean unsuccessful.
Generation context
Imported historical artifact. Its exact original instructions and lineage are not known.
Generation prompt · exact text unavailable
See the current prompt for reference. It may differ from the instructions used to generate this artifact.
Inspect original source 28,241 bytes · SHA-256 a8fc9587c042
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Gorillaz — Banana Bombers</title>
<style>
@import url('https://fonts.googleapis.com/css2?family=Press+Start+2P&family=Space+Mono:wght@400;700&display=swap');
:root{--ink:#17142b;--paper:#f4edda;--cream:#fff9e8;--lime:#d8f36a;--pink:#ff5b79;--blue:#59d9f5;--gold:#ffcb55;--muted:#827f91;--line:#28253c;--shadow:5px 5px 0 var(--ink)}
*{box-sizing:border-box}body{margin:0;min-height:100vh;background-color:#e9e2cf;background-image:radial-gradient(#b6ae9b 0.75px,transparent .75px);background-size:8px 8px;color:var(--ink);font-family:'Space Mono',monospace;padding:22px 20px 32px}
.shell{width:min(1250px,100%);margin:auto}.masthead{display:flex;align-items:center;justify-content:space-between;gap:15px;margin:0 0 14px}.brand{display:flex;align-items:center;gap:14px}.brand-mark{width:48px;height:48px;background:var(--pink);border:3px solid var(--ink);box-shadow:3px 3px var(--ink);display:grid;place-items:center;font-size:25px;transform:rotate(-4deg)}.brand h1{font-family:'Press Start 2P',monospace;font-size:clamp(17px,3vw,28px);letter-spacing:-2px;margin:0;text-shadow:2px 2px 0 var(--lime)}.brand h1 span{color:#e63e63}.subbrand{font-size:10px;letter-spacing:2px;font-weight:bold;margin-top:7px}.top-meta{display:flex;gap:9px;align-items:center}.badge{border:2px solid var(--ink);background:var(--cream);box-shadow:2px 2px var(--ink);padding:8px 10px;font-size:10px;font-weight:bold;white-space:nowrap}.badge.live{background:var(--lime)}.badge.live:before{content:'●';color:#208e65;margin-right:7px;animation:pulse 1.5s infinite}@keyframes pulse{50%{opacity:.35}}
.game-layout{display:grid;grid-template-columns:minmax(0,1fr) 306px;gap:16px;align-items:start}.arena-card,.panel{background:var(--cream);border:3px solid var(--ink);box-shadow:var(--shadow)}.arena-card{position:relative;overflow:hidden}.arena-head{height:50px;background:var(--ink);color:white;display:flex;align-items:center;justify-content:space-between;padding:0 15px;gap:10px}.round-label{font-family:'Press Start 2P';font-size:10px;color:var(--lime)}.turn-label{font-size:11px;font-weight:bold;letter-spacing:.5px}.wind{display:flex;align-items:center;gap:7px;background:#302d49;border:1px solid #5d5876;padding:6px 9px;font-size:10px;color:#fff}.wind-arrow{color:var(--gold);font-size:16px;line-height:10px}.canvas-wrap{position:relative;width:100%;aspect-ratio:16/9;background:#89d9ed}canvas{display:block;width:100%;height:100%;image-rendering:pixelated}.game-overlay{position:absolute;inset:0;display:none;align-items:center;justify-content:center;background:#17142bd9;text-align:center;color:white;padding:20px}.game-overlay.show{display:flex}.result-box{max-width:400px;border:3px solid var(--lime);background:var(--ink);padding:25px;box-shadow:6px 6px #0008}.result-box .medal{font-size:40px;margin-bottom:14px}.result-box h2{font-family:'Press Start 2P';font-size:17px;line-height:1.7;color:var(--lime);margin:0 0 9px}.result-box p{font-size:12px;color:#d1cde0;margin:0 0 18px}
.below-arena{display:flex;justify-content:space-between;align-items:center;padding:9px 13px;border-top:2px solid var(--ink);font-size:10px;gap:8px;color:#514d61}.keys{display:flex;gap:5px;align-items:center}.key{background:white;border:1px solid var(--ink);box-shadow:1px 1px var(--ink);padding:2px 5px;font-weight:bold;color:var(--ink)}
.panel{padding:13px}.player-row{display:grid;grid-template-columns:1fr 1fr;gap:8px;margin-bottom:11px}.player-card{position:relative;padding:9px 10px;border:2px solid var(--ink);background:#fff;min-height:57px}.player-card.active{box-shadow:inset 0 -5px var(--lime)}.player-name{font-family:'Press Start 2P';font-size:8px;display:flex;align-items:center;gap:6px}.dot{display:inline-block;width:11px;height:11px;border:2px solid var(--ink);background:var(--pink)}.player-card:nth-child(2) .dot{background:var(--blue)}.score{font-size:12px;font-weight:bold;margin-top:7px}.panel-title{font-family:'Press Start 2P';font-size:9px;margin:13px 0 12px;display:flex;align-items:center;gap:7px}.panel-title:before{content:'';width:9px;height:9px;background:var(--pink);border:1px solid var(--ink)}.aim-box{background:#f2eddd;border:2px solid var(--ink);padding:11px;margin-bottom:10px}.aim-line{display:flex;justify-content:space-between;align-items:center;margin-bottom:6px}.aim-line label{font-weight:bold;font-size:11px}.readout{font-family:'Press Start 2P';font-size:10px;color:#302d49}.small-note{font-size:9px;color:#777287;margin-top:5px}.range-row{display:flex;align-items:center;gap:7px}.nudge{width:27px;height:27px;border:2px solid var(--ink);background:white;font:bold 16px 'Space Mono';cursor:pointer;box-shadow:2px 2px var(--ink);padding:0}.nudge:active,.fire:active,.secondary:active{transform:translate(2px,2px);box-shadow:none}.range-row input[type=range]{width:100%;accent-color:#e94c6b;height:17px;cursor:pointer}.power-row input[type=range]{accent-color:#15a8cb}.range-labels{display:flex;justify-content:space-between;font-size:8px;color:#827f91;margin-top:4px}.fire{width:100%;border:2px solid var(--ink);box-shadow:3px 3px var(--ink);background:var(--pink);padding:13px;color:var(--ink);font:10px 'Press Start 2P';cursor:pointer;letter-spacing:.2px}.fire:disabled{opacity:.45;cursor:not-allowed}.fire .banana{font-size:16px;margin-right:8px;vertical-align:-2px}.control-hint{text-align:center;font-size:9px;color:#787488;margin:8px 0 12px}.divider{border-top:2px dashed #cbc5b5;margin:11px 0}.settings-toggle{display:flex;width:100%;justify-content:space-between;align-items:center;background:#eae4d3;border:2px solid var(--ink);padding:10px;font:bold 10px 'Space Mono';cursor:pointer}.settings-toggle span:last-child{font-size:15px}.settings{display:none;padding:10px 9px 2px;background:#f5f0e3;border:2px solid var(--ink);border-top:0}.settings.open{display:block}.setting{margin-bottom:10px}.setting label{display:flex;justify-content:space-between;gap:7px;font-size:9px;font-weight:bold;margin-bottom:4px}.setting input[type=number]{width:72px;border:1px solid #777287;background:white;padding:4px;font:10px 'Space Mono';color:var(--ink)}.setting .pair{display:flex;gap:6px;align-items:center}.setting .pair span{font-size:8px;color:#777287}.checkline{display:flex;align-items:center;gap:7px;font-size:9px;margin:7px 0}.checkline input{accent-color:#168c72}.selectline{width:100%;border:1px solid #777287;padding:5px;background:white;font:10px 'Space Mono'}.settings-foot{font-size:8px;line-height:1.5;color:#777287;border-top:1px dashed #c6c0b1;padding-top:7px;margin-top:7px}.bottom-actions{display:flex;gap:8px;margin-top:11px}.secondary{flex:1;background:white;border:2px solid var(--ink);box-shadow:2px 2px var(--ink);padding:8px 4px;font:bold 9px 'Space Mono';cursor:pointer}.footer{display:flex;justify-content:space-between;gap:12px;font-size:9px;color:#625e6e;margin-top:18px}.footer strong{color:var(--ink)}
@media(max-width:850px){body{padding:14px 12px 25px}.game-layout{grid-template-columns:1fr;gap:14px}.panel{display:grid;grid-template-columns:1fr 1fr;gap:0 13px}.player-row,.panel-title,.aim-box,.fire,.control-hint,.divider,.settings-toggle,.settings,.bottom-actions{grid-column:1/-1}.panel-title{margin-top:8px}.settings{grid-column:1/-1}.footer{margin-top:15px}}
@media(max-width:520px){body{padding:10px 8px 22px}.masthead{align-items:flex-start}.brand-mark{width:38px;height:38px;font-size:20px}.subbrand{font-size:8px}.top-meta{flex-direction:column;align-items:flex-end;gap:5px}.badge{font-size:8px;padding:6px}.arena-head{height:43px;padding:0 8px}.round-label{font-size:7px}.turn-label{font-size:9px}.wind{font-size:8px;padding:5px}.below-arena{font-size:8px}.keys{gap:3px}.panel{padding:10px}.player-name{font-size:7px}}
</style>
</head>
<body>
<div class="shell">
<header class="masthead"><div class="brand"><div class="brand-mark">🍌</div><div><h1>GORILLA<span>Z</span></h1><div class="subbrand">BANANA BOMBERS · CITY SHOWDOWN</div></div></div><div class="top-meta"><div class="badge">2 PLAYERS · LOCAL</div><div class="badge live" id="liveBadge">LIVE TUNING ACTIVE</div></div></header>
<main class="game-layout">
<section class="arena-card" aria-label="Game arena">
<div class="arena-head"><div class="round-label">ROUND <span id="roundNum">01</span></div><div class="turn-label" id="turnLabel">PLAYER 1 TURN</div><div class="wind"><span>WIND</span><span class="wind-arrow" id="windArrow">→</span><b id="windText">0</b><span>MPH</span></div></div>
<div class="canvas-wrap"><canvas id="game" width="960" height="540" aria-label="Gorillaz city artillery game"></canvas><div class="game-overlay" id="endOverlay"><div class="result-box"><div class="medal">🏆</div><h2 id="winnerText">PLAYER 1 WINS!</h2><p id="resultLine">That was one bananas blast.</p><button class="fire" id="rematch"><span class="banana">🍌</span>REMATCH</button></div></div></div>
<div class="below-arena"><span id="statusText">Set your angle and power. Make it count!</span><span class="keys"><span class="key">←</span><span class="key">→</span> AIM <span class="key">↑</span><span class="key">↓</span> POWER <span class="key">SPACE</span> FIRE</span></div>
</section>
<aside class="panel">
<div class="player-row"><div class="player-card active" id="card1"><div class="player-name"><i class="dot"></i>PLAYER 1</div><div class="score">WINS <span id="score1">0</span></div></div><div class="player-card" id="card2"><div class="player-name"><i class="dot"></i>PLAYER 2</div><div class="score">WINS <span id="score2">0</span></div></div></div>
<div class="panel-title">AIM & LAUNCH</div>
<div class="aim-box"><div class="aim-line"><label>LAUNCH ANGLE</label><span class="readout"><span id="angleVal">45</span>°</span></div><div class="range-row"><button class="nudge" data-adjust="angle" data-step="-1">−</button><input id="angleSlider" type="range" min="10" max="80" value="45"><button class="nudge" data-adjust="angle" data-step="1">+</button></div><div class="range-labels"><span>LOW ARC</span><span>HIGH ARC</span></div></div>
<div class="aim-box power-row"><div class="aim-line"><label>BANANA POWER</label><span class="readout"><span id="powerVal">68</span><small>%</small></span></div><div class="range-row"><button class="nudge" data-adjust="power" data-step="-2">−</button><input id="powerSlider" type="range" min="1" max="100" value="68"><button class="nudge" data-adjust="power" data-step="2">+</button></div><div class="range-labels"><span>SOFT</span><span>FULL SEND</span></div></div>
<button class="fire" id="fireBtn"><span class="banana">🍌</span>THROW BANANA BOMB</button><div class="control-hint">Aim direction is set by your side of the skyline.</div>
<div class="divider"></div>
<button class="settings-toggle" id="settingsToggle"><span>⚙ LIVE GAME TUNING</span><span id="settingsChevron">+</span></button>
<div class="settings" id="settingsPanel">
<div class="setting"><label for="blastInput">Blast radius <span id="blastEcho">48 px</span></label><input id="blastInput" type="number" min="15" max="120" step="1" value="48"></div>
<div class="setting"><label for="gravityInput">Gravity strength <span>px/s²</span></label><input id="gravityInput" type="number" min="80" max="700" step="10" value="260"></div>
<div class="setting"><label>Wind speed range <span>px/s²</span></label><div class="pair"><span>MIN</span><input id="windMinInput" type="number" min="0" max="180" step="5" value="0"><span>MAX</span><input id="windMaxInput" type="number" min="0" max="180" step="5" value="55"></div></div>
<label class="checkline"><input id="windShiftInput" type="checkbox" checked> Allow wind direction shifts between turns</label>
<div class="setting"><label>Building count <span>8–14</span></label><input id="countInput" type="number" min="8" max="14" step="1" value="11"></div>
<div class="setting"><label>Building height range <span>px</span></label><div class="pair"><span>MIN</span><input id="heightMinInput" type="number" min="75" max="340" step="5" value="130"><span>MAX</span><input id="heightMaxInput" type="number" min="100" max="390" step="5" value="310"></div></div>
<div class="setting"><label>Projectile velocity range <span>px/s</span></label><div class="pair"><span>MIN</span><input id="velocityMinInput" type="number" min="150" max="900" step="10" value="250"><span>MAX</span><input id="velocityMaxInput" type="number" min="250" max="1200" step="10" value="690"></div></div>
<div class="setting"><label for="damageMode">Explosion damage threshold</label><select class="selectline" id="damageMode"><option value="strict">Inside radius = full hit</option><option value="0.1">Falloff · 10% damage threshold</option><option value="0.3">Falloff · 30% damage threshold</option><option value="0.5">Falloff · 50% damage threshold</option></select></div>
<div class="settings-foot">Tuning applies immediately to the next launch. City generation settings apply on rematch. Values persist for this session.</div>
</div>
<div class="bottom-actions"><button class="secondary" id="newMatch">↻ NEW CITY</button><button class="secondary" id="resetSettings">↺ RESET TUNING</button></div>
</aside>
</main>
<footer class="footer"><span><strong>GORILLAZ</strong> // ARTILLERY CLUB · EST. 1998</span><span>TAKE TURNS. BREAK HEARTS. NOT BUILDINGS.</span></footer>
</div>
<script>
(()=>{
const canvas=document.getElementById('game'),ctx=canvas.getContext('2d');
const W=960,H=540,ground=478;
const defaults={blast:48,gravity:260,windMin:0,windMax:55,windShift:true,count:11,heightMin:130,heightMax:310,velocityMin:250,velocityMax:690,damage:'strict'};
let cfg={...defaults},buildings=[],players=[],active=0,wind=0,windDir=1,angle=45,power=68,state='aiming',projectile=null,explosion=null,particles=[],scorch=[],scores=[0,0],round=1,clouds=[],last=0,acc=0,seed=0;
const $=id=>document.getElementById(id);const clamp=(v,a,b)=>Math.max(a,Math.min(b,v));
function random(){seed=(seed*1664525+1013904223)>>>0;return seed/4294967296}
function setStatus(s){$('statusText').textContent=s}
function readConfig(){
const num=(id,old,min,max)=>{let v=Number($(id).value);if(!Number.isFinite(v))v=old;v=clamp(v,min,max);$(id).value=v;return v};
cfg.blast=num('blastInput',cfg.blast,15,120);$('blastEcho').textContent=cfg.blast+' px';cfg.gravity=num('gravityInput',cfg.gravity,80,700);
cfg.windMin=num('windMinInput',cfg.windMin,0,180);cfg.windMax=num('windMaxInput',cfg.windMax,0,180);if(cfg.windMax<cfg.windMin){cfg.windMax=cfg.windMin;$('windMaxInput').value=cfg.windMax}
cfg.windShift=$('windShiftInput').checked;cfg.count=num('countInput',cfg.count,8,14);
cfg.heightMin=num('heightMinInput',cfg.heightMin,75,340);cfg.heightMax=num('heightMaxInput',cfg.heightMax,100,390);if(cfg.heightMax<cfg.heightMin+20){cfg.heightMax=cfg.heightMin+20;$('heightMaxInput').value=cfg.heightMax}
cfg.velocityMin=num('velocityMinInput',cfg.velocityMin,150,900);cfg.velocityMax=num('velocityMaxInput',cfg.velocityMax,250,1200);if(cfg.velocityMax<cfg.velocityMin+20){cfg.velocityMax=cfg.velocityMin+20;$('velocityMaxInput').value=cfg.velocityMax}
cfg.damage=$('damageMode').value;const changed=Object.keys(defaults).some(k=>cfg[k]!==defaults[k]);$('liveBadge').style.background=changed?'#d8f36a':'#fff9e8';$('liveBadge').textContent=changed?'LIVE TUNING ACTIVE':'DEFAULT TUNING';
}
function syncControls(){for(const [key,id] of [['blast','blastInput'],['gravity','gravityInput'],['windMin','windMinInput'],['windMax','windMaxInput'],['count','countInput'],['heightMin','heightMinInput'],['heightMax','heightMaxInput'],['velocityMin','velocityMinInput'],['velocityMax','velocityMaxInput']])$(id).value=defaults[key];$('windShiftInput').checked=defaults.windShift;$('damageMode').value=defaults.damage;readConfig()}
function updateWind(first=false){if(first||cfg.windShift){windDir=random()<.5?-1:1;wind=cfg.windMin+random()*(cfg.windMax-cfg.windMin)}wind=Math.round(wind);$('windText').textContent=wind;$('windArrow').textContent=wind===0?'↔':windDir>0?'→':'←';$('windArrow').style.transform=windDir<0?'':'none';}
function newCity(){seed=(Math.random()*0xffffffff)>>>0;buildings=[];players=[];scorch=[];particles=[];state='aiming';projectile=null;explosion=null;angle=45;power=68;$('angleSlider').value=angle;$('powerSlider').value=power;$('endOverlay').classList.remove('show');const n=cfg.count,gap=5,totalW=900,slot=totalW/n;let x=30;
for(let i=0;i<n;i++){const bw=slot-(5+random()*9);let h=cfg.heightMin+random()*(cfg.heightMax-cfg.heightMin);h=clamp(h,75,ground-95);let palette=['#ef6b72','#eab949','#4cb6a7','#7a8dd1','#e8844a','#9a76bd','#50a5c4','#cf6e9b'];buildings.push({x:x+(slot-bw)/2,y:ground-h,w:bw,h,color:palette[i%palette.length],windows:[]});for(let wx=buildings[i].x+10;wx<buildings[i].x+bw-7;wx+=17)for(let wy=buildings[i].y+19;wy<ground-12;wy+=22)if(random()>.2)buildings[i].windows.push([wx,wy,random()>.55]);x+=slot;}
let left=buildings.filter(b=>b.x+b.w/2< W*.36),right=buildings.filter(b=>b.x+b.w/2>W*.64);let b1=left[Math.floor(random()*left.length)]||buildings[1],b2=right[Math.floor(random()*right.length)]||buildings[n-2];
players=[{x:b1.x+b1.w/2,y:b1.y,color:'#f24968',name:'PLAYER 1',building:b1,alive:true},{x:b2.x+b2.w/2,y:b2.y,color:'#43c9e6',name:'PLAYER 2',building:b2,alive:true}];active=0;updateWind(true);syncHUD();setStatus('Set your angle and power. Make it count!');draw();}
function syncHUD(){ $('turnLabel').textContent=state==='flying'?'BANANA IN FLIGHT':state==='exploding'?'BOOM!':`PLAYER ${active+1} TURN`; $('card1').classList.toggle('active',active===0&&state!=='won');$('card2').classList.toggle('active',active===1&&state!=='won');$('score1').textContent=scores[0];$('score2').textContent=scores[1];$('angleVal').textContent=angle;$('powerVal').textContent=power;$('fireBtn').disabled=state!=='aiming'; }
function fire(){if(state!=='aiming')return;readConfig();const p=players[active],dir=active===0?1:-1,rad=angle*Math.PI/180,speed=cfg.velocityMin+(power/100)*(cfg.velocityMax-cfg.velocityMin);projectile={x:p.x+dir*17,y:p.y-30,vx:dir*Math.cos(rad)*speed,vy:-Math.sin(rad)*speed,trail:[]};state='flying';setStatus(`PLAYER ${active+1} launched at ${angle}° · ${Math.round(speed)} px/s`);syncHUD();beep(520,.06,'square');}
function beep(freq,dur,type='sine'){try{const ac=beep.ac||(beep.ac=new(window.AudioContext||window.webkitAudioContext)()),o=ac.createOscillator(),g=ac.createGain();o.type=type;o.frequency.value=freq;g.gain.value=.035;o.connect(g);g.connect(ac.destination);o.start();g.gain.exponentialRampToValueAtTime(.001,ac.currentTime+dur);o.stop(ac.currentTime+dur)}catch(e){}}
function collide(){const q=projectile;if(!q)return false;const enemy=players[1-active];if(enemy.alive&&Math.hypot(q.x-enemy.x,q.y-(enemy.y-24))<16){detonate(q.x,q.y,true);return true}if(q.y>=ground){detonate(q.x,ground,false);return true}for(const b of buildings){if(q.x>=b.x&&q.x<=b.x+b.w&&q.y>=b.y&&q.y<=ground){detonate(q.x,Math.max(q.y,b.y),false);return true}}if(q.x<0||q.x>W||q.y<0||q.y>H){detonate(clamp(q.x,0,W),clamp(q.y,0,ground),false);return true}return false}
function detonate(x,y,direct){projectile=null;state='exploding';explosion={x,y,t:0,direct};scorch.push({x,y,t:0});for(let i=0;i<25;i++){let a=random()*Math.PI*2,s=35+random()*180;particles.push({x,y,vx:Math.cos(a)*s,vy:Math.sin(a)*s-35,t:0,life:.35+random()*.55,size:2+random()*5,color:['#ffe16b','#ff7749','#f6f0ca'][i%3]})}beep(95,.24,'sawtooth');setStatus('KABOOM! Checking the blast radius…');syncHUD()}
function resolveBlast(){let eliminated=[];for(let i=0;i<2;i++){let p=players[i],dist=Math.hypot(explosion.x-p.x,explosion.y-(p.y-24));let radialDamage=clamp(1-dist/cfg.blast,0,1);let threshold=cfg.damage==='strict'?0:Number(cfg.damage);let hit=(dist<=cfg.blast&&radialDamage>=threshold)||(explosion.direct&&i!==active&&dist<32);if(hit){p.alive=false;eliminated.push(i)}}if(eliminated.length){let winner=eliminated.length===2?-1:1-eliminated[0];state='won';if(winner>=0){scores[winner]++;$('winnerText').textContent=`PLAYER ${winner+1} WINS!`; $('resultLine').textContent=winner===active?'Bullseye! A truly bananas finish.':'The splash got you! Rematch for revenge.';}else{$('winnerText').textContent='DOUBLE K.O.!';$('resultLine').textContent='A blast for the history books. Rematch?'}$('endOverlay').classList.add('show');setStatus('Round over — hit rematch for a fresh city.');syncHUD();beep(740,.3);setTimeout(()=>beep(990,.38),130);return}
active=1-active;angle=45;power=68;$('angleSlider').value=angle;$('powerSlider').value=power;updateWind(false);state='aiming';setStatus(`PLAYER ${active+1}: your turn. Wind has shifted.`);syncHUD();}
function step(dt){if(state==='flying'&&projectile){let q=projectile;q.trail.push({x:q.x,y:q.y});if(q.trail.length>14)q.trail.shift();q.vx+=wind*windDir*dt;q.vy+=cfg.gravity*dt;q.x+=q.vx*dt;q.y+=q.vy*dt;collide()}
if(state==='exploding'&&explosion){explosion.t+=dt;if(explosion.t>.62){resolveBlast();explosion=null}}
for(let i=particles.length-1;i>=0;i--){let p=particles[i];p.t+=dt;p.x+=p.vx*dt;p.y+=p.vy*dt;p.vy+=330*dt;p.vx*=.985;if(p.t>p.life)particles.splice(i,1)}for(const s of scorch)s.t+=dt;}
function drawBackground(){let g=ctx.createLinearGradient(0,0,0,H);g.addColorStop(0,'#77cce8');g.addColorStop(.72,'#c6eddf');g.addColorStop(1,'#f6cf9a');ctx.fillStyle=g;ctx.fillRect(0,0,W,H);
ctx.fillStyle='#fff4c1';ctx.beginPath();ctx.arc(800,100,39,0,Math.PI*2);ctx.fill();ctx.strokeStyle='#fff4c188';ctx.lineWidth=9;ctx.stroke();
// soft clouds
ctx.fillStyle='#ffffff91';for(const c of clouds){ctx.beginPath();ctx.ellipse(c.x,c.y,c.w,13,0,0,Math.PI*2);ctx.ellipse(c.x-16,c.y+3,16,10,0,0,Math.PI*2);ctx.ellipse(c.x+17,c.y+1,19,12,0,0,Math.PI*2);ctx.fill()}
// distant silhouettes
ctx.fillStyle='#91c6c6';for(let i=0;i<13;i++){let x=i*82-20,h=45+(i*37%95);ctx.fillRect(x,ground-h,58,h)}
ctx.fillStyle='#283047';ctx.fillRect(0,ground,W,H-ground);ctx.fillStyle='#e6c871';ctx.fillRect(0,ground,W,5);ctx.fillStyle='#f1ce6d';for(let x=15;x<W;x+=58)ctx.fillRect(x,ground+27,32,3);
}
function drawBuildings(){for(const b of buildings){ctx.fillStyle='#20243b';ctx.fillRect(b.x+4,b.y+5,b.w,b.h);ctx.fillStyle=b.color;ctx.fillRect(b.x,b.y,b.w,b.h);ctx.fillStyle='#ffffff38';ctx.fillRect(b.x,b.y,b.w,5);ctx.fillStyle='#402f46';for(const [wx,wy,on] of b.windows){ctx.fillStyle=on?'#ffe396':'#614d58';ctx.fillRect(wx,wy,7,10)}ctx.fillStyle='#20243b';ctx.fillRect(b.x-2,b.y,b.w+4,5)}
// rooftop details
buildings.forEach((b,i)=>{if(i%3===1){ctx.fillStyle='#454963';ctx.fillRect(b.x+b.w*.45,b.y-14,9,14);ctx.fillRect(b.x+b.w*.45-4,b.y-17,17,4)}})}
function drawGorilla(p,index){if(!p.alive)return;let x=p.x,y=p.y;ctx.save();ctx.translate(Math.round(x),Math.round(y));// feet at roof
ctx.fillStyle='#1d2032';ctx.fillRect(-13,-10,11,9);ctx.fillRect(3,-10,11,9);
ctx.fillStyle=index===0?'#aa3150':'#237d99';ctx.fillRect(-17,-29,34,20);ctx.fillRect(-22,-25,8,18);ctx.fillRect(14,-25,8,18);
ctx.fillStyle='#29263a';ctx.fillRect(-16,-43,32,21);ctx.fillRect(-22,-39,8,12);ctx.fillRect(14,-39,8,12);ctx.fillRect(-18,-48,36,14);
ctx.fillStyle='#f2d5ad';ctx.fillRect(-9,-39,6,5);ctx.fillRect(5,-39,6,5);ctx.fillStyle='#17142b';ctx.fillRect(-7,-38,3,4);ctx.fillRect(7,-38,3,4);ctx.fillStyle='#d99278';ctx.fillRect(-5,-30,10,4);ctx.fillStyle='#fff';ctx.fillRect(-3,-30,6,2);
// bandana
ctx.fillStyle=p.color;ctx.fillRect(-18,-47,36,5);ctx.fillRect(12,-45,8,4);ctx.fillRect(18,-43,6,5);
// tiny banana holster
ctx.fillStyle='#f5d253';ctx.fillRect(index===0?12:-16,-17,5,8);
ctx.restore();}
function drawAim(){if(state!=='aiming')return;let p=players[active],dir=active===0?1:-1,r=angle*Math.PI/180;let sx=p.x+dir*18,sy=p.y-34,ex=sx+dir*Math.cos(r)*95,ey=sy-Math.sin(r)*95;
ctx.save();ctx.setLineDash([5,6]);ctx.strokeStyle='#25233d99';ctx.lineWidth=2;ctx.beginPath();ctx.moveTo(sx,sy);ctx.lineTo(ex,ey);ctx.stroke();ctx.setLineDash([]);ctx.fillStyle='#25233d';ctx.beginPath();ctx.arc(ex,ey,3,0,Math.PI*2);ctx.fill();ctx.restore();}
function draw(){ctx.clearRect(0,0,W,H);drawBackground();drawBuildings();for(const s of scorch){ctx.fillStyle='#34233555';ctx.beginPath();ctx.ellipse(s.x,ground-2,25+Math.min(s.t*4,10),5,0,0,Math.PI*2);ctx.fill()}
drawAim();players.forEach(drawGorilla);
if(projectile){for(let i=0;i<projectile.trail.length;i++){let t=projectile.trail[i],a=(i+1)/projectile.trail.length;ctx.globalAlpha=a*.42;ctx.fillStyle='#fff2ad';ctx.beginPath();ctx.arc(t.x,t.y,2+a*2,0,Math.PI*2);ctx.fill()}ctx.globalAlpha=1;ctx.save();ctx.translate(projectile.x,projectile.y);ctx.rotate(Math.atan2(projectile.vy,projectile.vx)+.25);ctx.fillStyle='#eab938';ctx.beginPath();ctx.ellipse(0,0,11,5,0,0,Math.PI*2);ctx.fill();ctx.fillStyle='#f8df70';ctx.beginPath();ctx.ellipse(-2,-1,7,3,0,0,Math.PI*2);ctx.fill();ctx.fillStyle='#654431';ctx.fillRect(9,-2,3,3);ctx.restore()}
if(explosion){let t=Math.min(explosion.t/.62,1),rad=cfg.blast*(.35+.65*t);ctx.fillStyle=`rgba(255,185,57,${.56*(1-t)+.14})`;ctx.beginPath();ctx.arc(explosion.x,explosion.y,rad,0,Math.PI*2);ctx.fill();ctx.strokeStyle='#fff0a2';ctx.lineWidth=4*(1-t)+1;ctx.beginPath();ctx.arc(explosion.x,explosion.y,rad,0,Math.PI*2);ctx.stroke();ctx.fillStyle=`rgba(244,73,59,${.65*(1-t)})`;ctx.beginPath();ctx.arc(explosion.x,explosion.y,rad*.55,0,Math.PI*2);ctx.fill()}
for(const p of particles){ctx.globalAlpha=Math.max(0,1-p.t/p.life);ctx.fillStyle=p.color;ctx.fillRect(p.x,p.y,p.size,p.size);ctx.globalAlpha=1}
// small top skyline label
ctx.fillStyle='#20243b';ctx.fillRect(13,13,177,23);ctx.fillStyle='#fff4d6';ctx.font='bold 10px Space Mono, monospace';ctx.fillText('DOWNTOWN BANANAVILLE',21,29);
}
function loop(ts){if(!last)last=ts;let dt=Math.min((ts-last)/1000,.05);last=ts;acc+=dt;while(acc>=1/120){step(1/120);acc-=1/120}draw();requestAnimationFrame(loop)}
$('angleSlider').addEventListener('input',e=>{angle=+e.target.value;$('angleVal').textContent=angle;});$('powerSlider').addEventListener('input',e=>{power=+e.target.value;$('powerVal').textContent=power;});document.querySelectorAll('.nudge').forEach(b=>b.addEventListener('click',()=>{if(state!=='aiming')return;let kind=b.dataset.adjust,step=+b.dataset.step;if(kind==='angle'){angle=clamp(angle+step,10,80);$('angleSlider').value=angle;$('angleVal').textContent=angle}else{power=clamp(power+step,1,100);$('powerSlider').value=power;$('powerVal').textContent=power}}));
$('fireBtn').addEventListener('click',fire);$('rematch').addEventListener('click',()=>{round++;$('roundNum').textContent=String(round).padStart(2,'0');newCity()});$('newMatch').addEventListener('click',()=>{round++;$('roundNum').textContent=String(round).padStart(2,'0');newCity()});$('resetSettings').addEventListener('click',syncControls);$('settingsToggle').addEventListener('click',()=>{$('settingsPanel').classList.toggle('open');$('settingsChevron').textContent=$('settingsPanel').classList.contains('open')?'−':'+'});
['blastInput','gravityInput','windMinInput','windMaxInput','windShiftInput','countInput','heightMinInput','heightMaxInput','velocityMinInput','velocityMaxInput','damageMode'].forEach(id=>$(id).addEventListener('change',()=>{readConfig();if(id==='windMinInput'||id==='windMaxInput')updateWind(true)}));
document.addEventListener('keydown',e=>{if(['ArrowLeft','ArrowRight','ArrowUp','ArrowDown',' '].includes(e.key))e.preventDefault();if(e.key===' '||e.key==='Enter'){fire();return}if(state!=='aiming')return;if(e.key==='ArrowLeft'||e.key.toLowerCase()==='a'){angle=clamp(angle-1,10,80)}if(e.key==='ArrowRight'||e.key.toLowerCase()==='d'){angle=clamp(angle+1,10,80)}if(e.key==='ArrowUp'||e.key.toLowerCase()==='w'){power=clamp(power+2,1,100)}if(e.key==='ArrowDown'||e.key.toLowerCase()==='s'){power=clamp(power-2,1,100)}$('angleSlider').value=angle;$('angleVal').textContent=angle;$('powerSlider').value=power;$('powerVal').textContent=power});
readConfig();newCity();requestAnimationFrame(loop);
})();
</script>
</body>
</html>
<!-- agent-meta {"model":"openai/gpt-6-luna","provider":"openrouter","persona":"full","sessionId":"a5160f12-5928-4cee-86fd-1de36702a17a","tokensIn":184051,"tokensOut":13453,"tokensTotal":197504,"cost":0.01096106,"turns":11,"toolCalls":10,"failedToolCalls":0,"timestamp":"2026-09-23T00:30:05.121Z"} -->