anycoder-55fb0874 / index.html
JjpieRazorwing2's picture
Upload folder using huggingface_hub
c28fdfa verified
Raw History Blame Contribute Delete
39.2 kB
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<title>NEON SURVIVOR - Cyberpunk Zombie Survival</title>
<script src="https://cdn.tailwindcss.com"></script>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Orbitron:wght@400;700;900&family=Rajdhani:wght@300;500;700&display=swap" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>
:root {
--neon-cyan: #00f0ff;
--neon-pink: #ff00ff;
--neon-green: #39ff14;
--neon-red: #ff073a;
--neon-yellow: #ffff00;
--dark-bg: #0a0a1a;
--panel-bg: rgba(10, 10, 30, 0.92);
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
touch-action: none;
-webkit-tap-highlight-color: transparent;
}
body {
background: var(--dark-bg);
font-family: 'Rajdhani', sans-serif;
overflow: hidden;
user-select: none;
-webkit-user-select: none;
width: 100vw;
height: 100vh;
}
canvas {
display: block;
image-rendering: pixelated;
}
/* CRT Scanline Effect */
.crt-overlay {
position: fixed;
inset: 0;
pointer-events: none;
z-index: 9999;
background: repeating-linear-gradient(
0deg,
rgba(0, 0, 0, 0.03) 0px,
rgba(0, 0, 0, 0.03) 1px,
transparent 1px,
transparent 2px
);
}
.crt-overlay::after {
content: '';
position: absolute;
inset: 0;
background: radial-gradient(ellipse at center, transparent 60%, rgba(0, 0, 0, 0.4) 100%);
}
/* Neon Glow Utilities */
.neon-text-cyan {
color: var(--neon-cyan);
text-shadow: 0 0 10px var(--neon-cyan), 0 0 20px var(--neon-cyan), 0 0 40px var(--neon-cyan);
}
.neon-text-pink {
color: var(--neon-pink);
text-shadow: 0 0 10px var(--neon-pink), 0 0 20px var(--neon-pink);
}
.neon-text-green {
color: var(--neon-green);
text-shadow: 0 0 10px var(--neon-green), 0 0 20px var(--neon-green);
}
.neon-text-red {
color: var(--neon-red);
text-shadow: 0 0 10px var(--neon-red), 0 0 20px var(--neon-red);
}
.neon-box {
border: 2px solid var(--neon-cyan);
box-shadow: 0 0 10px var(--neon-cyan), inset 0 0 10px rgba(0, 240, 255, 0.1);
}
.neon-box-pink {
border: 2px solid var(--neon-pink);
box-shadow: 0 0 10px var(--neon-pink), inset 0 0 10px rgba(255, 0, 255, 0.1);
}
/* UI Animations */
@keyframes glitch {
0%, 100% { transform: translate(0); }
20% { transform: translate(-2px, 2px); }
40% { transform: translate(-2px, -2px); }
60% { transform: translate(2px, 2px); }
80% { transform: translate(2px, -2px); }
}
.glitch {
animation: glitch 0.3s ease-in-out;
}
@keyframes pulse-glow {
0%, 100% { opacity: 1; }
50% { opacity: 0.6; }
}
.pulse-glow {
animation: pulse-glow 2s ease-in-out infinite;
}
@keyframes float {
0%, 100% { transform: translateY(0); }
50% { transform: translateY(-10px); }
}
.float {
animation: float 3s ease-in-out infinite;
}
@keyframes scan-line {
0% { top: -5%; }
100% { top: 105%; }
}
.scan-line {
position: absolute;
left: 0;
right: 0;
height: 3px;
background: linear-gradient(90deg, transparent, var(--neon-cyan), transparent);
animation: scan-line 4s linear infinite;
opacity: 0.5;
}
/* Progress Bars */
.hp-bar {
height: 6px;
background: #333;
border-radius: 3px;
overflow: hidden;
}
.hp-fill {
height: 100%;
background: linear-gradient(90deg, var(--neon-red), var(--neon-pink));
transition: width 0.3s ease;
box-shadow: 0 0 8px var(--neon-red);
}
.xp-bar {
height: 4px;
background: #222;
border-radius: 2px;
overflow: hidden;
}
.xp-fill {
height: 100%;
background: linear-gradient(90deg, var(--neon-cyan), var(--neon-green));
transition: width 0.3s ease;
box-shadow: 0 0 6px var(--neon-cyan);
}
/* Buttons */
.neon-btn {
position: relative;
padding: 14px 36px;
font-family: 'Orbitron', sans-serif;
font-size: 14px;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 3px;
color: var(--neon-cyan);
background: transparent;
border: 2px solid var(--neon-cyan);
cursor: pointer;
overflow: hidden;
transition: all 0.3s ease;
}
.neon-btn::before {
content: '';
position: absolute;
top: 0;
left: -100%;
width: 100%;
height: 100%;
background: linear-gradient(90deg, transparent, rgba(0, 240, 255, 0.3), transparent);
transition: left 0.5s ease;
}
.neon-btn:hover::before {
left: 100%;
}
.neon-btn:hover {
background: rgba(0, 240, 255, 0.1);
box-shadow: 0 0 20px var(--neon-cyan), inset 0 0 20px rgba(0, 240, 255, 0.1);
transform: scale(1.05);
}
.neon-btn-pink {
color: var(--neon-pink);
border-color: var(--neon-pink);
}
.neon-btn-pink::before {
background: linear-gradient(90deg, transparent, rgba(255, 0, 255, 0.3), transparent);
}
.neon-btn-pink:hover {
background: rgba(255, 0, 255, 0.1);
box-shadow: 0 0 20px var(--neon-pink), inset 0 0 20px rgba(255, 0, 255, 0.1);
}
/* Screens */
.screen {
position: fixed;
inset: 0;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
z-index: 100;
transition: opacity 0.5s ease, transform 0.5s ease;
}
.screen.hidden {
opacity: 0;
pointer-events: none;
transform: scale(1.1);
}
/* HUD */
.hud-panel {
background: var(--panel-bg);
border: 1px solid rgba(0, 240, 255, 0.3);
border-radius: 8px;
padding: 8px 14px;
backdrop-filter: blur(10px);
}
/* Skill Cards */
.skill-card {
background: rgba(10, 10, 30, 0.9);
border: 2px solid rgba(0, 240, 255, 0.3);
border-radius: 12px;
padding: 20px;
cursor: pointer;
transition: all 0.3s ease;
position: relative;
overflow: hidden;
}
.skill-card::before {
content: '';
position: absolute;
inset: 0;
background: linear-gradient(135deg, transparent 40%, rgba(0, 240, 255, 0.1) 100%);
opacity: 0;
transition: opacity 0.3s;
}
.skill-card:hover::before {
opacity: 1;
}
.skill-card:hover {
border-color: var(--neon-cyan);
box-shadow: 0 0 20px rgba(0, 240, 255, 0.2);
transform: translateY(-4px);
}
.skill-card.selected {
border-color: var(--neon-green);
box-shadow: 0 0 30px rgba(57, 255, 20, 0.3);
}
/* Virtual Joystick */
.joystick-zone {
position: fixed;
bottom: 80px;
left: 80px;
width: 140px;
height: 140px;
z-index: 50;
}
.joystick-base {
position: absolute;
width: 120px;
height: 120px;
border: 2px solid rgba(0, 240, 255, 0.3);
border-radius: 50%;
background: rgba(0, 240, 255, 0.05);
}
.joystick-stick {
position: absolute;
width: 50px;
height: 50px;
background: var(--neon-cyan);
border-radius: 50%;
box-shadow: 0 0 20px var(--neon-cyan);
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
transition: none;
}
/* Mobile Controls */
.action-btn {
position: fixed;
width: 70px;
height: 70px;
border-radius: 50%;
border: 2px solid var(--neon-pink);
background: rgba(255, 0, 255, 0.1);
color: var(--neon-pink);
display: flex;
align-items: center;
justify-content: center;
font-size: 24px;
z-index: 50;
cursor: pointer;
box-shadow: 0 0 15px rgba(255, 0, 255, 0.3);
}
.action-btn:active {
background: rgba(255, 0, 255, 0.3);
transform: scale(0.95);
}
/* Damage numbers */
.damage-popup {
position: absolute;
font-family: 'Orbitron', sans-serif;
font-weight: 900;
font-size: 18px;
pointer-events: none;
animation: damage-float 1s ease-out forwards;
z-index: 60;
}
@keyframes damage-float {
0% { opacity: 1; transform: translateY(0) scale(1); }
100% { opacity: 0; transform: translateY(-60px) scale(1.3); }
}
/* Level up flash */
@keyframes level-flash {
0%, 100% { opacity: 0; }
50% { opacity: 0.3; }
}
.level-flash {
position: fixed;
inset: 0;
background: var(--neon-cyan);
pointer-events: none;
z-index: 55;
animation: level-flash 0.5s ease-out;
}
/* Grid background for screens */
.grid-bg {
background-image:
linear-gradient(rgba(0, 240, 255, 0.03) 1px, transparent 1px),
linear-gradient(90deg, rgba(0, 240, 255, 0.03) 1px, transparent 1px);
background-size: 50px 50px;
}
/* Particle effects container */
.particles-container {
position: fixed;
inset: 0;
pointer-events: none;
z-index: 45;
overflow: hidden;
}
.particle {
position: absolute;
border-radius: 50%;
pointer-events: none;
}
/* Responsive */
@media (max-width: 768px) {
.joystick-zone {
bottom: 60px;
left: 60px;
width: 110px;
height: 110px;
}
.joystick-base {
width: 90px;
height: 90px;
}
.joystick-stick {
width: 40px;
height: 40px;
}
.action-btn {
width: 55px;
height: 55px;
font-size: 20px;
}
}
/* Scrollbar */
::-webkit-scrollbar {
width: 6px;
}
::-webkit-scrollbar-track {
background: #0a0a1a;
}
::-webkit-scrollbar-thumb {
background: var(--neon-cyan);
border-radius: 3px;
}
/* Title animation */
@keyframes title-glow {
0%, 100% {
text-shadow: 0 0 20px var(--neon-cyan), 0 0 40px var(--neon-cyan), 0 0 80px var(--neon-cyan);
}
50% {
text-shadow: 0 0 30px var(--neon-pink), 0 0 60px var(--neon-pink), 0 0 100px var(--neon-pink);
}
}
.title-glow {
animation: title-glow 3s ease-in-out infinite;
}
/* Combo counter */
@keyframes combo-pop {
0% { transform: scale(0.5); opacity: 0; }
50% { transform: scale(1.3); }
100% { transform: scale(1); opacity: 1; }
}
.combo-pop {
animation: combo-pop 0.3s ease-out;
}
/* Wave notification */
@keyframes wave-slide {
0% { transform: translateX(-100%); opacity: 0; }
20% { transform: translateX(0); opacity: 1; }
80% { transform: translateX(0); opacity: 1; }
100% { transform: translateX(100%); opacity: 0; }
}
.wave-notify {
animation: wave-slide 3s ease-in-out forwards;
}
/* Minimap */
.minimap {
width: 120px;
height: 120px;
border: 2px solid var(--neon-cyan);
border-radius: 8px;
background: rgba(0, 0, 0, 0.7);
position: relative;
overflow: hidden;
}
.minimap-dot {
position: absolute;
border-radius: 50%;
}
.minimap-player {
width: 6px;
height: 6px;
background: var(--neon-cyan);
box-shadow: 0 0 6px var(--neon-cyan);
}
.minimap-enemy {
width: 4px;
height: 4px;
background: var(--neon-red);
}
.minimap-orb {
width: 3px;
height: 3px;
background: var(--neon-green);
}
/* Stats display */
.stat-row {
display: flex;
align-items: center;
gap: 8px;
padding: 4px 0;
}
.stat-icon {
width: 24px;
height: 24px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 4px;
font-size: 12px;
}
/* Kill feed */
.kill-feed {
position: fixed;
top: 80px;
right: 20px;
z-index: 50;
display: flex;
flex-direction: column;
gap: 4px;
align-items: flex-end;
}
.kill-entry {
background: rgba(0, 0, 0, 0.7);
border-right: 3px solid var(--neon-green);
padding: 4px 12px;
border-radius: 4px 0 0 4px;
font-size: 12px;
color: #fff;
animation: kill-slide 2s ease-out forwards;
}
@keyframes kill-slide {
0% { transform: translateX(50px); opacity: 0; }
20% { transform: translateX(0); opacity: 1; }
80% { transform: translateX(0); opacity: 1; }
100% { transform: translateX(50px); opacity: 0; }
}
/* Floating text */
.floating-text {
position: absolute;
font-family: 'Orbitron', sans-serif;
font-weight: 700;
pointer-events: none;
z-index: 60;
animation: float-up 1.5s ease-out forwards;
}
@keyframes float-up {
0% { opacity: 1; transform: translateY(0); }
100% { opacity: 0; transform: translateY(-80px); }
}
</style>
</head>
<body>
<!-- CRT Overlay -->
<div class="crt-overlay"></div>
<!-- Particles Container -->
<div class="particles-container" id="particlesContainer"></div>
<!-- Game Canvas -->
<canvas id="gameCanvas"></canvas>
<!-- Kill Feed -->
<div class="kill-feed" id="killFeed"></div>
<!-- START SCREEN -->
<div class="screen grid-bg" id="startScreen">
<div class="scan-line"></div>
<div class="text-center px-4">
<div class="mb-2">
<span class="text-xs tracking-[6px] text-gray-500 uppercase">Built with</span>
<a href="https://huggingface.co/spaces/akhaliq/anycoder" target="_blank" class="text-xs tracking-[6px] text-cyan-400 hover:text-cyan-300 uppercase transition-colors ml-1">anycoder</a>
</div>
<h1 class="text-5xl md:text-7xl font-black mb-2 title-glow" style="font-family: 'Orbitron', sans-serif;">
<span class="neon-text-cyan">NEON</span>
<span class="neon-text-pink">SURVIVOR</span>
</h1>
<p class="text-gray-400 text-sm md:text-base tracking-[4px] mb-8 uppercase">Cyberpunk Zombie Survival</p>
<div class="flex flex-col items-center gap-4 mb-8">
<div class="neon-box rounded-lg p-4 max-w-md w-full">
<div class="grid grid-cols-2 gap-3 text-xs text-gray-300">
<div class="flex items-center gap-2">
<i class="fas fa-keyboard text-cyan-400"></i>
<span>WASD / Arrows to Move</span>
</div>
<div class="flex items-center gap-2">
<i class="fas fa-mouse text-pink-400"></i>
<span>Auto-aims nearest enemy</span>
</div>
<div class="flex items-center gap-2">
<i class="fas fa-bolt text-yellow-400"></i>
<span>Collect orbs to level up</span>
</div>
<div class="flex items-center gap-2">
<i class="fas fa-skull text-red-400"></i>
<span>Survive endless waves</span>
</div>
</div>
</div>
</div>
<div class="flex flex-col sm:flex-row gap-4">
<button class="neon-btn" id="startBtn">
<i class="fas fa-play mr-2"></i>Start Game
</button>
<button class="neon-btn neon-btn-pink" id="settingsBtn">
<i class="fas fa-cog mr-2"></i>Settings
</button>
</div>
<div class="mt-8 text-xs text-gray-600 tracking-wider">
<p>v2.0.1 | Endless Survival Mode</p>
</div>
</div>
</div>
<!-- SETTINGS SCREEN -->
<div class="screen grid-bg hidden" id="settingsScreen">
<div class="text-center px-4 max-w-md w-full">
<h2 class="text-3xl font-bold neon-text-cyan mb-6" style="font-family: 'Orbitron'">SETTINGS</h2>
<div class="neon-box rounded-lg p-6 mb-6 space-y-4">
<div class="flex items-center justify-between">
<span class="text-gray-300 text-sm">Sound Effects</span>
<button class="w-12 h-6 rounded-full bg-cyan-900 relative transition-colors" id="soundToggle">
<div class="w-5 h-5 rounded-full bg-cyan-400 absolute top-0.5 left-0.5 transition-transform" id="soundKnob"></div>
</button>
</div>
<div class="flex items-center justify-between">
<span class="text-gray-300 text-sm">Music</span>
<button class="w-12 h-6 rounded-full bg-cyan-900 relative transition-colors" id="musicToggle">
<div class="w-5 h-5 rounded-full bg-cyan-400 absolute top-0.5 left-0.5 transition-transform" id="musicKnob"></div>
</button>
</div>
<div class="flex items-center justify-between">
<span class="text-gray-300 text-sm">Screen Shake</span>
<button class="w-12 h-6 rounded-full bg-cyan-900 relative transition-colors" id="shakeToggle">
<div class="w-5 h-5 rounded-full bg-cyan-400 absolute top-0.5 left-0.5 transition-transform" id="shakeKnob"></div>
</button>
</div>
<div class="flex items-center justify-between">
<span class="text-gray-300 text-sm">Show Minimap</span>
<button class="w-12 h-6 rounded-full bg-cyan-900 relative transition-colors" id="minimapToggle">
<div class="w-5 h-5 rounded-full bg-cyan-400 absolute top-0.5 left-0.5 transition-transform" id="minimapKnob"></div>
</button>
</div>
<div class="flex items-center justify-between">
<span class="text-gray-300 text-sm">Particle Effects</span>
<select class="bg-gray-900 border border-cyan-500/30 rounded px-3 py-1 text-sm text-cyan-400" id="particleSelect">
<option value="high">High</option>
<option value="medium">Medium</option>
<option value="low">Low</option>
</select>
</div>
</div>
<button class="neon-btn" id="backBtn">
<i class="fas fa-arrow-left mr-2"></i>Back
</button>
</div>
</div>
<!-- HUD (Heads Up Display) -->
<div id="hud" class="hidden" style="position: fixed; inset: 0; pointer-events: none; z-index: 40;">
<!-- Top Left: Stats -->
<div class="absolute top-4 left-4 space-y-2">
<div class="hud-panel">
<div class="flex items-center gap-3 mb-1">
<div class="w-8 h-8 rounded-full bg-gradient-to-br from-cyan-500 to-blue-600 flex items-center justify-center text-white text-xs font-bold" id="levelBadge">1</div>
<div class="flex-1 min-w-[140px]">
<div class="flex justify-between text-xs mb-0.5">
<span class="text-cyan-400 font-bold">LVL <span id="levelText">1</span></span>
<span class="text-gray-400" id="xpText">0/100</span>
</div>
<div class="xp-bar">
<div class="xp-fill" id="xpFill" style="width: 0%"></div>
</div>
</div>
</div>
</div>
<div class="hud-panel">
<div class="flex items-center gap-2 mb-1">
<i class="fas fa-heart text-red-500 text-xs"></i>
<div class="flex-1">
<div class="hp-bar">
<div class="hp-fill" id="hpFill" style="width: 100%"></div>
</div>
</div>
<span class="text-xs text-red-400 font-bold" id="hpText">100/100</span>
</div>
</div>
<div class="hud-panel flex items-center gap-4 text-xs">
<div class="flex items-center gap-1">
<i class="fas fa-clock text-yellow-400"></i>
<span class="text-yellow-400 font-bold" id="timeText">00:00</span>
</div>
<div class="flex items-center gap-1">
<i class="fas fa-skull text-red-400"></i>
<span class="text-red-400 font-bold" id="killsText">0</span>
</div>
<div class="flex items-center gap-1">
<i class="fas fa-wave-square text-pink-400"></i>
<span class="text-pink-400 font-bold" id="waveText">Wave 1</span>
</div>
</div>
</div>
<!-- Top Right: Minimap & Combo -->
<div class="absolute top-4 right-4 space-y-2">
<div class="hud-panel" id="minimapPanel">
<div class="minimap" id="minimap">
<div class="minimap-dot minimap-player" id="minimapPlayer"></div>
</div>
</div>
<div class="hud-panel text-center hidden" id="comboPanel">
<div class="text-2xl font-black neon-text-yellow" id="comboCount" style="font-family: 'Orbitron'">x1</div>
<div class="text-xs text-yellow-400">COMBO</div>
</div>
</div>
<!-- Bottom: Weapons & Skills -->
<div class="absolute bottom-4 left-1/2 -translate-x-1/2 flex gap-2" id="weaponBar">
<!-- Weapon slots generated by JS -->
</div>
<!-- Bottom Right: Mobile Controls -->
<div class="joystick-zone md:hidden" id="joystickZone">
<div class="joystick-base"></div>
<div class="joystick-stick" id="joystickStick"></div>
</div>
<!-- Action buttons for mobile -->
<div class="md:hidden">
<div class="action-btn" style="bottom: 80px; right: 80px;" id="dashBtn">
<i class="fas fa-bolt"></i>
</div>
<div class="action-btn" style="bottom: 160px; right: 40px;" id="ultBtn">
<i class="fas fa-radiation"></i>
</div>
</div>
<!-- Center notifications -->
<div class="absolute top-1/3 left-1/2 -translate-x-1/2 -translate-y-1/2 text-center pointer-events-none" id="centerNotify"></div>
</div>
<!-- LEVEL UP SCREEN -->
<div class="screen hidden" id="levelUpScreen" style="background: rgba(0,0,0,0.85); backdrop-filter: blur(10px);">
<div class="text-center px-4 max-w-2xl w-full">
<h2 class="text-4xl font-black neon-text-green mb-2 float" style="font-family: 'Orbitron'">LEVEL UP!</h2>
<p class="text-gray-400 text-sm mb-8 tracking-wider">Choose your upgrade</p>
<div class="grid grid-cols-1 md:grid-cols-3 gap-4" id="skillCards">
<!-- Generated by JS -->
</div>
</div>
</div>
<!-- PAUSE SCREEN -->
<div class="screen hidden" id="pauseScreen" style="background: rgba(0,0,0,0.8); backdrop-filter: blur(5px);">
<div class="text-center px-4">
<h2 class="text-4xl font-bold neon-text-cyan mb-8" style="font-family: 'Orbitron'">PAUSED</h2>
<div class="flex flex-col gap-4">
<button class="neon-btn" id="resumeBtn">
<i class="fas fa-play mr-2"></i>Resume
</button>
<button class="neon-btn neon-btn-pink" id="quitBtn">
<i class="fas fa-door-open mr-2"></i>Quit to Menu
</button>
</div>
</div>
</div>
<!-- GAME OVER SCREEN -->
<div class="screen hidden" id="gameOverScreen" style="background: rgba(0,0,0,0.9);">
<div class="text-center px-4 max-w-md w-full">
<h2 class="text-5xl font-black neon-text-red mb-4" style="font-family: 'Orbitron'">ELIMINATED</h2>
<div class="neon-box-pink rounded-lg p-6 mb-6 space-y-3">
<div class="flex justify-between text-sm">
<span class="text-gray-400">Survival Time</span>
<span class="neon-text-cyan font-bold" id="finalTime">00:00</span>
</div>
<div class="flex justify-between text-sm">
<span class="text-gray-400">Enemies Defeated</span>
<span class="neon-text-green font-bold" id="finalKills">0</span>
</div>
<div class="flex justify-between text-sm">
<span class="text-gray-400">Level Reached</span>
<span class="neon-text-yellow font-bold" id="finalLevel">1</span>
</div>
<div class="flex justify-between text-sm">
<span class="text-gray-400">Max Combo</span>
<span class="neon-text-pink font-bold" id="finalCombo">x1</span>
</div>
<div class="flex justify-between text-sm">
<span class="text-gray-400">Waves Survived</span>
<span class="neon-text-cyan font-bold" id="finalWave">1</span>
</div>
<div class="border-t border-gray-700 pt-2 flex justify-between text-base">
<span class="text-gray-300 font-bold">SCORE</span>
<span class="neon-text-green font-black text-xl" id="finalScore">0</span>
</div>
</div>
<div class="flex flex-col sm:flex-row gap-4">
<button class="neon-btn" id="restartBtn">
<i class="fas fa-redo mr-2"></i>Try Again
</button>
<button class="neon-btn neon-btn-pink" id="menuBtn">
<i class="fas fa-home mr-2"></i>Main Menu
</button>
</div>
</div>
</div>
<!-- Wave Notification -->
<div class="fixed top-1/4 left-0 right-0 text-center pointer-events-none z-50 hidden" id="waveNotify">
<div class="wave-notify inline-block">
<h3 class="text-3xl md:text-5xl font-black neon-text-pink" style="font-family: 'Orbitron'" id="waveNotifyText">WAVE 1</h3>
<p class="text-gray-400 text-sm mt-1" id="waveNotifySub">The horde approaches...</p>
</div>
</div>
<script>
// ==================== GAME ENGINE ====================
const canvas = document.getElementById('gameCanvas');
const ctx = canvas.getContext('2d');
// Game State
const STATE = {
MENU: 0,
PLAYING: 1,
PAUSED: 2,
LEVEL_UP: 3,
GAME_OVER: 4
};
let gameState = STATE.MENU;
let lastTime = 0;
let gameTime = 0;
let frameCount = 0;
// Settings
const settings = {
sound: true,
music: false,
shake: true,
minimap: true,
particles: 'high'
};
// Camera
const camera = { x: 0, y: 0, shakeX: 0, shakeY: 0, shakeIntensity: 0 };
// World
const WORLD_SIZE = 4000;
const TILE_SIZE = 80;
// Player
const player = {
x: WORLD_SIZE / 2,
y: WORLD_SIZE / 2,
radius: 18,
speed: 200,
hp: 100,
maxHp: 100,
xp: 0,
xpToLevel: 100,
level: 1,
kills: 0,
combo: 0,
maxCombo: 1,
comboTimer: 0,
invincible: 0,
dashCooldown: 0,
ultCharge: 0,
ultMax: 100,
weapons: [],
skills: {
damage: 1,
speed: 1,
hpRegen: 0,
pickupRange: 80,
critChance: 0.05,
critDamage: 1.5,
fireRate: 1,
projectileSpeed: 1,
pierce: 0,
area: 1
},
facing: 0
};
// Weapons
const WEAPON_TYPES = {
PISTOL: { name: 'Plasma Pistol', icon: 'fa-gun', color: '#00f0ff', damage: 12, fireRate: 400, projectileSpeed: 400, pierce: 0, auto: true },
SHOTGUN: { name: 'Scatter Cannon', icon: 'fa-bullseye', color: '#ff00ff', damage: 8, fireRate: 800, projectileSpeed: 350, pierce: 1, count: 5, spread: 0.3, auto: false },
RIFLE: { name: 'Pulse Rifle', icon: 'fa-crosshairs', color: '#39ff14', damage: 6, fireRate: 150, projectileSpeed: 500, pierce: 1, auto: true },
LASER: { name: 'Beam Cutter', icon: 'fa-bolt', color: '#ffff00', damage: 3, fireRate: 50, projectileSpeed: 800, pierce: 3, auto: true },
ROCKET: { name: 'RPG-7', icon: 'fa-rocket', color: '#ff073a', damage: 40, fireRate: 1500, projectileSpeed: 250, pierce: 0, explosion: 80, auto: false },
ORBITAL: { name: 'Orbital Drones', icon: 'fa-satellite', color: '#00f0ff', damage: 15, fireRate: 0, orbital: true, orbitRadius: 60, orbitSpeed: 3, auto: true }
};
// Enemies
const ENEMY_TYPES = {
WALKER: { name: 'Walker', hp: 30, damage: 10, speed: 80, radius: 16, color: '#ff4444', xp: 10, score: 10 },
RUNNER: { name: 'Runner', hp: 20, damage: 15, speed: 160, radius: 14, color: '#ff8800', xp: 15, score: 20 },
TANK: { name: 'Tank', hp: 120, damage: 25, speed: 50, radius: 28, color: '#8800ff', xp: 40, score: 50 },
SPITTER: { name: 'Spitter', hp: 40, damage: 12, speed: 60, radius: 16, color: '#00ff44', xp: 20, score: 30, ranged: true, projectileSpeed: 200 },
BOSS: { name: 'Overlord', hp: 500, damage: 40, speed: 40, radius: 45, color: '#ff0088', xp: 200, score: 200, boss: true }
};
// Arrays
let enemies = [];
let projectiles = [];
let particles = [];
let orbs = [];
let enemyProjectiles = [];
let damageNumbers = [];
let floatingTexts = [];
// Wave System
let wave = 1;
let waveTimer = 0;
let waveDuration = 60;
let spawnTimer = 0;
let bossSpawned = false;
// Input
const keys = {};
const mouse = { x: 0, y: 0, worldX: 0, worldY: 0, down: false };
const joystick = { active: false, dx: 0, dy: 0, startX: 0, startY: 0 };
// Resize
function resize() {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
}
window.addEventListener('resize', resize);
resize();
// ==================== INPUT HANDLING ====================
document.addEventListener('keydown', e => {
keys[e.code] = true;
if (e.code === 'Escape' && gameState === STATE.PLAYING) {
pauseGame();
}
if (e.code === 'KeyP' && gameState === STATE.PLAYING) {
pauseGame();
}
});
document.addEventListener('keyup', e => keys[e.code] = false);
canvas.addEventListener('mousemove', e => {
mouse.x = e.clientX;
mouse.y = e.clientY;
mouse.worldX = e.clientX + camera.x;
mouse.worldY = e.clientY + camera.y;
});
canvas.addEventListener('mousedown', () => mouse.down = true);
canvas.addEventListener('mouseup', () => mouse.down = false);
// Touch / Joystick
const joystickZone = document.getElementById('joystickZone');
const joystickStick = document.getElementById('joystickStick');
joystickZone.addEventListener('touchstart', e => {
e.preventDefault();
const touch = e.touches[0];
const rect = joystickZone.getBoundingClientRect();
joystick.active = true;
joystick.startX = rect.left + rect.width / 2;
joystick.startY = rect.top + rect.height / 2;
updateJoystick(touch.clientX, touch.clientY);
}, { passive: false });
joystickZone.addEventListener('touchmove', e => {
e.preventDefault();
if (joystick.active) {
updateJoystick(e.touches[0].clientX, e.touches[0].clientY);
}
}, { passive: false });
joystickZone.addEventListener('touchend', e => {
e.preventDefault();
joystick.active = false;
joystick.dx = 0;
joystick.dy = 0;
joystickStick.style.transform = 'translate(-50%, -50%)';
});
function updateJoystick(x, y) {
const maxDist = 45;
let dx = x - joystick.startX;
let dy = y - joystick.startY;
const dist = Math.sqrt(dx * dx + dy * dy);
if (dist > maxDist) {
dx = (dx / dist) * maxDist;
dy = (dy / dist) * maxDist;
}
joystick.dx = dx / maxDist;
joystick.dy = dy / maxDist;
joystickStick.style.transform = `translate(calc(-50% + ${dx}px), calc(-50% + ${dy}px))`;
}
// Mobile action buttons
document.getElementById('dashBtn').addEventListener('touchstart', e => {
e.preventDefault();
dash();
});
document.getElementById('ultBtn').addEventListener('touchstart', e => {
e.preventDefault();
useUltimate();
});
// ==================== SCREEN MANAGEMENT ====================
function showScreen(id) {
document.querySelectorAll('.screen').forEach(s => s.classList.add('hidden'));
document.getElementById(id).classList.remove('hidden');
}
function hideAllScreens() {
document.querySelectorAll('.screen').forEach(s => s.classList.add('hidden'));
}
// Start Screen
document.getElementById('startBtn').addEventListener('click', startGame);
document.getElementById('settingsBtn').addEventListener('click', () => showScreen('settingsScreen'));
document.getElementById('backBtn').addEventListener('click', () => showScreen('startScreen'));
// Settings toggles
function setupToggle(id, knobId, settingKey) {
const btn = document.getElementById(id);
const knob = document.getElementById(knobId);
btn.addEventListener('click', () => {
settings[settingKey] = !settings[settingKey];
btn.style.background = settings[settingKey] ? '#004444' : '#222';
knob.style.transform = settings[settingKey] ? 'translateX(24px)' : 'translateX(0)';
knob.style.background = settings[settingKey] ? 'var(--neon-cyan)' : '#666';
});
// Init
btn.style.background = settings[settingKey] ? '#004444' : '#222';
knob.style.transform = settings[settingKey] ? 'translateX(24px)' : 'translateX(0)';
knob.style.background = settings[settingKey] ? 'var(--neon-cyan)' : '#666';
}
setupToggle('soundToggle', 'soundKnob', 'sound');
setupToggle('musicToggle', 'musicKnob', 'music');
setupToggle('shakeToggle', 'shakeKnob', 'shake');
setupToggle('minimapToggle', 'minimapKnob', 'minimap');
document.getElementById('particleSelect').addEventListener('change', e => {
settings.particles = e.target.value;
});
// Pause
document.getElementById('resumeBtn').addEventListener('click', resumeGame);
document.getElementById('quitBtn').addEventListener('click', () => {
gameState = STATE.M