Загрузка данных


<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="utf-8">
<title>Space Miner Shooter — health pickups</title>
<style>
  * { margin:0; padding:0; box-sizing:border-box; }
  body { background:#000; overflow:hidden; font-family:monospace; cursor:crosshair; }
  canvas { display:block; }
  #hud {
    position:fixed; top:190px; left:12px;
    color:#0ff; font-size:14px; line-height:1.6;
    text-shadow:0 0 6px #0ff;
    pointer-events:none; user-select:none;
  }
  #hud .label { color:#888; }
  #hud .gold { color:#fc4; }
  #hud .green { color:#4f4; }
  #hud .orange { color:#f80; }
  #hint {
    position:fixed; bottom:14px; left:50%; transform:translateX(-50%);
    color:#0ff; font-size:13px; text-shadow:0 0 6px #0ff;
    opacity:1; transition:opacity 1s; pointer-events:none;
    user-select:none; text-align:center;
  }
  #over {
    position:fixed; inset:0; display:none; flex-direction:column;
    align-items:center; justify-content:center; color:#fff;
    background:rgba(0,0,0,.75); text-align:center; font-size:20px;
  }
  #over h1 { color:#f44; margin-bottom:16px; letter-spacing:2px; }
  #over button {
    margin-top:20px; padding:10px 30px; background:#0ff; border:none;
    font-family:monospace; font-size:16px; cursor:pointer;
  }
</style>
</head>
<body>
<canvas id="game"></canvas>
<div id="hud"></div>
<div id="hint">W/S — тяга · A/D — поворот · Мышь — прицел · ЛКМ — пули · ПКМ — ракеты · 1/2/3 — прокачка</div>
<div id="over">
  <h1>КОРАБЛЬ УНИЧТОЖЕН</h1>
  <div id="finalStats"></div>
  <button onclick="restart()">В БОЙ СНОВА</button>
</div>

<script>
// ============================================================
//  SPACE MINER SHOOTER — v12
//  • Аптечки падают с ботов (всегда +20) и с астероидов (30%, +25)
//  • Пассивный реген: +1 HP/сек через 3 сек без урона
//  • Пули: fireRate 50, damage 50
//  • ЛКМ + ПКМ одновременно
//  • Боты 3 фракций воюют и качаются
// ============================================================
const canvas = document.getElementById('game');
const ctx    = canvas.getContext('2d');
const hud    = document.getElementById('hud');
const hintEl = document.getElementById('hint');
const overEl = document.getElementById('over');
const finalStats = document.getElementById('finalStats');

// ---------- Утилиты ----------
const rand  = (a, b) => Math.random() * (b - a) + a;
const randI = (a, b) => Math.floor(rand(a, b + 1));
const dist  = (a, b) => Math.hypot(a.x - b.x, a.y - b.y);
const mod   = (n, m) => ((n % m) + m) % m;

function normalizeAngle(a) {
  while (a >  Math.PI) a -= Math.PI * 2;
  while (a < -Math.PI) a += Math.PI * 2;
  return a;
}
function angleLerp(current, target, t) {
  const diff = normalizeAngle(target - current);
  return normalizeAngle(current + diff * t);
}

// ---------- Фракции ----------
const FACTION_COLORS = [
  { stroke: '#ff3355', glow: '#ff3355', fill: 'rgba(255,50,85,0.15)' },
  { stroke: '#ffaa33', glow: '#ffaa33', fill: 'rgba(255,170,50,0.15)' },
  { stroke: '#cc66ff', glow: '#cc66ff', fill: 'rgba(200,100,255,0.15)' },
];
let factionCounter = 0;

// ---------- Константы ----------
const HEAL_PER_UPGRADE    = 15;
const DESPAWN_DIST        = 2000;
const MINIMAP_SIZE        = 160;
const MINIMAP_X           = 12;
const MINIMAP_Y           = 12;
const MAX_TURRET_OFFSET   = 120 * Math.PI / 180;
const MAX_BOT_LEVEL       = 5;

// Лечение
const HEALTH_DROP_ENEMY   = 20;      // +20 HP с бота
const HEALTH_DROP_ASTEROID= 25;      // +25 HP с астероида
const ASTEROID_HEAL_CHANCE= 0.30;    // 30% что астероид дропнет аптечку
const HEALTH_PICKUP_LIFE  = 1200;    // 20 сек живёт аптечка
const HEALTH_MAGNET_RANGE = 150;

// Пассивный реген
const REGEN_DELAY         = 180;     // 3 сек без урона
const REGEN_INTERVAL      = 60;      // +1 HP в секунду
const REGEN_AMOUNT        = 1;

const camera = { x: 0, y: 0 };

// ---------- Звёзды ----------
let stars = [];
function generateStars() {
  stars = Array.from({ length: 160 }, () => ({
    x: Math.random() * canvas.width,
    y: Math.random() * canvas.height,
    s: Math.random() * 1.5 + 0.3,
    a: 0.3 + Math.random() * 0.5,
  }));
}

function resize() {
  canvas.width  = window.innerWidth;
  canvas.height = window.innerHeight;
  generateStars();
}
window.addEventListener('resize', resize);
resize();

// ---------- Ввод ----------
const keys = {};
const mouse = {
  x: canvas.width / 2,
  y: canvas.height / 2 - 100,
  leftDown: false,
  rightDown: false,
};

function setKey(e, down) {
  const code = e.code || '';
  const key  = e.key  || '';
  if (code === 'Space' || key === ' ') {
    keys['Space'] = down;
    if (e.preventDefault) e.preventDefault();
    return;
  }
  if (code) keys[code] = down;
}

window.addEventListener('keydown', e => {
  setKey(e, true);
  if (e.code === 'Digit1') upgrade('damage');
  if (e.code === 'Digit2') upgrade('fireRate');
  if (e.code === 'Digit3') upgrade('speed');
  if (e.code === 'KeyR' && gameState.gameOver) restart();
});
window.addEventListener('keyup', e => setKey(e, false));

window.addEventListener('mousemove', e => {
  mouse.x = e.clientX;
  mouse.y = e.clientY;
});

canvas.addEventListener('mousedown', e => {
  if (e.button === 0) { mouse.leftDown  = true; e.preventDefault(); }
  if (e.button === 2) { mouse.rightDown = true; e.preventDefault(); }
});
canvas.addEventListener('mouseup', e => {
  if (e.button === 0) mouse.leftDown  = false;
  if (e.button === 2) mouse.rightDown = false;
});
window.addEventListener('mouseup', e => {
  if (e.button === 0) mouse.leftDown  = false;
  if (e.button === 2) mouse.rightDown = false;
});
window.addEventListener('blur', () => {
  mouse.leftDown = false;
  mouse.rightDown = false;
});
canvas.addEventListener('mouseleave', () => {
  mouse.leftDown = false;
  mouse.rightDown = false;
});
canvas.addEventListener('contextmenu', e => e.preventDefault());

canvas.addEventListener('touchstart', e => {
  mouse.leftDown = true;
  if (e.touches[0]) { mouse.x = e.touches[0].clientX; mouse.y = e.touches[0].clientY; }
  e.preventDefault();
}, { passive:false });
canvas.addEventListener('touchend',   () => { mouse.leftDown = false; }, { passive:true });
canvas.addEventListener('touchmove',  e => {
  if (e.touches[0]) {
    mouse.x = e.touches[0].clientX;
    mouse.y = e.touches[0].clientY;
  }
}, { passive:true });

// ---------- Состояние ----------
let gameState = {};

function init() {
  camera.x = -canvas.width  / 2;
  camera.y = -canvas.height / 2;
  factionCounter = 0;

  gameState = {
    player: {
      x: 0, y: 0,
      vx: 0, vy: 0,
      bodyAngle: -Math.PI / 2,
      targetBodyAngle: -Math.PI / 2,
      turretAngle: -Math.PI / 2,
      radius: 14, maxHealth: 100, health: 100,
      fireCooldown: 0,
      fireRate: 50,
      missileCooldown: 0,
      missileFireRate: 75,
      damage: 50,
      thrust: 0.04,
      reverseThrust: 0.03,
      turnSpeed: 0.022,
      bodySmooth: 0.18,
      maxSpeed: 1.5,
      invuln: 0,
      lastDamageTime: -1000,    // когда последний раз получал урон
      regenTick: 0,             // тикер регена
    },
    bullets: [], enemyBullets: [], missiles: [],
    asteroids: [], enemies: [],
    resources: [], healthPickups: [], particles: [], floaters: [],
    score: 0, resourcesCount: 0,
    upgrades: { damage: 0, fireRate: 0, speed: 0 },
    upgradeCost: { damage: 5, fireRate: 5, speed: 5 },
    asteroidTimer: 0, enemyTimer: 0,
    gameOver: false, time: 0,
  };
}

// ---------- Спавн ----------
function edgeSpawnAroundViewport(margin) {
  const left   = camera.x - margin;
  const right  = camera.x + canvas.width  + margin;
  const top    = camera.y - margin;
  const bottom = camera.y + canvas.height + margin;
  const side = randI(0, 3);
  if (side === 0) return { x: rand(left, right), y: top };
  if (side === 1) return { x: right, y: rand(top, bottom) };
  if (side === 2) return { x: rand(left, right), y: bottom };
  return { x: left, y: rand(top, bottom) };
}

function spawnAsteroid() {
  const pos   = edgeSpawnAroundViewport(60);
  const size  = rand(20, 48);
  const verts = randI(7, 11);
  const points = [];
  for (let i = 0; i < verts; i++) {
    points.push({ a: (i / verts) * Math.PI * 2, r: size * rand(0.72, 1.15) });
  }
  gameState.asteroids.push({
    x: pos.x, y: pos.y,
    vx: rand(-0.75, 0.75), vy: rand(-0.75, 0.75),
    radius: size, hp: size * 1.8, maxHp: size * 1.8,
    rotation: rand(0, 6.28), rotSpeed: rand(-0.01, 0.01),
    points, size,
  });
}

function spawnEnemy() {
  const pos = edgeSpawnAroundViewport(60);
  const faction = factionCounter % 3;
  factionCounter++;

  const baseHp    = 30;
  const baseSpeed = 0.9 + Math.min(gameState.time / 10000, 0.5);

  gameState.enemies.push({
    x: pos.x, y: pos.y, vx: 0, vy: 0, angle: 0,
    radius: 13,
    hp: baseHp, maxHp: baseHp, baseHp,
    level: 1,
    faction,
    damage: 8,
    speed: baseSpeed, baseSpeed,
    cooldown: rand(30, 90), fireRate: 90,
    target: null,
  });
}

// ---------- Аптечки ----------
function spawnHealthPickup(x, y, amount) {
  const ang = rand(0, 6.28);
  const spd = rand(0.5, 1.5);
  gameState.healthPickups.push({
    x, y,
    vx: Math.cos(ang) * spd,
    vy: Math.sin(ang) * spd,
    amount,
    life: HEALTH_PICKUP_LIFE,
    maxLife: HEALTH_PICKUP_LIFE,
    phase: rand(0, 6.28),
  });
}

// ---------- Прокачка игрока ----------
function upgrade(type) {
  if (gameState.gameOver) return;
  const cost = gameState.upgradeCost[type];
  if (gameState.resourcesCount < cost) return;

  gameState.resourcesCount -= cost;
  gameState.upgrades[type]++;
  gameState.upgradeCost[type] = Math.floor(cost * 1.6);

  const p = gameState.player;
  if (type === 'damage')   p.damage += 25;
  if (type === 'fireRate') p.fireRate = Math.max(25, p.fireRate - 5);
  if (type === 'speed')  {
    p.thrust   += 0.005;
    p.maxSpeed += 0.125;
    p.maxHealth += HEAL_PER_UPGRADE;
    p.health = Math.min(p.maxHealth, p.health + HEAL_PER_UPGRADE);
    spawnFloater(p.x, p.y - 30, `+${HEAL_PER_UPGRADE} HP`, '#4f4');
  }

  for (let i = 0; i < 12; i++) {
    gameState.particles.push({
      x: p.x, y: p.y,
      vx: rand(-3, 3), vy: rand(-3, 3),
      life: 40, maxLife: 40, color: '#0ff', radius: 2,
    });
  }
}

// ---------- Прокачка бота ----------
function levelUpEnemy(e) {
  if (e.level >= MAX_BOT_LEVEL) return;
  e.level++;
  e.maxHp   = e.baseHp + (e.level - 1) * 15;
  e.hp      = e.maxHp;
  e.damage  = 8 + (e.level - 1) * 2;
  e.speed   = e.baseSpeed + (e.level - 1) * 0.15;
  e.radius  = 13 + (e.level - 1) * 1.5;

  burst(e.x, e.y, FACTION_COLORS[e.faction].stroke, 15);
}

// ---------- Стрельба игрока ----------
function playerShoot() {
  const p = gameState.player;
  const muzzleX = p.x + Math.cos(p.turretAngle) * (p.radius + 6);
  const muzzleY = p.y + Math.sin(p.turretAngle) * (p.radius + 6);

  gameState.bullets.push({
    x: muzzleX, y: muzzleY,
    prevX: muzzleX, prevY: muzzleY,
    vx: Math.cos(p.turretAngle) * 6.5,
    vy: Math.sin(p.turretAngle) * 6.5,
    damage: p.damage,
    radius: 6,
    life: 140,
  });

  burst(muzzleX, muzzleY, '#0ff', 5);
}

// ---------- Ракета игрока ----------
function fireMissile() {
  const p = gameState.player;
  const muzzleX = p.x + Math.cos(p.turretAngle) * (p.radius + 6);
  const muzzleY = p.y + Math.sin(p.turretAngle) * (p.radius + 6);

  gameState.missiles.push({
    x: muzzleX, y: muzzleY,
    prevX: muzzleX, prevY: muzzleY,
    vx: Math.cos(p.turretAngle) * 5.5,
    vy: Math.sin(p.turretAngle) * 5.5,
    speed: 5.5,
    damage: 40,
    radius: 5,
    life: 300,
    turnRate: 0.08,
    target: null,
  });

  burst(muzzleX, muzzleY, '#f80', 5);
}

function findNearestEnemyForMissile(from) {
  const gs = gameState;
  let best = null, bestD = Infinity;
  for (const e of gs.enemies) {
    const d = dist(from, e);
    if (d < bestD) { bestD = d; best = e; }
  }
  return best;
}

function updateMissiles() {
  const gs = gameState;

  for (let i = gs.missiles.length - 1; i >= 0; i--) {
    const m = gs.missiles[i];
    m.prevX = m.x; m.prevY = m.y;

    if (!m.target || m.target.hp <= 0) {
      m.target = findNearestEnemyForMissile(m);
    }

    if (m.target) {
      const desiredAngle = Math.atan2(m.target.y - m.y, m.target.x - m.x);
      const currentAngle = Math.atan2(m.vy, m.vx);
      const newAngle = angleLerp(currentAngle, desiredAngle, m.turnRate);
      m.vx = Math.cos(newAngle) * m.speed;
      m.vy = Math.sin(newAngle) * m.speed;
    }

    m.x += m.vx; m.y += m.vy; m.life--;

    if (m.life <= 0) { gs.missiles.splice(i, 1); continue; }

    let hit = false;
    for (let j = gs.asteroids.length - 1; j >= 0; j--) {
      const a = gs.asteroids[j];
      if (dist(m, a) < a.radius + m.radius) {
        a.hp -= m.damage;
        burst(m.x, m.y, '#f80', 12);
        hit = true;
        if (a.hp <= 0) {
          explodeAsteroid(a);
          gs.asteroids.splice(j, 1);
        }
        break;
      }
    }
    if (hit) { gs.missiles.splice(i, 1); continue; }

    for (let k = gs.enemies.length - 1; k >= 0; k--) {
      const e = gs.enemies[k];
      if (dist(m, e) < e.radius + m.radius) {
        e.hp -= m.damage;
        burst(m.x, m.y, '#f80', 16);
        gs.missiles.splice(i, 1);
        if (e.hp <= 0) {
          explodeEnemy(e, gs.player);
          gs.enemies.splice(k, 1);
        }
        break;
      }
    }
  }
}

// ---------- Урон игроку ----------
function damagePlayer(amount) {
  const p = gameState.player;
  if (p.invuln > 0) return;
  p.health -= amount;
  p.invuln = 20;
  p.lastDamageTime = gameState.time;    // ← запоминаем время урона
  p.regenTick = 0;
  if (p.health <= 0) {
    p.health = 0;
    gameState.gameOver = true;
    showGameOver();
  }
}

function showGameOver() {
  overEl.style.display = 'flex';
  finalStats.innerHTML =
    `Очки: <span style="color:#0ff">${gameState.score}</span><br>` +
    `Ресурсы: <span style="color:#fc4">${gameState.resourcesCount}</span><br>` +
    `Время: ${Math.floor(gameState.time / 60)} сек`;
}

// ---------- Выбор цели ботом ----------
function findBotTarget(me) {
  const gs = gameState;
  let best = null, bestD = Infinity;

  if (!gs.gameOver) {
    const d = dist(me, gs.player) * 0.85;
    if (d < bestD) { bestD = d; best = gs.player; }
  }

  for (const e of gs.enemies) {
    if (e === me) continue;
    if (e.faction === me.faction) continue;
    const d = dist(me, e);
    if (d < bestD) { bestD = d; best = e; }
  }

  return best;
}

// ---------- Обновление ----------
function update() {
  const gs = gameState;
  const p  = gs.player;
  gs.time++;

  if (!gs.gameOver) {
    if (keys['KeyA'] || keys['ArrowLeft'])  p.targetBodyAngle -= p.turnSpeed;
    if (keys['KeyD'] || keys['ArrowRight']) p.targetBodyAngle += p.turnSpeed;
    p.targetBodyAngle = normalizeAngle(p.targetBodyAngle);

    p.bodyAngle = angleLerp(p.bodyAngle, p.targetBodyAngle, p.bodySmooth);

    let thrusting = false;
    let reversing = false;
    if (keys['KeyW'] || keys['ArrowUp']) {
      p.vx += Math.cos(p.bodyAngle) * p.thrust;
      p.vy += Math.sin(p.bodyAngle) * p.thrust;
      thrusting = true;
    }
    if (keys['KeyS'] || keys['ArrowDown']) {
      p.vx -= Math.cos(p.bodyAngle) * p.reverseThrust;
      p.vy -= Math.sin(p.bodyAngle) * p.reverseThrust;
      reversing = true;
    }

    p.vx *= 0.99; p.vy *= 0.99;
    const spd = Math.hypot(p.vx, p.vy);
    if (spd > p.maxSpeed) {
      p.vx = p.vx / spd * p.maxSpeed;
      p.vy = p.vy / spd * p.maxSpeed;
    }
    p.x += p.vx; p.y += p.vy;

    const wmx = mouse.x + camera.x;
    const wmy = mouse.y + camera.y;
    const desiredTurret = Math.atan2(wmy - p.y, wmx - p.x);
    let offset = normalizeAngle(desiredTurret - p.bodyAngle);
    if (offset >  MAX_TURRET_OFFSET) offset =  MAX_TURRET_OFFSET;
    if (offset < -MAX_TURRET_OFFSET) offset = -MAX_TURRET_OFFSET;
    p.turretAngle = normalizeAngle(p.bodyAngle + offset);

    if (p.fireCooldown    > 0) p.fireCooldown--;
    if (p.missileCooldown > 0) p.missileCooldown--;
    if (p.invuln          > 0) p.invuln--;

    // === ПАССИВНЫЙ РЕГЕН ===
    if (gs.time - p.lastDamageTime > REGEN_DELAY && p.health < p.maxHealth) {
      p.regenTick++;
      if (p.regenTick >= REGEN_INTERVAL) {
        p.regenTick = 0;
        p.health = Math.min(p.maxHealth, p.health + REGEN_AMOUNT);
      }
    } else {
      p.regenTick = 0;
    }

    if ((mouse.leftDown || keys['Space']) && p.fireCooldown <= 0) {
      playerShoot();
      p.fireCooldown = p.fireRate;
    }
    if (mouse.rightDown && p.missileCooldown <= 0) {
      fireMissile();
      p.missileCooldown = p.missileFireRate;
    }

    p._thrusting = thrusting;
    p._reversing = reversing;
  }

  camera.x = p.x - canvas.width  / 2;
  camera.y = p.y - canvas.height / 2;

  gs.asteroidTimer--;
  if (gs.asteroidTimer <= 0) {
    spawnAsteroid();
    gs.asteroidTimer = Math.max(20, 70 - Math.floor(gs.time / 600) * 8);
  }

  gs.enemyTimer--;
  if (gs.enemyTimer <= 0) {
    spawnEnemy();
    gs.enemyTimer = Math.max(120, 400 - Math.floor(gs.time / 300) * 20);
  }

  for (let i = gs.bullets.length - 1; i >= 0; i--) {
    const b = gs.bullets[i];
    b.prevX = b.x; b.prevY = b.y;
    b.x += b.vx; b.y += b.vy; b.life--;

    if (b.life <= 0) { gs.bullets.splice(i, 1); continue; }

    let hit = false;
    for (let j = gs.asteroids.length - 1; j >= 0; j--) {
      const a = gs.asteroids[j];
      if (dist(b, a) < a.radius + b.radius) {
        a.hp -= b.damage;
        burst(b.x, b.y, '#ff0', 4);
        hit = true;
        if (a.hp <= 0) {
          explodeAsteroid(a);
          gs.asteroids.splice(j, 1);
        }
        break;
      }
    }
    if (hit) { gs.bullets.splice(i, 1); continue; }

    for (let k = gs.enemies.length - 1; k >= 0; k--) {
      const e = gs.enemies[k];
      if (dist(b, e) < e.radius + b.radius) {
        e.hp -= b.damage;
        burst(b.x, b.y, FACTION_COLORS[e.faction].stroke, 4);
        gs.bullets.splice(i, 1);
        if (e.hp <= 0) {
          explodeEnemy(e, gs.player);
          gs.enemies.splice(k, 1);
        }
        break;
      }
    }
  }

  updateMissiles();

  for (let i = gs.enemyBullets.length - 1; i >= 0; i--) {
    const b = gs.enemyBullets[i];
    b.x += b.vx; b.y += b.vy; b.life--;
    if (b.life <= 0) { gs.enemyBullets.splice(i, 1); continue; }

    if (!gs.gameOver && dist(b, p) < p.radius + b.radius) {
      damagePlayer(b.damage);
      gs.enemyBullets.splice(i, 1);
      burst(b.x, b.y, '#f44', 6);
      continue;
    }

    let consumed = false;
    for (let k = gs.enemies.length - 1; k >= 0; k--) {
      const e = gs.enemies[k];
      if (e === b.owner) continue;
      if (e.faction === b.faction) continue;
      if (dist(b, e) < e.radius + b.radius) {
        e.hp -= b.damage;
        burst(b.x, b.y, FACTION_COLORS[e.faction].stroke, 4);
        gs.enemyBullets.splice(i, 1);
        consumed = true;
        if (e.hp <= 0) {
          explodeEnemy(e, b.owner);
          gs.enemies.splice(k, 1);
        }
        break;
      }
    }
    if (consumed) continue;
  }

  for (let k = gs.enemies.length - 1; k >= 0; k--) {
    const e = gs.enemies[k];

    if (!gs.gameOver) {
      const target = findBotTarget(e);
      e.target = target;

      if (target) {
        const ang = Math.atan2(target.y - e.y, target.x - e.x);
        e.angle = ang;
        e.vx += Math.cos(ang) * 0.02;
        e.vy += Math.sin(ang) * 0.02;
        const s = Math.hypot(e.vx, e.vy);
        if (s > e.speed) { e.vx = e.vx / s * e.speed; e.vy = e.vy / s * e.speed; }
      }
    }
    e.x += e.vx; e.y += e.vy;
    e.cooldown--;

    if (e.cooldown <= 0 && e.target && !gs.gameOver) {
      const a = Math.atan2(e.target.y - e.y, e.target.x - e.x);
      gs.enemyBullets.push({
        x: e.x, y: e.y,
        vx: Math.cos(a) * 2.5, vy: Math.sin(a) * 2.5,
        radius: 4, damage: e.damage, life: 360,
        owner: e, faction: e.faction,
      });
      e.cooldown = e.fireRate;
    }

    if (!gs.gameOver && dist(e, p) < e.radius + p.radius) {
      damagePlayer(15);
      gs.enemies.splice(k, 1);
      burst(e.x, e.y, FACTION_COLORS[e.faction].stroke, 14);
    }
  }

  for (const a of gs.asteroids) {
    a.x += a.vx; a.y += a.vy; a.rotation += a.rotSpeed;
    if (!gs.gameOver && dist(a, p) < a.radius + p.radius) {
      damagePlayer(10 + a.radius * 0.3);
      const ang = Math.atan2(p.y - a.y, p.x - a.x);
      p.vx += Math.cos(ang) * 1.5;
      p.vy += Math.sin(ang) * 1.5;
    }
  }

  // === РЕСУРСЫ ===
  for (let i = gs.resources.length - 1; i >= 0; i--) {
    const r = gs.resources[i];
    r.life--;
    if (!gs.gameOver) {
      const d = dist(r, p);
      if (d < HEALTH_MAGNET_RANGE) {
        const ang = Math.atan2(p.y - r.y, p.x - r.x);
        r.x += Math.cos(ang) * 4;
        r.y += Math.sin(ang) * 4;
      }
      if (d < p.radius + 8) {
        gs.resourcesCount++;
        gs.score += 10;
        spawnFloater(p.x, p.y - 30, `+1`, '#fc4');
        burst(r.x, r.y, '#fc4', 8);
        gs.resources.splice(i, 1);
        continue;
      }
    }
    if (r.life <= 0) gs.resources.splice(i, 1);
  }

  // === АПТЕЧКИ ===
  for (let i = gs.healthPickups.length - 1; i >= 0; i--) {
    const hp = gs.healthPickups[i];
    hp.life--;

    // лёгкий дрейф
    hp.x += hp.vx; hp.y += hp.vy;
    hp.vx *= 0.97; hp.vy *= 0.97;

    if (!gs.gameOver) {
      const d = dist(hp, p);
      // притяжение
      if (d < HEALTH_MAGNET_RANGE) {
        const ang = Math.atan2(p.y - hp.y, p.x - hp.x);
        const pull = 4 + (1 - d / HEALTH_MAGNET_RANGE) * 4;
        hp.x += Math.cos(ang) * pull;
        hp.y += Math.sin(ang) * pull;
      }
      // подбор
      if (d < p.radius + 10) {
        const before = p.health;
        p.health = Math.min(p.maxHealth, p.health + hp.amount);
        const healed = Math.round(p.health - before);
        if (healed > 0) {
          spawnFloater(p.x, p.y - 30, `+${healed} HP`, '#4f4');
        }
        burst(hp.x, hp.y, '#4f4', 10);
        gs.healthPickups.splice(i, 1);
        continue;
      }
    }
    if (hp.life <= 0) gs.healthPickups.splice(i, 1);
  }

  for (let i = gs.particles.length - 1; i >= 0; i--) {
    const pt = gs.particles[i];
    pt.x += pt.vx; pt.y += pt.vy;
    pt.vx *= 0.96; pt.vy *= 0.96;
    pt.life--;
    if (pt.life <= 0) gs.particles.splice(i, 1);
  }

  for (let i = gs.floaters.length - 1; i >= 0; i--) {
    const f = gs.floaters[i];
    f.y -= 0.6; f.life--;
    if (f.life <= 0) gs.floaters.splice(i, 1);
  }

  for (let i = gs.asteroids.length - 1; i >= 0; i--) {
    if (dist(gs.asteroids[i], p) > DESPAWN_DIST) gs.asteroids.splice(i, 1);
  }
  for (let i = gs.enemies.length - 1; i >= 0; i--) {
    if (dist(gs.enemies[i], p) > DESPAWN_DIST) gs.enemies.splice(i, 1);
  }
  for (let i = gs.bullets.length - 1; i >= 0; i--) {
    if (dist(gs.bullets[i], p) > DESPAWN_DIST) gs.bullets.splice(i, 1);
  }
  for (let i = gs.enemyBullets.length - 1; i >= 0; i--) {
    if (dist(gs.enemyBullets[i], p) > DESPAWN_DIST) gs.enemyBullets.splice(i, 1);
  }
  for (let i = gs.missiles.length - 1; i >= 0; i--) {
    if (dist(gs.missiles[i], p) > DESPAWN_DIST) gs.missiles.splice(i, 1);
  }
  for (let i = gs.resources.length - 1; i >= 0; i--) {
    if (dist(gs.resources[i], p) > DESPAWN_DIST) gs.resources.splice(i, 1);
  }
  for (let i = gs.healthPickups.length - 1; i >= 0; i--) {
    if (dist(gs.healthPickups[i], p) > DESPAWN_DIST) gs.healthPickups.splice(i, 1);
  }
}

function spawnFloater(x, y, text, color) {
  gameState.floaters.push({ x, y, text, color, life: 60, maxLife: 60 });
}

function explodeAsteroid(a) {
  const gs = gameState;
  gs.score += Math.floor(a.size / 5);
  burst(a.x, a.y, '#fa0', 20);

  // ресурсы
  const drops = randI(1, 3);
  for (let i = 0; i < drops; i++) {
    gs.resources.push({
      x: a.x + rand(-20, 20), y: a.y + rand(-20, 20),
      vx: rand(-1, 1), vy: rand(-1, 1),
      life: 600, radius: 6, phase: rand(0, 6.28),
    });
  }

  // 💊 шанс аптечки
  if (Math.random() < ASTEROID_HEAL_CHANCE) {
    spawnHealthPickup(a.x, a.y, HEALTH_DROP_ASTEROID);
  }
}

function explodeEnemy(e, killer) {
  const gs = gameState;
  burst(e.x, e.y, FACTION_COLORS[e.faction].stroke, 18);

  // ресурс
  gs.resources.push({
    x: e.x, y: e.y, vx: rand(-0.5, 0.5), vy: rand(-0.5, 0.5),
    life: 600, radius: 7, phase: rand(0, 6.28),
  });

  // 💊 аптечка всегда
  spawnHealthPickup(e.x, e.y, HEALTH_DROP_ENEMY);

  if (killer === gs.player) {
    gs.score += 25;
  }

  if (killer && killer !== gs.player && killer.hp > 0) {
    levelUpEnemy(killer);
  }
}

function burst(x, y, color, count) {
  for (let i = 0; i < count; i++) {
    const ang = rand(0, 6.28), spd = rand(1, 5);
    gameState.particles.push({
      x, y, vx: Math.cos(ang) * spd, vy: Math.sin(ang) * spd,
      life: randI(20, 45), maxLife: 45, color, radius: rand(1, 3),
    });
  }
}

function isVisible(obj, margin = 80) {
  return obj.x + margin > camera.x &&
         obj.x - margin < camera.x + canvas.width &&
         obj.y + margin > camera.y &&
         obj.y - margin < camera.y + canvas.height;
}

// ---------- Отрисовка ----------
function draw() {
  const gs = gameState;
  const p  = gs.player;

  ctx.fillStyle = '#03030a';
  ctx.fillRect(0, 0, canvas.width, canvas.height);
  drawStarsParallax();

  ctx.save();
  ctx.translate(-camera.x, -camera.y);

  // ресурсы (жёлтые ромбы)
  for (const r of gs.resources) {
    if (!isVisible(r)) continue;
    const glow = 0.5 + Math.sin(gs.time * 0.1 + r.phase) * 0.5;
    ctx.save();
    ctx.shadowColor = '#fc4';
    ctx.shadowBlur = 12 * glow;
    ctx.fillStyle = '#fc4';
    ctx.beginPath();
    ctx.moveTo(r.x, r.y - 6);
    ctx.lineTo(r.x + 5, r.y);
    ctx.lineTo(r.x, r.y + 6);
    ctx.lineTo(r.x - 5, r.y);
    ctx.closePath();
    ctx.fill();
    ctx.restore();
  }

  // === АПТЕЧКИ (зелёные крестики) ===
  for (const hp of gs.healthPickups) {
    if (!isVisible(hp, 30)) continue;
    const pulse = 0.6 + Math.sin(gs.time * 0.12 + hp.phase) * 0.4;
    const blink = hp.life < 180 ? (Math.floor(gs.time / 6) % 2 === 0 ? 0.35 : 1) : 1;

    ctx.save();
    ctx.globalAlpha = blink;
    // аура
    ctx.shadowColor = '#4f4';
    ctx.shadowBlur = 14 * pulse;
    // крестик
    ctx.fillStyle = '#4f4';
    ctx.fillRect(hp.x - 7, hp.y - 2.5, 14, 5);
    ctx.fillRect(hp.x - 2.5, hp.y - 7, 5, 14);
    // внутренний светлый крестик
    ctx.shadowBlur = 0;
    ctx.fillStyle = '#bfffbf';
    ctx.fillRect(hp.x - 5, hp.y - 1.5, 10, 3);
    ctx.fillRect(hp.x - 1.5, hp.y - 5, 3, 10);
    // внешний контур
    ctx.strokeStyle = 'rgba(180,255,180,0.7)';
    ctx.lineWidth = 1;
    ctx.strokeRect(hp.x - 7, hp.y - 2.5, 14, 5);
    ctx.strokeRect(hp.x - 2.5, hp.y - 7, 5, 14);
    ctx.restore();
  }

  for (const a of gs.asteroids) {
    if (!isVisible(a, a.radius)) continue;
    ctx.save();
    ctx.translate(a.x, a.y);
    ctx.rotate(a.rotation);
    ctx.strokeStyle = a.hp < a.maxHp * 0.5 ? '#f84' : '#8aa';
    ctx.fillStyle = 'rgba(60,70,90,0.4)';
    ctx.lineWidth = 2;
    ctx.beginPath();
    for (let i = 0; i < a.points.length; i++) {
      const pt = a.points[i];
      const x = Math.cos(pt.a) * pt.r;
      const y = Math.sin(pt.a) * pt.r;
      if (i === 0) ctx.moveTo(x, y); else ctx.lineTo(x, y);
    }
    ctx.closePath();
    ctx.fill(); ctx.stroke();
    ctx.restore();
  }

  for (const e of gs.enemies) {
    if (!isVisible(e, e.radius + 20)) continue;
    const fc = FACTION_COLORS[e.faction];

    if (e.level > 1) {
      ctx.save();
      ctx.strokeStyle = fc.stroke;
      ctx.globalAlpha = 0.25 + e.level * 0.1;
      ctx.lineWidth = 1.5;
      ctx.shadowColor = fc.glow;
      ctx.shadowBlur = 8;
      ctx.beginPath();
      ctx.arc(e.x, e.y, e.radius + 6, 0, 6.28);
      ctx.stroke();
      ctx.restore();
    }

    ctx.save();
    ctx.translate(e.x, e.y);
    ctx.rotate(e.angle);
    ctx.strokeStyle = fc.stroke;
    ctx.fillStyle = fc.fill;
    ctx.lineWidth = 2;
    ctx.shadowColor = fc.glow;
    ctx.shadowBlur = 6;
    ctx.beginPath();
    ctx.moveTo( e.radius * 0.92,  0);
    ctx.lineTo(-e.radius * 0.62, -e.radius * 0.69);
    ctx.lineTo(-e.radius * 0.31,  0);
    ctx.lineTo(-e.radius * 0.62,  e.radius * 0.69);
    ctx.closePath();
    ctx.fill(); ctx.stroke();
    ctx.restore();

    if (e.hp < e.maxHp) {
      ctx.fillStyle = '#300';
      ctx.fillRect(e.x - 14, e.y - 22, 28, 3);
      ctx.fillStyle = fc.stroke;
      ctx.fillRect(e.x - 14, e.y - 22, 28 * (e.hp / e.maxHp), 3);
    }

    if (e.level > 1) {
      ctx.fillStyle = fc.stroke;
      ctx.font = 'bold 11px monospace';
      ctx.textAlign = 'center';
      ctx.shadowColor = fc.glow;
      ctx.shadowBlur = 6;
      ctx.fillText(e.level, e.x, e.y - 27);
      ctx.shadowBlur = 0;
    }
  }
  ctx.textAlign = 'start';

  ctx.save();
  for (const b of gs.bullets) {
    if (!isVisible(b)) continue;
    ctx.strokeStyle = 'rgba(0,255,255,0.35)';
    ctx.lineWidth = b.radius * 1.6;
    ctx.beginPath();
    ctx.moveTo(b.prevX ?? b.x, b.prevY ?? b.y);
    ctx.lineTo(b.x, b.y);
    ctx.stroke();

    ctx.shadowColor = '#0ff';
    ctx.shadowBlur = 20;
    ctx.fillStyle = '#fff';
    ctx.beginPath();
    ctx.arc(b.x, b.y, b.radius, 0, 6.28);
    ctx.fill();

    ctx.shadowBlur = 0;
    ctx.fillStyle = '#0ff';
    ctx.beginPath();
    ctx.arc(b.x, b.y, b.radius * 0.65, 0, 6.28);
    ctx.fill();
  }
  ctx.restore();

  ctx.save();
  for (const m of gs.missiles) {
    if (!isVisible(m, 60)) continue;
    ctx.strokeStyle = 'rgba(255,140,40,0.5)';
    ctx.lineWidth = m.radius * 1.4;
    ctx.lineCap = 'round';
    ctx.beginPath();
    ctx.moveTo(m.x - m.vx * 3.5, m.y - m.vy * 3.5);
    ctx.lineTo(m.x, m.y);
    ctx.stroke();
    ctx.lineCap = 'butt';

    ctx.shadowColor = '#f80';
    ctx.shadowBlur = 16;
    ctx.fillStyle = '#f80';
    ctx.beginPath();
    ctx.arc(m.x, m.y, m.radius, 0, 6.28);
    ctx.fill();
    ctx.shadowBlur = 0;
    ctx.fillStyle = '#fff';
    ctx.beginPath();
    ctx.arc(m.x, m.y, m.radius * 0.45, 0, 6.28);
    ctx.fill();
  }
  ctx.restore();

  ctx.save();
  ctx.shadowBlur = 10;
  for (const b of gs.enemyBullets) {
    if (!isVisible(b)) continue;
    const fc = FACTION_COLORS[b.faction] || FACTION_COLORS[0];
    ctx.shadowColor = fc.glow;
    ctx.fillStyle = fc.stroke;
    ctx.beginPath();
    ctx.arc(b.x, b.y, b.radius, 0, 6.28);
    ctx.fill();
  }
  ctx.restore();

  if (!gs.gameOver) {
    ctx.save();
    ctx.translate(p.x, p.y);
    ctx.rotate(p.bodyAngle);
    if (p.invuln > 0 && Math.floor(p.invuln / 3) % 2 === 0) ctx.globalAlpha = 0.3;
    ctx.strokeStyle = '#0ff';
    ctx.fillStyle = 'rgba(0,255,255,0.1)';
    ctx.lineWidth = 2;
    ctx.shadowColor = '#0ff';
    ctx.shadowBlur = 10;
    ctx.beginPath();
    ctx.moveTo(14, 0);
    ctx.lineTo(-10, -9);
    ctx.lineTo(-5, 0);
    ctx.lineTo(-10, 9);
    ctx.closePath();
    ctx.fill(); ctx.stroke();
    ctx.shadowBlur = 0;

    if (p._thrusting) {
      ctx.fillStyle = '#f80';
      ctx.beginPath();
      ctx.moveTo(-6, -4);
      ctx.lineTo(-14 - Math.random() * 8, 0);
      ctx.lineTo(-6, 4);
      ctx.closePath();
      ctx.fill();
    } else if (p._reversing) {
      ctx.fillStyle = '#48f';
      ctx.beginPath();
      ctx.moveTo(-4, -3);
      ctx.lineTo(-10 - Math.random() * 5, 0);
      ctx.lineTo(-4, 3);
      ctx.closePath();
      ctx.fill();
    }
    ctx.restore();

    ctx.save();
    ctx.translate(p.x, p.y);
    ctx.strokeStyle = 'rgba(0,255,255,0.12)';
    ctx.lineWidth = 1;
    ctx.beginPath();
    ctx.arc(0, 0, p.radius + 22, p.bodyAngle - MAX_TURRET_OFFSET, p.bodyAngle + MAX_TURRET_OFFSET);
    ctx.stroke();
    ctx.restore();

    ctx.save();
    ctx.translate(p.x, p.y);
    ctx.rotate(p.turretAngle);
    ctx.shadowColor = '#0ff';
    ctx.shadowBlur = 8;
    ctx.fillStyle = '#bff';
    ctx.fillRect(6, -2.5, 14, 5);
    ctx.fillStyle = '#0ff';
    ctx.beginPath();
    ctx.arc(0, 0, 6, 0, 6.28);
    ctx.fill();
    ctx.fillStyle = '#042';
    ctx.beginPath();
    ctx.arc(0, 0, 2.5, 0, 6.28);
    ctx.fill();
    ctx.shadowBlur = 0;
    ctx.restore();

    // HP bar
    ctx.fillStyle = '#300';
    ctx.fillRect(p.x - 22, p.y - 30, 44, 5);
    ctx.fillStyle = p.health > 40 ? '#0f0' : '#f80';
    ctx.fillRect(p.x - 22, p.y - 30, 44 * (p.health / p.maxHealth), 5);

    // индикатор регена (зелёная полоска-«искры» над HP)
    if (gs.time - p.lastDamageTime > REGEN_DELAY && p.health < p.maxHealth) {
      const ratio = p.regenTick / REGEN_INTERVAL;
      ctx.fillStyle = 'rgba(80,255,80,0.25)';
      ctx.fillRect(p.x - 22, p.y - 34, 44, 2);
      ctx.fillStyle = '#4f4';
      ctx.fillRect(p.x - 22, p.y - 34, 44 * ratio, 2);
    }

    // индикатор готовности орудия
    if (p.fireCooldown > 0) {
      const ratio = 1 - p.fireCooldown / p.fireRate;
      ctx.fillStyle = 'rgba(0,255,255,0.15)';
      ctx.fillRect(p.x - 16, p.y + 22, 32, 3);
      ctx.fillStyle = '#0ff';
      ctx.fillRect(p.x - 16, p.y + 22, 32 * ratio, 3);
    } else {
      ctx.fillStyle = '#0ff';
      ctx.fillRect(p.x - 16, p.y + 22, 32, 3);
    }
  }

  for (const pt of gs.particles) {
    if (!isVisible(pt, 10)) continue;
    const a = pt.life / pt.maxLife;
    ctx.globalAlpha = a;
    ctx.fillStyle = pt.color;
    ctx.beginPath();
    ctx.arc(pt.x, pt.y, pt.radius * a, 0, 6.28);
    ctx.fill();
  }
  ctx.globalAlpha = 1;

  for (const f of gs.floaters) {
    const a = f.life / f.maxLife;
    ctx.globalAlpha = a;
    ctx.fillStyle = f.color;
    ctx.font = 'bold 14px monospace';
    ctx.textAlign = 'center';
    ctx.shadowColor = f.color;
    ctx.shadowBlur = 8;
    ctx.fillText(f.text, f.x, f.y);
    ctx.shadowBlur = 0;
  }
  ctx.globalAlpha = 1;
  ctx.textAlign = 'start';

  ctx.restore();

  drawMinimap();

  if (!gs.gameOver) {
    ctx.save();
    ctx.strokeStyle = 'rgba(0,255,255,0.8)';
    ctx.lineWidth = 1.5;
    ctx.beginPath();
    ctx.arc(mouse.x, mouse.y, 8, 0, 6.28);
    ctx.stroke();
    ctx.beginPath();
    ctx.moveTo(mouse.x - 12, mouse.y); ctx.lineTo(mouse.x - 4, mouse.y);
    ctx.moveTo(mouse.x + 4, mouse.y);  ctx.lineTo(mouse.x + 12, mouse.y);
    ctx.moveTo(mouse.x, mouse.y - 12); ctx.lineTo(mouse.x, mouse.y - 4);
    ctx.moveTo(mouse.x, mouse.y + 4);  ctx.lineTo(mouse.x, mouse.y + 12);
    ctx.stroke();

    const wmx = mouse.x + camera.x;
    const wmy = mouse.y + camera.y;
    const desiredTurret = Math.atan2(wmy - p.y, wmx - p.x);
    const off = normalizeAngle(desiredTurret - p.bodyAngle);
    if (Math.abs(off) > MAX_TURRET_OFFSET) {
      ctx.strokeStyle = 'rgba(255,80,80,0.9)';
      ctx.beginPath();
      ctx.arc(mouse.x, mouse.y, 14, 0, 6.28);
      ctx.stroke();
    }
    ctx.restore();
  }

  updateHUD();
}

function drawStarsParallax() {
  const px = -camera.x * 0.2;
  const py = -camera.y * 0.2;
  ctx.fillStyle = '#fff';
  for (const s of stars) {
    const x = mod(s.x + px, canvas.width);
    const y = mod(s.y + py, canvas.height);
    ctx.globalAlpha = s.a;
    ctx.fillRect(x, y, s.s, s.s);
  }
  ctx.globalAlpha = 1;
}

function drawMinimap() {
  const gs = gameState;
  const p  = gs.player;

  const size = MINIMAP_SIZE;
  const mx = MINIMAP_X;
  const my = MINIMAP_Y;

  const range = Math.max(canvas.width, canvas.height) * 2.5;
  const scale = size / range;
  const cx = mx + size / 2;
  const cy = my + size / 2;

  ctx.fillStyle = 'rgba(0,15,25,0.9)';
  ctx.fillRect(mx, my, size, size);

  ctx.strokeStyle = 'rgba(0,255,255,0.08)';
  ctx.lineWidth = 1;
  for (let i = 1; i < 4; i++) {
    const gx = mx + (size / 4) * i;
    const gy = my + (size / 4) * i;
    ctx.beginPath();
    ctx.moveTo(gx, my); ctx.lineTo(gx, my + size);
    ctx.moveTo(mx, gy); ctx.lineTo(mx + size, gy);
    ctx.stroke();
  }

  ctx.save();
  ctx.beginPath();
  ctx.rect(mx, my, size, size);
  ctx.clip();

  ctx.fillStyle = '#8aa';
  for (const a of gs.asteroids) {
    const dx = (a.x - p.x) * scale;
    const dy = (a.y - p.y) * scale;
    if (Math.abs(dx) > size / 2 || Math.abs(dy) > size / 2) continue;
    ctx.fillRect(cx + dx - 1, cy + dy - 1, 2, 2);
  }

  for (const e of gs.enemies) {
    const dx = (e.x - p.x) * scale;
    const dy = (e.y - p.y) * scale;
    if (Math.abs(dx) > size / 2 || Math.abs(dy) > size / 2) continue;
    const fc = FACTION_COLORS[e.faction];
    ctx.fillStyle = fc.stroke;
    const sz = 2.5 + e.level * 0.5;
    ctx.fillRect(cx + dx - sz / 2, cy + dy - sz / 2, sz, sz);
  }

  ctx.fillStyle = '#fc4';
  for (const r of gs.resources) {
    const dx = (r.x - p.x) * scale;
    const dy = (r.y - p.y) * scale;
    if (Math.abs(dx) > size / 2 || Math.abs(dy) > size / 2) continue;
    ctx.fillRect(cx + dx - 1, cy + dy - 1, 2, 2);
  }

  // аптечки — маленькие зелёные крестики
  ctx.fillStyle = '#4f4';
  for (const hp of gs.healthPickups) {
    const dx = (hp.x - p.x) * scale;
    const dy = (hp.y - p.y) * scale;
    if (Math.abs(dx) > size / 2 || Math.abs(dy) > size / 2) continue;
    ctx.fillRect(cx + dx - 1, cy + dy - 3, 2, 6);
    ctx.fillRect(cx + dx - 3, cy + dy - 1, 6, 2);
  }

  const vw = canvas.width  * scale;
  const vh = canvas.height * scale;
  ctx.strokeStyle = 'rgba(0,255,255,0.55)';
  ctx.lineWidth = 1;
  ctx.strokeRect(cx - vw / 2, cy - vh / 2, vw, vh);

  ctx.strokeStyle = '#0ff';
  ctx.lineWidth = 1.5;
  ctx.beginPath();
  ctx.moveTo(cx, cy);
  ctx.lineTo(cx + Math.cos(p.bodyAngle) * 8, cy + Math.sin(p.bodyAngle) * 8);
  ctx.stroke();

  ctx.strokeStyle = 'rgba(252,200,60,0.6)';
  ctx.beginPath();
  ctx.arc(cx, cy, 9, p.bodyAngle - MAX_TURRET_OFFSET, p.bodyAngle + MAX_TURRET_OFFSET);
  ctx.stroke();

  ctx.strokeStyle = '#fc4';
  ctx.beginPath();
  ctx.moveTo(cx, cy);
  ctx.lineTo(cx + Math.cos(p.turretAngle) * 11, cy + Math.sin(p.turretAngle) * 11);
  ctx.stroke();

  ctx.fillStyle = '#0ff';
  ctx.shadowColor = '#0ff';
  ctx.shadowBlur = 6;
  ctx.beginPath();
  ctx.arc(cx, cy, 3, 0, 6.28);
  ctx.fill();
  ctx.shadowBlur = 0;

  ctx.restore();

  ctx.strokeStyle = '#0ff';
  ctx.lineWidth = 2;
  ctx.shadowColor = '#0ff';
  ctx.shadowBlur = 6;
  ctx.strokeRect(mx, my, size, size);
  ctx.shadowBlur = 0;
}

function updateHUD() {
  const gs = gameState;
  const p  = gs.player;
  const missileReady = p.missileCooldown <= 0;

  const factionStats = [0, 0, 0];
  let maxLevel = 1;
  for (const e of gs.enemies) {
    factionStats[e.faction]++;
    if (e.level > maxLevel) maxLevel = e.level;
  }

  // статус регена
  let regenStatus = '';
  if (p.health < p.maxHealth) {
    if (gs.time - p.lastDamageTime > REGEN_DELAY) {
      regenStatus = `<span class="green">▲ реген активен</span>`;
    } else {
      const left = Math.ceil((REGEN_DELAY - (gs.time - p.lastDamageTime)) / 60);
      regenStatus = `<span class="label">реген через ${left}с</span>`;
    }
  } else {
    regenStatus = `<span class="green">HP полное</span>`;
  }

  hud.innerHTML =
    `<div class="label">HP</div>` +
    `<div>${Math.ceil(p.health)} / ${p.maxHealth}</div>` +
    `<div style="font-size:11px">${regenStatus}</div><br>` +
    `<div class="label">ОЧКИ</div>` +
    `<div>${gs.score}</div><br>` +
    `<div class="label">РЕСУРСЫ</div>` +
    `<div class="gold">${gs.resourcesCount}</div><br>` +
    `<div class="label">РАКЕТА <span class="orange">(ПКМ)</span></div>` +
    `<div class="orange">${missileReady ? 'ГОТОВА' : `перезарядка ${Math.ceil(p.missileCooldown / 60 * 10) / 10}s`}</div><br>` +
    `<div class="label">ФРАКЦИИ</div>` +
    `<div><span style="color:${FACTION_COLORS[0].stroke}">●${factionStats[0]}</span> ` +
    `<span style="color:${FACTION_COLORS[1].stroke}">●${factionStats[1]}</span> ` +
    `<span style="color:${FACTION_COLORS[2].stroke}">●${factionStats[2]}</span> ` +
    `<span style="color:#888">(макс.ур ${maxLevel})</span></div><br>` +
    `<div class="label">ПРОКАЧКА</div>` +
    `<div>[1] Урон ${p.damage} — ${gs.upgradeCost.damage}💰</div>` +
    `<div>[2] Скорострельность ${(60/p.fireRate).toFixed(1)}/с — ${gs.upgradeCost.fireRate}💰</div>` +
    `<div>[3] +${HEAL_PER_UPGRADE} HP + двиг — ${gs.upgradeCost.speed}💰</div>`;
}

function restart() {
  overEl.style.display = 'none';
  init();
}

function loop() {
  if (!gameState.gameOver) {
    update();
  } else {
    for (let i = gameState.particles.length - 1; i >= 0; i--) {
      const pt = gameState.particles[i];
      pt.x += pt.vx; pt.y += pt.vy;
      pt.vx *= 0.96; pt.vy *= 0.96;
      pt.life--;
      if (pt.life <= 0) gameState.particles.splice(i, 1);
    }
    for (let i = gameState.floaters.length - 1; i >= 0; i--) {
      const f = gameState.floaters[i];
      f.y -= 0.6; f.life--;
      if (f.life <= 0) gameState.floaters.splice(i, 1);
    }
  }
  draw();
  requestAnimationFrame(loop);
}

init();
loop();

setTimeout(() => { hintEl.style.opacity = '0'; }, 8000);
</script>
</body>
</html>