← Worms results

Worms

Gemini 3.6 Flash · 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 22,250 bytes · SHA-256 b0725f19b9ba
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Worms Reloaded - Artillery Mayhem</title>
<style>
/* ── css: main-styles ── */
* {
  box-sizing: border-box;
  user-select: none;
  -webkit-user-select: none;
  margin: 0;
  padding: 0;
}

body, html {
  width: 100%;
  height: 100%;
  overflow: hidden;
  background-color: #0d0e15;
  font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
  color: #fff;
}

#game-container {
  position: relative;
  width: 100vw;
  height: 100vh;
  overflow: hidden;
}

#gameCanvas {
  display: block;
  width: 100%;
  height: 100%;
  background: #000;
  cursor: crosshair;
}

/* UI Overlay Layer */
.ui-layer {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
}

.ui-layer button, .ui-layer input, .overlay-screen button, .overlay-screen input {
  pointer-events: auto;
}

/* Top HUD Bar */
#top-bar {
  position: absolute;
  top: 12px;
  left: 12px;
  right: 12px;
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  gap: 15px;
}

#team-bars {
  display: flex;
  flex-direction: column;
  gap: 6px;
  min-width: 200px;
}

.team-hud-row {
  display: flex;
  align-items: center;
  gap: 8px;
  background: rgba(15, 18, 28, 0.75);
  backdrop-filter: blur(8px);
  padding: 4px 10px;
  border-radius: 8px;
  border: 1px solid rgba(255, 255, 255, 0.1);
  box-shadow: 0 4px 12px rgba(0,0,0,0.4);
}

.team-hud-row.active {
  border-color: #fff;
  box-shadow: 0 0 10px rgba(255,255,255,0.4);
}

.team-hud-name {
  font-size: 13px;
  font-weight: 700;
  width: 110px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.team-hud-hp-bar {
  flex: 1;
  height: 12px;
  background: rgba(255,255,255,0.15);
  border-radius: 6px;
  overflow: hidden;
  position: relative;
}

.team-hud-hp-fill {
  height: 100%;
  width: 100%;
  transition: width 0.3s ease;
  border-radius: 6px;
}

.team-hud-hp-text {
  font-size: 11px;
  font-weight: bold;
  width: 45px;
  text-align: right;
}

/* Timer */
#timer-box {
  width: 64px;
  height: 64px;
  background: rgba(15, 18, 28, 0.85);
  backdrop-filter: blur(8px);
  border-radius: 50%;
  border: 3px solid #ffcc00;
  display: flex;
  align-items: center;
  justify-content: center;
  box-shadow: 0 0 15px rgba(255,204,0,0.4);
  position: relative;
}

#timer-text {
  font-size: 24px;
  font-weight: 900;
  color: #fff;
}

#timer-box.urgent {
  animation: pulse-urgent 0.5s infinite alternate;
  border-color: #ff3344;
  box-shadow: 0 0 20px rgba(255,51,68,0.8);
}

@keyframes pulse-urgent {
  from { transform: scale(1); }
  to { transform: scale(1.15); }
}

/* Wind Meter */
#wind-meter {
  background: rgba(15, 18, 28, 0.75);
  backdrop-filter: blur(8px);
  padding: 6px 14px;
  border-radius: 10px;
  border: 1px solid rgba(255,255,255,0.1);
  display: flex;
  flex-direction: column;
  align-items: center;
  width: 160px;
}

.hud-label {
  font-size: 10px;
  font-weight: 800;
  letter-spacing: 1px;
  color: #8a9bb8;
}

#wind-bar-container {
  width: 100%;
  height: 10px;
  background: rgba(255,255,255,0.15);
  border-radius: 5px;
  margin: 4px 0;
  position: relative;
  overflow: hidden;
}

#wind-center {
  position: absolute;
  left: 50%;
  top: 0;
  width: 2px;
  height: 100%;
  background: #fff;
  z-index: 2;
}

#wind-bar {
  position: absolute;
  top: 0;
  height: 100%;
  background: linear-gradient(90deg, #00d2ff, #3a7bd5);
  width: 0%;
  left: 50%;
  transition: all 0.2s ease;
}

#wind-text {
  font-size: 12px;
  font-weight: 800;
}

/* Turn Banner */
#turn-banner {
  position: absolute;
  top: 90px;
  left: 50%;
  transform: translateX(-50%);
  background: rgba(10, 12, 20, 0.9);
  padding: 10px 30px;
  border-radius: 30px;
  border: 2px solid #ffcc00;
  font-size: 20px;
  font-weight: 900;
  text-transform: uppercase;
  letter-spacing: 1px;
  box-shadow: 0 5px 25px rgba(0,0,0,0.6);
  animation: banner-pop 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275);
}

@keyframes banner-pop {
  0% { transform: translate(-50%, -20px) scale(0.8); opacity: 0; }
  100% { transform: translate(-50%, 0) scale(1); opacity: 1; }
}

/* Bottom Hotbar & Selector */
#weapon-hotbar {
  position: absolute;
  bottom: 20px;
  left: 50%;
  transform: translateX(-50%);
  display: flex;
  gap: 12px;
  align-items: center;
  background: rgba(15, 18, 28, 0.85);
  backdrop-filter: blur(10px);
  padding: 8px 16px;
  border-radius: 14px;
  border: 1px solid rgba(255,255,255,0.15);
  box-shadow: 0 8px 32px rgba(0,0,0,0.5);
}

#weapon-menu-btn {
  background: linear-gradient(135deg, #ff8c00, #ff4500);
  border: none;
  color: white;
  padding: 8px 16px;
  border-radius: 8px;
  font-weight: bold;
  cursor: pointer;
  transition: transform 0.1s, box-shadow 0.1s;
}

#weapon-menu-btn:hover {
  transform: translateY(-2px);
  box-shadow: 0 4px 12px rgba(255, 69, 0, 0.4);
}

#selected-weapon-display {
  display: flex;
  align-items: center;
  gap: 10px;
  background: rgba(0,0,0,0.4);
  padding: 6px 14px;
  border-radius: 8px;
  font-weight: bold;
}

#cur-wpn-icon { font-size: 20px; }
#cur-wpn-name { font-size: 15px; color: #fff; }
#cur-wpn-ammo { font-size: 13px; color: #ffcc00; }

/* Weapon Grid Popup */
#weapon-selector {
  position: absolute;
  bottom: 80px;
  left: 50%;
  transform: translateX(-50%);
  background: rgba(15, 18, 28, 0.95);
  backdrop-filter: blur(12px);
  border-radius: 16px;
  border: 1px solid rgba(255, 255, 255, 0.2);
  padding: 16px;
  width: 420px;
  max-width: 90vw;
  box-shadow: 0 10px 40px rgba(0,0,0,0.8);
}

.weapon-grid-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  font-size: 14px;
  font-weight: 800;
  color: #8a9bb8;
  margin-bottom: 12px;
}

#close-weapon-menu {
  background: none;
  border: none;
  color: #aaa;
  font-size: 16px;
  cursor: pointer;
}

#weapon-grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 8px;
}

.wpn-card {
  background: rgba(255,255,255,0.05);
  border: 1px solid rgba(255,255,255,0.1);
  border-radius: 10px;
  padding: 8px;
  display: flex;
  flex-direction: column;
  align-items: center;
  cursor: pointer;
  transition: all 0.15s ease;
}

.wpn-card:hover {
  background: rgba(255,255,255,0.15);
  border-color: #ffcc00;
  transform: translateY(-2px);
}

.wpn-card.active {
  background: rgba(255, 204, 0, 0.2);
  border-color: #ffcc00;
}

.wpn-card.disabled {
  opacity: 0.3;
  pointer-events: none;
}

.wpn-icon { font-size: 24px; margin-bottom: 4px; }
.wpn-title { font-size: 11px; font-weight: 700; text-align: center; }
.wpn-ammo { font-size: 10px; color: #ffcc00; margin-top: 2px; }

/* Overlays / Screens */
.overlay-screen {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background: rgba(8, 10, 16, 0.85);
  backdrop-filter: blur(12px);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 100;
}

.menu-card {
  background: linear-gradient(145deg, #161b26, #0e121a);
  border: 1px solid rgba(255,255,255,0.15);
  border-radius: 20px;
  padding: 32px;
  width: 480px;
  max-width: 90vw;
  box-shadow: 0 20px 60px rgba(0,0,0,0.8);
  display: flex;
  flex-direction: column;
  gap: 20px;
}

.title-logo {
  font-size: 32px;
  font-weight: 900;
  text-align: center;
  letter-spacing: 2px;
  color: #fff;
}

.title-logo span {
  color: #ff4500;
  display: inline-block;
}

.subtitle {
  text-align: center;
  font-size: 13px;
  color: #8a9bb8;
  margin-top: -16px;
}

.menu-section h3 {
  font-size: 12px;
  font-weight: 800;
  color: #8a9bb8;
  letter-spacing: 1px;
  margin-bottom: 8px;
}

.button-group {
  display: flex;
  gap: 8px;
}

.map-btn, .type-btn {
  flex: 1;
  padding: 8px 12px;
  background: rgba(255,255,255,0.05);
  border: 1px solid rgba(255,255,255,0.1);
  color: #ccc;
  border-radius: 8px;
  font-size: 12px;
  font-weight: 700;
  cursor: pointer;
  transition: all 0.15s;
}

.map-btn.active, .type-btn.active {
  background: rgba(255, 69, 0, 0.25);
  border-color: #ff4500;
  color: #fff;
}

#teams-config {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.team-config-row {
  display: flex;
  align-items: center;
  gap: 8px;
  background: rgba(0,0,0,0.3);
  padding: 6px 10px;
  border-radius: 8px;
}

.team-dot {
  width: 12px;
  height: 12px;
  border-radius: 50%;
}

.team-name-input {
  flex: 1;
  background: transparent;
  border: none;
  color: white;
  font-size: 13px;
  font-weight: bold;
  outline: none;
}

.primary-btn {
  background: linear-gradient(135deg, #ff4500, #ff8c00);
  border: none;
  color: white;
  padding: 14px;
  border-radius: 12px;
  font-size: 16px;
  font-weight: 900;
  letter-spacing: 1px;
  cursor: pointer;
  transition: transform 0.15s, box-shadow 0.15s;
  box-shadow: 0 6px 20px rgba(255, 69, 0, 0.4);
}

.primary-btn:hover {
  transform: translateY(-2px);
  box-shadow: 0 10px 25px rgba(255, 69, 0, 0.6);
}

.victory-card {
  text-align: center;
  align-items: center;
}

#victory-title {
  font-size: 36px;
  font-weight: 900;
  color: #ffcc00;
}

.hidden { display: none !important; }
</style>
</head>
<body>
<div id="game-container">
  <canvas id="gameCanvas"></canvas>
  
  <!-- HUD Overlay -->
  <div id="hud-overlay" class="ui-layer">
    <div id="top-bar">
      <div id="team-bars"></div>
      <div id="timer-box">
        <div id="timer-ring"></div>
        <div id="timer-text">45</div>
      </div>
      <div id="wind-meter">
        <div class="hud-label">WIND</div>
        <div id="wind-bar-container">
          <div id="wind-bar"></div>
          <div id="wind-center"></div>
        </div>
        <div id="wind-text">0</div>
      </div>
    </div>

    <div id="turn-banner" class="hidden">
      <span id="turn-team-name">Red Team</span>'s Turn!
    </div>

    <div id="weapon-hotbar">
      <button id="weapon-menu-btn" title="Weapon Menu (Right Click / W)">📦 WEAPONS</button>
      <div id="selected-weapon-display">
        <span id="cur-wpn-icon">🚀</span>
        <span id="cur-wpn-name">Bazooka</span>
        <span id="cur-wpn-ammo">∞</span>
      </div>
    </div>

    <!-- Full Weapon Selector Grid -->
    <div id="weapon-selector" class="hidden">
      <div class="weapon-grid-header">SELECT WEAPON <button id="close-weapon-menu">✕</button></div>
      <div id="weapon-grid"></div>
    </div>
  </div>

  <!-- Main Menu Overlay -->
  <div id="main-menu" class="overlay-screen">
    <div class="menu-card">
      <h1 class="title-logo">WORMS <span>RELOADED</span></h1>
      <p class="subtitle">Tactical Artillery Mayhem</p>

      <div class="menu-section">
        <h3>MAP SETTINGS</h3>
        <div class="button-group">
          <button class="map-btn active" data-map="island">🏝️ Single Island</button>
          <button class="map-btn" data-map="two_islands">🏝️🏝️ Two Islands</button>
          <button class="map-btn" data-map="cave">🦇 Cave</button>
        </div>
      </div>

      <div class="menu-section">
        <h3>TEAMS CONFIGURATION</h3>
        <div id="teams-config">
          <div class="team-config-row" data-team="0">
            <span class="team-dot" style="background:#ff3355;"></span>
            <input type="text" value="Red Commandos" class="team-name-input">
            <button class="type-btn active" data-type="human">HUMAN</button>
            <button class="type-btn" data-type="cpu">CPU (AI)</button>
          </div>
          <div class="team-config-row" data-team="1">
            <span class="team-dot" style="background:#3388ff;"></span>
            <input type="text" value="Blue Ninjas" class="team-name-input">
            <button class="type-btn" data-type="human">HUMAN</button>
            <button class="type-btn active" data-type="cpu">CPU (AI)</button>
          </div>
          <div class="team-config-row" data-team="2">
            <span class="team-dot" style="background:#33cc55;"></span>
            <input type="text" value="Green Gunners" class="team-name-input">
            <button class="type-btn disabled" data-type="off">OFF</button>
            <button class="type-btn" data-type="human">HUMAN</button>
            <button class="type-btn" data-type="cpu">CPU</button>
          </div>
          <div class="team-config-row" data-team="3">
            <span class="team-dot" style="background:#ffaa00;"></span>
            <input type="text" value="Yellow Bombers" class="team-name-input">
            <button class="type-btn disabled" data-type="off">OFF</button>
            <button class="type-btn" data-type="human">HUMAN</button>
            <button class="type-btn" data-type="cpu">CPU</button>
          </div>
        </div>
      </div>

      <button id="start-game-btn" class="primary-btn">START BATTLE 🚀</button>
    </div>
  </div>

  <!-- Game Over Screen -->
  <div id="game-over-screen" class="overlay-screen hidden">
    <div class="menu-card victory-card">
      <h2 id="victory-title">VICTORY!</h2>
      <p id="victory-subtitle">Red Commandos wins the match!</p>
      <button id="restart-btn" class="primary-btn">PLAY AGAIN 🔄</button>
    </div>
  </div>
</div>
<script>
'use strict';

// ── class: SoundFX ──
// Web Audio Synthesizer for procedural retro sound effects
class SoundFX {
  constructor() {
    this.ctx = null;
    this.enabled = true;
  }

  init() {
    if (!this.ctx) {
      const AudioCtx = window.AudioContext || window.webkitAudioContext;
      if (AudioCtx) this.ctx = new AudioCtx();
    }
    if (this.ctx && this.ctx.state === 'suspended') {
      this.ctx.resume();
    }
  }

  playTone(freq, duration, type = 'sine', volume = 0.2) {
    if (!this.enabled) return;
    this.init();
    if (!this.ctx) return;
    try {
      const osc = this.ctx.createOscillator();
      const gain = this.ctx.createGain();
      osc.type = type;
      osc.frequency.setValueAtTime(freq, this.ctx.currentTime);
      gain.gain.setValueAtTime(volume, this.ctx.currentTime);
      gain.gain.exponentialRampToValueAtTime(0.001, this.ctx.currentTime + duration);
      osc.connect(gain);
      gain.connect(this.ctx.destination);
      osc.start();
      osc.stop(this.ctx.currentTime + duration);
    } catch(e){}
  }

  playUiClick() {
    this.playTone(800, 0.05, 'square', 0.1);
  }

  playShoot() {
    this.init();
    if (!this.ctx) return;
    try {
      const osc = this.ctx.createOscillator();
      const gain = this.ctx.createGain();
      osc.type = 'sawtooth';
      osc.frequency.setValueAtTime(400, this.ctx.currentTime);
      osc.frequency.exponentialRampToValueAtTime(80, this.ctx.currentTime + 0.2);
      gain.gain.setValueAtTime(0.3, this.ctx.currentTime);
      gain.gain.exponentialRampToValueAtTime(0.01, this.ctx.currentTime + 0.2);
      osc.connect(gain);
      gain.connect(this.ctx.destination);
      osc.start();
      osc.stop(this.ctx.currentTime + 0.2);
    } catch(e){}
  }

  playExplosion(size = 1.0) {
    this.init();
    if (!this.ctx) return;
    try {
      const bufferSize = this.ctx.sampleRate * 0.4 * size;
      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;
      }
      const noise = this.ctx.createBufferSource();
      noise.buffer = buffer;

      const filter = this.ctx.createBiquadFilter();
      filter.type = 'lowpass';
      filter.frequency.setValueAtTime(600 / size, this.ctx.currentTime);
      filter.frequency.linearRampToValueAtTime(100, this.ctx.currentTime + 0.4 * size);

      const gain = this.ctx.createGain();
      gain.gain.setValueAtTime(0.5, this.ctx.currentTime);
      gain.gain.exponentialRampToValueAtTime(0.01, this.ctx.currentTime + 0.4 * size);

      noise.connect(filter);
      filter.connect(gain);
      gain.connect(this.ctx.destination);
      noise.start();
    } catch(e){}
  }

  playJump() {
    this.init();
    if (!this.ctx) return;
    try {
      const osc = this.ctx.createOscillator();
      const gain = this.ctx.createGain();
      osc.type = 'sine';
      osc.frequency.setValueAtTime(150, this.ctx.currentTime);
      osc.frequency.exponentialRampToValueAtTime(450, this.ctx.currentTime + 0.15);
      gain.gain.setValueAtTime(0.25, this.ctx.currentTime);
      gain.gain.exponentialRampToValueAtTime(0.01, this.ctx.currentTime + 0.15);
      osc.connect(gain);
      gain.connect(this.ctx.destination);
      osc.start();
      osc.stop(this.ctx.currentTime + 0.15);
    } catch(e){}
  }

  playSplash() {
    this.playTone(180, 0.3, 'triangle', 0.3);
  }

  playTimerTick(isUrgent = false) {
    this.playTone(isUrgent ? 900 : 500, 0.04, 'sine', isUrgent ? 0.2 : 0.08);
  }
}

// ── const: audioFX ──
const audioFX = new SoundFX();

// ── const: WEAPONS ──
// Weapons and Utilities configuration
const WEAPONS = [
  {
    id: 'bazooka',
    name: 'Bazooka',
    icon: '🚀',
    type: 'indirect',
    category: 'Weapon',
    ammo: Infinity,
    radius: 35,
    damage: 45,
    affectedByWind: true,
    bouncy: false,
    timer: null,
    desc: 'Classic rocket influenced by wind.'
  },
  {
    id: 'grenade',
    name: 'Grenade',
    icon: '💣',
    type: 'indirect',
    category: 'Weapon',
    ammo: Infinity,
    radius: 40,
    damage: 50,
    affectedByWind: false,
    bouncy: true,
    fuse: 3.0,
    desc: '3-second timed bouncy explosive.'
  },
  {
    id: 'shotgun',
    name: 'Shotgun',
    icon: '🔫',
    type: 'direct',
    category: 'Weapon',
    ammo: 5,
    radius: 18,
    damage: 25,
    affectedByWind: false,
    desc: 'Direct line-of-sight attack. 2 shots per turn.'
  },
  {
    id: 'sniper',
    name: 'Sniper Rifle',
    icon: '🎯',
    type: 'direct',
    category: 'Weapon',
    ammo: 3,
    radius: 12,
    damage: 60,
    affectedByWind: false,
    desc: 'High precision laser bullet with high pushback.'
  },
  {
    id: 'cluster_bomb',
    name: 'Cluster Bomb',
    icon: '💥',
    type: 'indirect',
    category: 'Weapon',
    ammo: 3,
    radius: 25,
    damage: 30,
    affectedByWind: true,
    bouncy: true,
    fuse: 2.5,
    clusters: 5,
    desc: 'Explodes into 5 mini bomblets.'
  },
  {
    id: 'holy_grenade',
    name: 'Holy Bomb',
    icon: '✨',
    type: 'indirect',
    category: 'Weapon',
    ammo: 1,
    radius: 75,
    damage: 85,
    affectedByWind: false,
    bouncy: true,
    fuse: 3.0,
    desc: 'Devastating high-AoE bomb. HALLELUJAH!'
  },
  {
    id: 'airstrike',
    name: 'Air Strike',
    icon: '✈️',
    type: 'map_alter',
    category: 'Weapon',
    ammo: 2,
    radius: 30,
    damage: 35,
    bombs: 5,
    desc: 'Target map location for heavy carpet bombing.'
  },
  {
    id: 'drill_rocket',
    name: 'Drill Rocket',
    icon: '🌀',
    type: 'map_alter',
    category: 'Weapon',
    ammo: 2,
    radius: 25,
    damage: 40,
    desc: 'Bores deep into terrain before exploding.'
  },
  {
    id: 'girder',
    name: 'Bridge Girder',
    icon: '🏗️',
    type: 'utility',
    category: 'Utility',
    ammo: 3,
    desc: 'Place a steel girder to cross gaps or block shots.'
  },
  {
    id: 'teleport',
    name: 'Teleporter',
    icon: '🌀',
    type: 'utility',
    category: 'Utility',
    ammo: 2,
    desc: 'Instantly beam active worm to any target location.'
  }
];

// ── class: FloatingText ──
// Physics Engine, Particle System, Terrain Debris, Floating Text

class FloatingText {
  constructor(x, y, text, color = '#ff3344', scale = 1.0) {
    this.x = x;
    this.y = y;
    this.text = text;
    this.color = color;
    this.scale = scale;
    this.vy = -1.5;
    this.alpha = 1.0;
    this.life = 1.0;
  }

  update(dt) {
    this.y += this.vy;
    this.life -= dt * 0.8;
    this.alpha = Math.max(0, this.life);
  }

  draw(ctx) {
    ctx.save();
    ctx.globalAlpha = this.alpha;
    ctx.fillStyle = this.color;
    ctx.strokeStyle = '#000';
    ctx.lineWidth = 3;
    ctx.font = `900 ${Math.round(18 * this.scale)}px sans-serif`;
    ctx.textAlign = 'center';
    ctx.strokeText(this.text, this.x, this.y);
    ctx.fillText(this.text, this.x, this.y);
    ctx.restore();
  }
}

// ── class: Particle ──
class Particle {
  constructor(x, y, vx, vy, radius, color, life = 1.0, isSmoke = false) {
    this.x = x;
    this.y = y;
    this.vx = vx;
    this.vy = vy;
    this.radius = radius;
    this.color = color;
    this.life = life;
    this.maxLife = life;
    this.isSmoke = isSmoke;
  }

  update(dt, windX = 0) {
    this.x += (this.vx + windX * (this.isSmoke ? 0.3 : 0.05)) * dt * 60;
    this.y += this.vy * dt * 60;
    if (!this.isSmoke) this.vy += 0.2; // Gravity
    this.life -= dt;
  }

  draw(ctx) {
    if (this.life <= 0) return;
    ctx.save();
    const progress = this.life / this.maxLife;
    ctx.globalAlpha = Math.max(0, progress);
    ctx.fillStyle = this.color;
    ctx.beginPath();
    ctx.arc(this.x, this.y, this.radius * (this.isSmoke ? (2 - progress) : progress), 0, Math.PI * 2);
    ctx.fill();
    ctx.restore();
  }
}

// ── class: TerrainDebrisChunk ──
class TerrainDebrisChunk {
  constructor(x, y, vx, vy, size, color) {
    this.x = x;
    this.y = y;
    this.vx = vx;
    this.vy = vy;
    this.size = size;
    this.color = color;
    this.rotation = Math.random() * Math.PI * 2;
    this.vRot = (Math.random() - 0.5) * 0.2;
    this.active = true;
  }

  update(dt, terrain, windX) {
    if (!this.active) return;
    this.vy += 0.3; // Gravity
    this.vx += windX * 0.02;
    this.x += this.vx;
    this.y += this.vy;
    this.rotation += this.vRot;

    // Check collision with water or terrain
    if (this.y >= terrain.height - 30) {
      this.active = false; // Submerged
      return;
    }

    if (terrain.isSolid(Math.round(this.x), Math.round(this.y))) {
      this.vx *= -0.3;
      this.vy *= -0.3;
      if (Math.hypot(this.vx, this.vy) < 0.5) {
        this.active = false;
      }
    }
  }

  draw(ctx) {
    if (!this.active) return;
    ctx.save();
    ctx.translate(this.x, this.y);
    ctx.rotate(this.rotation);
    ctx.fillStyle = this.color;
    ctx.fillRect(-this.size/2, -this.size/2, this.size, this.size);
    ctx.restore();
  }
}
</script>
</body>
</html>