← Worms results

Worms

muse spark 1.2 · basic

Initial view of Worms

Browser check: Artifact runtime error. This is a technical check, not a quality review.

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,727 bytes · SHA-256 47678da30f6f
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0">
<title>WORMSHELL — Deluxe Artillery</title>
<link href="https://fonts.googleapis.com/css2?family=Bangers&family=Nunito:wght@700;800;900&family=Space+Grotesk:wght@600;700&display=swap" rel="stylesheet">
<style>
*{margin:0;padding:0;box-sizing:border-box}
:root{--bg:#0f1b2d;--panel:#162a47;--accent:#ffb02e;--accent2:#ff3b5c;--mint:#2ee6a6;--sky1:#87d8ff;--sky2:#4a6bff;--glass:rgba(255,255,255,.08)}
body{background:#0a1320;color:#fff;font-family:'Nunito',system-ui,sans-serif;overflow:hidden;height:100vh;height:100dvh;display:flex;flex-direction:column}
::-webkit-scrollbar{width:6px;height:6px}::-webkit-scrollbar-thumb{background:#2a4060;border-radius:99px}
/* HEADER */
header{height:56px;background:linear-gradient(180deg,#1a2f52 0%,#12233e 100%);border-bottom:3px solid #24406a;display:flex;align-items:center;justify-content:space-between;padding:0 14px;gap:10px;z-index:10;flex-shrink:0}
.logo{font-family:'Bangers',cursive;letter-spacing:2px;font-size:30px;color:#fff;text-shadow:0 3px 0 #0a1320,0 0 18px rgba(255,176,46,.6);display:flex;align-items:center;gap:8px}
.logo span{color:var(--accent);-webkit-text-stroke:1px #0a1320}
.logo small{font-family:'Space Grotesk',sans-serif;font-size:10px;letter-spacing:3px;background:var(--accent);color:#0a1320;padding:2px 6px;border-radius:4px;margin-left:4px}
.hud-center{display:flex;align-items:center;gap:10px}
.wind-box{background:#0d1e36;border:2px solid #2c4a78;border-radius:12px;padding:6px 12px;display:flex;align-items:center;gap:10px;min-width:170px}
.wind-label{font-size:9px;letter-spacing:2px;opacity:.7;font-weight:900}
.wind-bar{width:90px;height:8px;background:#0a1320;border-radius:99px;overflow:hidden;position:relative;border:1px solid #2c4a78}
.wind-fill{height:100%;background:linear-gradient(90deg,#2ee6a6,#ffb02e 50%,#ff3b5c);width:50%;transition:.6s}
.wind-val{font-family:'Space Grotesk';font-weight:700;font-size:13px;min-width:34px}
.timer-box{background:linear-gradient(180deg,#ff3b5c,#c81e3a);border:3px solid #fff;border-radius:12px;padding:4px 16px;text-align:center;min-width:92px;box-shadow:0 4px 12px rgba(255,59,92,.4);transition:.2s}
.timer-box.warn{animation:pulse .5s infinite alternate;background:linear-gradient(180deg,#ff6b2e,#ff1a1a);scale:1.08}
@keyframes pulse{from{transform:scale(1)}to{transform:scale(1.08)}}
.timer-num{font-family:'Bangers';font-size:28px;line-height:1}
.timer-sub{font-size:8px;letter-spacing:2px;opacity:.9;font-weight:900}
.turn-badge{background:var(--accent);color:#0a1320;font-weight:900;font-size:11px;padding:6px 10px;border-radius:99px;border:2px solid #fff;display:flex;align-items:center;gap:6px;white-space:nowrap}
.dot{width:10px;height:10px;border-radius:50%;border:2px solid #fff;box-shadow:0 0 8px currentColor}
/* LAYOUT */
#main{flex:1;display:grid;grid-template-columns:220px 1fr 260px;grid-template-rows:1fr;gap:10px;padding:10px;overflow:hidden;background:radial-gradient(1200px 600px at 50% -10%, #1b3a6b 0%, #0a1320 70%)}
.panel{background:linear-gradient(180deg,rgba(255,255,255,.09),rgba(255,255,255,.04));backdrop-filter:blur(12px);border:1px solid rgba(255,255,255,.12);border-radius:16px;overflow:hidden;display:flex;flex-direction:column;box-shadow:0 8px 24px rgba(0,0,0,.35)}
.panel-head{padding:10px 12px 8px;display:flex;align-items:center;justify-content:space-between;border-bottom:1px solid rgba(255,255,255,.08);background:rgba(255,255,255,.04)}
.panel-head h3{font-size:10px;letter-spacing:2px;opacity:.8}
.game-wrap{position:relative;border-radius:16px;overflow:hidden;border:2px solid rgba(255,255,255,.18);background:linear-gradient(180deg,var(--sky1) 0%, #6ab6ff 35%, #4a6bff 70%, #1a2f6b 100%);box-shadow:0 12px 40px rgba(0,0,0,.45);display:flex;flex-direction:column}
#c{width:100%;height:100%;display:block;cursor:crosshair}
#overlay{position:absolute;inset:0;pointer-events:none}
.float-txt{position:absolute;font-family:'Bangers';font-size:22px;color:#fff;-webkit-text-stroke:1.2px #000;filter:drop-shadow(0 3px 0 #000);animation:floatUp 1s forwards}
@keyframes floatUp{0%{transform:translate(-50%,0) scale(1);opacity:1}100%{transform:translate(-50%,-70px) scale(1.2);opacity:0}}
.team-card{padding:8px 10px;border-bottom:1px solid rgba(255,255,255,.06);display:flex;align-items:center;gap:8px}
.team-card.active{background:rgba(255,176,46,.18);box-shadow:inset 3px 0 0 var(--accent)}
.team-ico{width:28px;height:28px;border-radius:8px;display:grid;place-items:center;font-size:14px;border:2px solid #fff;flex-shrink:0}
.team-info{flex:1;min-width:0}
.team-name{font-weight:900;font-size:12px;letter-spacing:.5px;display:flex;align-items:center;gap:6px}
.team-worms{display:flex;gap:4px;margin-top:4px}
.worm-pip{width:26px;height:14px;border-radius:99px;border:1.5px solid #fff;position:relative;overflow:hidden;background:#0a1320;display:flex;align-items:center;justify-content:center;font-size:8px;font-weight:900}
.worm-pip b{position:absolute;inset:0;display:grid;place-items:center}
.health-fill{position:absolute;left:0;top:0;bottom:0;opacity:.9}
.cat-tabs{display:flex;gap:6px;padding:8px 8px 0;flex-wrap:wrap}
.cat-tab{font-size:9px;font-weight:900;letter-spacing:1px;padding:6px 8px;border-radius:99px;border:1px solid rgba(255,255,255,.15);background:rgba(255,255,255,.06);cursor:pointer;color:#fff;transition:.15s}
.cat-tab.active{background:var(--accent);color:#0a1320;border-color:#fff;box-shadow:0 2px 8px rgba(255,176,46,.5)}
.weapons-grid{display:grid;grid-template-columns:1fr 1fr;gap:6px;padding:8px}
.wpn{background:rgba(10,19,32,.75);border:1.5px solid rgba(255,255,255,.12);border-radius:12px;padding:8px;cursor:pointer;position:relative;transition:.15s;display:flex;gap:8px;align-items:center}
.wpn:hover{transform:translateY(-1px);border-color:rgba(255,255,255,.3)}
.wpn.active{border-color:var(--accent);background:linear-gradient(180deg,#2a1f0a,#3a2a0e);box-shadow:0 0 0 2px rgba(255,176,46,.3),0 6px 16px rgba(0,0,0,.4)}
.wpn-ico{width:32px;height:32px;border-radius:8px;display:grid;place-items:center;font-size:18px;background:rgba(255,255,255,.08);border:1px solid rgba(255,255,255,.1);flex-shrink:0}
.wpn-name{font-weight:900;font-size:10px;line-height:1.1}
.wpn-meta{font-size:8px;opacity:.65;font-weight:800;letter-spacing:.5px}
.wpn-ammo{position:absolute;top:4px;right:6px;font-size:9px;font-weight:900;background:#0a1320;border:1px solid rgba(255,255,255,.2);padding:1px 5px;border-radius:99px}
.controls{display:flex;gap:6px;padding:8px;flex-wrap:wrap}
.ctrl{flex:1;min-width:72px;background:#0f223e;border:1.5px solid #2a4a78;border-radius:10px;padding:6px 8px;text-align:center}
.ctrl kbd{background:#fff;color:#0a1320;font-weight:900;font-size:10px;padding:1px 5px;border-radius:4px;box-shadow:0 2px 0 #9aa}
.ctrl div{font-size:9px;opacity:.7;margin-top:3px;font-weight:800;letter-spacing:.5px}
.power-wrap{margin:8px;background:rgba(0,0,0,.35);border-radius:99px;height:14px;overflow:hidden;border:1px solid rgba(255,255,255,.15);position:relative}
.power-fill{height:100%;width:0%;background:linear-gradient(90deg,#2ee6a6,#ffb02e 60%,#ff3b5c);transition:.05s}
.power-label{position:absolute;inset:0;display:grid;place-items:center;font-size:8px;font-weight:900;letter-spacing:1.5px}
.angle-read{font-family:'Space Grotesk';font-weight:700;font-size:11px;opacity:.8;text-align:center;margin-top:2px}
.bottom-bar{display:flex;gap:8px;padding:8px}
.btn{flex:1;padding:10px;border-radius:12px;border:2px solid #fff;font-weight:900;letter-spacing:1px;font-size:11px;cursor:pointer;transition:.15s}
.btn-primary{background:linear-gradient(180deg,var(--accent),#e68f00);color:#0a1320;box-shadow:0 4px 12px rgba(255,176,46,.4)}
.btn-ghost{background:rgba(255,255,255,.08);color:#fff;border-color:rgba(255,255,255,.2)}
.btn:active{transform:scale(.97)}
.util-grid{display:grid;grid-template-columns:1fr 1fr 1fr;gap:6px;padding:0 8px 8px}
.util{background:linear-gradient(180deg,#1b3a5e,#102642);border:1px solid rgba(61,255,180,.25);border-radius:10px;padding:6px;text-align:center;cursor:pointer}
.util b{font-size:9px;display:block}
.util span{font-size:14px}
#startScreen{position:absolute;inset:0;background:radial-gradient(800px 500px at 50% 20%, rgba(255,176,46,.25), transparent 60%), linear-gradient(180deg,rgba(10,19,32,.92),rgba(10,19,32,.98));display:grid;place-items:center;z-index:20;padding:18px}
.start-card{width:min(560px,100%);background:linear-gradient(180deg,rgba(255,255,255,.1),rgba(255,255,255,.05));backdrop-filter:blur(16px);border:1px solid rgba(255,255,255,.15);border-radius:20px;padding:18px;box-shadow:0 20px 60px rgba(0,0,0,.5)}
.start-card h1{font-family:'Bangers';font-size:42px;line-height:.9;letter-spacing:1px;text-shadow:0 3px 0 #000}
.start-card h1 em{color:var(--accent);font-style:normal}
.start-card p{opacity:.75;font-size:13px;line-height:1.5;margin:8px 0 14px}
.opts{display:grid;grid-template-columns:1fr 1fr;gap:10px;margin-bottom:12px}
.opt{background:rgba(0,0,0,.3);border:1px solid rgba(255,255,255,.12);border-radius:12px;padding:10px}
.opt label{font-size:9px;letter-spacing:1.5px;opacity:.7;font-weight:900}
.opt select,.opt input{width:100%;margin-top:6px;background:#0a1320;color:#fff;border:1px solid #2a4060;border-radius:8px;padding:8px;font-weight:800}
.chips{display:flex;gap:6px;flex-wrap:wrap;margin-top:8px}
.chip{padding:6px 10px;border-radius:99px;border:1px solid rgba(255,255,255,.15);background:rgba(255,255,255,.06);font-size:11px;font-weight:900;cursor:pointer}
.chip.active{background:var(--accent);color:#0a1320;border-color:#fff}
@media(max-width:980px){#main{grid-template-columns:1fr;grid-template-rows: 420px auto auto}header{height:auto;flex-wrap:wrap;padding:8px} .hud-center{order:3;width:100%;justify-content:space-between} .opts{grid-template-columns:1fr} .logo{font-size:24px}}
</style>
</head>
<body>
<header>
  <div class="logo">🐛 WORM<span>SHELL</span> <small>DELUXE</small></div>
  <div class="hud-center">
    <div class="wind-box">
      <div style="text-align:center;line-height:1"><div class="wind-label">WIND</div><div id="windArrow" style="font-size:14px;transition:.4s">→</div></div>
      <div class="wind-bar"><div id="windFill" class="wind-fill"></div></div>
      <div class="wind-val" id="windVal">+3</div>
      <button class="btn btn-ghost" style="padding:6px 8px;font-size:10px" onclick="randomWind()">↻</button>
    </div>
    <div class="timer-box" id="timerBox"><div class="timer-num" id="timerNum">30</div><div class="timer-sub">SECONDS LEFT</div></div>
    <div class="turn-badge" id="turnBadge"><span class="dot" id="turnDot"></span><span id="turnText">TEAM RED — WORM 1</span></div>
  </div>
  <div style="display:flex;gap:6px;align-items:center">
    <button class="btn btn-ghost" style="padding:8px 10px" onclick="togglePause()">⏸</button>
    <button class="btn btn-ghost" style="padding:8px 10px" onclick="showStart()">☰</button>
  </div>
</header>

<div id="main">
  <!-- LEFT -->
  <div class="panel" style="overflow:auto">
    <div class="panel-head"><h3>◉ SQUADS</h3><span style="font-size:10px;opacity:.6" id="roundInfo">Round 1</span></div>
    <div id="teamList"></div>
    <div style="padding:10px;border-top:1px solid rgba(255,255,255,.08)">
      <div style="font-size:9px;letter-spacing:1px;opacity:.6;font-weight:900">WATER LEVEL</div>
      <div style="height:6px;background:#0a1320;border-radius:99px;overflow:hidden;margin-top:6px;border:1px solid #204060"><div style="height:100%;width:100%;background:linear-gradient(90deg,#0ea5e9,#22d3ee)"></div></div>
      <div style="font-size:9px;opacity:.6;margin-top:4px">Touch water = 25 damage per turn + drowning risk</div>
    </div>
  </div>

  <!-- CENTER GAME -->
  <div class="game-wrap" id="gameWrap">
    <canvas id="c" width="1000" height="520"></canvas>
    <div id="overlay"></div>
    <div id="startScreen">
      <div class="start-card">
        <h1>WORM<em>SHELL</em> BATTLE</h1>
        <p>Hot-seat chaos with wind physics, destructible islands & goofy armory. Aim the dashed trajectory, charge power with <b>hold mouse / SPACE</b>, and blast friends into the sea. Includes water, falling debris & screen shake!</p>
        <div class="opts">
          <div class="opt"><label>TERRAIN SHAPE</label><select id="mapSel"><option value="island">🏝️ Single Island</option><option value="two">🏝️🏝️ Two Islands</option><option value="cave">🕳️ Cave Bunker</option></select></div>
          <div class="opt"><label>TEAMS</label><select id="teamSel"><option value="2">2 Teams</option><option value="3">3 Teams</option><option value="4">4 Teams</option></select></div>
          <div class="opt"><label>CPU OPPONENTS</label><select id="cpuSel"><option value="0">No CPU (Hot-seat only)</option><option value="1">1 CPU Team</option><option value="2">2 CPU Teams</option><option value="3">3 CPU Teams</option></select></div>
          <div class="opt"><label>TURN TIME</label><select id="timeSel"><option value="20">20s Fast</option><option value="30" selected>30s Classic</option><option value="45">45s Casual</option></select></div>
        </div>
        <div style="font-size:9px;letter-spacing:1px;opacity:.6;font-weight:900;margin-bottom:6px">QUICK PRESETS</div>
        <div class="chips">
          <div class="chip active" onclick="preset(this,'island',2)">Classic Island</div>
          <div class="chip" onclick="preset(this,'two',3)">Twin Peaks</div>
          <div class="chip" onclick="preset(this,'cave',4)">Cave Mayhem</div>
        </div>
        <button class="btn btn-primary" style="width:100%;margin-top:14px;padding:14px;font-size:13px;letter-spacing:1.5px" onclick="startGame()">▶ DEPLOY WORMS — START BATTLE</button>
        <div style="display:flex;gap:6px;margin-top:8px;flex-wrap:wrap">
          <span class="ctrl" style="flex:1"><kbd>A D</kbd><div>Move</div></span>
          <span class="ctrl" style="flex:1"><kbd>W</kbd><div>Jump</div></span>
          <span class="ctrl" style="flex:1"><kbd>Mouse</kbd><div>Aim & Charge</div></span>
          <span class="ctrl" style="flex:1"><kbd>Space</kbd><div>Fire</div></span>
        </div>
      </div>
    </div>
  </div>

  <!-- RIGHT -->
  <div class="panel" style="overflow:auto">
    <div class="panel-head"><h3>⚔ ARMORY</h3><span style="font-size:10px;background:var(--accent);color:#0a1320;padding:2px 6px;border-radius:99px;font-weight:900" id="ammoInfo">∞</span></div>
    <div class="cat-tabs" id="catTabs"></div>
    <div class="weapons-grid" id="weaponsGrid"></div>
    <div class="panel-head"><h3>🧰 UTILITY</h3><span style="font-size:9px;opacity:.6">CREATIVE ITEMS</span></div>
    <div class="util-grid">
      <div class="util" onclick="useUtility('jetpack')"><span>🚀</span><b>JETPACK</b><small style="opacity:.6;font-size:8px">5s Fuel</small></div>
      <div class="util" onclick="useUtility('teleport')"><span>🌀</span><b>TELEPORT</b><small style="opacity:.6;font-size:8px">Click map</small></div>
      <div class="util" onclick="useUtility('girder')"><span>🧱</span><b>GIRDER</b><small style="opacity:.6;font-size:8px">Place block</small></div>
    </div>
    <div style="padding:8px;border-top:1px solid rgba(255,255,255,.08)">
      <div style="display:flex;justify-content:space-between;align-items:center"><span style="font-size:9px;letter-spacing:1px;font-weight:900;opacity:.7">POWER</span><span id="powerPct" style="font-size:11px;font-weight:900">0%</span></div>
      <div class="power-wrap"><div id="powerFill" class="power-fill"></div><div class="power-label">HOLD TO CHARGE — RELEASE TO FIRE</div></div>
      <div class="angle-read" id="angleRead">Angle 42° • Wind affects arc • Trajectory preview on</div>
      <div class="bottom-bar" style="padding:8px 0 0">
        <button class="btn btn-ghost" onclick="skipTurn()">SKIP ↷</button>
        <button class="btn btn-primary" onclick="firePressed()">FIRE ●</button>
      </div>
      <div style="display:flex;gap:6px;margin-top:6px">
        <span class="ctrl" style="flex:1"><kbd id="wKey">W</kbd><div>Jump</div></span>
        <span class="ctrl" style="flex:1"><kbd>A</kbd><div>←</div></span>
        <span class="ctrl" style="flex:1"><kbd>D</kbd><div>→</div></span>
      </div>
    </div>
  </div>
</div>

<script>
const canvas=document.getElementById('c'), ctx=canvas.getContext('2d');
const overlay=document.getElementById('overlay');
const W=canvas.width, H=canvas.height;
let WORLD_W=2000, WORLD_H=520;
let terrain=[], ceiling=[]; // heightmaps
let waterY = H-72;
let wind = 3; // -10 .. 10
let gravity=0.45;
let teams=[], currentTeam=0, currentWorm=0, turnTime=30, turnLeft=30, timerId=null, isPaused=false, gameStarted=false, round=1;
let selectedCat='All', selectedWpn='bazooka';
let charging=false, chargePower=0, aimAngle= -Math.PI/5, isAiming=false, mousePos={x:W/2,y:H/2};
let camera={x:0,y:0,scale:1,shake:0};
let projectiles=[], particles=[], debris=[], floaters=[], wormsFlat=[];
let phase='aim'; // aim, fired, ending
let activeProjectile=null;
let jetpackFuel=0, teleportMode=false, girderMode=false;
let keys={};

const TEAM_DEFS=[
  {name:'RED', color:'#ff3b5c', accent:'#ff6b7a', icon:'🦑'},
  {name:'BLUE', color:'#2e7bff', accent:'#6aa0ff', icon:'🐙'},
  {name:'YELLOW', color:'#ffb02e', accent:'#ffd27a', icon:'🐤'},
  {name:'MINT', color:'#1fe0a0', accent:'#7ff3cd', icon:'🐢'},
];
const WEAPONS=[
  {id:'bazooka', name:'Bazooka', cat:'Indirect', icon:'🚀', dmg:42, radius:62, ammo:Infinity, desc:'Parabolic • Wind'},
  {id:'grenade', name:'Grenade', cat:'Indirect', icon:'🍍', dmg:45, radius:58, ammo:3, desc:'Bounce • 3s fuse'},
  {id:'mortar', name:'Mortar', cat:'Indirect', icon:'💣', dmg:28, radius:48, ammo:2, desc:'Cluster x3'},
  {id:'shotgun', name:'Shotgun', cat:'Direct', icon:'🔫', dmg:22, radius:16, ammo:2, desc:'2× instant ray'},
  {id:'sniper', name:'Sniper', cat:'Direct', icon:'🎯', dmg:48, radius:12, ammo:2, desc:'Instant • No drop'},
  {id:'banana', name:'Banana Bomb', cat:'AoE', icon:'🍌', dmg:78, radius:96, ammo:1, desc:'HUGE • Chaos'},
  {id:'airstrike', name:'Airstrike', cat:'AoE', icon:'✈️', dmg:35, radius:52, ammo:1, desc:'3× from sky'},
  {id:'dynamite', name:'Dynamite', cat:'AoE', icon:'🧨', dmg:60, radius:72, ammo:2, desc:'Drop • 3s'},
];
const CATS=['All','Indirect','Direct','AoE'];

/* Audio */
let actx;
function sfx(type){
  try{
    if(!actx) actx=new (window.AudioContext||window.webkitAudioContext)();
    const o=actx.createOscillator(), g=actx.createGain();
    o.connect(g); g.connect(actx.destination);
    const t=actx.currentTime;
    if(type==='fire'){o.frequency.setValueAtTime(120,t);o.frequency.exponentialRampToValueAtTime(40,t+.18);g.gain.setValueAtTime(.4,t);g.gain.exponentialRampToValueAtTime(.01,t+.25)}
    else if(type==='explosion'){o.frequency.setValueAtTime(80,t);o.frequency.linearRampToValueAtTime(20,t+.5);g.gain.setValueAtTime(.7,t);g.gain.exponentialRampToValueAtTime(.01,t+.6);o.type='sawtooth'}
    else if(type==='bounce'){o.frequency.setValueAtTime(300,t);o.frequency.linearRampToValueAtTime(150,t+.08);g.gain.setValueAtTime(.25,t);g.gain.exponentialRampToValueAtTime(.01,t+.12)}
    else if(type==='jump'){o.frequency.setValueAtTime(400,t);o.frequency.linearRampToValueAtTime(600,t+.12);g.gain.setValueAtTime(.3,t);g.gain.exponentialRampToValueAtTime(.01,t+.15)}
    else if(type==='hurt'){o.frequency.setValueAtTime(600,t);o.frequency.linearRampToValueAtTime(200,t+.18);g.gain.setValueAtTime(.35,t);g.gain.exponentialRampToValueAtTime(.01,t+.22)}
    else if(type==='tick'){o.frequency.setValueAtTime(900,t);g.gain.setValueAtTime(.18,t);g.gain.exponentialRampToValueAtTime(.01,t+.08)}
    else if(type==='select'){o.frequency.setValueAtTime(700,t);o.frequency.linearRampToValueAtTime(900,t+.08);g.gain.setValueAtTime(.2,t);g.gain.exponentialRampToValueAtTime(.01,t+.12)}
    else if(type==='jet'){o.frequency.setValueAtTime(180,t);o.frequency.linearRampToValueAtTime(260,t+.2);g.gain.setValueAtTime(.25,t);g.gain.exponentialRampToValueAtTime(.01,t+.25);o.type='square'}
    o.start(t); o.stop(t+.6);
  }catch(e){}
}

/* Terrain generation */
function generateTerrain(shape){
  terrain=new Array(WORLD_W).fill(0);
  ceiling=new Array(WORLD_W).fill(0);
  const base = H-110;
  if(shape==='island'){
    for(let x=0;x<WORLD_W;x++){
      const nx = (x/WORLD_W -0.5)*2; // -1..1
      const hill = Math.cos(nx*Math.PI*0.9)*120;
      const detail = Math.sin(x*0.03)*10 + Math.sin(x*0.012)*18;
      const bump = Math.max(0, 40*Math.sin(x*0.008));
      terrain[x]= base - Math.max(0,hill+detail+bump*0.3);
    }
  } else if(shape==='two'){
    for(let x=0;x<WORLD_W;x++){
      const p = x/WORLD_W;
      let h=0;
      if(p<0.48) h = Math.sin(p*6.2)*80 + Math.cos(p*12)*12;
      else if(p>0.62) h = Math.sin((p-0.14)*6.2)*80 + Math.cos(p*12)*12;
      else h = -40 - Math.abs(p-0.55)*300; // water gap
      terrain[x]= base - Math.max(-10,h) + (Math.random()*6-3);
    }
  } else if(shape==='cave'){
    for(let x=0;x<WORLD_W;x++){
      const hill = Math.sin(x*0.006)*22 + Math.cos(x*0.018)*12;
      terrain[x]= base - 30 + hill;
      ceiling[x]= 70 + Math.sin(x*0.01)*18 + Math.cos(x*0.02)*10;
      // carve cave opening in middle
      const dist = Math.abs(x - WORLD_W/2);
      if(dist<520){
        const carve = Math.cos(dist/520*Math.PI/2)*120;
        ceiling[x]+= carve*0.55;
        terrain[x]+= carve*0.08;
      }
    }
  }
  // clamp
  for(let i=0;i<WORLD_W;i++){ terrain[i]=Math.max(90,Math.min(H-40, terrain[i])); if(ceiling[i]) ceiling[i]=Math.max(28,Math.min(220,ceiling[i])); }
}

function getGroundY(x){
  x=Math.max(0,Math.min(WORLD_W-1, Math.floor(x)));
  return terrain[x];
}
function getCeilingY(x){
  if(!ceiling[x] || ceiling[x]===0) return -999;
  return ceiling[x];
}

/* Worms */
function spawnTeams(n, cpuCount){
  teams=[];
  wormsFlat=[];
  for(let t=0;t<n;t++){
    const def=TEAM_DEFS[t];
    const isCPU = t >= n - cpuCount;
    let worms=[];
    for(let i=0;i<4;i++){
      let x;
      if(n===2) x = (t===0? 260+i*90 : WORLD_W-260 - i*90);
      else x = 180 + (WORLD_W-360)*( (t*4+i+0.5)/(n*4) ) + (Math.random()*40-20);
      x=Math.max(40,Math.min(WORLD_W-40,x));
      const y=getGroundY(x)-14;
      worms.push({x,y,vx:0,vy:0,health:100,alive:true,team:t,idx:i,aim:aimAngle,power:0, onGround:true, drown:0});
    }
    teams.push({id:t,name:def.name,color:def.color,accent:def.accent,icon:def.icon,worms, cpu:isCPU, score:0});
  }
}

function activeWorm(){
  if(!teams.length) return null;
  const tm=teams[currentTeam];
  if(!tm) return null;
  // find next alive worm in team
  let w=tm.worms[currentWorm];
  if(w && w.alive) return w;
  for(let i=0;i<tm.worms.length;i++){ if(tm.worms[i].alive) {currentWorm=i; return tm.worms[i];}}
  return null;
}
function nextTurn(){
  // check win
  const aliveTeams=teams.filter(t=>t.worms.some(w=>w.alive));
  if(aliveTeams.length<=1){
    phase='over';
    setTimeout(()=>alert((aliveTeams[0]? aliveTeams[0].name : 'Nobody')+' WINS! 🏆 Refresh to play again.'),200);
    return;
  }
  // rotate
  let tries=0;
  do{
    currentTeam=(currentTeam+1)%teams.length;
    if(teams[currentTeam].worms.some(w=>w.alive)) break;
    tries++; if(tries>10) break;
  }while(true);
  // next worm in team
  let tm=teams[currentTeam];
  let start=currentWorm;
  for(let k=1;k<=tm.worms.length;k++){
    let idx=(start+k)%tm.worms.length;
    if(tm.worms[idx].alive){currentWorm=idx;break;}
  }
  turnLeft=turnTime;
  phase='aim'; chargePower=0; charging=false;
  jetpackFuel=0;
  updateHUD();
  // wind jitter
  if(Math.random()<.35) wind+= (Math.random()*2-1)*1.2;
  wind=Math.max(-10,Math.min(10,wind));
  updateWindUI();
  if(teams[currentTeam].cpu) setTimeout(cpuTurn, 900);
}

function randomWind(){ wind = Math.floor(Math.random()*21)-10; if(wind===0) wind=2; updateWindUI(); sfx('select'); }
function updateWindUI(){
  document.getElementById('windVal').textContent=(wind>0?'+'+wind:wind);
  const pct=((wind+10)/20)*100;
  document.getElementById('windFill').style.width=pct+'%';
  document.getElementById('windArrow').textContent= wind>1?'→': wind<-1?'←':'↔';
  document.getElementById('windArrow').style.transform=`scaleX(${wind===0?1: Math.sign(wind)})`;
}

/* HUD */
function buildTeamList(){
  const el=document.getElementById('teamList'); el.innerHTML='';
  teams.forEach((t,ti)=>{
    const d=document.createElement('div'); d.className='team-card'+(ti===currentTeam?' active':'');
    const alive=t.worms.filter(w=>w.alive).length;
    d.innerHTML=`
      <div class="team-ico" style="background:${t.color};color:#fff">${t.icon}</div>
      <div class="team-info">
        <div class="team-name" style="color:${t.color}">${t.name} ${t.cpu?'🤖':''} <span style="font-size:9px;background:${alive?'#1fe0a0':'#ff3b5c'};color:#0a1320;padding:1px 5px;border-radius:99px">${alive}/4</span></div>
        <div class="team-worms">${t.worms.map(w=>`
          <div class="worm-pip" style="border-color:${t.color};opacity:${w.alive?1:.35}">
            <div class="health-fill" style="width:${w.health}%;background:${w.health>50?'#1fe0a0':w.health>25?'#ffb02e':'#ff3b5c'}"></div>
            <b style="color:${w.health>55?'#0a1320':'#fff'}">${w.alive?w.health:'✕'}</b>
          </div>`).join('')}</div>
      </div>
      <div style="font-size:10px;opacity:.55;font-weight:900">${t.score}★</div>
    `;
    el.appendChild(d);
  });
}
function buildWeapons(){
  const tabs=document.getElementById('catTabs'); tabs.innerHTML='';
  CATS.forEach(c=>{
    const b=document.createElement('button'); b.className='cat-tab'+(selectedCat===c?' active':''); b.textContent=c.toUpperCase(); b.onclick=()=>{selectedCat=c; buildWeapons(); sfx('select')}; tabs.appendChild(b);
  });
  const grid=document.getElementById('weaponsGrid'); grid.innerHTML='';
  WEAPONS.filter(w=>selectedCat==='All'||w.cat===selectedCat).forEach(w=>{
    const d=document.createElement('div'); d.className='wpn'+(selectedWpn===w.id?' active':'');
    d.innerHTML=`<div class="wpn-ico">${w.icon}</div><div><div class="wpn-name">${w.name.toUpperCase()}</div><div class="wpn-meta">${w.desc} • ${w.dmg} DMG</div></div><div class="wpn-ammo">${w.ammo===Infinity?'∞':w.ammo}</div>`;
    d.onclick=()=>{selectedWpn=w.id; buildWeapons(); updateHUD(); sfx('select')};
    grid.appendChild(d);
  });
}
function updateHUD(){
  buildTeamList();
  const w=activeWorm();
  const tm=teams[currentTeam];
  if(tm){
    document.getElementById('turnDot').style.background=tm.color;
    document.getElementById('turnText').textContent=`TEAM ${tm.name} — WORM ${ (currentWorm+1)} ${tm.cpu?'(CPU)':''} ${w && w.health<=0? 'KO':''}`;
  }
  document.getElementById('powerFill').style.width=chargePower+'%';
  document.getElementById('powerPct').textContent=Math.round(chargePower)+'%';
  const wpn=WEAPONS.find(x=>x.id===selectedWpn);
  document.getElementById('ammoInfo').textContent=wpn? (wpn.ammo===Infinity?'∞':wpn.ammo+' left') : '';
  const deg=Math.round((-aimAngle*180/Math.PI));
  document.getElementById('angleRead').textContent=`Angle ${deg}° • Wind ${wind>0?'+'+wind:wind} • ${phase==='aim'?'Aim & hold to charge':'Firing...'}`;
}

/* Input */
canvas.addEventListener('mousemove', e=>{
  const rect=canvas.getBoundingClientRect();
  const sx=(e.clientX-rect.left)*(canvas.width/rect.width);
  const sy=(e.clientY-rect.top)*(canvas.height/rect.height);
  // convert screen to world
  const wx = camera.x + sx / camera.scale;
  const wy = camera.y + sy / camera.scale;
  mousePos={x:wx,y:sy, sx, sy, wy};
  if(phase==='aim' && !charging){
    const w=activeWorm(); if(!w) return;
    const dx=wx - w.x, dy=wy - w.y;
    aimAngle=Math.atan2(dy,dx);
    // clamp -PI to 0 for upward aim? allow full 360 but keep bias
    updateHUD();
  }
});
canvas.addEventListener('mousedown', e=>{
  if(phase!=='aim') return;
  const tm=teams[currentTeam]; if(tm.cpu) return;
  if(teleportMode){ doTeleport(mousePos.x); return; }
  if(girderMode){ placeGirder(mousePos.x); return; }
  charging=true; chargePower=0;
});
canvas.addEventListener('mouseup', e=>{
  if(charging){ charging=false; tryFire(); }
});
canvas.addEventListener('click', e=>{
  if(teleportMode || girderMode) e.preventDefault();
});
window.addEventListener('keydown', e=>{
  keys[e.key.toLowerCase()]=true;
  if(e.code==='Space'){ if(phase==='aim' && !charging && !teams[currentTeam]?.cpu){ charging=true; chargePower=0; e.preventDefault(); } }
  if(e.key.toLowerCase()==='w'){ jumpWorm(); }
});
window.addEventListener('keyup', e=>{
  keys[e.key.toLowerCase()]=false;
  if(e.code==='Space' && charging){ charging=false; tryFire(); }
});
function jumpWorm(){
  const w=activeWorm(); if(!w||!w.alive) return;
  if(w.onGround){ w.vy=-8.5; w.onGround=false; w.vx += (keys['a']?-2:0)+(keys['d']?2:0); sfx('jump'); createDust(w.x,w.y,6)}
}

/* Utilities */
function useUtility(type){
  sfx('select');
  if(type==='jetpack'){
    const w=activeWorm(); if(!w) return;
    jetpackFuel=100; phase='aim';
    floatText(w.x,w.y-30,'JETPACK!', '#2ee6a6');
  } else if(type==='teleport'){
    teleportMode=true; floatText(activeWorm().x, activeWorm().y-30,'CLICK TO TELEPORT',' #6aa0ff'); setTimeout(()=>teleportMode=false,6000);
  } else if(type==='girder'){
    girderMode=true; floatText(activeWorm().x, activeWorm().y-30,'CLICK TO PLACE GIRDER','#ffb02e'); setTimeout(()=>girderMode=false,6000);
  }
}
function doTeleport(wx){
  const w=activeWorm(); w.x=Math.max(20,Math.min(WORLD_W-20,wx)); w.y=getGroundY(w.x)-14; w.vx=w.vy=0; createDust(w.x,w.y,12); teleportMode=false; sfx('bounce');
}
function placeGirder(wx){
  const gy=getGroundY(wx)-8;
  // create flat ground segment
  for(let dx=-45;dx<=45;dx++){ const x=Math.floor(wx+dx); if(x>=0&&x<WORLD_W) terrain[x]=Math.min(terrain[x], gy); }
  debris.push({x:wx,y:gy-6,w:90,h:8,vy:0,life:999, girder:true});
  girderMode=false; sfx('bounce');
}

/* Firing */
function firePressed(){ if(charging) return; charging=true; chargePower=0; setTimeout(()=>{ if(charging){charging=false; tryFire();}}, 400); }
function tryFire(){
  if(phase!=='aim') return;
  const power = Math.max(12, chargePower); // 0-100
  fireWeapon(selectedWpn, power, aimAngle);
  chargePower=0; updateHUD();
}
function fireWeapon(id, power, angle){
  const w=activeWorm(); if(!w) return;
  const wpn=WEAPONS.find(x=>x.id===id);
  // ammo check
  if(wpn.ammo!==Infinity){
    if(wpn.ammo<=0){ floatText(w.x,w.y-30,'NO AMMO!','#ff3b5c'); return; }
    wpn.ammo--;
    buildWeapons();
  }
  const spd = (id==='sniper'? 22 : id==='shotgun'? 18 : 0.22*power + 4); // map power to speed
  const vx=Math.cos(angle)*spd + wind*0.06;
  const vy=Math.sin(angle)*spd;
  phase='fired';
  sfx('fire');

  if(id==='shotgun'){
    for(let i=0;i<2;i++){
      setTimeout(()=>{
        // raycast instant
        const hit = raycast(w.x,w.y, angle + (i===0? -0.04:0.04), 1200);
        createExplosion(hit.x,hit.y, wpn.radius, wpn.dmg, false);
        camera.shake=6; impactFreeze(80);
        applyDamage(hit.x,hit.y, wpn.radius, wpn.dmg, w);
        addTracer(w.x,w.y,hit.x,hit.y);
      }, i*180);
    }
    setTimeout(()=>{ phase='aim'; nextTurnIfNeeded(); }, 700);
    return;
  }
  if(id==='sniper'){
    const hit=raycast(w.x,w.y,angle,2000);
    createExplosion(hit.x,hit.y, 14, wpn.dmg, false);
    applyDamage(hit.x,hit.y, 18, wpn.dmg+12, w);
    addTracer(w.x,w.y,hit.x,hit.y);
    camera.shake=4; impactFreeze(60);
    setTimeout(()=>nextTurnIfNeeded(), 500);
    return;
  }
  if(id==='airstrike'){
    for(let k=0;k<3;k++){
      const tx = w.x + Math.cos(angle)* (180 + k*70) + (Math.random()*60-30);
      setTimeout(()=>airStrikeAt(tx), k*320);
    }
    setTimeout(()=>nextTurnIfNeeded(), 1400);
    return;
  }
  // projectile
  const p={ x:w.x + Math.cos(angle)*18, y:w.y + Math.sin(angle)*12, vx, vy, radius:5, wpn:id, dmg:wpn.dmg, blast:wpn.radius, bounce: id==='grenade'? 0.62:0, fuse: id==='grenade'||id==='dynamite'? 180 : 9999, life:600, owner:w, trail:[]};
  if(id==='dynamite'){ p.vx*=0.35; p.vy=-2; p.fuse=110; }
  projectiles.push(p); activeProjectile=p;
  if(id==='banana') p.spin=0.3;
}
function airStrikeAt(tx){
  let bomb={x:tx, y: -40, vx: wind*0.05, vy: 6, radius:6, wpn:'airstrike', dmg:35, blast:52, life:600, trail:[]};
  projectiles.push(bomb); sfx('fire');
}
function raycast(x,y,angle,len){
  const step=6;
  for(let d=0; d<len; d+=step){
    const nx=x+Math.cos(angle)*d, ny=y+Math.sin(angle)*d;
    if(ny>= getGroundY(nx)-2) return {x:nx, y:ny-2, hit:true};
    const cy=getCeilingY(nx); if(cy>-500 && ny<=cy+2) return {x:nx,y:ny,hit:true};
    if(nx<0||nx>WORLD_W) return {x:nx,y:ny,hit:false};
  }
  return {x:x+Math.cos(angle)*len, y:y+Math.sin(angle)*len, hit:false};
}
function addTracer(x1,y1,x2,y2){
  particles.push({x:x1,y:y1,x2,y2,t:0,life:10, type:'tracer'});
}

/* Damage & Explosion */
function applyDamage(cx,cy, radius, dmg, owner){
  teams.forEach(tm=>tm.worms.forEach(w=>{
    if(!w.alive) return;
    const dx=w.x-cx, dy=w.y-cy;
    const dist=Math.hypot(dx,dy);
    if(dist<radius+18){
      const fall= Math.max(0, 1 - dist/(radius+18));
      let take=Math.round(dmg*fall);
      if(take<5 && dist<radius) take=5;
      if(take>0){
        w.health-=take; if(w.health<=0){w.health=0; w.alive=false; createExplosion(w.x,w.y, 26,0,false); sfx('explosion')} else sfx('hurt');
        floatText(w.x,w.y-26, '-'+take, take>35?'#ff3b5c':'#ffb02e');
        // knockback
        const kb = (radius*0.18 + take*0.2)* (fall+0.3);
        const ang=Math.atan2(dy,dx);
        w.vx+= Math.cos(ang)*kb*0.9;
        w.vy+= Math.sin(ang)*kb*0.9 -2;
        w.onGround=false;
        if(owner) owner.team!==undefined && teams[owner.team].score+= take;
      }
    }
  }));
  destroyTerrain(cx,cy,radius);
}
function destroyTerrain(cx,cy,r){
  const r2=r*r;
  for(let dx=-r;dx<=r;dx++){
    const x=Math.floor(cx+dx); if(x<0||x>=WORLD_W) continue;
    const dy=Math.sqrt(Math.max(0,r2-dx*dx));
    // ground carve
    const top = cy - dy;
    const bottom = cy + dy;
    // if explosion near ground, lower terrain
    const g=getGroundY(x);
    if(top < g && bottom > g-30){
      // carve crater
      const depth = Math.max(0, (r - Math.abs(dx))*0.55 );
      terrain[x]+= depth*1.1;
      if(terrain[x]>H-38) terrain[x]=H-38;
      // falling debris chunk
      if(Math.abs(dx)<r*0.7 && Math.random()<0.22){
        debris.push({x:x,y:g-6,w:6+Math.random()*10,h:4+Math.random()*8, vx:(Math.random()-0.5)*3 + wind*0.08, vy: Math.random()*-2, life: 220, girder:false});
      }
    }
    // ceiling carve for cave
    const ceil=getCeilingY(x);
    if(ceil>-500 && top < ceil+12 && bottom > ceil-18){
      ceiling[x]-= Math.max(0, (r - Math.abs(dx))*0.5);
    }
  }
}

function createExplosion(x,y,r,dmg,isChain){
  // particles
  const n= Math.round(r*0.9)+12;
  for(let i=0;i<n;i++){
    const a=Math.random()*Math.PI*2, s=Math.random()* (r*0.18+3);
    particles.push({x,y,vx:Math.cos(a)*s, vy:Math.sin(a)*s - Math.random()*3, life: 18+Math.random()*18, t:0, type:'fire', r:2+Math.random()*4, col: Math.random()<0.5?'#ffb02e':'#ff3b5c'});
  }
  for(let i=0;i<9;i++) particles.push({x,y,vx:(Math.random()-0.5)*6, vy:(Math.random()-0.5)*6 -2, life:24, t:0, type:'smoke'});
  for(let i=0;i<6;i++) particles.push({x,y,vx:(Math.random()-0.5)*4, vy:Math.random()*-4, life:30, t:0, type:'debris'});
  camera.shake = Math.max(camera.shake, Math.min(18, r*0.18));
  if(r>60) impactFreeze(110);
  sfx('explosion');
  createDust(x,y, Math.round(r*0.3));
}
function createDust(x,y,n){
  for(let i=0;i<n;i++) particles.push({x,y: y, vx:(Math.random()-0.5)*3, vy: Math.random()*-2.5, life:14+Math.random()*10, t:0, type:'dust'});
}
function floatText(x,y,txt,col){
  const el=document.createElement('div'); el.className='float-txt'; el.textContent=txt; el.style.color=col||'#fff';
  // project world to screen
  const sx=(x - camera.x)*camera.scale, sy=(y - camera.y)*camera.scale;
  el.style.left=sx+'px'; el.style.top=sy+'px';
  overlay.appendChild(el); setTimeout(()=>el.remove(),1000);
}
let freezeUntil=0;
function impactFreeze(ms){ freezeUntil=performance.now()+ms; }

/* Physics loops */
function updateProjectiles(){
  for(let i=projectiles.length-1;i>=0;i--){
    const p=projectiles[i];
    p.trail.push({x:p.x,y:p.y}); if(p.trail.length>14) p.trail.shift();
    // wind & gravity
    p.vx+= wind*0.008;
    p.vy+= gravity* (p.wpn==='airstrike'?0.24:0.48);
    p.x+= p.vx; p.y+= p.vy;
    if(p.spin) p.angle=(p.angle||0)+p.spin;
    // trail smoke
    if(Math.random()<0.5) particles.push({x:p.x,y:p.y,vx:(Math.random()-0.5)*0.8, vy:(Math.random()-0.5)*0.8, life:12, t:0, type:'smoke', r:2});
    // collision with terrain / ceiling / bounds
    const g=getGroundY(p.x), c=getCeilingY(p.x);
    let hit=false, hitY=g;
    if(p.y >= g-4){ p.y=g-4; hit=true; hitY=g; }
    if(c>-500 && p.y <= c+4){ p.y=c+4; p.vy*=-0.5; hit=true; hitY=c; }
    if(p.x<0||p.x>WORLD_W){ // bounce off walls
      p.vx*=-0.6; p.x=Math.max(1,Math.min(WORLD_W-1,p.x)); sfx('bounce');
    }
    if(hit){
      if(p.bounce>0 && p.fuse>30){
        p.vy*=-p.bounce; p.vx*=0.92; sfx('bounce'); createDust(p.x,p.y,4);
      } else {
        // explode
        const wpn=WEAPONS.find(w=>w.id===p.wpn) || {dmg:30, radius:50};
        let radius=wpn.radius, dmg=wpn.dmg;
        // cluster for mortar
        if(p.wpn==='mortar' && !p.isCluster){
          createExplosion(p.x,p.y, 42, 0,false);
          destroyTerrain(p.x,p.y, 42); applyDamage(p.x,p.y,44, 18, p.owner);
          for(let k=0;k<3;k++){
            projectiles.push({x:p.x+ (Math.random()-0.5)*10, y:p.y-8, vx:(Math.random()-0.5)*5 + wind*0.05, vy: Math.random()*-4 -2, radius:4, wpn:'mortar_cluster', dmg:18, blast:36, life:120, fuse:40, trail:[]});
          }
          projectiles.splice(i,1); continue;
        }
        if(p.wpn==='mortar_cluster'){ radius=36; dmg=18; }
        if(p.wpn==='banana'){ radius=92; dmg=78; }
        createExplosion(p.x,p.y, radius, dmg,false);
        destroyTerrain(p.x,p.y, radius);
        applyDamage(p.x,p.y, radius, dmg, p.owner);
        projectiles.splice(i,1);
        activeProjectile=null;
        setTimeout(()=>nextTurnIfNeeded(), 420);
        continue;
      }
    }
    // fuse timeout (grenade)
    if(p.fuse!==9999){ p.fuse--; if(p.fuse<=0){
        const wpn=WEAPONS.find(w=>w.id===p.wpn) || {dmg:30,radius:50};
        createExplosion(p.x,p.y, wpn.radius, wpn.dmg,false); destroyTerrain(p.x,p.y, wpn.radius); applyDamage(p.x,p.y, wpn.radius, wpn.dmg,p.owner);
        projectiles.splice(i,1); activeProjectile=null; setTimeout(()=>nextTurnIfNeeded(), 380);
    }}
    p.life--; if(p.life<=0){ projectiles.splice(i,1); activeProjectile=null; }
    // hit worms directly (approx)
    for(let tm of teams) for(let w of tm.worms) if(w.alive){
      if(Math.hypot(w.x-p.x, w.y-p.y)<14){
        const wpn=WEAPONS.find(x=>x.id===p.wpn)||{radius:50,dmg:30};
        createExplosion(p.x,p.y, wpn.radius, wpn.dmg,false); destroyTerrain(p.x,p.y, wpn.radius); applyDamage(p.x,p.y, wpn.radius, wpn.dmg,p.owner);
        projectiles.splice(i,1); activeProjectile=null; setTimeout(()=>nextTurnIfNeeded(), 380); break;
      }
    }
  }
}
function nextTurnIfNeeded(){
  if(phase==='fired' || projectiles.length===0){
    phase='aim';
    // wait for particles to settle a bit
    if(debris.some(d=>!d.girder && d.life>0 && Math.abs(d.vy)>0.5)) { setTimeout(nextTurnIfNeeded, 300); return; }
    nextTurn();
  }
}
function updateWorms(){
  wormsFlat = teams.flatMap(t=>t.worms);
  for(let w of wormsFlat){
    if(!w.alive) continue;
    // jetpack
    if(w===activeWorm() && jetpackFuel>0){
      if(keys['w']||keys[' ']||keys['arrowup']){ w.vy-=0.55; jetpackFuel-=1.2; particles.push({x:w.x, y:w.y+8, vx:(Math.random()-0.5)*1.2, vy: Math.random()*2+1, life:10, t:0, type:'fire', r:2}); if(Math.random()<0.2) sfx('jet'); }
      if(keys['a']) w.vx-=0.28;
      if(keys['d']) w.vx+=0.28;
      w.vy+=0.12; // lighter gravity
    } else {
      w.vy+= gravity*0.62;
    }
    w.vx*=0.96; // friction
    w.vx+= wind*0.003; // subtle wind drift
    w.x+= w.vx; w.y+= w.vy;

    // world bounds
    w.x=Math.max(10,Math.min(WORLD_W-10,w.x));
    const g=getGroundY(w.x);
    const c=getCeilingY(w.x);
    if(w.y >= g-10){ // on ground
      if(w.vy>10){ // fall damage
        const dmg=Math.round((w.vy-10)*2.2);
        if(dmg>0){ w.health-=dmg; floatText(w.x,w.y-20,'-'+dmg+' FALL','#ff3b5c'); sfx('hurt'); if(w.health<=0){w.health=0; w.alive=false;}}
      }
      w.y=g-10; w.vy=0; w.onGround=true;
      // slope slide
      const leftG=getGroundY(w.x-6), rightG=getGroundY(w.x+6);
      const slope=(rightG-leftG)*0.05; w.vx+= slope*0.12;
    } else {
      w.onGround=false;
    }
    if(c>-500 && w.y <= c+10){ w.y=c+10; w.vy= Math.max(0,w.vy); }
    // girders as platforms
    for(let d of debris) if(d.girder){
      if(Math.abs(w.x-d.x)<d.w/2 && w.y >= d.y-10 && w.y <= d.y+4 && w.vy>=0){ w.y=d.y-10; w.vy=0; w.onGround=true; }
    }
    // water
    if(w.y > waterY-8){
      w.drown+=1;
      w.vy = Math.min(w.vy, 1.2); w.vy-=0.35; // float
      w.vx*=0.92;
      if(w.drown%22===0){ w.health-= 18; floatText(w.x,w.y-18,'DROWNING!','#22d3ee'); sfx('hurt'); particles.push({x:w.x,y:waterY, vx:(Math.random()-0.5)*2, vy:Math.random()*-1, life:16,t:0,type:'splash'}); }
      if(w.health<=0){ w.alive=false; w.health=0; particles.push({x:w.x,y:w.y,vx:0,vy:-2,life:20,t:0,type:'splash'}); }
      // bob
      w.y = Math.min(w.y, waterY+6);
    } else w.drown=Math.max(0,w.drown-1);

    // clamp health ui
    if(w.health<0) w.health=0;
  }
  // manual movement for active worm (limited)
  const aw=activeWorm();
  if(aw && aw.alive && phase==='aim' && jetpackFuel===0){
    let mv=0; if(keys['a']) mv=-2.1; if(keys['d']) mv=2.1;
    if(mv!==0 && aw.onGround){
      const nx=aw.x+mv;
      const ng=getGroundY(nx);
      // allow only gentle slopes
      if(Math.abs(ng - getGroundY(aw.x))<14){ aw.x=nx; }
    }
  }
}
function updateDebris(){
  for(let i=debris.length-1;i>=0;i--){
    const d=debris[i];
    if(d.girder) continue;
    d.vy+=0.45; d.x+=d.vx; d.y+=d.vy; d.vx*=0.99;
    const g=getGroundY(d.x);
    if(d.y+g<0) continue;
    if(d.y >= g - d.h/2){ d.y=g - d.h/2; d.vy*=-0.28; d.vx*=0.82; if(Math.abs(d.vy)<0.6){ d.vy=0; d.life-=2; if(d.life<=0) debris.splice(i,1);} }
    d.life--; if(d.life<=0) debris.splice(i,1);
    if(d.y> waterY){ // splash
      if(Math.random()<0.08) particles.push({x:d.x,y:waterY,vx:(Math.random()-0.5)*2, vy:Math.random()*-2, life:12,t:0,type:'splash'});
      d.vy*=0.9; d.vx*=0.95;
    }
  }
}
function updateParticles(){
  for(let i=particles.length-1;i>=0;i--){
    const p=particles[i]; p.t++; p.life--;
    if(p.type==='tracer'){ p.t+=1; if(p.t>10) particles.splice(i,1); continue; }
    p.x+= p.vx||0; p.y+= p.vy||0;
    if(p.type==='fire'||p.type==='debris') p.vy+=0.22;
    if(p.type==='dust'){ p.vy+=0.12; p.vx*=0.98;}
    if(p.life<=0) particles.splice(i,1);
  }
}

/* CPU logic with risk assessment */
function cpuTurn(){
  const w=activeWorm(); if(!w||phase!=='aim') return;
  // pick target: highest value (low health, close, not near water)
  let best=null, bestScore=-999;
  teams.forEach(tm=>tm.worms.forEach(tw=>{
    if(!tw.alive || tw.team===currentTeam) return;
    const dist=Math.hypot(tw.x-w.x, tw.y-w.y);
    let score= 100 - tw.health; // prioritize wounded
    score+= Math.max(0, 400 - dist)*0.1;
    // avoid targeting if ally nearby blast
    let allyNear=false;
    teams[currentTeam].worms.forEach(aw=>{
      if(!aw.alive) return;
      if(Math.hypot(aw.x-tw.x, aw.y-tw.y)< 80) allyNear=true;
    });
    if(allyNear) score-=60;
    // risk: target near cliff/water is bonus (knock off)
    const nearWater = (getGroundY(tw.x) > waterY-20) ? 15 : 0;
    score+= nearWater;
    // prefer not to be too close to self
    if(dist<90) score-=30;
    if(score>bestScore){bestScore=score; best=tw;}
  }));
  if(!best) best=teams.flatMap(t=>t.worms).find(x=>x.alive && x.team!==currentTeam);
  if(!best) return;
  // simulate simple aim: compute angle to hit with wind compensation
  let dx=best.x - w.x, dy=best.y - w.y;
  let dist=Math.hypot(dx,dy);
  // choose weapon based on distance and ammo
  let choice='bazooka';
  if(dist<120 && WEAPONS.find(x=>x.id==='shotgun').ammo>0) choice='shotgun';
  else if(dist<160 && WEAPONS.find(x=>x.id==='grenade').ammo>0) choice='grenade';
  else if(dist>350 && WEAPONS.find(x=>x.id==='airstrike').ammo>0) choice='airstrike';
  else if(dist>260 && WEAPONS.find(x=>x.id==='banana').ammo>0 && Math.random()<0.5) choice='banana';
  selectedWpn=choice; buildWeapons();
  // calculate angle & power via iterative approximation
  let power= 55 + dist*0.12 + Math.random()*12;
  power=Math.max(28,Math.min(98,power));
  let angle=Math.atan2(dy,dx);
  // adjust for gravity + wind: naive correction
  const windComp = -wind*0.02;
  angle+= windComp*(dist/400);
  // lob higher for far targets
  if(dist>280) angle-=0.18;
  if(dist<150) angle-=0.05;
  // avoid shooting into hill: if direct line hits ground, lob higher
  const test=raycast(w.x,w.y,angle, dist);
  if(test.hit && Math.hypot(test.x-w.x,test.y-w.y)<dist*0.7){ angle-=0.25; power+=10; }
  aimAngle=angle; chargePower=power; updateHUD();
  // animate charging then fire
  let cp=0;
  const iv=setInterval(()=>{
    cp+= 9+Math.random()*6; chargePower=Math.min(power,cp); updateHUD();
    if(cp>=power){ clearInterval(iv); setTimeout(()=>{ fireWeapon(choice, power, angle); chargePower=0; }, 160); }
  },40);
}

/* Drawing */
function draw(){
  // camera follow active worm or projectile
  let targetX= (activeProjectile? activeProjectile.x : (activeWorm()? activeWorm().x : WORLD_W/2)) - W/2;
  // also follow y a bit
  let targetY= (activeProjectile? activeProjectile.y : (activeWorm()? activeWorm().y : H/2)) - H/2;
  // clamp
  targetX=Math.max(-80, Math.min(WORLD_W - W +80, targetX));
  targetY=Math.max(-40, Math.min(80, targetY));
  camera.x += (targetX - camera.x)*0.08;
  camera.y += (targetY - camera.y)*0.08;
  if(camera.shake>0){ camera.x+= (Math.random()-0.5)*camera.shake; camera.y+= (Math.random()-0.5)*camera.shake; camera.shake*=0.9; if(camera.shake<0.2) camera.shake=0; }

  ctx.save();
  ctx.scale(canvas.width/W, canvas.height/H); // keep for responsive? we already set canvas size fixed, but use camera transform
  ctx.clearRect(0,0,W,H);
  ctx.save();
  ctx.translate(-camera.x, -camera.y);

  // sky gradient already CSS, but draw clouds
  ctx.globalAlpha=0.9;
  for(let i=0;i<4;i++){
    const cx= (i*480 + (Date.now()*0.02 + wind*12)%480) % (WORLD_W+200) -100;
    drawCloud(cx, 58+ i*14 + Math.sin(i)*10);
  }
  ctx.globalAlpha=1;

  // distant hills
  ctx.fillStyle='rgba(255,255,255,.14)';
  ctx.beginPath(); ctx.moveTo(-100, waterY);
  for(let x=-100;x<WORLD_W+100;x+=14){ const y= waterY-60 - Math.sin(x*0.005)*22 - Math.cos(x*0.01)*10; ctx.lineTo(x,y); }
  ctx.lineTo(WORLD_W+100, waterY); ctx.closePath(); ctx.fill();

  // terrain fill
  drawTerrain();

  // water
  const wave=Math.sin(Date.now()*0.0015)*4;
  ctx.fillStyle='#0ea5e9';
  ctx.globalAlpha=0.92;
  ctx.fillRect(-100, waterY+wave, WORLD_W+200, H-waterY+100);
  ctx.fillStyle='rgba(255,255,255,.25)';
  for(let x=-100;x<WORLD_W+100;x+=22){
    ctx.fillRect(x+ (wave*2), waterY+wave-2, 14,2);
  }
  ctx.globalAlpha=1;
  // water shine
  ctx.strokeStyle='rgba(255,255,255,.22)'; ctx.lineWidth=2;
  ctx.beginPath(); ctx.moveTo(-100, waterY+wave); for(let x=-100;x<WORLD_W+100;x+=10) ctx.lineTo(x, waterY+wave + Math.sin(x*0.05+Date.now()*0.002)*2); ctx.stroke();

  // girders
  debris.forEach(d=>{
    if(!d.girder) return;
    ctx.fillStyle='#8b5a2b'; ctx.strokeStyle='#fff'; ctx.lineWidth=2;
    ctx.fillRect(d.x-d.w/2, d.y-d.h/2, d.w, d.h);
    ctx.strokeRect(d.x-d.w/2, d.y-d.h/2, d.w, d.h);
    // rivets
    ctx.fillStyle='#fff'; for(let rx=-d.w/2+8; rx<d.w/2; rx+=18) ctx.fillRect(d.x+rx, d.y-1, 4,2);
  });

  // debris chunks
  debris.forEach(d=>{
    if(d.girder) return;
    ctx.fillStyle='#5a3e1b'; ctx.strokeStyle='rgba(255,255,255,.6)'; ctx.lineWidth=1;
    ctx.fillRect(d.x-d.w/2, d.y-d.h/2, d.w, d.h);
    ctx.strokeRect(d.x-d.w/2, d.y-d.h/2, d.w, d.h);
  });

  // trajectory preview
  if(phase==='aim'){
    const w=activeWorm();
    if(w){
      drawTrajectory(w.x, w.y, aimAngle, (Math.max(12,chargePower)/100)*18+4, wind);
      // aim line + crosshair
      const crossX= w.x + Math.cos(aimAngle)*96, crossY= w.y + Math.sin(aimAngle)*96;
      ctx.strokeStyle='rgba(255,255,255,.9)'; ctx.setLineDash([4,4]); ctx.lineWidth=1.4;
      ctx.beginPath(); ctx.moveTo(w.x,w.y); ctx.lineTo(crossX,crossY); ctx.stroke(); ctx.setLineDash([]);
      ctx.strokeStyle='#fff'; ctx.lineWidth=2; ctx.beginPath(); ctx.arc(crossX,crossY,8,0,Math.PI*2); ctx.stroke();
      ctx.beginPath(); ctx.moveTo(crossX-8,crossY); ctx.lineTo(crossX+8,crossY); ctx.moveTo(crossX,crossY-8); ctx.lineTo(crossX,crossY+8); ctx.stroke();
    }
  }

  // worms
  teams.forEach(tm=>tm.worms.forEach(w=>{
    if(!w.alive){
      // tombstone
      ctx.fillStyle='rgba(0,0,0,.35)'; ctx.beginPath(); ctx.ellipse(w.x, getGroundY(w.x)-2, 16,4,0,0,Math.PI*2); ctx.fill();
      ctx.font='16px serif'; ctx.textAlign='center'; ctx.fillText('🪦', w.x, w.y-2);
      return;
    }
    // shadow
    ctx.fillStyle='rgba(0,0,0,.28)'; ctx.beginPath(); ctx.ellipse(w.x, getGroundY(w.x)-1, 13,4,0,0,Math.PI*2); ctx.fill();
    // body
    const isActive = w===activeWorm();
    ctx.save(); ctx.translate(w.x,w.y);
    // facing direction
    const face = Math.cos(w.aim||aimAngle) >0 ? 1: -1;
    ctx.scale(face,1);
    // worm shape
    ctx.fillStyle=isActive?'#fff':'#ffe9b0';
    ctx.strokeStyle=tm.color; ctx.lineWidth=3;
    ctx.beginPath(); ctx.ellipse(0,0,12,10,0,0,Math.PI*2); ctx.fill(); ctx.stroke();
    // belly
    ctx.fillStyle='#fff'; ctx.globalAlpha=0.9; ctx.beginPath(); ctx.ellipse(0,4,7,5,0,0,Math.PI*2); ctx.fill(); ctx.globalAlpha=1;
    // eyes
    ctx.fillStyle='#0a1320'; ctx.beginPath(); ctx.arc(4, -3, 2.4,0,Math.PI*2); ctx.arc(-2, -3, 2.4,0,Math.PI*2); ctx.fill();
    ctx.fillStyle='#fff'; ctx.beginPath(); ctx.arc(5,-3.8,1,0,Math.PI*2); ctx.arc(-1,-3.8,1,0,Math.PI*2); ctx.fill();
    // mouth
    ctx.strokeStyle='#0a1320'; ctx.lineWidth=1.2; ctx.beginPath(); ctx.arc(1,2,3,0.15,Math.PI-0.15); ctx.stroke();
    // helmet for active
    if(isActive){
      ctx.fillStyle=tm.color; ctx.beginPath(); ctx.ellipse(0,-8,9,5,0,0,Math.PI*2); ctx.fill();
      ctx.fillStyle='#fff'; ctx.font='7px sans-serif'; ctx.textAlign='center'; ctx.fillText('★',0,-5);
    }
    ctx.restore();
    // health bar
    ctx.fillStyle='rgba(0,0,0,.55)'; ctx.fillRect(w.x-16,w.y-18,32,6);
    ctx.fillStyle= w.health>50?'#1fe0a0': w.health>25?'#ffb02e':'#ff3b5c'; ctx.fillRect(w.x-15,w.y-17, Math.max(0, w.health*0.30),4);
    ctx.fillStyle='#fff'; ctx.font='700 7px Nunito'; ctx.textAlign='center'; ctx.fillText(w.health, w.x, w.y-13);
    if(isActive){
      ctx.fillStyle=tm.color; ctx.font='900 8px Nunito'; ctx.fillText('▲', w.x, w.y-24);
    }
  }));

  // projectiles
  projectiles.forEach(p=>{
    // trail
    ctx.strokeStyle='rgba(255,255,255,.55)'; ctx.lineWidth=2; ctx.beginPath();
    p.trail.forEach((t,i)=>{ if(i===0) ctx.moveTo(t.x,t.y); else ctx.lineTo(t.x,t.y); }); ctx.stroke();
    // bomb body
    ctx.save(); ctx.translate(p.x,p.y); if(p.angle) ctx.rotate(p.angle);
    const col = p.wpn==='banana'?'#ffdf2e' : p.wpn==='grenade'?'#6a8a2e' : '#1a1a1a';
    ctx.fillStyle=col; ctx.strokeStyle='#fff'; ctx.lineWidth=1.6;
    if(p.wpn==='banana'){
      ctx.beginPath(); ctx.ellipse(0,0,10,5,0,0,Math.PI*2); ctx.fill(); ctx.stroke();
      ctx.fillStyle='#0a1320'; ctx.font='8px serif'; ctx.textAlign='center'; ctx.fillText('🍌',0,3);
    } else {
      ctx.beginPath(); ctx.arc(0,0,6,0,Math.PI*2); ctx.fill(); ctx.stroke();
      ctx.fillStyle='#ff3b5c'; ctx.beginPath(); ctx.arc(2,-2,2,0,Math.PI*2); ctx.fill();
    }
    ctx.restore();
    // velocity line
    ctx.strokeStyle='rgba(255,255,255,.35)'; ctx.beginPath(); ctx.moveTo(p.x,p.y); ctx.lineTo(p.x-p.vx*2, p.y-p.vy*2); ctx.stroke();
  });

  // particles
  particles.forEach(p=>{
    if(p.type==='tracer'){
      ctx.strokeStyle='rgba(255,255,0,.95)'; ctx.lineWidth=2; ctx.beginPath(); ctx.moveTo(p.x,p.y); ctx.lineTo(p.x2,p.y2); ctx.stroke();
      // glow
      ctx.fillStyle='rgba(255,255,0,.3)'; ctx.beginPath(); ctx.arc(p.x2,p.y2,6,0,Math.PI*2); ctx.fill();
    } else if(p.type==='fire'){
      ctx.fillStyle=p.col||'#ffb02e'; ctx.globalAlpha=Math.max(0, p.life/20);
      ctx.beginPath(); ctx.arc(p.x,p.y,p.r||3,0,Math.PI*2); ctx.fill(); ctx.globalAlpha=1;
    } else if(p.type==='smoke'){
      ctx.fillStyle='rgba(90,90,90,.45)'; ctx.beginPath(); ctx.arc(p.x,p.y, 5+ (20-p.life)*0.18,0,Math.PI*2); ctx.fill();
    } else if(p.type==='dust'){
      ctx.fillStyle='rgba(180,140,90,.9)'; ctx.fillRect(p.x,p.y,2,2);
    } else if(p.type==='debris'){
      ctx.fillStyle='#3d2a10'; ctx.fillRect(p.x,p.y,3,3);
    } else if(p.type==='splash'){
      ctx.fillStyle='rgba(34,211,238,.9)'; ctx.beginPath(); ctx.arc(p.x,p.y,3,0,Math.PI*2); ctx.fill();
    }
  });

  ctx.restore(); // camera

  // vignette
  ctx.fillStyle='radial-gradient(600px 300px at 50% 40%, transparent, rgba(0,0,0,.18))';
}

function drawCloud(x,y){
  ctx.fillStyle='rgba(255,255,255,.92)';
  ctx.beginPath();
  ctx.ellipse(x,y,26,14,0,0,Math.PI*2);
  ctx.ellipse(x+18,y+4,22,12,0,0,Math.PI*2);
  ctx.ellipse(x-16,y+3,18,11,0,0,Math.PI*2);
  ctx.fill();
  ctx.fillStyle='rgba(255,255,255,.45)';
  ctx.beginPath(); ctx.ellipse(x,y+8,30,6,0,0,Math.PI*2); ctx.fill();
}
function drawTerrain(){
  // ground polygon
  ctx.fillStyle='#7ac04a';
  ctx.strokeStyle='#3a5f1f'; ctx.lineWidth=4;
  ctx.beginPath();
  ctx.moveTo(-100, H+20);
  for(let x=-100;x<WORLD_W+100;x+=6){
    const idx=Math.max(0,Math.min(WORLD_W-1, Math.floor(x)));
    const y= terrain[idx] || H-100;
    ctx.lineTo(x,y);
  }
  ctx.lineTo(WORLD_W+100, H+20); ctx.closePath();
  ctx.fill(); ctx.stroke();
  // grass highlight
  ctx.strokeStyle='#b6e67a'; ctx.lineWidth=3; ctx.beginPath();
  for(let x=-100;x<WORLD_W+100;x+=6){
    const y=terrain[Math.max(0,Math.min(WORLD_W-1,Math.floor(x)))]||H-100;
    if(x===-100) ctx.moveTo(x,y); else ctx.lineTo(x,y);
  } ctx.stroke();
  // underground texture dots
  ctx.fillStyle='rgba(0,0,0,.08)';
  for(let x=0;x<WORLD_W;x+=18){
    const y=terrain[x]+8;
    ctx.fillRect(x, y, 2,2);
  }
  // cave ceiling
  if(ceiling[0]!==0){
    ctx.fillStyle='#5d3a1a';
    ctx.strokeStyle='#2a1a0a'; ctx.lineWidth=3;
    ctx.beginPath(); ctx.moveTo(-100,-20); ctx.lineTo(WORLD_W+100,-20); ctx.lineTo(WORLD_W+100,0);
    for(let x=WORLD_W+100;x>=-100;x-=6){
      const idx=Math.max(0,Math.min(WORLD_W-1,Math.floor(x)));
      const y=ceiling[idx]||0; ctx.lineTo(x,y);
    }
    ctx.closePath(); ctx.fill(); ctx.stroke();
    // stalactites
    ctx.fillStyle='#3d2310';
    for(let x=0;x<WORLD_W;x+=70){ const y=ceiling[x]; if(y) ctx.fillRect(x,y,4,12+Math.random()*10); }
  }
}

function drawTrajectory(sx,sy, angle, speed, wind){
  const pts=[];
  let x=sx, y=sy, vx=Math.cos(angle)*speed, vy=Math.sin(angle)*speed;
  for(let i=0;i<40;i++){
    vx+= wind*0.014; vy+= gravity*0.48;
    x+= vx*2.2; y+= vy*2.2;
    pts.push({x,y});
    if(y>= getGroundY(x)-2) break;
    if(x<0||x>WORLD_W) break;
  }
  // dotted line with fading
  pts.forEach((p,i)=>{
    if(i%2!==0) return;
    const alpha=1 - i/40;
    ctx.fillStyle=`rgba(255,255,255,${0.9*alpha})`;
    ctx.strokeStyle=`rgba(0,0,0,${0.35*alpha})`; ctx.lineWidth=1;
    const r= i===pts.length-1?5:3;
    ctx.beginPath(); ctx.arc(p.x,p.y,r,0,Math.PI*2); ctx.fill(); ctx.stroke();
    if(i===pts.length-1){
      // impact marker
      ctx.strokeStyle='#ff3b5c'; ctx.lineWidth=2; ctx.beginPath(); ctx.arc(p.x,p.y,10,0,Math.PI*2); ctx.stroke();
      ctx.beginPath(); ctx.moveTo(p.x-8,p.y); ctx.lineTo(p.x+8,p.y); ctx.moveTo(p.x,p.y-8); ctx.lineTo(p.x,p.y+8); ctx.stroke();
    }
  });
}

/* Game loop */
let last=performance.now();
function loop(now){
  requestAnimationFrame(loop);
  const dt=Math.min(32, now-last); last=now;
  if(now < freezeUntil) return;
  if(!gameStarted || isPaused) { draw(); return; }

  // charging power animation + timer tension
  if(charging && phase==='aim'){
    chargePower+= 0.9 + Math.sin(now*0.008)*0.18;
    if(chargePower>100) chargePower=100;
    updateHUD();
  }
  // turn timer
  // handled via setInterval for second ticks elsewhere, but also tension scaling
  updateParticles(); updateDebris(); updateProjectiles(); updateWorms();
  draw();
  // update overlay positions for float texts (they are DOM, anchored) - already placed
}
requestAnimationFrame(loop);

/* Timer */
function startTimer(){
  clearInterval(timerId);
  timerId=setInterval(()=>{
    if(isPaused||phase!=='aim') return;
    turnLeft-=1; if(turnLeft<0) turnLeft=0;
    document.getElementById('timerNum').textContent=turnLeft;
    const box=document.getElementById('timerBox');
    if(turnLeft<=10){ box.classList.add('warn'); if(turnLeft>0) sfx('tick'); } else box.classList.remove('warn');
    // scale tension
    box.style.transform= turnLeft<=5? `scale(${1+ (5-turnLeft)*0.06})` : '';
    if(turnLeft<=0){ sfx('explosion'); nextTurn(); }
  },1000);
}

// controls helpers
function showStart(){ document.getElementById('startScreen').style.display='grid'; }
function hideStart(){ document.getElementById('startScreen').style.display='none'; }
function togglePause(){ isPaused=!isPaused; sfx('select'); }
function skipTurn(){ sfx('select'); nextTurn(); }
function preset(el,map,n){
  document.querySelectorAll('.chip').forEach(c=>c.classList.remove('active')); el.classList.add('active');
  document.getElementById('mapSel').value=map; document.getElementById('teamSel').value=n;
}

function startGame(){
  const shape=document.getElementById('mapSel').value;
  const nTeams=parseInt(document.getElementById('teamSel').value);
  const cpuN=parseInt(document.getElementById('cpuSel').value);
  turnTime=parseInt(document.getElementById('timeSel').value);
  generateTerrain(shape);
  spawnTeams(nTeams, Math.min(cpuN, nTeams));
  currentTeam=0; currentWorm=0; turnLeft=turnTime; round=1;
  wind= Math.floor(Math.random()*11)-5; if(wind===0) wind=2;
  updateWindUI(); buildWeapons(); updateHUD(); buildTeamList();
  // reset weapons ammo
  WEAPONS.forEach(w=>{
    if(w.id==='bazooka') w.ammo=Infinity;
    else if(w.id==='grenade') w.ammo=3;
    else if(w.id==='mortar') w.ammo=2;
    else if(w.id==='shotgun') w.ammo=2;
    else if(w.id==='sniper') w.ammo=2;
    else if(w.id==='banana') w.ammo=1;
    else if(w.id==='airstrike') w.ammo=1;
    else if(w.id==='dynamite') w.ammo=2;
  });
  projectiles=[]; particles=[]; debris=[]; camera.x=0; camera.y=0; camera.shake=0;
  gameStarted=true; hideStart(); startTimer();
  document.getElementById('timerNum').textContent=turnLeft;
  sfx('select');
  if(teams[currentTeam].cpu) setTimeout(cpuTurn,800);
}

// init preview
generateTerrain('island'); spawnTeams(2,0); buildWeapons(); updateWindUI(); updateHUD(); draw();

// resize canvas scaling
function fitCanvas(){
  const wrap=document.getElementById('gameWrap');
  const r=wrap.getBoundingClientRect();
  // keep internal resolution 1000x520, but CSS scales
}
window.addEventListener('resize', fitCanvas); fitCanvas();
</script>
</body>
</html>
<!-- agent-meta {"model":"meta/muse-spark-1.2","provider":"openrouter","persona":"basic","sessionId":"4c350c30-56ad-4f27-985d-6bd65ebdfcb6","tokensIn":526,"tokensOut":21220,"tokensTotal":21746,"cost":0.0908425,"turns":1,"toolCalls":0,"failedToolCalls":0,"timestamp":"2026-08-06T15:23:40.848Z"} -->