Spaces:
Running
Running
<html> | |
<head> | |
<title>Chaos Cannon - CRT Edition</title> | |
<script src="https://cdn.tailwindcss.com"></script> | |
<style> | |
body { | |
background: #000; | |
overflow: hidden; | |
font-family: 'Courier New', monospace; | |
} | |
#crt-screen { | |
position: relative; | |
width: 800px; | |
height: 600px; | |
margin: 20px auto; | |
background: #111; | |
border-radius: 10px; | |
box-shadow: 0 0 20px rgba(0, 255, 0, 0.3), | |
inset 0 0 50px rgba(0, 255, 0, 0.1); | |
overflow: hidden; | |
padding: 10px; | |
} | |
#crt-effect { | |
position: absolute; | |
top: 0; | |
left: 0; | |
width: 100%; | |
height: 100%; | |
background: linear-gradient(rgba(0, 255, 0, 0.1) 1px, transparent 1px); | |
background-size: 100% 2px; | |
pointer-events: none; | |
z-index: 10; | |
} | |
#crt-curve { | |
position: absolute; | |
top: 0; | |
left: 0; | |
width: 100%; | |
height: 100%; | |
border-radius: 50% 50% 50% 50% / 10% 10% 10% 10%; | |
box-shadow: inset 0 0 20px rgba(0, 255, 0, 0.2); | |
pointer-events: none; | |
z-index: 5; | |
} | |
#crt-glow { | |
position: absolute; | |
top: 0; | |
left: 0; | |
width: 100%; | |
height: 100%; | |
background: radial-gradient(circle at center, rgba(0, 255, 0, 0.05) 0%, transparent 70%); | |
pointer-events: none; | |
z-index: 1; | |
} | |
#crt-noise { | |
position: absolute; | |
top: 0; | |
left: 0; | |
width: 100%; | |
height: 100%; | |
background: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAYAAAAeP4ixAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAABOSURBVGhD7c4xAQAgDAAx9I9h6Q8O3kQk5+0BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADgBx5hBQHXQ1JQAAAAAElFTkSuQmCC'); | |
opacity: 0.03; | |
pointer-events: none; | |
z-index: 15; | |
} | |
#game { | |
display: block; | |
width: 100%; | |
height: 100%; | |
background: #000; | |
border-radius: 5px; | |
} | |
.crt-reflection { | |
position: absolute; | |
top: 50%; | |
left: 50%; | |
width: 100%; | |
height: 100%; | |
background: linear-gradient(135deg, rgba(0,255,0,0.1) 0%, transparent 50%); | |
transform: translate(-50%, -50%); | |
pointer-events: none; | |
z-index: 20; | |
} | |
.crt-label { | |
position: absolute; | |
bottom: 10px; | |
right: 20px; | |
color: #0f0; | |
font-size: 12px; | |
text-shadow: 0 0 5px #0f0; | |
z-index: 30; | |
} | |
.crt-power { | |
position: absolute; | |
bottom: 10px; | |
left: 20px; | |
color: #0f0; | |
font-size: 12px; | |
text-shadow: 0 0 5px #0f0; | |
z-index: 30; | |
} | |
.crt-power::before { | |
content: ""; | |
display: inline-block; | |
width: 8px; | |
height: 8px; | |
background: #0f0; | |
border-radius: 50%; | |
margin-right: 5px; | |
box-shadow: 0 0 5px #0f0; | |
animation: power-pulse 1s infinite alternate; | |
} | |
@keyframes power-pulse { | |
0% { opacity: 0.5; } | |
100% { opacity: 1; } | |
} | |
@keyframes flicker { | |
0% { opacity: 0.9; } | |
5% { opacity: 0.8; } | |
10% { opacity: 0.95; } | |
15% { opacity: 0.85; } | |
20% { opacity: 1; } | |
25% { opacity: 0.9; } | |
30% { opacity: 0.98; } | |
35% { opacity: 0.87; } | |
40% { opacity: 0.94; } | |
45% { opacity: 0.91; } | |
50% { opacity: 0.99; } | |
100% { opacity: 1; } | |
} | |
.flicker { | |
animation: flicker 0.1s infinite; | |
} | |
</style> | |
</head> | |
<body class="bg-black flex items-center justify-center h-screen"> | |
<div id="crt-screen" class="relative"> | |
<div id="crt-glow"></div> | |
<div id="crt-curve"></div> | |
<canvas id="game" width="800" height="600"></canvas> | |
<div id="crt-effect"></div> | |
<div id="crt-noise"></div> | |
<div class="crt-reflection"></div> | |
<div class="crt-power">POWER</div> | |
<div class="crt-label">CHAOS CANNON v1.0</div> | |
</div> | |
<script> | |
const canvas = document.getElementById('game'); | |
const ctx = canvas.getContext('2d'); | |
const audioContext = new (window.AudioContext || window.webkitAudioContext)(); | |
// Add vintage color palette | |
const colors = { | |
background: '#0a0a0a', | |
turretBase: '#00ff00', | |
turretBarrel: '#00cc00', | |
projectile: '#ff6600', | |
enemy: '#00ff00', | |
text: '#00ff00', | |
particle1: '#ff3300', | |
particle2: '#ff9900', | |
particle3: '#ffff00' | |
}; | |
class Game { | |
constructor() { | |
this.state = 'title'; | |
this.score = 0; | |
this.shake = 0; | |
this.turret = new Turret(); | |
this.projectiles = []; | |
this.enemies = []; | |
this.particles = []; | |
this.explosions = []; | |
this.flickerTimer = 0; | |
canvas.addEventListener('click', (e) => { | |
if(this.state === 'title') { | |
this.start(); | |
} else if(this.state === 'playing') { | |
this.turret.shoot(this); | |
} | |
}); | |
canvas.addEventListener('mousemove', (e) => { | |
this.turret.angle = Math.atan2( | |
e.clientY - canvas.offsetTop - this.turret.y, | |
e.clientX - canvas.offsetLeft - this.turret.x | |
); | |
}); | |
} | |
start() { | |
this.state = 'playing'; | |
this.score = 0; | |
this.spawnEnemies(); | |
} | |
spawnEnemies() { | |
setInterval(() => { | |
this.enemies.push(new Enemy( | |
Math.random() * canvas.width, | |
-50, | |
Math.random() * 2 + 1 | |
)); | |
}, 1500); | |
} | |
update() { | |
if(this.state !== 'playing') return; | |
this.flickerTimer++; | |
if(this.flickerTimer > 10) { | |
this.flickerTimer = 0; | |
document.getElementById('crt-effect').classList.toggle('flicker'); | |
} | |
// Update all entities | |
this.turret.update(); | |
this.projectiles.forEach(p => p.update(this)); | |
this.enemies.forEach(e => e.update(this)); | |
this.particles.forEach(p => p.update()); | |
this.explosions.forEach(e => e.update(this)); | |
// Check collisions | |
this.projectiles.forEach(projectile => { | |
this.enemies.forEach((enemy, i) => { | |
if(Math.hypot( | |
projectile.x - enemy.x, | |
projectile.y - enemy.y | |
) < 20) { | |
this.explosions.push(new Explosion(enemy.x, enemy.y)); | |
this.enemies.splice(i, 1); | |
this.score += 100; | |
this.shake = 10; | |
this.playExplosionSound(); | |
} | |
}); | |
}); | |
// Cleanup | |
this.projectiles = this.projectiles.filter(p => !p.dead); | |
this.particles = this.particles.filter(p => !p.dead); | |
this.explosions = this.explosions.filter(e => !e.dead); | |
} | |
draw() { | |
ctx.save(); | |
if(this.shake > 0) { | |
ctx.translate( | |
Math.random() * this.shake - this.shake/2, | |
Math.random() * this.shake - this.shake/2 | |
); | |
this.shake *= 0.9; | |
} | |
// Draw scanlines effect | |
ctx.fillStyle = 'rgba(0, 20, 0, 0.3)'; | |
for(let y = 0; y < canvas.height; y += 2) { | |
ctx.fillRect(0, y, canvas.width, 1); | |
} | |
// Draw vignette | |
const gradient = ctx.createRadialGradient( | |
canvas.width/2, canvas.height/2, 0, | |
canvas.width/2, canvas.height/2, Math.max(canvas.width, canvas.height)/2 | |
); | |
gradient.addColorStop(0, 'rgba(0, 0, 0, 0)'); | |
gradient.addColorStop(0.7, 'rgba(0, 10, 0, 0.5)'); | |
gradient.addColorStop(1, 'rgba(0, 20, 0, 0.8)'); | |
ctx.fillStyle = gradient; | |
ctx.fillRect(0, 0, canvas.width, canvas.height); | |
// Draw all entities | |
this.turret.draw(ctx); | |
this.projectiles.forEach(p => p.draw(ctx)); | |
this.enemies.forEach(e => e.draw(ctx)); | |
this.particles.forEach(p => p.draw(ctx)); | |
this.explosions.forEach(e => e.draw(ctx)); | |
// UI | |
ctx.fillStyle = colors.text; | |
ctx.font = '20px "Courier New", monospace'; | |
ctx.fillText(`SCORE: ${this.score}`, 20, 30); | |
if(this.state === 'title') { | |
ctx.fillStyle = 'rgba(0,0,0,0.8)'; | |
ctx.fillRect(0, 0, canvas.width, canvas.height); | |
// Title text with glow effect | |
ctx.fillStyle = colors.text; | |
ctx.font = 'bold 48px "Courier New", monospace'; | |
const title = 'CHAOS CANNON'; | |
const titleWidth = ctx.measureText(title).width; | |
ctx.fillText(title, canvas.width/2 - titleWidth/2, canvas.height/2); | |
// Glow effect | |
for(let i = 0; i < 3; i++) { | |
ctx.strokeStyle = `rgba(0, 255, 0, ${0.3 - i*0.1})`; | |
ctx.lineWidth = 5 - i*2; | |
ctx.strokeText(title, canvas.width/2 - titleWidth/2, canvas.height/2); | |
} | |
ctx.font = '24px "Courier New", monospace'; | |
ctx.fillText('CLICK TO START', canvas.width/2 - 100, canvas.height/2 + 50); | |
} | |
ctx.restore(); | |
} | |
playExplosionSound() { | |
const oscillator = audioContext.createOscillator(); | |
const gainNode = audioContext.createGain(); | |
oscillator.connect(gainNode); | |
gainNode.connect(audioContext.destination); | |
oscillator.type = 'square'; | |
oscillator.frequency.value = 100 + Math.random() * 100; | |
gainNode.gain.setValueAtTime(0.2, audioContext.currentTime); | |
gainNode.gain.exponentialRampToValueAtTime(0.01, audioContext.currentTime + 0.3); | |
oscillator.start(); | |
oscillator.stop(audioContext.currentTime + 0.3); | |
} | |
} | |
class Turret { | |
constructor() { | |
this.x = canvas.width/2; | |
this.y = canvas.height - 50; | |
this.angle = 0; | |
this.cooldown = 0; | |
} | |
update() { | |
if(this.cooldown > 0) this.cooldown--; | |
} | |
shoot(game) { | |
if(this.cooldown <= 0) { | |
game.projectiles.push(new Projectile( | |
this.x + Math.cos(this.angle) * 40, | |
this.y + Math.sin(this.angle) * 40, | |
Math.cos(this.angle) * 15, | |
Math.sin(this.angle) * 15 | |
)); | |
this.cooldown = 10; | |
} | |
} | |
draw(ctx) { | |
ctx.save(); | |
ctx.translate(this.x, this.y); | |
ctx.rotate(this.angle); | |
// Barrel | |
ctx.fillStyle = colors.turretBarrel; | |
ctx.fillRect(0, -5, 40, 10); | |
// Add barrel glow | |
const barrelGradient = ctx.createLinearGradient(0, -5, 40, 10); | |
barrelGradient.addColorStop(0, 'rgba(0, 255, 0, 0.8)'); | |
barrelGradient.addColorStop(1, 'rgba(0, 200, 0, 0.8)'); | |
ctx.fillStyle = barrelGradient; | |
ctx.fillRect(0, -5, 40, 10); | |
// Base | |
ctx.beginPath(); | |
ctx.arc(0, 0, 30, 0, Math.PI * 2); | |
// Add base glow | |
const baseGradient = ctx.createRadialGradient(0, 0, 10, 0, 0, 30); | |
baseGradient.addColorStop(0, 'rgba(0, 255, 0, 0.8)'); | |
baseGradient.addColorStop(1, 'rgba(0, 100, 0, 0.8)'); | |
ctx.fillStyle = baseGradient; | |
ctx.fill(); | |
ctx.restore(); | |
} | |
} | |
class Projectile { | |
constructor(x, y, vx, vy) { | |
this.x = x; | |
this.y = y; | |
this.vx = vx; | |
this.vy = vy; | |
this.dead = false; | |
} | |
update(game) { | |
this.x += this.vx; | |
this.y += this.vy; | |
this.vy += 0.2; | |
if(this.y > canvas.height + 50) this.dead = true; | |
// Trail particles | |
const color = Math.random() > 0.5 ? colors.particle1 : | |
Math.random() > 0.5 ? colors.particle2 : colors.particle3; | |
game.particles.push(new Particle( | |
this.x, this.y, | |
this.vx * -0.2 + Math.random() * 2 - 1, | |
this.vy * -0.2 + Math.random() * 2 - 1, | |
color, 20 | |
)); | |
} | |
draw(ctx) { | |
// Add glow effect | |
const glowGradient = ctx.createRadialGradient( | |
this.x, this.y, 0, | |
this.x, this.y, 8 | |
); | |
glowGradient.addColorStop(0, 'rgba(255, 100, 0, 0.8)'); | |
glowGradient.addColorStop(1, 'rgba(255, 50, 0, 0)'); | |
ctx.fillStyle = glowGradient; | |
ctx.beginPath(); | |
ctx.arc(this.x, this.y, 8, 0, Math.PI * 2); | |
ctx.fill(); | |
// Projectile core | |
ctx.fillStyle = colors.projectile; | |
ctx.beginPath(); | |
ctx.arc(this.x, this.y, 3, 0, Math.PI * 2); | |
ctx.fill(); | |
} | |
} | |
class Enemy { | |
constructor(x, y, speed) { | |
this.x = x; | |
this.y = y; | |
this.speed = speed; | |
this.flicker = 0; | |
} | |
update(game) { | |
this.y += this.speed; | |
this.flicker = (this.flicker + 1) % 10; | |
if(this.y > canvas.height - 50) { | |
game.state = 'gameover'; | |
} | |
} | |
draw(ctx) { | |
ctx.save(); | |
ctx.translate(this.x, this.y); | |
// Flicker effect | |
if(this.flicker < 2) { | |
ctx.fillStyle = 'rgba(0, 255, 0, 0.7)'; | |
} else { | |
ctx.fillStyle = colors.enemy; | |
} | |
ctx.beginPath(); | |
ctx.moveTo(-15, 0); | |
ctx.lineTo(15, 0); | |
ctx.lineTo(0, 30); | |
ctx.fill(); | |
// Add glow | |
const glowGradient = ctx.createLinearGradient(-15, 0, 15, 30); | |
glowGradient.addColorStop(0, 'rgba(0, 255, 0, 0.3)'); | |
glowGradient.addColorStop(1, 'rgba(0, 150, 0, 0.3)'); | |
ctx.fillStyle = glowGradient; | |
ctx.beginPath(); | |
ctx.moveTo(-15, 0); | |
ctx.lineTo(15, 0); | |
ctx.lineTo(0, 30); | |
ctx.fill(); | |
ctx.restore(); | |
} | |
} | |
class Explosion { | |
constructor(x, y) { | |
this.x = x; | |
this.y = y; | |
this.particles = []; | |
this.dead = false; | |
for(let i = 0; i < 50; i++) { | |
const hue = Math.random() * 30 + 20; | |
this.particles.push(new Particle( | |
x, y, | |
Math.random() * 10 - 5, | |
Math.random() * 10 - 5, | |
`hsl(${hue}, 100%, 50%)`, | |
40 | |
)); | |
} | |
} | |
update(game) { | |
this.particles.forEach(p => p.update()); | |
this.dead = this.particles.every(p => p.dead); | |
} | |
draw(ctx) { | |
this.particles.forEach(p => p.draw(ctx)); | |
// Add explosion glow | |
if(!this.dead) { | |
const glowGradient = ctx.createRadialGradient( | |
this.x, this.y, 0, | |
this.x, this.y, 50 | |
); | |
glowGradient.addColorStop(0, 'rgba(255, 100, 0, 0.5)'); | |
glowGradient.addColorStop(1, 'rgba(255, 50, 0, 0)'); | |
ctx.fillStyle = glowGradient; | |
ctx.beginPath(); | |
ctx.arc(this.x, this.y, 50, 0, Math.PI * 2); | |
ctx.fill(); | |
} | |
} | |
} | |
class Particle { | |
constructor(x, y, vx, vy, color, life) { | |
this.x = x; | |
this.y = y; | |
this.vx = vx; | |
this.vy = vy; | |
this.color = color; | |
this.life = life; | |
this.dead = false; | |
this.size = Math.random() * 3 + 1; | |
} | |
update() { | |
this.x += this.vx; | |
this.y += this.vy; | |
this.vy += 0.1; | |
this.life -= 1; | |
this.dead = this.life <= 0; | |
} | |
draw(ctx) { | |
// Add glow | |
const glowGradient = ctx.createRadialGradient( | |
this.x, this.y, 0, | |
this.x, this.y, this.size * 2 | |
); | |
glowGradient.addColorStop(0, this.color); | |
glowGradient.addColorStop(1, 'rgba(255, 255, 0, 0)'); | |
ctx.fillStyle = glowGradient; | |
ctx.beginPath(); | |
ctx.arc(this.x, this.y, this.size * 2, 0, Math.PI * 2); | |
ctx.fill(); | |
// Particle core | |
ctx.fillStyle = this.color; | |
ctx.beginPath(); | |
ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2); | |
ctx.fill(); | |
} | |
} | |
// Game loop | |
const game = new Game(); | |
function frame() { | |
game.update(); | |
game.draw(); | |
requestAnimationFrame(frame); | |
} | |
frame(); | |
// Add CRT power on effect | |
setTimeout(() => { | |
document.getElementById('crt-screen').style.boxShadow = | |
'0 0 20px rgba(0, 255, 0, 0.5), inset 0 0 50px rgba(0, 255, 0, 0.2)'; | |
}, 500); | |
</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-crt" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> | |
</html> |