chaos-cannon-turbo / index.html
LukasBe's picture
Add 3 files
6e2e189 verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Chaos Cannon | MAYHEM EDITION</title> <!-- Changed Title -->
<script src="https://cdn.tailwindcss.com"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>
/* CSS remains the same as before */
@import url('https://fonts.googleapis.com/css2?family=Press+Start+2P&family=Orbitron:wght@400;700&display=swap');
body {
font-family: 'Orbitron', sans-serif;
background: linear-gradient(135deg, #111 0%, #222 100%);
overflow: hidden;
user-select: none;
margin: 0; padding: 0; display: flex;
min-height: 100vh; align-items: center; justify-content: center;
flex-direction: column;
}
#game-container { position: relative; width: 800px; height: 600px; margin-top: 1rem; }
#game {
background: radial-gradient(ellipse at center, #1a1a2e 0%, #16213e 100%);
box-shadow: 0 0 40px rgba(0, 180, 255, 0.4); /* Enhanced glow */
border-radius: 8px;
border: 3px solid rgba(0, 180, 255, 0.6); /* Brighter border */
display: block; cursor: crosshair;
}
.title-text { font-family: 'Press Start 2P', cursive; text-shadow: 0 0 15px #ff6600, 0 0 25px #ff6600, 0 0 5px #fff; /* Added white core */ letter-spacing: 3px; /* More spacing */ }
.btn-glow { box-shadow: 0 0 15px rgba(255, 102, 0, 0.7); transition: all 0.3s ease; }
.btn-glow:hover { box-shadow: 0 0 30px rgba(255, 102, 0, 1.0); /* Stronger hover */ transform: translateY(-3px) scale(1.05); /* Add scale */}
.btn-glow:active { transform: translateY(1px) scale(1.0); }
.score-display { background: rgba(0, 0, 0, 0.75); border: 2px solid rgba(0, 180, 255, 0.7); border-radius: 8px; text-shadow: 0 0 6px #00ff00, 0 0 10px #00ff00; /* Brighter score */ }
.game-over { background: rgba(0, 0, 0, 0.9); border: 3px solid rgba(255, 0, 0, 0.7); box-shadow: 0 0 40px rgba(255, 0, 0, 0.6); }
.star { position: absolute; background: white; border-radius: 50%; pointer-events: none; }
#titleScreen, #gameOverScreen { position: absolute; inset: 0; display: flex; flex-direction: column; align-items: center; justify-content: center; background-color: rgba(0, 0, 0, 0.85); border-radius: 8px; z-index: 20; }
#titleScreen { opacity: 1; transition: opacity 0.5s ease-out; pointer-events: auto; }
#gameOverScreen { opacity: 0; transition: opacity 0.5s ease-in, background-color 0.5s ease-in; pointer-events: none; }
#gameOverScreen.visible { opacity: 1; pointer-events: auto; background-color: rgba(0, 0, 0, 0.9); }
/* Twinkle animation (no changes needed) */
@keyframes twinkle { from { opacity: 0.1; } to { opacity: ${Math.random() * 0.5 + 0.5}; } }
</style>
</head>
<body class="p-4 text-white">
<div class="absolute top-0 left-0 w-full h-full overflow-hidden z-0" id="stars"></div>
<div class="text-center mb-4 z-10">
<!-- Title updated -->
<h1 class="title-text text-4xl md:text-5xl mb-2">CHAOS CANNON</h1>
<div class="flex justify-center gap-4">
<div class="score-display px-4 py-2 text-xl">
<i class="fas fa-trophy mr-2 text-yellow-400"></i>
<span id="highScore">0</span>
</div>
<div class="score-display px-4 py-2 text-xl">
<i class="fas fa-bolt mr-2 text-orange-500"></i>
<span id="streak">0x</span>
</div>
</div>
</div>
<div id="game-container" class="z-10">
<canvas id="game" width="800" height="600"></canvas>
<div id="titleScreen">
<h2 class="title-text text-5xl mb-8">CHAOS CANNON</h2>
<p class="text-xl mb-8 text-orange-400">MAYHEM EDITION</p> <!-- Subtitle -->
<button id="startBtn" class="btn-glow bg-orange-600 hover:bg-orange-700 text-white font-bold py-3 px-8 rounded-full text-xl mb-4">
<i class="fas fa-play mr-2"></i>START MAYHEM
</button>
<div class="text-gray-400 mt-4">
<p class="mb-2"><i class="fas fa-mouse-pointer mr-2"></i>Aim with mouse</p>
<p><i class="fas fa-mouse mr-2"></i>Click to SHOOT A LOT</p>
</div>
</div>
<div id="gameOverScreen">
<div class="game-over p-8 rounded-lg text-center max-w-md">
<h2 class="title-text text-4xl text-red-500 mb-6">GAME OVER</h2>
<p class="text-2xl mb-2">Your Score:</p>
<p id="finalScore" class="text-4xl font-bold text-orange-500 mb-6">0</p>
<p class="text-lg mb-6 text-gray-300">The mayhem subsided... for now.</p>
<button id="restartBtn" class="btn-glow bg-orange-600 hover:bg-orange-700 text-white font-bold py-3 px-8 rounded-full text-xl">
<i class="fas fa-redo mr-2"></i>RESTART MAYHEM
</button>
</div>
</div>
</div>
<div class="mt-6 text-gray-400 text-sm z-10">
<p>Made with <i class="fas fa-burn text-orange-500"></i> <!-- Changed icon --> for particle lovers</p>
</div>
<script>
// --- Star Background (Same) ---
const starsContainer = document.getElementById('stars');
for (let i = 0; i < 250; i++) { // More stars
const star = document.createElement('div');
star.className = 'star';
star.style.width = `${Math.random() * 3.5}px`; // Slightly bigger max
star.style.height = star.style.width;
star.style.left = `${Math.random() * 100}%`;
star.style.top = `${Math.random() * 100}%`;
const initialOpacity = Math.random() * 0.7 + 0.1;
star.style.opacity = initialOpacity;
star.style.animation = `twinkle ${1.5 + Math.random() * 5}s infinite alternate ease-in-out`; // Faster min twinkle
starsContainer.appendChild(star);
}
const style = document.createElement('style');
style.textContent = ` @keyframes twinkle { from { opacity: 0.1; } to { opacity: ${Math.random() * 0.6 + 0.4}; } } `;
document.head.appendChild(style);
// --- Game Setup (Same) ---
const canvas = document.getElementById('game');
const ctx = canvas.getContext('2d');
let audioContext;
const titleScreen = document.getElementById('titleScreen');
const gameOverScreen = document.getElementById('gameOverScreen');
const startBtn = document.getElementById('startBtn');
const restartBtn = document.getElementById('restartBtn');
const finalScore = document.getElementById('finalScore');
const highScoreDisplay = document.getElementById('highScore');
const streakDisplay = document.getElementById('streak');
let canvasRect = canvas.getBoundingClientRect();
let highScore = localStorage.getItem('chaosCannonHighScore') || 0;
highScoreDisplay.textContent = highScore;
let game;
let lastTime = 0;
let animationFrameId = null;
let mousePos = { x: canvas.width / 2, y: 0 };
// --- Audio Context Initialization (Same) ---
function initAudioContext() { if (!audioContext && (window.AudioContext || window.webkitAudioContext)) { try { audioContext = new (window.AudioContext || window.webkitAudioContext)(); if (audioContext.state === 'suspended') { audioContext.resume(); } } catch (e) { console.error("Audio Error", e); audioContext = null; } } }
document.addEventListener('DOMContentLoaded', initAudioContext);
// --- Particle Class (MAYHEM Edition) ---
class Particle {
constructor(x, y, vx, vy, color, lifespan, size, gravity = 0.05, friction = 0.99, blendMode = 'source-over') { // Added blendMode
this.x = x; this.y = y; this.vx = vx; this.vy = vy; this.color = color;
this.initialLifespan = lifespan; this.lifespan = lifespan;
this.size = size; this.dead = false; this.opacity = 1;
this.gravity = gravity; this.friction = friction;
this.blendMode = blendMode; // Store blend mode
}
update(deltaTime) {
const dtFactor = Math.min(deltaTime / 16.67, 4);
this.x += this.vx * dtFactor; this.y += this.vy * dtFactor;
this.vy += this.gravity * dtFactor;
// Apply friction less aggressively for more hang time
const frictionFactor = Math.pow(this.friction, dtFactor);
this.vx *= frictionFactor; this.vy *= frictionFactor;
this.lifespan -= deltaTime;
// Fade out more slowly initially, then faster
this.opacity = Math.max(0, Math.pow(this.lifespan / this.initialLifespan, 0.5));
if (this.lifespan <= 0) this.dead = true;
}
draw(ctx) {
ctx.globalCompositeOperation = this.blendMode; // Set blend mode
ctx.fillStyle = this.color;
ctx.globalAlpha = this.opacity;
ctx.beginPath();
const currentSize = Math.max(0, this.size * (this.opacity * 1.1)); // Size shrinks slightly slower than opacity
ctx.arc(this.x, this.y, currentSize, 0, Math.PI * 2);
ctx.fill();
ctx.globalAlpha = 1.0;
ctx.globalCompositeOperation = 'source-over'; // Reset blend mode
}
}
// --- Explosion Class (MAYHEM Edition) ---
class Explosion {
constructor(x, y, type, game) {
this.x = x; this.y = y; this.dead = false; this.type = type;
this.game = game; this.duration = 600; // Slightly longer duration
const isBoss = type === 'boss';
// MORE POWER
const basePower = isBoss ? 15 : 9;
const particleMultiplier = isBoss ? 3.0 : 1.8; // MOOORE PARTICLES
// 0. Central Flash
const flashSize = isBoss ? 60 : 35;
game.particles.push(new Particle(this.x, this.y, 0, 0, 'rgba(255, 255, 255, 0.9)', 80, flashSize, 0, 1, 'lighter')); // Short life, big size, lighter blend
game.particles.push(new Particle(this.x, this.y, 0, 0, `hsl(${Math.random()*60}, 100%, 70%)`, 120, flashSize * 0.7, 0, 1, 'lighter')); // Colored flash under white
// 1. Fireball Core Particles
const coreCount = Math.floor(35 * particleMultiplier); // MORE
for (let i = 0; i < coreCount; i++) {
const angle = Math.random() * Math.PI * 2;
const speed = Math.random() * basePower * 1.8 + basePower * 0.6; // Faster max speed
const vx = Math.cos(angle) * speed; const vy = Math.sin(angle) * speed;
const color = `hsl(${Math.random() * 40 + 10}, 100%, ${65 + Math.random() * 35}%)`; // Brighter range
const lifespan = Math.random() * 300 + 150; // Longer short lifespan
const size = Math.random() * (isBoss ? 12 : 8) + 5;
game.particles.push(new Particle(this.x, this.y, vx, vy, color, lifespan, size, 0.03, 0.96, 'lighter')); // Lighter blend for core
}
// 2. Sparks / Debris Particles
const sparkCount = Math.floor(60 * particleMultiplier); // MORE
for (let i = 0; i < sparkCount; i++) {
const angle = Math.random() * Math.PI * 2;
const speed = Math.random() * basePower * 1.2; // Slightly faster debris
const vx = Math.cos(angle) * speed; const vy = Math.sin(angle) * speed - Math.random() * 1.5;
const color = `hsl(${Math.random() * 45}, 100%, ${55 + Math.random() * 25}%)`; // More vibrant reds/oranges
const lifespan = Math.random() * 600 + 400; // Longer medium lifespan
const size = Math.random() * (isBoss ? 5 : 4) + 1.5;
game.particles.push(new Particle(this.x, this.y, vx, vy, color, lifespan, size, 0.06, 0.97)); // More friction
}
// 3. Smoke Particles
const smokeCount = Math.floor(45 * particleMultiplier); // MORE
for (let i = 0; i < smokeCount; i++) {
const angle = Math.random() * Math.PI * 2;
const speed = Math.random() * basePower * 0.5 + 0.5; // Slightly faster smoke base
const vx = Math.cos(angle) * speed + (Math.random() - 0.5) * 1.5; // More drift
const vy = Math.sin(angle) * speed - Math.random() * 0.8 - 0.3; // Stronger upward drift
const gray = 10 + Math.random() * 50; // Darker range
const color = `rgba(${gray}, ${gray}, ${gray+5}, ${0.5 + Math.random() * 0.4})`; // More opaque max
const lifespan = Math.random() * 1500 + 1000; // Even longer lifespan
const size = Math.random() * (isBoss ? 18 : 12) + 8; // Bigger smoke
game.particles.push(new Particle(this.x, this.y, vx, vy, color, lifespan, size, -0.015, 0.95)); // More anti-grav, more friction
}
}
update(game, deltaTime) { this.duration -= deltaTime; if (this.duration <= 0) this.dead = true; }
draw(ctx) { /* Only particles draw */ }
}
// --- Game Class (MAYHEM Edition) ---
class Game {
// Constructor remains largely the same
constructor() {
this.state = 'title'; this.score = 0; this.shake = 0; this.turret = new Turret();
this.projectiles = []; this.enemies = []; this.particles = []; this.explosions = []; this.powerUps = [];
this.streak = 0; this.lastHitTime = 0; this.stars = []; this.enemySpawnTimer = 0; this.powerUpSpawnTimer = 0;
this.enemySpawnInterval = 1600; // Slightly faster initial spawn
this.powerUpSpawnInterval = 9000; // Faster powerups
this.difficultyTimer = 0;
this.boundClickHandler = this.clickHandler.bind(this);
this.boundMouseMoveHandler = this.mouseMoveHandler.bind(this);
canvas.addEventListener('click', this.boundClickHandler);
canvas.addEventListener('mousemove', this.boundMouseMoveHandler);
startBtn.addEventListener('click', () => this.start());
restartBtn.addEventListener('click', () => this.restart());
for(let i = 0; i < 70; i++) { // More background stars
this.stars.push({ x: Math.random() * canvas.width, y: Math.random() * canvas.height, size: Math.random() * 2.5 + 0.5, opacity: Math.random() * 0.5 + 0.1, speed: Math.random() * 0.15 + 0.05 }); // Slightly faster stars
}
}
// clickHandler, mouseMoveHandler, start, restart (remain the same)
clickHandler(e) { if (!audioContext) initAudioContext(); if (audioContext && audioContext.state === 'suspended') { audioContext.resume(); } if (this.state === 'playing') { this.turret.shoot(this); } }
mouseMoveHandler(e) { canvasRect = canvas.getBoundingClientRect(); mousePos.x = e.clientX - canvasRect.left; mousePos.y = e.clientY - canvasRect.top; const MIN_ANGLE = -Math.PI * 0.95; const MAX_ANGLE = -Math.PI * 0.05; let targetAngle = Math.atan2(mousePos.y - this.turret.y, mousePos.x - this.turret.x); this.turret.angle = Math.max(MIN_ANGLE, Math.min(MAX_ANGLE, targetAngle)); }
start() { if (animationFrameId) { cancelAnimationFrame(animationFrameId); animationFrameId = null; } if (!audioContext) initAudioContext(); if (audioContext && audioContext.state === 'suspended') { audioContext.resume(); } this.state = 'playing'; this.score = 0; this.streak = 0; this.lastHitTime = 0; this.enemySpawnTimer = 0; this.powerUpSpawnTimer = 0; this.enemySpawnInterval = 1600; this.powerUpSpawnInterval = 9000; this.difficultyTimer = 0; this.shake = 0; this.turret = new Turret(); this.projectiles = []; this.enemies = []; this.particles = []; this.explosions = []; this.powerUps = []; this.updateScoreDisplay(); streakDisplay.textContent = `0x`; titleScreen.style.opacity = '0'; titleScreen.style.pointerEvents = 'none'; gameOverScreen.classList.remove('visible'); lastTime = performance.now(); this.animate(); }
restart() { this.start(); }
spawnEnemies(deltaTime) {
this.enemySpawnTimer += deltaTime;
if (this.enemySpawnTimer >= this.enemySpawnInterval) {
this.enemySpawnTimer -= this.enemySpawnInterval;
const isBoss = Math.random() < (0.12 + this.difficultyTimer / 250000); // Faster boss scaling
const type = isBoss ? 'boss' : 'normal';
const edgeBuffer = type === 'boss' ? 50 : 30;
// Faster base speed, faster scaling
const speed = (isBoss ? 0.8 : 1.2) * (Math.random() * 1.8 + 1.0 + (this.difficultyTimer / 60000));
const newEnemy = new Enemy( Math.random() * (canvas.width - edgeBuffer * 2) + edgeBuffer, -60, speed, type );
this.enemies.push(newEnemy);
}
}
spawnPowerUps(deltaTime) {
this.powerUpSpawnTimer += deltaTime;
if (this.powerUpSpawnTimer >= this.powerUpSpawnInterval) {
this.powerUpSpawnTimer = 0;
if(Math.random() > 0.4) { // Even more powerups!
const edgeBuffer = 40;
this.powerUps.push(new PowerUp( Math.random() * (canvas.width - edgeBuffer * 2) + edgeBuffer, -30, ['rapid', 'explosive', 'multi'][Math.floor(Math.random() * 3)] ));
}
}
}
increaseDifficulty(deltaTime) {
this.difficultyTimer += deltaTime;
const decreaseFactor = 1 - (deltaTime / 200000); // Faster difficulty scaling (interval decrease)
this.enemySpawnInterval = Math.max(250, this.enemySpawnInterval * decreaseFactor); // Lower min interval (0.25s)
}
endGame() { if (this.state === 'gameover') return; this.state = 'gameover'; this.playGameOverSound(); if(this.score > highScore) { highScore = this.score; localStorage.setItem('chaosCannonHighScore', highScore); highScoreDisplay.textContent = highScore; } finalScore.textContent = this.score; gameOverScreen.classList.add('visible'); }
update(deltaTime) {
if(this.state !== 'playing') return;
this.increaseDifficulty(deltaTime);
this.spawnEnemies(deltaTime);
this.spawnPowerUps(deltaTime);
// Update entities (pass deltaTime) - Turret needs it for idle particles now
this.turret.update(deltaTime, this); // Pass game reference for particles
[...this.projectiles, ...this.enemies, ...this.particles, ...this.explosions, ...this.powerUps].forEach(e => e.update(this, deltaTime));
streakDisplay.textContent = `${this.streak}x`;
if(this.streak > 0 && Date.now() - this.lastHitTime > 3000) this.streak = 0;
// --- Collision Detection ---
for (let i = this.projectiles.length - 1; i >= 0; i--) {
const projectile = this.projectiles[i];
if (projectile.dead) continue;
for (let j = this.enemies.length - 1; j >= 0; j--) {
const enemy = this.enemies[j];
const enemyRadius = enemy.type === 'boss' ? 40 : 20;
if (Math.hypot(projectile.x - enemy.x, projectile.y - enemy.y) < (enemyRadius + projectile.size)) {
enemy.health--; // Health reduced earlier, easier enemies!
if (enemy.health <= 0) {
// Enemy destroyed
if (Date.now() - this.lastHitTime < 3000) this.streak++; else this.streak = 1;
this.lastHitTime = Date.now();
this.explosions.push(new Explosion(enemy.x, enemy.y, enemy.type, this)); // MAYHEM explosion
const basePoints = enemy.type === 'boss' ? 250 : 50; // Less points needed as they die faster
this.score += Math.round(basePoints * (1 + this.streak * 0.20)); // Higher streak bonus
this.updateScoreDisplay();
this.enemies.splice(j, 1);
this.shake = enemy.type === 'boss' ? 25 : 12; // MORE SHAKE
this.playExplosionSound(enemy.type);
if(enemy.type === 'boss' && Math.random() > 0.3) { // Very high powerup drop chance
this.powerUps.push(new PowerUp( enemy.x, enemy.y, ['rapid', 'explosive', 'multi'][Math.floor(Math.random() * 3)] ));
}
} else {
// --- Enemy Hit Spark Particles ---
this.playHitSound();
this.shake = 6; // Keep small hit shake
const impactAngle = Math.atan2(projectile.y - enemy.y, projectile.x - enemy.x);
for (let k = 0; k < 5 + Math.random() * 5; k++) { // 5-10 sparks
const angle = impactAngle + (Math.random() - 0.5) * 1.5; // Spread sparks
const speed = Math.random() * 3 + 1;
const vx = Math.cos(angle) * speed;
const vy = Math.sin(angle) * speed;
const color = `hsl(${Math.random()*20 + 20}, 80%, ${50 + Math.random()*20}%)`; // Orange/Yellow sparks
const lifespan = Math.random() * 150 + 50; // Short life
const size = Math.random() * 2 + 0.5;
this.particles.push(new Particle(projectile.x, projectile.y, vx, vy, color, lifespan, size, 0.1, 0.96)); // More gravity
}
}
if (projectile.explosive) {
this.explosions.push(new Explosion(projectile.x, projectile.y, 'normal', this)); // Explosive projectiles also get MAYHEM explosions
this.playExplosionSound('normal');
}
projectile.dead = true;
break;
}
}
}
// Powerup vs Turret (Same logic)
for (let i = this.powerUps.length - 1; i >= 0; i--) { const powerUp = this.powerUps[i]; if (Math.hypot(powerUp.x - this.turret.x, powerUp.y - (this.turret.y + 10)) < (30 + powerUp.size / 2)) { this.turret.activatePowerUp(powerUp.type); this.powerUps.splice(i, 1); this.playPowerUpSound(); this.score += 100; this.updateScoreDisplay(); } }
// --- Cleanup --- Filter dead entities
this.projectiles = this.projectiles.filter(p => !p.dead && p.y > -150 && p.y < canvas.height + 150 && p.x > -150 && p.x < canvas.width + 150); // Even wider bounds
this.particles = this.particles.filter(p => !p.dead);
// Limit total particles if needed (optional performance safeguard)
// const MAX_PARTICLES = 3000;
// if (this.particles.length > MAX_PARTICLES) {
// this.particles.splice(0, this.particles.length - MAX_PARTICLES);
// }
this.explosions = this.explosions.filter(e => !e.dead);
this.powerUps = this.powerUps.filter(p => !p.dead);
// Update in-game stars (Same)
this.stars.forEach(star => { star.y += star.speed * (deltaTime / 16.67); if (star.y > canvas.height) { star.y = -star.size; star.x = Math.random() * canvas.width; } });
}
// Draw method (minor adjustments for mayhem feel)
draw() {
ctx.save();
if(this.shake > 0) { const shakeX = Math.random() * this.shake - this.shake / 2; const shakeY = Math.random() * this.shake - this.shake / 2; ctx.translate(shakeX, shakeY); this.shake *= 0.92; if (this.shake < 0.5) this.shake = 0; }
// Background Color
ctx.fillStyle = '#080814'; // Even darker
ctx.fillRect(0, 0, canvas.width, canvas.height);
// Background Moving Stars
this.stars.forEach(star => { ctx.fillStyle = `rgba(255, 255, 255, ${star.opacity * 1.2})`; ctx.fillRect(star.x, star.y, star.size, star.size); }); // Brighter stars
// Grid Lines (Fainter)
ctx.strokeStyle = 'rgba(0, 150, 255, 0.06)'; ctx.lineWidth = 0.8;
for(let x = 0; x < canvas.width; x += 50) { ctx.beginPath(); ctx.moveTo(x, 0); ctx.lineTo(x, canvas.height); ctx.stroke(); }
for(let y = 0; y < canvas.height; y += 50) { ctx.beginPath(); ctx.moveTo(0, y); ctx.lineTo(canvas.width, y); ctx.stroke(); }
// Game Entities (Order matters for layering)
this.powerUps.forEach(p => p.draw(ctx));
this.turret.draw(ctx);
this.enemies.forEach(e => e.draw(ctx));
this.projectiles.forEach(p => p.draw(ctx));
this.particles.forEach(p => p.draw(ctx)); // Draw ALL particles last
ctx.restore(); // Restore before UI
// --- UI (Drawn after restoring context) ---
// Score Box
ctx.fillStyle = 'rgba(0, 0, 0, 0.7)'; ctx.strokeStyle = 'rgba(0, 180, 255, 0.7)'; ctx.lineWidth = 2;
ctx.strokeRect(10, 10, 200, 40); ctx.fillRect(10, 10, 200, 40);
ctx.font = '18px Orbitron'; ctx.fillStyle = '#00ff00'; ctx.textAlign = 'left'; ctx.textBaseline = 'middle';
ctx.shadowColor = '#0f0'; ctx.shadowBlur = 5; // Add glow to score text
ctx.fillText(`SCORE: ${this.score}`, 20, 30);
ctx.shadowBlur = 0; // Reset shadow
// Powerup Timer (minor visual tweaks)
if(this.turret.powerUpActive) { const remaining = (this.turret.powerUpEnd - Date.now()) / 1000; if(remaining > 0) { const powerColor = this.turret.getPowerUpColor(); ctx.fillStyle = 'rgba(0, 0, 0, 0.7)'; ctx.strokeStyle = powerColor; ctx.lineWidth = 2.5; /* Thicker border */ const timerWidth = 220; const timerHeight = 30; const timerX = canvas.width - timerWidth - 10; const timerY = 10; ctx.strokeRect(timerX, timerY, timerWidth, timerHeight); ctx.fillRect(timerX, timerY, timerWidth, timerHeight); ctx.fillStyle = powerColor; ctx.font = '14px Orbitron'; ctx.textAlign = 'left'; ctx.textBaseline = 'middle'; ctx.shadowColor = powerColor; ctx.shadowBlur = 8; /* Glow for timer text */ if (remaining < 3 && Math.floor(Date.now() / 150) % 2 === 0) { ctx.fillStyle = '#FFFFFF'; ctx.shadowColor = '#FFF'; } /* Faster flash */ ctx.fillText(`${this.turret.powerUpType.toUpperCase()} ACTIVE: ${remaining.toFixed(1)}s`, timerX + 10, timerY + timerHeight / 2); ctx.shadowBlur = 0; } }
}
// updateScoreDisplay, Sound Effects, animate, drawStaticBackground, drawUI (remain same structure)
updateScoreDisplay() { /* Optional direct DOM update */ }
_playSound(oscType, freqStart, freqEnd, gainVal, duration, filterType = null, filterFreqStart = 20000, filterFreqEnd = 20000) { if (!audioContext || audioContext.state !== 'running') return; const oscillator = audioContext.createOscillator(); const gainNode = audioContext.createGain(); let lastNode = oscillator; if (filterType) { const filter = audioContext.createBiquadFilter(); filter.type = filterType; filter.frequency.setValueAtTime(filterFreqStart, audioContext.currentTime); filter.frequency.exponentialRampToValueAtTime(filterFreqEnd, audioContext.currentTime + duration); oscillator.connect(filter); lastNode = filter; } lastNode.connect(gainNode); gainNode.connect(audioContext.destination); oscillator.type = oscType; oscillator.frequency.setValueAtTime(freqStart, audioContext.currentTime); if (freqStart !== freqEnd) { oscillator.frequency.exponentialRampToValueAtTime(freqEnd, audioContext.currentTime + duration * 0.8); } gainNode.gain.setValueAtTime(gainVal, audioContext.currentTime); gainNode.gain.exponentialRampToValueAtTime(0.0001, audioContext.currentTime + duration); oscillator.start(audioContext.currentTime); oscillator.stop(audioContext.currentTime + duration); }
playShootSound() { this._playSound('triangle', 700, 120, 0.10, 0.12); } // Slightly adjusted pitch/vol
playHitSound() { this._playSound('square', 500, 400, 0.06, 0.08); } // Lower pitch hit
playPowerUpSound() { this._playSound('sine', 440, 1300, 0.18, 0.4); }
playGameOverSound() { this._playSound('sawtooth', 100, 40, 0.3, 1.2); this._playSound('square', 90, 35, 0.3, 1.2); }
playExplosionSound(type) { const isBoss = type === 'boss'; this._playSound(isBoss ? 'noise' : 'square', isBoss ? 50 : 80 + Math.random() * 50, isBoss ? 20 : 40, isBoss ? 0.35 : 0.18, isBoss ? 0.6 : 0.45, 'lowpass', isBoss ? 1800 : 3500, 80); } // Use 'noise' for boss, louder/longer, lower filter end
animate() { animationFrameId = requestAnimationFrame((ts) => this.animate(ts)); const now = performance.now(); const deltaTime = Math.min(now - lastTime, 100); lastTime = now; ctx.clearRect(0, 0, canvas.width, canvas.height); if (this.state === 'playing') { this.update(deltaTime); this.draw(); } else if (this.state === 'gameover') { this.drawStaticBackground(); this.particles.forEach(p => p.update(this, deltaTime)); this.particles = this.particles.filter(p => !p.dead); this.particles.forEach(p => p.draw(ctx)); this.turret.draw(ctx); this.drawUI(); } else if (this.state === 'title') { this.drawStaticBackground(); } }
drawStaticBackground() { ctx.save(); ctx.fillStyle = '#080814'; ctx.fillRect(0, 0, canvas.width, canvas.height); this.stars.forEach(star => { ctx.fillStyle = `rgba(255, 255, 255, ${star.opacity * 1.2})`; ctx.fillRect(star.x, star.y, star.size, star.size); }); ctx.strokeStyle = 'rgba(0, 150, 255, 0.06)'; ctx.lineWidth = 0.8; for (let x = 0; x < canvas.width; x += 50) { ctx.beginPath(); ctx.moveTo(x, 0); ctx.lineTo(x, canvas.height); ctx.stroke(); } for (let y = 0; y < canvas.height; y += 50) { ctx.beginPath(); ctx.moveTo(0, y); ctx.lineTo(canvas.width, y); ctx.stroke(); } ctx.restore(); }
drawUI() { ctx.fillStyle = 'rgba(0, 0, 0, 0.7)'; ctx.strokeStyle = 'rgba(0, 180, 255, 0.7)'; ctx.lineWidth = 2; ctx.strokeRect(10, 10, 200, 40); ctx.fillRect(10, 10, 200, 40); ctx.font = '18px Orbitron'; ctx.fillStyle = '#00ff00'; ctx.textAlign = 'left'; ctx.textBaseline = 'middle'; ctx.shadowColor = '#0f0'; ctx.shadowBlur = 5; ctx.fillText(`SCORE: ${this.score}`, 20, 30); ctx.shadowBlur = 0; }
}
// --- Turret Class (MAYHEM Edition) ---
class Turret {
// Constructor and angle setter/getter same
constructor() { this.x = canvas.width / 2; this.y = canvas.height - 30; this.angle = -Math.PI / 2; this.cooldown = 0; this.powerUpActive = false; this.powerUpType = null; this.powerUpEnd = 0; this.barrelLength = 45; this.recoil = 0; this.targetAngle = -Math.PI / 2; this.aimSpeed = 0.2; this._currentAngle = -Math.PI/2; this.idleParticleTimer = 0; } // Add idle timer
set angle(target) { this.targetAngle = target; }
get angle() { return this._currentAngle || -Math.PI / 2; }
update(deltaTime, game) { // Added game reference
const current = this.angle; const target = this.targetAngle; let diff = target - current; while (diff < -Math.PI) diff += Math.PI * 2; while (diff > Math.PI) diff -= Math.PI * 2;
this._currentAngle = current + diff * this.aimSpeed * (deltaTime / 16.67);
if (this.cooldown > 0) this.cooldown -= deltaTime;
if (this.recoil > 0) this.recoil -= deltaTime * 0.4; // Even faster recoil recovery
if (this.recoil < 0) this.recoil = 0;
if(this.powerUpActive && Date.now() > this.powerUpEnd) { this.powerUpActive = false; this.powerUpType = null; }
// --- Idle Turret Particles ---
this.idleParticleTimer += deltaTime;
const idleInterval = this.powerUpActive ? 30 : 150; // Faster sparks when powered up
if (this.idleParticleTimer > idleInterval) {
this.idleParticleTimer -= idleInterval;
const angle = Math.random() * Math.PI * 2; // Emit from base
const speed = Math.random() * 0.5 + 0.1;
const vx = Math.cos(angle) * speed;
const vy = Math.sin(angle) * speed;
const color = this.powerUpActive ? this.getPowerUpColor() + '80' : 'rgba(100, 150, 255, 0.5)'; // Use powerup color or default blue
const lifespan = Math.random() * 300 + 200;
const size = Math.random() * 1.5 + 0.5;
game.particles.push(new Particle(this.x + Math.cos(angle) * 20, this.y + Math.sin(angle) * 10, vx, vy, color, lifespan, size, 0.01, 0.98));
}
}
shoot(game) {
if(this.cooldown <= 0) {
game.playShootSound();
this.recoil = 10; // More recoil visual
const shootAngle = this.angle;
const muzzleOffsetX = Math.cos(shootAngle) * (this.barrelLength - this.recoil);
const muzzleOffsetY = Math.sin(shootAngle) * (this.barrelLength - this.recoil);
const startX = this.x + muzzleOffsetX;
const startY = this.y + muzzleOffsetY;
// --- Muzzle Flash Particles ---
const flashCount = this.powerUpActive && this.powerUpType === 'explosive' ? 25 : 15; // More flash for explosive
const flashPower = this.powerUpActive && this.powerUpType === 'explosive' ? 6 : 4;
const flashBaseColor = this.powerUpActive && this.powerUpType === 'explosive' ? 15 : 40; // Orange/Red for explosive, Yellow otherwise
for (let i = 0; i < flashCount; i++) {
const angle = shootAngle + (Math.random() - 0.5) * 0.8; // Cone shape
const speed = Math.random() * flashPower + 1.0;
const vx = Math.cos(angle) * speed;
const vy = Math.sin(angle) * speed;
const color = `hsl(${flashBaseColor + Math.random()*20 - 10}, 100%, ${70 + Math.random()*30}%)`; // Bright yellow/orange/white
const lifespan = Math.random() * 80 + 40; // Very short life
const size = Math.random() * 4 + 1;
game.particles.push(new Particle(startX, startY, vx, vy, color, lifespan, size, 0.02, 0.92, 'lighter')); // Lighter blend
}
// --- Projectile Spawning ---
const projectileSpeed = 14; // Faster projectiles
const vx = Math.cos(shootAngle) * projectileSpeed;
const vy = Math.sin(shootAngle) * projectileSpeed;
const isExplosive = this.powerUpActive && this.powerUpType === 'explosive';
if(this.powerUpActive && this.powerUpType === 'multi') {
const spreadAngle = 0.2; // Even wider multi
for(let i = -1; i <= 1; i++) {
const angle = shootAngle + (i * spreadAngle);
game.projectiles.push(new Projectile( startX, startY, Math.cos(angle) * projectileSpeed, Math.sin(angle) * projectileSpeed, isExplosive ));
}
this.cooldown = 250; // Keep multi cooldown reasonable
} else {
game.projectiles.push(new Projectile(startX, startY, vx, vy, isExplosive));
// Much faster firing rate!
this.cooldown = this.powerUpActive && this.powerUpType === 'rapid' ? 40 : 100; // RAPID FIRE!
}
}
}
// activatePowerUp, getPowerUpColor, draw (remain same structure, visuals already good)
activatePowerUp(type) { this.powerUpActive = true; this.powerUpType = type; this.powerUpEnd = Date.now() + 9000; } // Longer powerup duration
getPowerUpColor() { switch(this.powerUpType) { case 'rapid': return '#00ffff'; case 'explosive': return '#ff3300'; case 'multi': return '#ffff00'; default: return '#ffffff'; } }
draw(ctx) { ctx.save(); ctx.translate(this.x, this.y); ctx.fillStyle = 'rgba(60, 60, 80, 0.9)'; ctx.strokeStyle = 'rgba(100, 100, 120, 1)'; ctx.lineWidth = 2; ctx.beginPath(); ctx.moveTo(-50, 20); ctx.lineTo(50, 20); ctx.arc(0, 20, 50, 0, Math.PI, false); ctx.closePath(); ctx.fill(); ctx.stroke(); ctx.rotate(this.angle); const barrelX = -this.recoil; const gradient = ctx.createLinearGradient(barrelX, -6, barrelX + this.barrelLength, 6); gradient.addColorStop(0, '#AAA'); gradient.addColorStop(0.5, '#888'); gradient.addColorStop(1, '#777'); ctx.fillStyle = gradient; ctx.strokeStyle = '#555'; ctx.lineWidth = 1; ctx.beginPath(); ctx.rect(barrelX, -6, this.barrelLength, 12); ctx.fill(); ctx.stroke(); const baseGradient = ctx.createRadialGradient(0, 0, 5, 0, 0, 25); baseGradient.addColorStop(0, '#888'); baseGradient.addColorStop(1, '#555'); ctx.fillStyle = baseGradient; ctx.strokeStyle = '#444'; ctx.lineWidth = 2; ctx.beginPath(); ctx.arc(0, 0, 25, 0, Math.PI * 2); ctx.fill(); ctx.stroke(); const coreRadius = 12; let powerColor = '#555'; let glow = false; if(this.powerUpActive) { powerColor = this.getPowerUpColor(); glow = true; } ctx.fillStyle = powerColor; ctx.beginPath(); ctx.arc(0, 0, coreRadius, 0, Math.PI * 2); ctx.fill(); if (glow) { ctx.shadowColor = powerColor; ctx.shadowBlur = 30; /* More intense glow */ ctx.fillStyle = 'rgba(255, 255, 255, 0.9)'; ctx.beginPath(); ctx.arc(0, 0, coreRadius * 0.6, 0, Math.PI * 2); ctx.fill(); ctx.shadowBlur = 0; } ctx.restore(); }
}
// --- Projectile Class (MAYHEM Edition) ---
class Projectile {
constructor(x, y, vx, vy, explosive) {
this.x = x; this.y = y; this.vx = vx; this.vy = vy;
this.dead = false; this.explosive = explosive;
this.size = explosive ? 8 : 5; // Slightly larger base size
this.trailTimer = 0;
this.trailInterval = 8; // FASTER trail emission
}
update(game, deltaTime) {
const dtFactor = Math.min(deltaTime / 16.67, 4);
this.x += this.vx * dtFactor; this.y += this.vy * dtFactor;
if(this.y < -150 || this.y > canvas.height + 150 || this.x < -150 || this.x > canvas.width + 150) this.dead = true;
this.trailTimer += deltaTime;
if (this.trailTimer >= this.trailInterval) {
this.trailTimer -= this.trailInterval;
const trailColor = this.explosive ?
`hsl(${Math.random() * 25 + 2}, 100%, ${65 + Math.random() * 25}%)` : // More intense red/orange
`hsl(${Math.random() * 25 + 30}, 100%, ${75 + Math.random() * 25}%)`; // Brighter yellow/orange
game.particles.push(new Particle(
this.x + (Math.random()-0.5)*this.size, // Emit from within projectile area
this.y + (Math.random()-0.5)*this.size,
this.vx * -0.05 + Math.random() * 1 - 0.5, // Less opposing velocity
this.vy * -0.05 + Math.random() * 1 - 0.5,
trailColor,
200 + Math.random() * 150, // Longer trail lifespan
this.explosive ? 5 : 3, // Slightly larger trail particles
0.01, 0.97, 'lighter' // Use lighter blend for trails too
));
}
}
// Draw method (same structure, lighter blend will make it brighter)
draw(ctx) {
const coreColor = this.explosive ? '#ffeecc' : '#ffffdd';
const outerColor1 = this.explosive ? '#ff6600' : '#ffaa00';
const outerColor2 = this.explosive ? '#cc2200' : '#dd6600';
const gradient = ctx.createRadialGradient(this.x, this.y, 0, this.x, this.y, this.size);
gradient.addColorStop(0, coreColor); gradient.addColorStop(0.4, outerColor1); gradient.addColorStop(1, outerColor2);
ctx.globalCompositeOperation = 'lighter'; // Draw projectile with lighter blend
ctx.fillStyle = gradient;
ctx.beginPath(); ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2); ctx.fill();
ctx.shadowColor = this.explosive ? '#ff3300' : '#ff9900';
ctx.shadowBlur = this.explosive ? 22 : 15; // More glow
ctx.fill(); // Draw again with shadow enabled
ctx.shadowBlur = 0;
ctx.globalCompositeOperation = 'source-over'; // Reset blend mode
}
}
// --- Enemy Class (MAYHEM Edition - Health Change!) ---
class Enemy {
constructor(x, y, speed, type) {
this.x = x; this.y = y; this.speed = speed; this.type = type || 'normal';
// --- HEALTH CHANGE ---
this.initialHealth = type === 'boss' ? 3 : 1; // BOSS: 3 HP, NORMAL: 1 HP
this._health = this.initialHealth; // Initialize private health
// --------------------
this.wobble = Math.random() * Math.PI * 2; this.wobbleSpeed = type === 'boss' ? 0.05 : 0.08; // Faster wobble
this.wobbleAmount = type === 'boss' ? 8 : 5; // More wobble
this.angle = 0; this.hitTimer = 0; this.hitDuration = 80; // Shorter hit flash
}
set health(value) { if (value < this._health) { this.hitTimer = this.hitDuration; } this._health = value; }
get health() { return this._health; } // No need for default here anymore
// Update method (same logic, checks remain)
update(game, deltaTime) { const dtFactor = Math.min(deltaTime / 16.67, 4); this.y += this.speed * dtFactor; this.wobble += this.wobbleSpeed * dtFactor; const wobbleOffset = Math.sin(this.wobble) * this.wobbleAmount; this.x += wobbleOffset * 0.1 * dtFactor; if (this.hitTimer > 0) { this.hitTimer -= deltaTime; } const radius = this.type === 'boss' ? 40 : 20; this.x = Math.max(radius, Math.min(canvas.width - radius, this.x)); if(this.y > canvas.height + radius * 2) { if (this.type !== 'boss') { game.endGame(); } else { const index = game.enemies.indexOf(this); if (index > -1) game.enemies.splice(index, 1); } } }
// Draw method (same structure, flash effect remains)
draw(ctx) { ctx.save(); const wobbleOffsetY = Math.sin(this.wobble) * this.wobbleAmount * 0.5; ctx.translate(this.x, this.y + wobbleOffsetY); ctx.rotate(this.angle); const isHit = this.hitTimer > 0; if(this.type === 'boss') { const width = 80; const height = 55; const gradient = ctx.createRadialGradient(0, 0, 10, 0, 0, width / 2); gradient.addColorStop(0, isHit ? '#ffffff' : '#ff4444'); gradient.addColorStop(0.6, '#cc0000'); gradient.addColorStop(1, '#880000'); ctx.fillStyle = gradient; ctx.strokeStyle = '#660000'; ctx.lineWidth = 2; ctx.beginPath(); ctx.ellipse(0, 0, width / 2, height / 2, 0, 0, Math.PI * 2); ctx.fill(); ctx.stroke(); ctx.fillStyle = isHit ? '#ffcccc' : '#ffff00'; ctx.beginPath(); ctx.ellipse(-18, -8, 10, 6, -0.2, 0, Math.PI * 2); ctx.fill(); ctx.beginPath(); ctx.ellipse(18, -8, 10, 6, 0.2, 0, Math.PI * 2); ctx.fill(); ctx.fillStyle = '#000000'; ctx.beginPath(); ctx.arc(-18, -8, 3, 0, Math.PI * 2); ctx.fill(); ctx.beginPath(); ctx.arc(18, -8, 3, 0, Math.PI * 2); ctx.fill(); const healthBarWidth = 60; const healthBarHeight = 8; const healthBarY = -(height / 2) - 15; ctx.fillStyle = 'rgba(80, 80, 80, 0.7)'; ctx.fillRect(-healthBarWidth / 2, healthBarY, healthBarWidth, healthBarHeight); const healthPercent = Math.max(0, this.health / this.initialHealth); ctx.fillStyle = healthPercent > 0.66 ? '#00ff00' : healthPercent > 0.33 ? '#ffff00' : '#ff0000'; /* Adjusted thresholds for lower HP */ ctx.fillRect(-healthBarWidth / 2, healthBarY, healthBarWidth * healthPercent, healthBarHeight); ctx.strokeStyle = 'rgba(200, 200, 200, 0.8)'; ctx.lineWidth = 1; ctx.strokeRect(-healthBarWidth / 2, healthBarY, healthBarWidth, healthBarHeight); } else { const size = 20; const gradient = ctx.createLinearGradient(0, -size * 0.7, 0, size * 0.3); gradient.addColorStop(0, isHit ? '#ffffff' : '#33dd33'); gradient.addColorStop(1, '#008800'); ctx.fillStyle = gradient; ctx.strokeStyle = '#005500'; ctx.lineWidth = 1.5; ctx.beginPath(); ctx.moveTo(0, -size * 0.7); ctx.lineTo(-size, size * 0.3); ctx.lineTo(size, size * 0.3); ctx.closePath(); ctx.fill(); ctx.stroke(); ctx.fillStyle = isHit ? 'rgba(255, 100, 100, 0.9)' : 'rgba(0, 200, 255, 0.6)'; ctx.shadowColor = isHit ? 'rgba(255, 0, 0, 1)' : 'rgba(0, 200, 255, 1)'; ctx.shadowBlur = 8; ctx.beginPath(); ctx.arc(0, -size * 0.1, size * 0.3, 0, Math.PI * 2); ctx.fill(); ctx.shadowBlur = 0; } ctx.restore(); }
}
// --- PowerUp Class (Same structure, visuals already good) ---
class PowerUp { constructor(x, y, type) { this.x = x; this.y = y; this.type = type; this.vy = 1.8; /* Slightly faster fall */ this.dead = false; this.size = 18; this.angle = Math.random() * Math.PI * 2; this.rotationSpeed = (Math.random() - 0.5) * 0.05; this.pulseTimer = Math.random() * 1000; } update(game, deltaTime) { const dtFactor = Math.min(deltaTime / 16.67, 4); this.y += this.vy * dtFactor; this.angle += this.rotationSpeed * dtFactor; this.pulseTimer += deltaTime; if(this.y > canvas.height + this.size * 2) this.dead = true; } draw(ctx) { ctx.save(); ctx.translate(this.x, this.y); ctx.rotate(this.angle); let color1, color2, symbolFunc; const iconSize = this.size * 0.8; switch(this.type) { case 'rapid': color1 = '#00ffff'; color2 = '#00aaff'; symbolFunc = () => { ctx.beginPath(); ctx.moveTo(-iconSize*0.2, -iconSize*0.5); ctx.lineTo(iconSize*0.3, 0); ctx.lineTo(-iconSize*0.3, 0); ctx.lineTo(iconSize*0.2, iconSize*0.5); ctx.strokeStyle=color2; ctx.lineWidth=3; ctx.stroke(); }; break; case 'explosive': color1 = '#ff6600'; color2 = '#ffaa00'; symbolFunc = () => { ctx.fillStyle=color2; ctx.beginPath(); ctx.arc(0, 0, iconSize*0.4, 0, Math.PI*2); ctx.fill(); ctx.strokeStyle='#555'; ctx.lineWidth=2; ctx.beginPath(); ctx.moveTo(0, -iconSize*0.4); ctx.lineTo(iconSize*0.3, -iconSize*0.6); ctx.stroke(); ctx.fillStyle='#fff'; ctx.beginPath(); ctx.arc(iconSize*0.3, -iconSize*0.6, 2, 0, Math.PI*2); ctx.fill(); }; break; case 'multi': color1 = '#ffff00'; color2 = '#ffcc00'; symbolFunc = () => { ctx.fillStyle=color2; const r = iconSize*0.2; ctx.beginPath(); ctx.arc(-iconSize*0.3, iconSize*0.15, r, 0, Math.PI*2); ctx.fill(); ctx.beginPath(); ctx.arc( iconSize*0.3, iconSize*0.15, r, 0, Math.PI*2); ctx.fill(); ctx.beginPath(); ctx.arc( 0, -iconSize*0.3, r, 0, Math.PI*2); ctx.fill(); }; break; } const pulseFactor = 1.0 + Math.sin(this.pulseTimer / 250) * 0.12; ctx.strokeStyle = color1; ctx.lineWidth = 2; ctx.globalAlpha = 0.6 + Math.sin(this.pulseTimer / 250) * 0.3; ctx.beginPath(); ctx.arc(0, 0, this.size * 0.9 * pulseFactor, 0, Math.PI * 2); ctx.stroke(); ctx.globalAlpha = 1.0; ctx.fillStyle = 'rgba(50, 50, 70, 0.8)'; ctx.strokeStyle = color1; ctx.lineWidth = 1.5; const backSize = this.size * 0.9; ctx.beginPath(); ctx.roundRect(-backSize, -backSize, backSize*2, backSize*2, 5); ctx.fill(); ctx.stroke(); symbolFunc(); ctx.restore(); } }
// --- Initialize and Start Game (Same) ---
function initGame() { game = new Game(); game.drawStaticBackground(); }
function resizeCanvas() { canvasRect = canvas.getBoundingClientRect(); }
window.addEventListener('resize', resizeCanvas);
resizeCanvas();
initGame();
</script>
<p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=LukasBe/chaos-cannon-turbo" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body>
</html>