← Worms results

Worms

muse spark 1.1 · composer

Initial view of Worms

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 59,878 bytes · SHA-256 6ec886c1cb86
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>CRATER CUP - Artillery Chaos</title>
<style>
/* ── css: main ── */
@import url('https://fonts.googleapis.com/css2?family=Fredoka:wght@400;600;700&family=Space+Grotesk:wght@500;700&display=swap');
*{margin:0;padding:0;box-sizing:border-box}
html,body{width:100%;height:100%;overflow:hidden;background:#0c1220;font-family:'Space Grotesk',sans-serif;user-select:none}
#gameRoot{position:relative;width:100vw;height:100vh;overflow:hidden;background:radial-gradient(120% 120% at 50% 0%, #2a3a6b 0%, #141e3a 45%, #080d1c 100%)}
#canvas{position:absolute;left:0;top:0;display:block;width:100%;height:100%}
#fxFlash{position:absolute;inset:0;pointer-events:none;background:#fff;opacity:0;z-index:50}
#hud{position:absolute;inset:0;pointer-events:none;z-index:20;display:flex;flex-direction:column;justify-content:space-between}
#topBar{display:flex;align-items:flex-start;justify-content:space-between;padding:16px 18px;gap:12px}
#logo{font-family:'Fredoka';font-weight:800;font-size:28px;letter-spacing:-0.02em;color:#fff;text-shadow:0 3px 0 #000, 0 0 20px rgba(120,180,255,.5);line-height:.9}
#logo span{display:block;font-size:11px;letter-spacing:.25em;opacity:.7;font-weight:600;margin-top:4px}
#centerTop{display:flex;align-items:center;gap:14px}
#windBox{pointer-events:auto;display:flex;align-items:center;gap:8px;background:rgba(0,0,0,.45);backdrop-filter:blur(12px);border:1px solid rgba(255,255,255,.15);border-radius:999px;padding:8px 14px;color:#fff;box-shadow:0 8px 24px rgba(0,0,0,.3)}
#windArrow{font-size:18px;transition:transform .4s cubic-bezier(.34,1.56,.64,1)}
#windVal{font-weight:700;min-width:26px;text-align:center}
#windBox i{font-style:normal;font-size:10px;opacity:.6;letter-spacing:.1em;text-transform:uppercase;margin-left:2px}
#turnTimer{position:relative;width:86px;height:46px;background:rgba(0,0,0,.55);border:1px solid rgba(255,255,255,.18);border-radius:14px;overflow:hidden;display:flex;align-items:center;justify-content:center;box-shadow:0 8px 24px rgba(0,0,0,.35)}
#timerFill{position:absolute;left:0;top:0;bottom:0;background:linear-gradient(90deg,#ffde59,#ff8a3d);width:100%;transition:width .2s linear}
#timerText{position:relative;z-index:1;font-family:'Fredoka';font-weight:800;font-size:22px;color:#1a1a1a;text-shadow:0 1px 0 rgba(255,255,255,.6)}
#timerText.panic{animation:panicPulse .25s infinite alternate}
@keyframes panicPulse{from{transform:scale(1)}to{transform:scale(1.25);color:#b40000}}
#teamBars{display:flex;flex-direction:column;gap:6px;max-width:260px}
.teamRow{display:flex;align-items:center;gap:6px;background:rgba(0,0,0,.45);backdrop-filter:blur(10px);border-radius:999px;padding:4px 8px 4px 6px;border:1px solid rgba(255,255,255,.12)}
.teamDot{width:22px;height:22px;border-radius:50%;border:2px solid #fff;box-shadow:0 2px 6px rgba(0,0,0,.5);flex-shrink:0}
.teamHpBar{width:64px;height:6px;background:rgba(255,255,255,.15);border-radius:99px;overflow:hidden}
.teamHpBar>div{height:100%;border-radius:99px;transition:width .4s}
.teamLabel{font-size:10px;color:#fff;font-weight:700;letter-spacing:.06em;min-width:36px}
#bottomBar{pointer-events:auto;display:flex;align-items:flex-end;justify-content:space-between;padding:12px 16px 16px;gap:14px;background:linear-gradient(0deg, rgba(0,0,0,.65) 0%, rgba(0,0,0,0) 100%)}
#weaponWheel{display:flex;gap:8px;flex-wrap:wrap;max-width:520px}
.wpn{width:62px;height:62px;border-radius:16px;background:rgba(20,28,55,.9);border:1.5px solid rgba(255,255,255,.12);backdrop-filter:blur(8px);display:flex;flex-direction:column;align-items:center;justify-content:center;gap:2px;cursor:pointer;transition:.18s cubic-bezier(.34,1.56,.64,1);position:relative;overflow:hidden}
.wpn:hover{transform:translateY(-3px) scale(1.04);border-color:rgba(255,255,255,.35);box-shadow:0 10px 20px rgba(0,0,0,.4)}
.wpn.sel{border-color:#ffde59;box-shadow:0 0 0 2px #ffde59, 0 10px 24px rgba(255,222,89,.35);transform:translateY(-4px) scale(1.06);background:rgba(40,36,20,.95)}
.wpn .ico{font-size:22px;line-height:1;filter:drop-shadow(0 2px 0 rgba(0,0,0,.5))}
.wpn .lbl{font-size:8.5px;font-weight:700;letter-spacing:.06em;color:#fff;opacity:.9;text-transform:uppercase}
.wpn .cat{position:absolute;top:4px;right:5px;font-size:7px;padding:2px 4px;border-radius:99px;font-weight:800;letter-spacing:.05em}
.cat-IND{background:#5ee1ff;color:#002a33}.cat-DIR{background:#ff7ac1;color:#3a0020}.cat-AOE{background:#ff5b4a;color:#fff}.cat-UTL{background:#a7f3a7;color:#0f2a0f}
#activeInfo{flex:1;max-width:360px;background:rgba(12,18,35,.78);border:1px solid rgba(255,255,255,.14);backdrop-filter:blur(16px);border-radius:20px;padding:12px 14px;box-shadow:0 12px 32px rgba(0,0,0,.45)}
#activeName{font-family:'Fredoka';font-weight:700;font-size:15px;color:#fff;letter-spacing:.02em;margin-bottom:6px}
#healthBig{position:relative;height:22px;background:rgba(0,0,0,.5);border-radius:999px;overflow:hidden;border:1px solid rgba(255,255,255,.12)}
#healthFill{position:absolute;left:0;top:0;bottom:0;width:100%;background:linear-gradient(90deg,#5dff8e,#2ecb6f);transition:width .35s cubic-bezier(.34,1.56,.64,1)}
#healthText{position:relative;z-index:1;display:flex;height:100%;align-items:center;justify-content:center;font-weight:800;font-size:13px;color:#fff;text-shadow:0 1px 2px rgba(0,0,0,.8)}
#powerBar{margin-top:8px;height:8px;background:rgba(255,255,255,.12);border-radius:99px;overflow:hidden;opacity:.3;transition:.2s}
#powerBar.charging{opacity:1}
#powerFill{height:100%;width:0%;background:linear-gradient(90deg,#ffe87a,#ff4d3a);box-shadow:0 0 14px #ff6a3a}
#controlsHint{margin-top:8px;font-size:10px;opacity:.55;color:#fff;letter-spacing:.02em}
#controlsHint b{color:#ffde59}
#minimapWrap{width:180px;height:90px;border-radius:14px;overflow:hidden;border:1.5px solid rgba(255,255,255,.18);background:rgba(0,0,0,.5);box-shadow:0 8px 24px rgba(0,0,0,.4);position:relative}
#minimap{display:block;width:100%;height:100%}
#aimReadout{position:absolute;left:50%;top:38%;transform:translate(-50%,-50%);background:rgba(0,0,0,.55);border:1px solid rgba(255,255,255,.15);backdrop-filter:blur(10px);color:#fff;padding:6px 12px;border-radius:999px;font-size:12px;font-weight:700;display:flex;gap:12px;opacity:0;transition:.2s}
#aimReadout.show{opacity:1}
#turnBanner{position:absolute;left:50%;top:42%;transform:translate(-50%,-50%) scale(.8);pointer-events:none;font-family:'Fredoka';font-weight:800;font-size:54px;color:#fff;text-align:center;text-shadow:0 4px 0 #000, 0 0 30px rgba(255,255,255,.5);opacity:0;transition:.4s cubic-bezier(.34,1.56,.64,1)}
#turnBanner.show{opacity:1;transform:translate(-50%,-50%) scale(1)}
#menuOverlay{position:absolute;inset:0;z-index:40;background:radial-gradient(80% 80% at 50% 20%, rgba(80,110,200,.35), rgba(5,10,20,.92));backdrop-filter:blur(18px);display:flex;align-items:center;justify-content:center;transition:.45s}
#menuOverlay.hide{opacity:0;pointer-events:none}
#menuCard{width:min(520px,92vw);background:linear-gradient(180deg, rgba(255,255,255,.08), rgba(255,255,255,.02));border:1px solid rgba(255,255,255,.18);border-radius:28px;padding:26px 22px 18px;box-shadow:0 30px 80px rgba(0,0,0,.6), inset 0 1px 0 rgba(255,255,255,.2);text-align:center}
#menuCard h1{font-family:'Fredoka';font-weight:800;font-size:46px;letter-spacing:-.02em;color:#fff;text-shadow:0 4px 0 rgba(0,0,0,.6);line-height:.9}
#menuCard .sub{margin-top:6px;letter-spacing:.22em;font-size:11px;color:#a8c0ff;font-weight:700}
.menuGrid{margin-top:18px;display:flex;flex-direction:column;gap:14px;text-align:left}
.field label{font-size:10px;letter-spacing:.18em;text-transform:uppercase;color:rgba(255,255,255,.55);font-weight:700;display:block;margin-bottom:6px}
.opts{display:flex;gap:6px;flex-wrap:wrap}
.opts button{pointer-events:auto;cursor:pointer;border:1.5px solid rgba(255,255,255,.15);background:rgba(0,0,0,.35);color:#d6e2ff;padding:8px 12px;border-radius:999px;font-weight:700;font-size:13px;transition:.18s}
.opts button.sel{background:#ffde59;border-color:#ffde59;color:#1a1500;box-shadow:0 6px 18px rgba(255,222,89,.35);transform:translateY(-1px)}
#playBtn{pointer-events:auto;margin-top:18px;width:100%;padding:14px 18px;border-radius:16px;border:0;background:linear-gradient(180deg,#ffeb8a,#ffb02e);font-family:'Fredoka';font-weight:800;font-size:18px;letter-spacing:.04em;color:#2a1e00;cursor:pointer;box-shadow:0 12px 0 #b07000, 0 20px 40px rgba(255,176,46,.45);transition:.12s}
#playBtn:active{transform:translateY(6px);box-shadow:0 6px 0 #b07000, 0 10px 20px rgba(255,176,46,.4)}
#menuCard .tips{margin-top:16px;font-size:10.5px;opacity:.5;color:#fff;line-height:1.5}
#weaponToast{position:absolute;left:50%;top:22%;transform:translateX(-50%) translateY(10px);pointer-events:none;z-index:30;background:rgba(0,0,0,.6);border:1px solid rgba(255,255,255,.18);backdrop-filter:blur(10px);color:#fff;padding:10px 16px;border-radius:999px;font-weight:700;font-size:13px;opacity:0;transition:.25s}
#weaponToast.show{opacity:1;transform:translateX(-50%) translateY(0)}
@media(max-width:800px){#bottomBar{flex-wrap:wrap}#minimapWrap{display:none}#teamBars{max-width:140px}.wpn{width:48px;height:48px}#logo{font-size:20px}}
</style>
</head>
<body>
<div id="gameRoot">
  <canvas id="canvas"></canvas>
  <div id="fxFlash"></div>
  <div id="hud">
    <div id="topBar">
      <div id="logo">CRATER CUP<span>TACTICAL MAYHEM</span></div>
      <div id="centerTop">
        <div id="windBox"><div id="windArrow">➤</div><span id="windVal">0</span><i>Wind</i></div>
        <div id="turnTimer"><div id="timerFill"></div><span id="timerText">30</span></div>
      </div>
      <div id="teamBars"></div>
    </div>
    <div id="bottomBar">
      <div id="weaponWheel"></div>
      <div id="activeInfo">
        <div id="activeName">Blue Worm 1</div>
        <div id="healthBig"><div id="healthFill"></div><span id="healthText">100</span></div>
        <div id="powerBar"><div id="powerFill"></div></div>
        <div id="controlsHint"><b>A/D</b> Move <b>W</b> Jump <b>Mouse</b> Aim <b>HOLD SPACE</b> Fire • <b>TAB</b> Weapon</div>
      </div>
      <div id="minimapWrap"><canvas id="minimap" width="180" height="90"></canvas></div>
    </div>
    <div id="aimReadout"><span id="angleTxt">45°</span> <span id="powerTxt">0%</span></div>
    <div id="turnBanner"></div>
  </div>
  <div id="menuOverlay">
    <div id="menuCard">
      <h1>CRATER CUP</h1>
      <p class="sub">Toss. Blast. Drown. Repeat.</p>
      <div class="menuGrid">
        <div class="field"><label>Map</label><div class="opts" id="mapOpts"><button data-v="island" class="sel">Atoll</button><button data-v="dual">Twin Peaks</button><button data-v="cave">Gloom Cave</button></div></div>
        <div class="field"><label>Teams</label><div class="opts" id="teamOpts"><button data-v="2" class="sel">2</button><button data-v="3">3</button><button data-v="4">4</button></div></div>
        <div class="field"><label>Enemies</label><div class="opts" id="cpuOpts"><button data-v="0" class="sel">Humans Only</button><button data-v="1">1 CPU</button><button data-v="2">2 CPU</button><button data-v="3">3 CPU</button></div></div>
      </div>
      <button id="playBtn">DROP IN →</button>
      <div class="tips">• Wind bends shots • Water drowns • Terrain shatters into falling rocks</div>
    </div>
  </div>
  <div id="weaponToast"></div>
</div>
<script>
'use strict';

// ── const: CONFIG ──
const CONFIG = {
  WORLD_W: 2400,
  WORLD_H: 900,
  WATER_Y: 760,
  GRAVITY: 0.28,
  FRICTION: 0.82,
  TURN_TIME: 32,
  FALL_DMG_THRESH: 9,
  TEAMS_COLORS: [
    { id:0, name:'AQUA', col:'#5ee1ff', dark:'#0a4a5a' },
    { id:1, name:'BLUSH', col:'#ff7ab7', dark:'#5a102e' },
    { id:2, name:'LIME', col:'#a8ff60', dark:'#244a0a' },
    { id:3, name:'SUN', col:'#ffde59', dark:'#5a4100' },
  ]
};

// ── const: WEAPONS ──
const WEAPONS = [
  { key:'bazooka', name:'Bazooka', ico:'🚀', cat:'IND', dmg:42, rad:80, power:14, desc:'Classic parabolic rocket. Wind sensitive.', type:'projectile', grav:0.28, bounce:0.2 },
  { key:'grenade', name:'Grenade', ico:'🍍', cat:'IND', dmg:55, rad:92, power:12, desc:'Bounces, 3s fuse. Nasty in holes.', type:'grenade' },
  { key:'cluster', name:'Cluster', ico:'✨', cat:'IND', dmg:18, rad:55, power:13, desc:'Splits into 5 bomblets mid-air.', type:'cluster' },
  { key:'shotgun', name:'Boomstick', ico:'💥', cat:'DIR', dmg:22, rad:8, power:0, desc:'2 instant pellets, point and shoot.', type:'direct', shots:2 },
  { key:'rail', name:'Railgun', ico:'⚡', cat:'DIR', dmg:70, rad:10, power:0, desc:'Piercing laser. No drop.', type:'hitscan' },
  { key:'bertha', name:'Big Bertha', ico:'☄️', cat:'AOE', dmg:90, rad:150, power:11, desc:'Map-warping nuke. Screen shakes.', type:'projectile', grav:0.32, big:true },
  { key:'meteor', name:'Meteor', ico:'🌠', cat:'AOE', dmg:35, rad:75, power:0, desc:'Calls 7 meteors from sky.', type:'airstrike', count:7 },
];

// ── const: UTILITIES ──
const UTILITIES = [
  { key:'jetpack', name:'Jetpack', ico:'🎒', cat:'UTL', fuel:200, desc:'Hold W to fly.' },
  { key:'tele', name:'Tele', ico:'🌀', cat:'UTL', desc:'Click anywhere to teleport.' },
];

// ── function: rand ──
function rand(a=1,b=0){ if(b===0){b=a;a=0} return a+Math.random()*(b-a); }

// ── function: lerp ──
function lerp(a,b,t){return a+(b-a)*t}

// ── function: clamp ──
function clamp(v,a,b){return Math.max(a,Math.min(b,v))}

// ── function: dist ──
function dist(ax,ay,bx,by){return Math.hypot(ax-bx,ay-by)}

// ── function: easeOutCubic ──
function easeOutCubic(x){return 1-Math.pow(1-x,3)}

// ── function: smoothstep ──
function smoothstep(e0,e1,x){x=clamp((x-e0)/(e1-e0),0,1);return x*x*(3-2*x)}

// ── let: _seed ──
let _seed=12345;

// ── function: seedR ──
function seedR(){ _seed=( _seed*9301+49297)%233280; return _seed/233280; }

// ── function: noise1D ──
function noise1D(x){ return Math.sin(x*0.017)*0.6 + Math.sin(x*0.05)*0.3 + Math.sin(x*0.12)*0.1 }

// ── class: AudioEngine ──
class AudioEngine{
  constructor(){ this.ctx=null; this.enabled=true; this.master=0.5 }
  init(){ if(this.ctx) return; this.ctx=new (window.AudioContext||window.webkitAudioContext)(); }
  _tone(freq,type,dur,vol=0.3,slideTo){
    if(!this.ctx) return; const o=this.ctx.createOscillator(), g=this.ctx.createGain(), f=this.ctx.createBiquadFilter();
    o.type=type||'sine'; o.frequency.value=freq;
    if(slideTo){ o.frequency.linearRampToValueAtTime(slideTo,this.ctx.currentTime+dur); }
    g.gain.setValueAtTime(vol,this.ctx.currentTime); g.gain.exponentialRampToValueAtTime(0.0001,this.ctx.currentTime+dur);
    f.type='lowpass'; f.frequency.value=2000; f.Q.value=1;
    o.connect(f); f.connect(g); g.connect(this.ctx.destination); o.start(); o.stop(this.ctx.currentTime+dur);
  }
  _noise(dur,vol,filterFreq){
    if(!this.ctx) return; const bufferSize=this.ctx.sampleRate*dur;
    const buffer=this.ctx.createBuffer(1,bufferSize,this.ctx.sampleRate); const data=buffer.getChannelData(0);
    for(let i=0;i<bufferSize;i++) data[i]=(Math.random()*2-1)*Math.pow(1-i/bufferSize,0.7);
    const src=this.ctx.createBufferSource(); src.buffer=buffer;
    const g=this.ctx.createGain(); g.gain.value=vol;
    const f=this.ctx.createBiquadFilter(); f.type='bandpass'; f.frequency.value=filterFreq||800;
    src.connect(f); f.connect(g); g.connect(this.ctx.destination); src.start();
  }
  play(name,opt={}){
    try{ this.init(); }catch{return}
    if(this.ctx.state==='suspended') this.ctx.resume();
    const t=this.ctx.currentTime;
    switch(name){
      case 'jump': this._tone(320,'sine',0.18,0.35,650); setTimeout(()=>this._tone(650,'sine',0.15,0.25,900),60); break;
      case 'walk': this._tone(rand(180,260),'square',0.08,0.08); break;
      case 'charge': this._tone(180+opt.p*6,'triangle',0.08,0.15); break;
      case 'shoot': this._tone(900,'sawtooth',0.12,0.25,150); this._noise(0.2,0.25,1200); break;
      case 'shotgun': this._noise(0.15,0.5,2000); this._tone(200,'square',0.1,0.4,60); break;
      case 'rail': this._tone(1200,'sawtooth',0.35,0.35,40); this._tone(100,'sine',0.5,0.2); break;
      case 'bounce': this._tone(300,'sine',0.1,0.2,150); break;
      case 'tick': this._tone(opt.fast?900:500,'sine',0.08,opt.fast?0.25:0.12); break;
      case 'turn': this._tone(500,'triangle',0.4,0.3,800); break;
      case 'hurt': this._tone(440,'square',0.15,0.25,220); break;
      case 'die': this._tone(600,'sawtooth',0.6,0.3,80); this._tone(200,'sine',0.6,0.2,40); break;
      case 'splash': this._noise(0.4,0.35,600); this._tone(300,'sine',0.3,0.2,100); break;
      case 'explode_small': this._noise(0.35,0.55,900); this._tone(180,'triangle',0.4,0.4,40); break;
      case 'explode_big': this._noise(0.8,0.85,700); this._tone(120,'sine',0.9,0.6,25); if(navigator.vibrate) navigator.vibrate([40,20,120]); break;
      case 'click': this._tone(700,'sine',0.08,0.15,1000); break;
      case 'select': this._tone(600,'triangle',0.12,0.22,900); break;
      case 'drown': this._tone(300,'sine',0.5,0.25,100); this._noise(0.5,0.2,400); break;
      case 'jet': this._tone(300+Math.random()*100,'sawtooth',0.12,0.07); break;
    }
  }
}

// ── const: SFX ──
const SFX = new AudioEngine();

// ── class: Particle ──
class Particle{
  constructor(x,y,vx,vy,life,col,size,type){
    this.x=x; this.y=y; this.vx=vx; this.vy=vy; this.life=life; this.max=life; this.col=col; this.size=size||3; this.type=type||'dust'; this.alpha=1;
  }
  update(wind, terrain){
    this.vy+=CONFIG.GRAVITY* (this.type==='debris'?1:0.35);
    this.vx+=wind*0.02; this.vx*=0.995; this.x+=this.vx; this.y+=this.vy;
    if(this.type==='dust' || this.type==='smoke'){ this.vx*=0.98; this.vy*=0.98; this.size*=1.01 }
    if(terrain && terrain.isSolid(this.x,this.y) && this.type!=='spark'){ this.vy*=-0.3; this.vx*=0.6; this.y-=1.5; if(Math.abs(this.vy)<0.4) this.life*=0.6 }
    this.life--; this.alpha=this.life/this.max;
  }
  draw(c){ 
    c.save(); c.globalAlpha=Math.max(0,this.alpha);
    if(this.type==='smoke'){ c.fillStyle=this.col; c.beginPath(); c.arc(this.x,this.y,this.size,0,Math.PI*2); c.fill(); }
    else if(this.type==='spark'){ c.fillStyle=this.col; c.fillRect(this.x,this.y,this.size,this.size); c.shadowBlur=8; c.shadowColor=this.col; c.fillRect(this.x,this.y,this.size,this.size); }
    else { c.fillStyle=this.col; c.beginPath(); c.arc(this.x,this.y,this.size,0,Math.PI*2); c.fill(); }
    c.restore();
  }
}

// ── class: FloatingText ──
class FloatingText{
  constructor(x,y,text,col){ this.x=x; this.y=y; this.tx=text; this.col=col; this.life=60; this.vy=-1.8; }
  update(){ this.y+=this.vy; this.vy*=0.96; this.life--; }
  draw(c){ c.save(); c.globalAlpha=this.life/60; c.font='900 20px Fredoka'; c.fillStyle=this.col; c.strokeStyle='#000'; c.lineWidth=4; c.strokeText(this.tx,this.x,this.y); c.fillText(this.tx,this.x,this.y); c.restore(); }
}

// ── class: Debris ──
class Debris{
  constructor(x,y,sz){
    this.x=x; this.y=y; this.vx=rand(-6,6); this.vy=rand(-8,-1); this.w=sz; this.h=sz; this.rot=rand(Math.PI*2); this.vr=rand(-0.3,0.3); this.life=400; this.settled=false;
    this.col=`hsl(${rand(25,38)}, ${rand(20,35)}%, ${rand(25,45)}%)`;
  }
  update(terrain){
    if(this.settled){ this.life--; return; }
    this.vy+=CONFIG.GRAVITY*0.9; this.x+=this.vx; this.y+=this.vy; this.rot+=this.vr; this.vx*=0.995;
    if(terrain.isSolid(this.x+this.w/2, this.y+this.h)){
      this.y-=this.vy; this.vy*=-0.35; this.vx*=0.6; 
      if(Math.abs(this.vy)<0.5){ this.settled=true; this.vy=0; this.vr*=0.1; }
    }
    if(this.y>CONFIG.WATER_Y-4){ this.settled=true; }
    this.life--;
  }
  draw(c){
    c.save(); c.translate(this.x,this.y); c.rotate(this.rot); c.fillStyle=this.col; c.fillRect(0,0,this.w,this.h); c.strokeStyle='rgba(0,0,0,.3)'; c.lineWidth=1; c.strokeRect(0,0,this.w,this.h); c.restore();
  }
}

// ── class: Terrain ──
class Terrain{
  constructor(w,h){
    this.w=w; this.h=h;
    this.canvas=document.createElement('canvas'); this.canvas.width=w; this.canvas.height=h;
    this.ctx=this.canvas.getContext('2d',{willReadFrequently:true});
    this.waterY=CONFIG.WATER_Y;
    this.type='island';
    this.solid=null;
  }
  generate(type){
    this.type=type; const ctx=this.ctx; ctx.clearRect(0,0,this.w,this.h);
    ctx.fillStyle='#b79d6e';
    _seed=Math.random()*10000;
    if(type==='island'){
      ctx.fillStyle='#7fae5c';
      ctx.beginPath();
      let base=560;
      for(let x=0;x<this.w;x++){
        let fall=Math.sin(Math.PI* clamp((x/this.w),0,1))*210;
        let n=noise1D(x)*70 + Math.sin(x*0.01+_seed)*30;
        let y=base - fall + n;
        if(x===0) ctx.moveTo(x,y); else ctx.lineTo(x,y);
      }
      ctx.lineTo(this.w,this.h); ctx.lineTo(0,this.h); ctx.closePath(); ctx.fill();
    } else if(type==='dual'){
      ctx.fillStyle='#7fae5c';
      ctx.beginPath();
      for(let x=0;x<this.w;x++){
        let left = Math.exp(-Math.pow((x- this.w*0.28)/ (this.w*0.18),2))*240;
        let right= Math.exp(-Math.pow((x- this.w*0.75)/ (this.w*0.18),2))*240;
        let n=noise1D(x*1.5)*50;
        let y=620 - left - right + n;
        if(x===0) ctx.moveTo(x,y); else ctx.lineTo(x,y);
      }
      ctx.lineTo(this.w,this.h); ctx.lineTo(0,this.h); ctx.closePath(); ctx.fill();
    } else if(type==='cave'){
      ctx.fillStyle='#6d5a3c';
      ctx.fillRect(0,0,this.w,this.h);
      ctx.globalCompositeOperation='destination-out';
      for(let k=0;k<14;k++){
        let cx= this.w*0.15 + (k/14)*this.w*0.7 + rand(-120,120);
        let cy= 260 + Math.sin(k*0.8)*90 + rand(-40,40);
        let rx= 260 + rand(-80,80), ry= 190 + rand(-40,40);
        ctx.beginPath(); ctx.ellipse(cx,cy,rx,ry, rand(-0.4,0.4),0,Math.PI*2); ctx.fill();
      }
      for(let i=0;i<8;i++){
        let x=rand(0,this.w), y=rand(200,520), r=rand(45,90);
        ctx.beginPath(); ctx.arc(x,y,r,0,Math.PI*2); ctx.fill();
      }
      ctx.globalCompositeOperation='source-over';
      ctx.fillStyle='#6d5a3c';
      ctx.fillRect(0,0,this.w,72);
      ctx.fillRect(0,this.h-80,this.w,80);
      for(let i=0;i<6;i++){ let x=rand(100,this.w-100); ctx.fillRect(x,260, rand(38,70), 300); }
    }
    let imgData=ctx.getImageData(0,0,this.w,this.h);
    this.solid=imgData.data;
    ctx.fillStyle='rgba(0,0,0,.12)';
    for(let i=0;i<800;i++){ let x=Math.random()*this.w, y=Math.random()*this.h; ctx.fillRect(x,y,2,2); }
    let imgData2=ctx.getImageData(0,0,this.w,this.h);
    this.solid=imgData2.data;
    this._addTopGrass();
  }
  _addTopGrass(){
    const ctx=this.ctx;
    ctx.save();
    ctx.fillStyle='#89d160';
    let image=this.ctx.getImageData(0,0,this.w,this.h);
    let data=image.data;
    for(let x=0;x<this.w;x+=3){
      for(let y=1;y<this.h;y++){
        let idx=(y*this.w + x)*4 +3; let above=( (y-1)*this.w + x)*4+3;
        if(data[idx]>100 && data[above]<50){ ctx.fillRect(x, y-1, 3, 5); break; }
      }
    }
    ctx.restore();
    this.solid=this.ctx.getImageData(0,0,this.w,this.h).data;
  }
  isSolidCanvas(x,y){ 
    if(!this.solid) return false;
    if(x<0||x>=this.w||y<0||y>=this.h) return true; 
    let idx=( (Math.floor(y))*this.w + Math.floor(x))*4+3; 
    if(idx<0||idx>=this.solid.length) return false;
    return this.solid[idx]>50; 
  }
  isSolid(x,y){
    if(x<0||x>=this.w) return true;
    if(y<0) return false;
    if(y>=this.h) return true;
    if(!this.solid) return false;
    let idx=( (Math.floor(y))*this.w + Math.floor(x))*4+3;
    if(idx<0||idx>=this.solid.length) return false;
    return this.solid[idx]>40;
  }
  destroyCircle(x,y,r){
    const ctx=this.ctx;
    ctx.save(); ctx.globalCompositeOperation='destination-out';
    ctx.beginPath(); ctx.arc(x,y,r,0,Math.PI*2); ctx.fill();
    for(let i=0;i<6;i++){ let ang=rand(Math.PI*2); let rr=r*rand(0.5,1.1); ctx.beginPath(); ctx.arc(x+Math.cos(ang)*r*0.8, y+Math.sin(ang)*r*0.8, rr*0.35,0,Math.PI*2); ctx.fill(); }
    ctx.restore();
    let minX=Math.max(0, Math.floor(x-r-4)), maxX=Math.min(this.w, Math.ceil(x+r+4));
    let minY=Math.max(0, Math.floor(y-r-4)), maxY=Math.min(this.h, Math.ceil(y+r+4));
    let w=maxX-minX, h=maxY-minY;
    if(w<=0||h<=0) return;
    let img=ctx.getImageData(minX,minY,w,h);
    if(!this.solid) return;
    for(let yy=0; yy<h; yy++){
      for(let xx=0; xx<w; xx++){
        let sIdx=((minY+yy)*this.w + (minX+xx))*4+3;
        let lIdx=(yy*w+xx)*4+3;
        if(sIdx>=0 && sIdx<this.solid.length) this.solid[sIdx]=img.data[lIdx];
      }
    }
  }
  findGround(x){
    if(!this.solid) return this.h-10;
    for(let y=0;y<this.h;y++){
      if(this.isSolid(x,y)) return y;
    }
    return this.h-10;
  }
  findSpawn(){
    let attempts=0;
    while(attempts<600){
      let x=rand(100,this.w-100);
      let gy=0;
      for(let y=0;y<this.h;y++){ if(this.isSolid(x,y)){ gy=y; break; } if(y===this.h-1) gy=this.h-5; }
      if(gy>this.waterY-30) { attempts++; continue; }
      if(gy<60) { attempts++; continue; }
      if(this.isSolid(x,gy-28)) { attempts++; continue; }
      let flat=true;
      for(let dx=-12;dx<=12;dx++){ if(this.isSolid(x+dx, gy-12)) flat=false; }
      if(!flat){ attempts++; continue; }
      return {x, y:gy-22};
    }
    return {x:rand(200,this.w-200), y:120};
  }
}

// ── class: Worm ──
class Worm{
  constructor(team, idx, x, y){
    this.team=team; this.idx=idx; this.x=x; this.y=y; this.vx=0; this.vy=0;
    this.w=18; this.h=22; this.health=100; this.onGround=false; this.fallStart=0; this.dir=1;
    this.isActive=false; this.moveFuel=100; this.drownT=0; this.alive=true; this.anim=0;
    this.color=CONFIG.TEAMS_COLORS[team].col;
  }
  takeDamage(d, fromX){
    if(!this.alive) return 0;
    this.health-=d; SFX.play('hurt');
    this.vx+= (this.x - fromX)*0.18;
    this.vy+=-Math.abs(d)*0.18;
    if(this.health<=0){ this.health=0; this.alive=false; SFX.play('die'); }
    return d;
  }
  update(terrain, wind, inputs){
    if(!this.alive) { 
      if(this.y<CONFIG.WATER_Y+120){ this.vy+=CONFIG.GRAVITY*0.9; this.y+=this.vy; this.vx*=0.98; this.x+=this.vx; if(terrain.isSolid(this.x,this.y+8)){ this.vy*= -0.2; this.vx*=0.6 } }
      return;
    }
    this.anim+=0.18;
    // water
    if(this.y>terrain.waterY-10){
      this.drownT+=0.02;
      this.vy+= -0.15 + Math.sin(this.anim)*0.05; // float
      this.vx+= wind*0.04;
      this.vx*=0.93; this.vy*=0.88;
      this.x+=this.vx; this.y+=this.vy;
      if(this.drownT>1.2){ this.takeDamage(1,null); this.drownT=0.8; SFX.play('drown'); }
      return;
    } else { this.drownT=Math.max(0,this.drownT-0.03); }

    // controls if active
    if(this.isActive && inputs){
      if(inputs.left){ this.vx-=0.42; this.dir=-1; this.moveFuel-=0.4; if(this.onGround && Math.floor(this.anim)%10===0) SFX.play('walk'); }
      if(inputs.right){ this.vx+=0.42; this.dir=1; this.moveFuel-=0.4; if(this.onGround && Math.floor(this.anim)%10===0) SFX.play('walk'); }
      if(inputs.jump && this.onGround){ this.vy=-6.8; this.onGround=false; SFX.play('jump'); }
    }

    // physics
    let wasOnGround=this.onGround;
    this.vy+=CONFIG.GRAVITY; this.vx+=wind*0.008;
    let nextX=this.x+this.vx, nextY=this.y+this.vy;
    // horizontal collision
    if(terrain.isSolid(nextX, this.y)){ 
      this.vx*= -0.2; nextX=this.x;
    } else { this.x=nextX; }

    // vertical
    if(terrain.isSolid(this.x, nextY+10)){
      if(!wasOnGround){
        let fallDist=this.y - this.fallStart;
        if(fallDist>90){ let dmg=Math.floor((fallDist-90)*0.45); if(dmg>0) this.takeDamage(dmg, this.x); }
      }
      // snap to ground
      let gy=this.y;
      for(let k=0;k<12;k++){ if(!terrain.isSolid(this.x, gy+10)){ break;} gy-=1; }
      // try find ground below
      for(let k=0;k<22;k++){ if(terrain.isSolid(this.x, gy+k+10)){ this.y=gy; break; } gy+=1; if(k>20) gy=this.y; }
      this.vy=0; this.onGround=true;
      this.vx*=CONFIG.FRICTION;
    } else {
      this.y=nextY; this.onGround=false;
      if(this.vy>0 && this.fallStart===0) this.fallStart=this.y;
    }
    if(this.onGround) this.fallStart=this.y;

    if(!this.onGround && this.vy>0){ this.fallStart=Math.min(this.fallStart, this.y); }

    this.x=clamp(this.x, 20, terrain.w-20);
    // push other worms
    // bounds
    if(this.y>CONFIG.WORLD_H+200){ this.takeDamage(100,this.x); }
  }
  draw(c, cam){
    if(!this.alive && this.y>CONFIG.WATER_Y+100) return;
    let sx=(this.x - cam.x)*cam.zoom + cam.w/2;
    let sy=(this.y - cam.y)*cam.zoom + cam.h/2;
    c.save(); c.translate(sx,sy);
    if(this.dir===-1) c.scale(-1,1);
    c.scale(cam.zoom*0.7, cam.zoom*0.7);
    // shadow
    c.fillStyle='rgba(0,0,0,.25)'; c.beginPath(); c.ellipse(0,18,16,6,0,0,Math.PI*2); c.fill();
    // body
    c.fillStyle=this.color;
    // wobbly
    let wob = this.onGround? Math.sin(this.anim*0.6)*1.2 : 0;
    // torso blob
    c.beginPath();
    c.moveTo(-10, -6+wob); c.bezierCurveTo(-12,-18, 12,-18, 10,-6); c.bezierCurveTo(13,10, -13,10, -10,-6); c.fill();
    c.strokeStyle='rgba(0,0,0,.35)'; c.lineWidth=1.8; c.stroke();
    // eyes
    c.fillStyle='#fff'; c.beginPath(); c.arc(-4,-12,5.5,0,Math.PI*2); c.arc(6,-12,5.5,0,Math.PI*2); c.fill();
    c.fillStyle='#111'; c.beginPath(); let lx= this.isActive?2:0, ly= this.isActive?1:0; c.arc(-4+lx,-11+ly,2.2,0,Math.PI*2); c.arc(6+lx,-11+ly,2.2,0,Math.PI*2); c.fill();
    if(this.health<35){ c.fillStyle='#000'; c.beginPath(); c.moveTo(-6,-9); c.lineTo(-2,-13); c.stroke(); c.beginPath(); c.moveTo(4,-13); c.lineTo(8,-9); c.stroke(); }
    // dead X
    if(!this.alive){ c.strokeStyle='#000'; c.lineWidth=2; c.beginPath(); c.moveTo(-6,-14); c.lineTo(-2,-10); c.moveTo(-2,-14); c.lineTo(-6,-10); c.moveTo(4,-14); c.lineTo(8,-10); c.moveTo(8,-14); c.lineTo(4,-10); c.stroke(); }
    // active arrow
    if(this.isActive){ c.save(); c.translate(0,-32); let bob=Math.sin(this.anim*0.25)*3; c.translate(0,bob); c.fillStyle='#ffde59'; c.strokeStyle='#000'; c.lineWidth=1.5; c.beginPath(); c.moveTo(0,10); c.lineTo(-8,0); c.lineTo(8,0); c.closePath(); c.fill(); c.stroke(); c.restore(); }
    c.restore();
    // health bar
    if(this.alive){ c.save(); c.translate(sx, sy-26*cam.zoom); c.fillStyle='rgba(0,0,0,.55)'; c.fillRect(-18,-4,36,7); c.fillStyle=this.health>60?'#5dff8e':this.health>30?'#ffde59':'#ff5b4a'; c.fillRect(-17,-3, 34*clamp(this.health/100,0,1),5); c.strokeStyle='rgba(255,255,255,.6)'; c.lineWidth=0.5; c.strokeRect(-18,-4,36,7); c.restore(); }
  }
}

// ── class: Projectile ──
class Projectile{
  constructor(x,y,ang,power,weapon,owner){
    this.x=x; this.y=y; this.weapon=weapon; this.owner=owner;
    this.vx=Math.cos(ang)*power; this.vy=Math.sin(ang)*power;
    this.life=500; this.bounce=weapon.bounce||0.35; this.fuse=weapon.key==='grenade'?180:-1;
    this.trail=[]; this.alive=true; this.clustered=false;
  }
  update(terrain, wind, particles){
    if(!this.alive) return false;
    this.trail.push({x:this.x,y:this.y}); if(this.trail.length>14) this.trail.shift();
    this.vx+=wind*0.05; this.vy+=this.weapon.grav||CONFIG.GRAVITY*0.78;
    let nx=this.x+this.vx, ny=this.y+this.vy;
    if(terrain.isSolid(nx,ny)){
      if(this.weapon.key==='grenade'){
        // bounce
        if(terrain.isSolid(this.x, ny)){ this.vy*=-this.bounce; this.vx*=0.75; } else { this.vx*=-this.bounce; }
        if(Math.abs(this.vx)<0.2) this.vx=0;
        if(Math.abs(this.vy)<0.6) this.vy=0;
        SFX.play('bounce'); for(let i=0;i<6;i++) particles.push(new Particle(this.x,this.y, rand(-2,2), rand(-3,-0.5), 20, '#d8c6a0', rand(2,4), 'dust'));
        // stay
        this.x+=this.vx*0.2; this.y+=this.vy*0.2;
      } else {
        this.x=nx; this.y=ny; return 'explode';
      }
    } else { this.x=nx; this.y=ny; }
    if(this.fuse>0){ this.fuse--; if(this.fuse<=0) return 'explode'; }
    if(this.y>CONFIG.WORLD_H+100) return 'remove';
    if(this.weapon.key==='cluster' && this.vy>0 && !this.clustered && this.life<460){
      this.clustered=true; return 'cluster';
    }
    this.life--;
    if(this.y>CONFIG.WATER_Y+6) return 'explode'; // water splash
    return false;
  }
  draw(c, cam){
    let sx=(this.x - cam.x)*cam.zoom + cam.w/2, sy=(this.y - cam.y)*cam.zoom + cam.h/2;
    // trail
    c.save();
    for(let i=0;i<this.trail.length;i++){ let p=this.trail[i]; let psx=(p.x - cam.x)*cam.zoom + cam.w/2, psy=(p.y - cam.y)*cam.zoom + cam.h/2; c.globalAlpha=i/this.trail.length*0.6; c.fillStyle=this.weapon.cat==='AOE'?'#ff7a3d':'#ffde59'; c.beginPath(); c.arc(psx,psy, 2*cam.zoom,0,Math.PI*2); c.fill(); }
    c.restore();
    c.save(); c.translate(sx,sy); c.rotate(Math.atan2(this.vy,this.vx));
    c.fillStyle='#202030'; c.fillRect(-8,-4,16,8); c.fillStyle='#ffde59'; c.fillRect(6,-2,4,4);
    c.restore();
  }
}

// ── class: Camera ──
class Camera{
  constructor(w,h){ this.x=1200; this.y=300; this.w=w; this.h=h; this.zoom=1; this.tx=1200; this.ty=300; this.tzoom=1; this.shake=0; }
  follow(x,y,zoom){ this.tx=x; this.ty=y; if(zoom) this.tzoom=zoom; }
  addShake(v){ this.shake=Math.max(this.shake, v); }
  update(){
    this.x=lerp(this.x,this.tx,0.09); this.y=lerp(this.y,this.ty,0.09); this.zoom=lerp(this.zoom,this.tzoom,0.07);
    this.zoom=clamp(this.zoom,0.55,1.6);
    if(this.shake>0){ this.x+=rand(-this.shake,this.shake); this.y+=rand(-this.shake,this.shake); this.shake*=0.88; if(this.shake<0.15) this.shake=0; }
  }
  worldToScreen(wx,wy){ return {x:(wx-this.x)*this.zoom + this.w/2, y:(wy-this.y)*this.zoom + this.h/2} }
  screenToWorld(sx,sy){ return {x:(sx-this.w/2)/this.zoom + this.x, y:(sy-this.h/2)/this.zoom + this.y} }
}

// ── class: Game ──
class Game{
  constructor(canvas){
    this.canvas=canvas; this.ctx=canvas.getContext('2d');
    this.minimap=document.getElementById('minimap'); this.mctx=this.minimap.getContext('2d');
    this.terrain=new Terrain(CONFIG.WORLD_W, CONFIG.WORLD_H);
    this.camera=new Camera(canvas.width, canvas.height);
    this.worms=[]; this.particles=[]; this.debris=[]; this.projectiles=[]; this.texts=[];
    this.wind=rand(-2.2,2.2); this.time=0; this.turnTime=CONFIG.TURN_TIME; this.turnMax=CONFIG.TURN_TIME;
    this.teamCount=2; this.cpuCount=0; this.mapType='island';
    this.activeTeam=0; this.activeWorm=0; this.state='menu'; this.shots=0;
    this.currentWpn=WEAPONS[0]; this.aimAng=-0.6; this.aimPower=0; this.charging=false; this.mouse={x:0,y:0,down:false};
    this.inputs={left:false,right:false,jump:false}; this.freeze=0; this.windTimer=0;
    this.jetFuel=0; this.teleporting=false; this._cpuPlan=null; this._cpuCooldown=0;
    this.resize(); window.addEventListener('resize',()=>this.resize());
  }
  resize(){ this.canvas.width=window.innerWidth*devicePixelRatio; this.canvas.height=window.innerHeight*devicePixelRatio; this.canvas.style.width=window.innerWidth+'px'; this.canvas.style.height=window.innerHeight+'px'; this.ctx.setTransform(devicePixelRatio,0,0,devicePixelRatio,0,0); this.camera.w=window.innerWidth; this.camera.h=window.innerHeight; }
  startGame(){
    this.terrain.generate(this.mapType);
    this.worms=[]; this.particles=[]; this.debris=[]; this.projectiles=[]; this.texts=[]; this.shots=0;
    this.activeTeam=0; this.wind=rand(-3,3);
    for(let t=0;t<this.teamCount;t++){
      for(let i=0;i<4;i++){
        let sp=this.terrain.findSpawn(); let w=new Worm(t,i,sp.x,sp.y); this.worms.push(w);
      }
    }
    this.pickNextActive(true); this.state='turn'; this.turnTime=this.turnMax; this.updateHUD(); this.showTurnBanner();
  }
  getActive(){ return this.worms.find(w=>w.isActive); }
  getAliveTeams(){ let s=new Set(); this.worms.forEach(w=>{if(w.alive) s.add(w.team)}); return [...s]; }
  pickNextActive(first=false){
    this.worms.forEach(w=>w.isActive=false);
    let aliveTeams=this.getAliveTeams();
    if(aliveTeams.length<=1 && !first){
      this.state='over'; this.showTurnBanner(aliveTeams[0]);
      setTimeout(()=>{
        let over=document.getElementById('menuOverlay');
        over.style.display='flex'; over.classList.remove('hide');
        document.getElementById('menuCard').querySelector('h1').textContent= aliveTeams.length? `${CONFIG.TEAMS_COLORS[aliveTeams[0]].name} VICTORY!` : 'DRAW!';
        document.getElementById('playBtn').textContent='PLAY AGAIN →';
      },2600);
      return;
    }
    let attempts=0;
    while(attempts<60){
      if(!first){ this.activeTeam=(this.activeTeam+1)%this.teamCount; }
      let candidates=this.worms.filter(w=>w.team===this.activeTeam && w.alive);
      if(candidates.length>0){
        let idx=this.activeWorm % candidates.length;
        let w=candidates[idx]; if(first){ w=candidates[0]; }
        w.isActive=true; w.moveFuel=100; this.activeWorm=(idx+1)%4;
        this.camera.follow(w.x, w.y, 1.1);
        break;
      }
      this.activeTeam=(this.activeTeam+1)%this.teamCount; attempts++; if(first) break;
    }
    if(first) this.activeTeam=0;
    this.turnTime=this.turnMax; this.charging=false; this.aimPower=0;
    this._cpuPlan=null; this._cpuCooldown=30;
  }
  showTurnBanner(teamIdx){
    let b=document.getElementById('turnBanner');
    if(this.state==='over'){
      let col=CONFIG.TEAMS_COLORS[teamIdx]||CONFIG.TEAMS_COLORS[0];
      b.innerHTML=`🏆 ${col.name} WINS!`; b.style.color=col.col;
    } else{
      let aw=this.getActive(); if(!aw) return;
      let col=CONFIG.TEAMS_COLORS[aw.team]; b.innerHTML=`${col.name} • ${aw.idx+1}`; b.style.color=col.col;
    }
    b.classList.add('show'); SFX.play('turn'); setTimeout(()=>b.classList.remove('show'), 1600);
  }
  explode(x,y,rad,dmg,owner){
    SFX.play(rad>110?'explode_big':'explode_small');
    this.terrain.destroyCircle(x,y,rad);
    for(let i=0;i<Math.min(rad*0.28,24);i++) this.debris.push(new Debris(x+rand(-12,12), y+rand(-12,12), rand(6,16)));
    if(rad>80){
      this.camera.addShake(rad*0.18);
      this.freeze=rad>110? 10 : 5;
      let f=document.getElementById('fxFlash'); f.style.transition='none'; f.style.opacity='0.68'; setTimeout(()=>{ f.style.transition='opacity 0.28s'; f.style.opacity='0'; }, 38);
    }
    let count=rad>100?90:42;
    for(let i=0;i<count;i++){
      let ang=rand(Math.PI*2), sp=rand(1, rad*0.14);
      let col= rand()>0.5? '#ff9a3d' : rand()>0.55? '#5a3a1a' : '#ffde59';
      let typ = Math.random()>0.65?'smoke':'dust';
      this.particles.push(new Particle(x,y, Math.cos(ang)*sp + rand(-2,2), Math.sin(ang)*sp + rand(-3,0), rand(18,62), col, rand(2,6), typ));
    }
    for(let i=0;i<18;i++) this.particles.push(new Particle(x,y, rand(-5,5), rand(-7,-1), rand(18,34), '#ffd86a', rand(1.5,3), 'spark'));
    this.worms.forEach(w=>{
      if(!w.alive) return;
      let d=dist(x,y,w.x,w.y);
      if(d<rad+22){
        let falloff=1 - clamp((d-18)/(rad),0,1);
        let dmgDo=Math.floor(dmg*falloff);
        if(dmgDo>2){
          w.takeDamage(dmgDo, x);
          this.texts.push(new FloatingText(w.x,w.y-30, '-'+dmgDo, dmgDo>50?'#ff2a2a': dmgDo>25?'#ff8a3d':'#ffde59'));
        }
        if(falloff>0.15){
          w.vx+= (w.x-x)*falloff*0.58; w.vy+= (w.y-y)*falloff*0.45 - falloff*2.2;
        }
      }
    });
  }
  fireProjectile(power){
    let act=this.getActive(); if(!act) return;
    let wx=act.x, wy=act.y-10;
    let ang=this.aimAng;
    let pw=this.currentWpn;
    if(pw.type==='direct' || pw.type==='hitscan' || pw.type==='airstrike'){
      this.handleInstantFire(wx,wy,ang,pw,act);
    } else {
      let proj=new Projectile(wx,wy,ang,power*0.045, pw, act);
      this.projectiles.push(proj);
      this.state='projectile';
    }
    SFX.play(pw.key==='rail'?'rail': pw.key==='shotgun'?'shotgun':'shoot');
    this.shots++;
  }
  handleInstantFire(x,y,ang,wpn,owner){
    if(wpn.type==='direct'){
      let shots=wpn.shots||1;
      for(let s=0;s<shots;s++){
        let a=ang + rand(-0.04,0.04);
        let dx=Math.cos(a), dy=Math.sin(a);
        let tx=x+dx*1200, ty=y+dy*1200;
        for(let t=0;t<1200;t+=6){ let cx=x+dx*t, cy=y+dy*t; if(this.terrain.isSolid(cx,cy)){ tx=cx; ty=cy; break; } }
        for(let i=0;i<26;i++){ let tt=i/26; this.particles.push(new Particle(lerp(x,tx,tt), lerp(y,ty,tt), rand(-0.5,0.5), rand(-0.5,0.5), 11, '#ffd86a',2,'spark')); }
        this.explode(tx,ty, wpn.rad+20, wpn.dmg, owner);
      }
      this.state='projectile'; setTimeout(()=>this.endTurn(), 620);
    } else if(wpn.type==='hitscan'){
      let dx=Math.cos(ang), dy=Math.sin(ang);
      let tx=x+dx*1800, ty=y+dy*1800;
      for(let t=0;t<1800;t+=5){ let cx=x+dx*t, cy=y+dy*t; if(this.terrain.isSolid(cx,cy)){ tx=cx; ty=cy; break; } }
      for(let i=0;i<64;i++){ let tt=i/64; this.particles.push(new Particle(lerp(x,tx,tt)+rand(-1,1), lerp(y,ty,tt)+rand(-1,1),0,0,20,'#7ae2ff',1.8,'spark')); }
      this.camera.addShake(12); this.explode(tx,ty,28, wpn.dmg, owner);
      this.state='projectile'; setTimeout(()=>this.endTurn(),780);
    } else if(wpn.type==='airstrike'){
      let count=wpn.count||5;
      for(let i=0;i<count;i++){
        setTimeout(()=>{
          let rx=x + rand(-420,420) + Math.cos(ang)*180;
          let p=new Projectile(rx, -120, Math.PI*0.48+rand(-0.28,0.28), rand(8,12), {key:'bazooka', rad:74, dmg:wpn.dmg, grav:0.28}, owner);
          p.vx=rand(-1.6,1.6); this.projectiles.push(p);
        }, i*320);
      }
      this.state='projectile';
    }
  }
  endTurn(){
    if(this.projectiles.length>0) return;
    this.pickNextActive(); this.updateHUD(); if(this.state!=='over') this.showTurnBanner();
  }
  update(){
    this.time++;
    if(this.freeze>0){ this.freeze--; return; }
    this.windTimer++; if(this.windTimer>560){ if(Math.random()<0.028){ this.wind+=rand(-0.9,0.9); this.wind=clamp(this.wind,-4.2,4.2);} }
    if(this.state==='projectile'){
      for(let i=this.projectiles.length-1;i>=0;i--){
        let pr=this.projectiles[i]; let res=pr.update(this.terrain,this.wind,this.particles);
        if(res==='explode'){ this.explode(pr.x,pr.y, pr.weapon.rad||62, pr.weapon.dmg||40, pr.owner); this.projectiles.splice(i,1); }
        else if(res==='remove'){ this.projectiles.splice(i,1); }
        else if(res==='cluster'){
          for(let k=0;k<5;k++){ let a=rand(-Math.PI,Math.PI), p=rand(3,7); let b=new Projectile(pr.x,pr.y, a, p, {key:'bomblet', rad:46, dmg:20, grav:0.28}, pr.owner); this.projectiles.push(b); }
          this.projectiles.splice(i,1);
        }
      }
      if(this.projectiles.length===0){
        let moving=this.worms.some(w=>!w.onGround && w.alive && Math.abs(w.vx)+Math.abs(w.vy)>0.35);
        if(!moving) this.endTurn();
      }
    }
    if(this.state==='turn'){
      let act=this.getActive();
      if(act){
        this.turnTime-=1/60; if(this.turnTime<=0){ this.endTurn(); }
        if(this.projectiles.length===0){
          let targetZoom= act.isActive && this.charging? 0.86 : 1.18;
          this.camera.follow(act.x, act.y-22, targetZoom);
        }
        if(act.team >= (this.teamCount - this.cpuCount) && this.cpuCount>0){ this.updateCPU(act); }
        this.worms.forEach(w=>w.update(this.terrain,this.wind, w===act? this.inputs : {left:false,right:false,jump:false}));
        if(this.jetFuel>0 && this.inputs.jump && act.isActive){
          act.vy-=0.54; act.vx*=0.92; this.jetFuel-=1.6;
          this.particles.push(new Particle(act.x+rand(-2,2), act.y+10, rand(-1.2,1.2), rand(1.8,4.2), 15, '#ff8a3d',3.2,'smoke'));
          if(this.time%7===0) SFX.play('jet');
          if(this.jetFuel<=0){ this.jetFuel=0; this.showToast('🎒 Fuel depleted'); }
        }
      } else { this.endTurn(); }
    }
    for(let i=this.particles.length-1;i>=0;i--){ this.particles[i].update(this.wind, this.terrain); if(this.particles[i].life<=0) this.particles.splice(i,1); }
    for(let i=this.debris.length-1;i>=0;i--){ this.debris[i].update(this.terrain); if(this.debris[i].life<=0) this.debris.splice(i,1); }
    for(let i=this.texts.length-1;i>=0;i--){ this.texts[i].update(); if(this.texts[i].life<=0) this.texts.splice(i,1); }
    this.camera.update();
  }
  draw(){
    const ctx=this.canvas.getContext('2d');
    const W=window.innerWidth, H=window.innerHeight;
    let grd=ctx.createLinearGradient(0,0,0,H);
    grd.addColorStop(0,'#2e457a'); grd.addColorStop(0.34,'#1a284f'); grd.addColorStop(1,'#0c1326');
    ctx.fillStyle=grd; ctx.fillRect(0,0,W,H);
    ctx.save(); ctx.globalAlpha=0.36;
    for(let i=0;i<6;i++){ let x=(this.time*0.16* (this.wind*0.24+0.7)+ i*320) % (W+220) -110; let y=68 + i*44 + Math.sin(this.time*0.01+i)*11; ctx.fillStyle='#fff'; ctx.beginPath(); ctx.ellipse(x,y, 84,19,0,0,Math.PI*2); ctx.ellipse(x+22,y-7,52,15,0,0,Math.PI*2); ctx.fill(); }
    ctx.restore();
    let act=this.getActive();
    if(act && this.state==='turn' && this.currentWpn && (this.currentWpn.type==='projectile' || this.currentWpn.type==='grenade' || this.currentWpn.type==='cluster')){
      let p=Math.max(this.aimPower, 1);
      let sx=act.x, sy=act.y-10; let vx=Math.cos(this.aimAng)*p*0.045, vy=Math.sin(this.aimAng)*p*0.045;
      let cam=this.camera;
      ctx.save(); ctx.beginPath();
      ctx.strokeStyle='rgba(255,222,89,0.88)'; ctx.lineWidth=2.2/cam.zoom; ctx.setLineDash([7/cam.zoom,7/cam.zoom]);
      let pathX=[], pathY=[];
      let hit=null;
      for(let i=0;i<42;i++){ vx+=this.wind*0.05; vy+= (this.currentWpn.grav||CONFIG.GRAVITY*0.78); sx+=vx; sy+=vy; pathX.push(sx); pathY.push(sy); if(this.terrain.isSolid(sx,sy)){ hit={x:sx,y:sy}; break; } }
      if(pathX.length>1){
        let cam=this.camera;
        ctx.moveTo((pathX[0]-cam.x)*cam.zoom + W/2, (pathY[0]-cam.y)*cam.zoom + H/2);
        for(let i=1;i<pathX.length;i++){ let px=(pathX[i]-cam.x)*cam.zoom + W/2, py=(pathY[i]-cam.y)*cam.zoom + H/2; ctx.lineTo(px,py); }
      }
      ctx.stroke(); ctx.setLineDash([]);
      if(hit){ let hp=cam.worldToScreen(hit.x,hit.y); ctx.fillStyle='rgba(255,90,74,0.92)'; ctx.beginPath(); ctx.arc(hp.x,hp.y, (this.currentWpn.rad||60)*0.28*cam.zoom,0,Math.PI*2); ctx.fill(); ctx.strokeStyle='#fff'; ctx.lineWidth=1.6; ctx.stroke(); }
      ctx.restore();
    }
    let cam=this.camera;
    let sx=Math.floor(cam.x - cam.w/2/cam.zoom), sy=Math.floor(cam.y - cam.h/2/cam.zoom);
    let sw=cam.w/cam.zoom, sh=cam.h/cam.zoom;
    sx=clamp(sx,0,this.terrain.w-sw); sy=clamp(sy,0,this.terrain.h-sh);
    ctx.drawImage(this.terrain.canvas, sx,sy,sw,sh,0,0,W,H);
    let waterScreenY=(this.terrain.waterY - cam.y)*cam.zoom + H/2;
    if(waterScreenY < H){
      ctx.fillStyle='#174e85'; ctx.globalAlpha=0.9; ctx.fillRect(0,waterScreenY,W,H-waterScreenY);
      ctx.globalAlpha=0.38; ctx.fillStyle='#86dcff';
      for(let x=0;x<W;x+=28){ let wave=Math.sin((x+this.time*2)*0.01)*3 + Math.sin(this.time*0.05); ctx.fillRect(x, waterScreenY+wave,13,2.2); }
      ctx.globalAlpha=1; ctx.strokeStyle='rgba(120,210,255,.62)'; ctx.lineWidth=2; ctx.beginPath(); ctx.moveTo(0,waterScreenY); for(let x=0;x<W;x+=14){ ctx.lineTo(x, waterScreenY + Math.sin((x*0.022+this.time*0.052))*3.2); } ctx.stroke();
    }
    this.worms.forEach(w=>w.draw(ctx,cam));
    this.debris.forEach(d=>{
      let p=cam.worldToScreen(d.x+d.w/2,d.y+d.h/2);
      ctx.save(); ctx.translate(p.x,p.y); ctx.rotate(d.rot);
      ctx.fillStyle=d.col; ctx.fillRect(-d.w*cam.zoom/2,-d.h*cam.zoom/2,d.w*cam.zoom,d.h*cam.zoom);
      ctx.restore();
    });
    this.particles.forEach(pt=>{
      let pp=cam.worldToScreen(pt.x,pt.y);
      ctx.save(); ctx.globalAlpha=pt.alpha;
      ctx.fillStyle=pt.col; if(pt.type==='spark'){ ctx.shadowBlur=10; ctx.shadowColor=pt.col; }
      ctx.beginPath(); ctx.arc(pp.x,pp.y, pt.size*cam.zoom,0,Math.PI*2); ctx.fill(); ctx.restore();
    });
    this.projectiles.forEach(pr=>{ pr.draw(ctx,cam); });
    this.texts.forEach(t=>{
      let p=cam.worldToScreen(t.x,t.y);
      ctx.save(); ctx.globalAlpha=t.life/60;
      ctx.font='900 '+(19*cam.zoom)+'px Fredoka'; ctx.fillStyle=t.col; ctx.strokeStyle='#000'; ctx.lineWidth=3.2; ctx.strokeText(t.tx,p.x,p.y); ctx.fillText(t.tx,p.x,p.y); ctx.restore();
    });
    // minimap
    this.mctx.clearRect(0,0,180,90); this.mctx.drawImage(this.terrain.canvas,0,0,180,90);
    this.worms.forEach(w=>{ if(!w.alive) return; this.mctx.fillStyle=w.color; this.mctx.fillRect((w.x/this.terrain.w)*180-1.2, (w.y/this.terrain.h)*90-1.2,3.4,3.4); });
    let cx=(cam.x/this.terrain.w)*180, cy=(cam.y/this.terrain.h)*90, cw=(cam.w/cam.zoom/this.terrain.w)*180, ch=(cam.h/cam.zoom/this.terrain.h)*90;
    this.mctx.strokeStyle='#fff'; this.mctx.lineWidth=1; this.mctx.strokeRect(cx-cw/2,cy-ch/2,cw,ch);
  }
  loop(){ this.update(); this.draw(); this.renderHUD(); requestAnimationFrame(()=>this.loop()); }
  renderHUD(){
    let act=this.getActive();
    document.getElementById('windVal').textContent=(this.wind>=0?'+':'')+this.wind.toFixed(1);
    document.getElementById('windArrow').style.transform=`rotate(${this.wind*18}deg) scaleX(${this.wind<0?-1:1})`;
    let timerEl=document.getElementById('timerText'); let fill=document.getElementById('timerFill');
    timerEl.textContent=Math.ceil(Math.max(0,this.turnTime)).toString();
    fill.style.width=(clamp(this.turnTime/this.turnMax,0,1)*100)+'%';
    if(this.turnTime<8){ timerEl.classList.add('panic'); fill.style.background='#ff3b3b'; if(Math.floor(this.time)%30===0) SFX.play('tick',{fast:true}); } else { timerEl.classList.remove('panic'); fill.style.background='linear-gradient(90deg,#ffde59,#ff8a3d)'; if(Math.floor(this.time)%62===0 && this.state==='turn') SFX.play('tick',{fast:false}); }
    if(act){
      document.getElementById('activeName').textContent=CONFIG.TEAMS_COLORS[act.team].name+' • '+ (act.idx+1) + (this.jetFuel>0?` [JET ${Math.floor(this.jetFuel)}]`:'');
      document.getElementById('healthFill').style.width=act.health+'%';
      document.getElementById('healthText').textContent=act.health;
      let pb=document.getElementById('powerBar'); if(this.charging){ pb.classList.add('charging'); pb.querySelector('#powerFill').style.width=this.aimPower+'%'; } else { pb.classList.remove('charging'); pb.querySelector('#powerFill').style.width='0%'; }
      document.getElementById('angleTxt').textContent=Math.round((-this.aimAng*180/Math.PI))+'°';
      document.getElementById('powerTxt').textContent=Math.round(this.aimPower)+'%';
      let showAim=this.currentWpn.type.includes('projectile')||this.currentWpn.key==='grenade'||this.currentWpn.key==='cluster';
      document.getElementById('aimReadout').classList.toggle('show', showAim && this.state==='turn');
    }
    let tb=document.getElementById('teamBars'); tb.innerHTML='';
    for(let t=0;t<this.teamCount;t++){
      let teamW=this.worms.filter(w=>w.team===t && w.alive); let tot=teamW.reduce((s,w)=>s+w.health,0); let avg=teamW.length? tot/teamW.length :0;
      let col=CONFIG.TEAMS_COLORS[t];
      let row=document.createElement('div'); row.className='teamRow';
      row.innerHTML=`<div class="teamDot" style="background:${col.col}"></div><div class="teamLabel">${col.name} ${teamW.length>0?teamW.length+'×':''}</div><div class="teamHpBar"><div style="width:${clamp(avg,0,100)}%;background:${col.col}"></div></div><div style="font-size:10px;color:#fff;font-weight:700;min-width:28px;text-align:right">${tot}</div>`;
      tb.appendChild(row);
    }
  }
  updateHUD(){
    let wheel=document.getElementById('weaponWheel'); if(!wheel) return; wheel.innerHTML='';
    [...WEAPONS,...UTILITIES].forEach(w=>{
      let div=document.createElement('div'); div.className='wpn'+(this.currentWpn.key===w.key?' sel':'');
      let cat=w.cat||'UTL'; div.innerHTML=`<div class="cat cat-${cat}">${cat}</div><div class="ico">${w.ico}</div><div class="lbl">${w.name}</div>`;
      div.onclick=()=>{ this.currentWpn=w; SFX.play('select'); this.updateHUD(); this.showToast(`${w.ico} ${w.name} — ${w.desc}`); };
      wheel.appendChild(div);
    });
  }
  showToast(txt){ let t=document.getElementById('weaponToast'); t.textContent=txt; t.classList.add('show'); clearTimeout(this._toast); this._toast=setTimeout(()=>t.classList.remove('show'),2100); }
  updateCPU(worm){
    if(this._cpuCooldown>0){ this._cpuCooldown--; return; }
    if(!this._cpuPlan){
      let enemies=this.worms.filter(v=>v.team!==worm.team && v.alive);
      if(enemies.length===0){ this.endTurn(); return; }
      let best=null, bestScore=-Infinity, bestAng=0, bestPow=50;
      for(let e of enemies){
        let alliesNear = this.worms.filter(a=>a.team===worm.team && a.alive && a!==worm).some(a=>dist(a.x,a.y,e.x,e.y)<90);
        if(alliesNear) continue;
        let waterRisk = e.y>this.terrain.waterY-50? -120 : 0;
        let ownNearWater = worm.y>this.terrain.waterY-70? -60 : 0;
        for(let ang=-Math.PI*0.08; ang>-Math.PI*0.9; ang-=0.2){
          for(let pow=28; pow<=100; pow+=14){
            let sim=this.simulateShot(worm.x,worm.y-10,ang,pow*0.045, this.wind);
            let simDist=dist(sim.x,sim.y,e.x,e.y);
            let value= (260 - simDist) + (100-e.health)*1.1 + waterRisk + ownNearWater;
            if(sim.hitTerrain && simDist>110) value-=70;
            if(dist(sim.x,sim.y,worm.x,worm.y)<85) value-=260;
            // prefer not to blow up cover
            if(simDist<60) value+=40;
            if(value>bestScore){ bestScore=value; best=e; bestAng=ang; bestPow=pow; }
          }
        }
      }
      if(best && bestScore>-100){
        this._cpuPlan={target:best, ang:bestAng, pow:bestPow, stage:0, t:0};
      } else {
        this.inputs.left=Math.random()<0.5; this.inputs.right=!this.inputs.left; this._cpuCooldown=35;
        if(Math.random()<0.11){ this.inputs.jump=true; setTimeout(()=>this.inputs.jump=false,90); }
        return;
      }
    }
    let p=this._cpuPlan; if(!p) return; p.t++;
    this.aimAng=lerp(this.aimAng,p.ang,0.09);
    if(p.t>52 && p.stage===0){ p.stage=1; this.charging=true; this.aimPower=0; }
    if(p.stage===1){
      this.aimPower+=1.9; if(this.aimPower>=p.pow){ this.fireProjectile(this.aimPower); this.charging=false; this._cpuPlan=null; this._cpuCooldown=70; this.inputs.left=false; this.inputs.right=false; }
    }
  }
  simulateShot(sx,sy,ang,pow,wind){
    let x=sx, y=sy; let vx=Math.cos(ang)*pow, vy=Math.sin(ang)*pow; let hitTerrain=false;
    for(let i=0;i<140;i++){ vx+=wind*0.05; vy+=0.28; x+=vx; y+=vy; if(this.terrain.isSolid(x,y)){ hitTerrain=true; break; } if(y>CONFIG.WATER_Y) break; }
    return {x,y,hitTerrain};
  }
}

// ── main ──
const canvas=document.getElementById('canvas');
const game=new Game(canvas);
let uiMap='island', uiTeams=2, uiCPU=0;

function bindMenu(){
  document.querySelectorAll('#mapOpts button').forEach(b=>{
    b.onclick=()=>{ document.querySelectorAll('#mapOpts button').forEach(x=>x.classList.remove('sel')); b.classList.add('sel'); uiMap=b.dataset.v; SFX.play('click'); };
  });
  document.querySelectorAll('#teamOpts button').forEach(b=>{
    b.onclick=()=>{ document.querySelectorAll('#teamOpts button').forEach(x=>x.classList.remove('sel')); b.classList.add('sel'); uiTeams=parseInt(b.dataset.v); SFX.play('click'); };
  });
  document.querySelectorAll('#cpuOpts button').forEach(b=>{
    b.onclick=()=>{ document.querySelectorAll('#cpuOpts button').forEach(x=>x.classList.remove('sel')); b.classList.add('sel'); uiCPU=parseInt(b.dataset.v); SFX.play('click'); };
  });
  document.getElementById('playBtn').onclick=()=>{
    SFX.init(); SFX.play('select');
    game.mapType=uiMap; game.teamCount=uiTeams; game.cpuCount=uiCPU;
    document.getElementById('menuOverlay').classList.add('hide');
    game.startGame();
    setTimeout(()=>document.getElementById('menuOverlay').style.display='none', 500);
  };
}
bindMenu();

game.updateHUD();

// INPUTS
let chargingInterval=null;
window.addEventListener('keydown', (e)=>{
  if(e.code==='KeyA'){ game.inputs.left=true; }
  if(e.code==='KeyD'){ game.inputs.right=true; }
  if(e.code==='KeyW'){ game.inputs.jump=true; if(game.jetFuel>0){} }
  if(e.code==='Space'){
    if(game.state==='turn' && !game.charging){ game.charging=true; game.aimPower=0; e.preventDefault(); }
  }
  if(e.code==='Tab'){ e.preventDefault(); let idx=WEAPONS.findIndex(w=>w.key===game.currentWpn.key); game.currentWpn=WEAPONS[(idx+1)%WEAPONS.length]; game.updateHUD(); game.showToast(`${game.currentWpn.ico} ${game.currentWpn.name}`); SFX.play('select'); }
  let num=parseInt(e.key); if(num>=1 && num<=8){ let all=[...WEAPONS,...UTILITIES]; if(all[num-1]){ game.currentWpn=all[num-1]; game.updateHUD(); SFX.play('select'); } }
});
window.addEventListener('keyup',(e)=>{
  if(e.code==='KeyA') game.inputs.left=false;
  if(e.code==='KeyD') game.inputs.right=false;
  if(e.code==='KeyW'){ game.inputs.jump=false; }
  if(e.code==='Space'){
    if(game.charging && game.state==='turn'){
      let power=game.aimPower;
      if(game.currentWpn.key==='jetpack'){ game.jetFuel=game.currentWpn.fuel||200; game.showToast('🎒 Jetpack engaged! Hold W'); game.currentWpn=WEAPONS[0]; game.updateHUD(); game.charging=false; return; }
      if(game.currentWpn.key==='tele'){ game.teleporting=true; game.showToast('🌀 Click map to teleport'); game.charging=false; return; }
      game.fireProjectile(power);
      game.charging=false; game.aimPower=0;
    }
  }
});

canvas.addEventListener('mousemove',(e)=>{
  let act=game.getActive(); if(!act) return;
  let rect=canvas.getBoundingClientRect();
  let mx=e.clientX-rect.left, my=e.clientY-rect.top;
  let worldPos=game.camera.screenToWorld(mx,my);
  game.aimAng=Math.atan2(worldPos.y - act.y, worldPos.x - act.x);
  // clamp to not shoot backwards weirdly too far? allow full
  game.mouse.x=mx; game.mouse.y=my;
});

canvas.addEventListener('mousedown',(e)=>{
  if(game.teleporting){
    let act=game.getActive(); if(act){
      let rect=canvas.getBoundingClientRect(); let mx=e.clientX-rect.left, my=e.clientY-rect.top;
      let w=game.camera.screenToWorld(mx,my);
      if(!game.terrain.isSolid(w.x,w.y) && w.y<game.terrain.waterY-20){ act.x=w.x; act.y=w.y; game.particles.push(...Array.from({length:20},()=>new Particle(w.x,w.y,rand(-3,3),rand(-4,-0.5),30,'#a18fff',3,'spark'))); SFX.play('select'); }
    }
    game.teleporting=false; game.currentWpn=WEAPONS[0]; game.updateHUD(); return;
  }
  if(e.button===0){
    if(game.state==='turn' && !game.charging){ game.charging=true; game.aimPower=0; }
  }
});
canvas.addEventListener('mouseup',(e)=>{
  if(e.button===0 && game.charging && game.state==='turn'){
    let power=game.aimPower; 
    if(game.currentWpn.key==='jetpack'){ game.jetFuel=200; game.showToast('🎒 Jetpack ON (Hold W)'); game.currentWpn=WEAPONS[0]; game.updateHUD(); }
    else if(game.currentWpn.key==='tele'){ game.teleporting=true; game.showToast('🌀 Click anywhere'); }
    else { game.fireProjectile(power); }
    game.charging=false; game.aimPower=0;
  }
});
canvas.addEventListener('wheel',(e)=>{
  game.camera.tzoom=clamp(game.camera.tzoom - e.deltaY*0.001, 0.55, 1.7);
});

setInterval(()=>{ if(game.charging){ game.aimPower=Math.min(100, game.aimPower+1.7); SFX.play('charge',{p:game.aimPower}); } }, 50);

game.loop();

// initial demo draw for menu bg
(function menuBG(){
  if(game.state==='menu'){
    let c=canvas.getContext('2d'); c.fillStyle='#141e3a'; c.fillRect(0,0,window.innerWidth,window.innerHeight);
    // stars
    for(let i=0;i<120;i++){ c.fillStyle=`rgba(255,255,255,${Math.random()*0.5+0.2})`; let x=(i*137)%window.innerWidth, y=(Math.sin(i)*200+200); c.beginPath(); c.arc(x,y, Math.random()*1.6,0,Math.PI*2); c.fill(); }
    c.fillStyle='rgba(255,255,255,.04)'; c.font='900 22vw Fredoka'; c.textAlign='center'; c.fillText('CRATER', window.innerWidth/2, window.innerHeight*0.6);
  }
  if(game.state==='menu') requestAnimationFrame(menuBG);
})();
</script>
</body>
</html>
<!-- agent-meta {"model":"meta/muse-spark-1.1","provider":"openrouter","persona":"composer","sessionId":"4a89b834-b5b4-4948-bf65-d21415a1ca91","tokensIn":532217,"tokensOut":35450,"tokensTotal":567667,"turns":22,"toolCalls":21,"failedToolCalls":1,"timestamp":"2026-07-17T04:41:20.012Z"} -->