Spaces:
Running
Running
Upload folder using huggingface_hub
Browse files- index.html +940 -651
index.html
CHANGED
|
@@ -1,677 +1,966 @@
|
|
| 1 |
<!DOCTYPE html>
|
| 2 |
<html lang="en">
|
| 3 |
-
|
| 4 |
<head>
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
}
|
| 20 |
-
|
| 21 |
-
body {
|
| 22 |
-
background-color: var(--deep-space);
|
| 23 |
-
color: white;
|
| 24 |
-
font-family: 'Space Grotesk', sans-serif;
|
| 25 |
-
overflow-x: hidden;
|
| 26 |
-
}
|
| 27 |
-
|
| 28 |
-
.mono {
|
| 29 |
-
font-family: 'JetBrains Mono', monospace;
|
| 30 |
-
}
|
| 31 |
-
|
| 32 |
-
/* Custom Scrollbar */
|
| 33 |
-
::-webkit-scrollbar {
|
| 34 |
-
width: 8px;
|
| 35 |
-
}
|
| 36 |
-
|
| 37 |
-
::-webkit-scrollbar-track {
|
| 38 |
-
background: #0a0a1a;
|
| 39 |
-
}
|
| 40 |
-
|
| 41 |
-
::-webkit-scrollbar-thumb {
|
| 42 |
-
background: #333;
|
| 43 |
-
border-radius: 4px;
|
| 44 |
-
}
|
| 45 |
-
|
| 46 |
-
::-webkit-scrollbar-thumb:hover {
|
| 47 |
-
background: var(--neon-cyan);
|
| 48 |
-
}
|
| 49 |
-
|
| 50 |
-
/* Glassmorphism Utilities */
|
| 51 |
-
.glass-panel {
|
| 52 |
-
background: var(--glass-bg);
|
| 53 |
-
backdrop-filter: blur(12px);
|
| 54 |
-
-webkit-backdrop-filter: blur(12px);
|
| 55 |
-
border: 1px solid var(--glass-border);
|
| 56 |
-
box-shadow: 0 8px 32px 0 rgba(0, 0, 0, 0.37);
|
| 57 |
-
}
|
| 58 |
-
|
| 59 |
-
.glass-card {
|
| 60 |
-
background: linear-gradient(135deg, rgba(255, 255, 255, 0.05) 0%, rgba(255, 255, 255, 0.01) 100%);
|
| 61 |
-
backdrop-filter: blur(10px);
|
| 62 |
-
border: 1px solid rgba(255, 255, 255, 0.05);
|
| 63 |
-
transition: all 0.3s ease;
|
| 64 |
-
}
|
| 65 |
-
|
| 66 |
-
.glass-card:hover {
|
| 67 |
-
transform: translateY(-5px);
|
| 68 |
-
border-color: var(--neon-cyan);
|
| 69 |
-
box-shadow: 0 0 20px rgba(0, 243, 255, 0.2);
|
| 70 |
-
}
|
| 71 |
-
|
| 72 |
-
/* Animations */
|
| 73 |
-
@keyframes float {
|
| 74 |
-
0% {
|
| 75 |
-
transform: translateY(0px);
|
| 76 |
-
}
|
| 77 |
-
|
| 78 |
-
50% {
|
| 79 |
-
transform: translateY(-10px);
|
| 80 |
-
}
|
| 81 |
-
|
| 82 |
-
100% {
|
| 83 |
-
transform: translateY(0px);
|
| 84 |
-
}
|
| 85 |
-
}
|
| 86 |
-
|
| 87 |
-
.animate-float {
|
| 88 |
-
animation: float 6s ease-in-out infinite;
|
| 89 |
-
}
|
| 90 |
-
|
| 91 |
-
@keyframes pulse-glow {
|
| 92 |
-
|
| 93 |
-
0%,
|
| 94 |
-
100% {
|
| 95 |
-
opacity: 0.5;
|
| 96 |
-
}
|
| 97 |
-
|
| 98 |
-
50% {
|
| 99 |
-
opacity: 1;
|
| 100 |
-
}
|
| 101 |
-
}
|
| 102 |
-
|
| 103 |
-
.glow-text {
|
| 104 |
-
text-shadow: 0 0 10px var(--neon-cyan), 0 0 20px var(--neon-cyan);
|
| 105 |
-
}
|
| 106 |
-
|
| 107 |
-
/* Range Slider Styling */
|
| 108 |
-
input[type=range] {
|
| 109 |
-
-webkit-appearance: none;
|
| 110 |
-
width: 100%;
|
| 111 |
-
background: transparent;
|
| 112 |
-
}
|
| 113 |
-
|
| 114 |
-
input[type=range]::-webkit-slider-thumb {
|
| 115 |
-
-webkit-appearance: none;
|
| 116 |
-
height: 16px;
|
| 117 |
-
width: 16px;
|
| 118 |
-
border-radius: 50%;
|
| 119 |
-
background: var(--neon-cyan);
|
| 120 |
-
cursor: pointer;
|
| 121 |
-
margin-top: -6px;
|
| 122 |
-
box-shadow: 0 0 10px var(--neon-cyan);
|
| 123 |
-
}
|
| 124 |
-
|
| 125 |
-
input[type=range]::-webkit-slider-runnable-track {
|
| 126 |
-
width: 100%;
|
| 127 |
-
height: 4px;
|
| 128 |
-
cursor: pointer;
|
| 129 |
-
background: rgba(255, 255, 255, 0.2);
|
| 130 |
-
border-radius: 2px;
|
| 131 |
-
}
|
| 132 |
-
|
| 133 |
-
/* Grid Background */
|
| 134 |
-
.bg-grid {
|
| 135 |
-
background-size: 40px 40px;
|
| 136 |
-
background-image: linear-gradient(to right, rgba(255, 255, 255, 0.02) 1px, transparent 1px),
|
| 137 |
-
linear-gradient(to bottom, rgba(255, 255, 255, 0.02) 1px, transparent 1px);
|
| 138 |
-
mask-image: radial-gradient(circle at center, black 40%, transparent 80%);
|
| 139 |
-
}
|
| 140 |
-
|
| 141 |
-
.crt-overlay {
|
| 142 |
-
background: linear-gradient(rgba(18, 16, 16, 0) 50%, rgba(0, 0, 0, 0.25) 50%), linear-gradient(90deg, rgba(255, 0, 0, 0.06), rgba(0, 255, 0, 0.02), rgba(0, 0, 255, 0.06));
|
| 143 |
-
background-size: 100% 2px, 3px 100%;
|
| 144 |
-
pointer-events: none;
|
| 145 |
-
}
|
| 146 |
-
|
| 147 |
-
/* Glitch Effect Class */
|
| 148 |
-
.glitch-hover:hover {
|
| 149 |
-
animation: glitch 0.3s cubic-bezier(.25, .46, .45, .94) both infinite;
|
| 150 |
-
color: var(--neon-pink);
|
| 151 |
-
}
|
| 152 |
-
|
| 153 |
-
@keyframes glitch {
|
| 154 |
-
0% {
|
| 155 |
-
transform: translate(0)
|
| 156 |
-
}
|
| 157 |
-
|
| 158 |
-
20% {
|
| 159 |
-
transform: translate(-2px, 2px)
|
| 160 |
-
}
|
| 161 |
-
|
| 162 |
-
40% {
|
| 163 |
-
transform: translate(-2px, -2px)
|
| 164 |
-
}
|
| 165 |
-
|
| 166 |
-
60% {
|
| 167 |
-
transform: translate(2px, 2px)
|
| 168 |
-
}
|
| 169 |
-
|
| 170 |
-
80% {
|
| 171 |
-
transform: translate(2px, -2px)
|
| 172 |
-
}
|
| 173 |
-
|
| 174 |
-
100% {
|
| 175 |
-
transform: translate(0)
|
| 176 |
-
}
|
| 177 |
-
}
|
| 178 |
-
</style>
|
| 179 |
-
</head>
|
| 180 |
|
| 181 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 182 |
|
| 183 |
-
|
| 184 |
-
|
| 185 |
-
|
| 186 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 187 |
|
| 188 |
-
|
| 189 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 190 |
|
| 191 |
-
|
| 192 |
-
|
| 193 |
-
|
| 194 |
-
|
| 195 |
-
|
| 196 |
-
</div>
|
| 197 |
-
<h1 class="text-2xl font-bold tracking-tighter uppercase mono">NEXUS<span class="text-cyan-400">GEN</span></h1>
|
| 198 |
-
</div>
|
| 199 |
-
<nav class="hidden md:flex gap-8 text-sm font-medium text-gray-400">
|
| 200 |
-
<a href="#" class="hover:text-cyan-400 transition-colors">Explore</a>
|
| 201 |
-
<a href="#generator" class="hover:text-cyan-400 transition-colors">Create</a>
|
| 202 |
-
<a href="#about" class="hover:text-cyan-400 transition-colors">About</a>
|
| 203 |
-
</nav>
|
| 204 |
-
<a href="https://huggingface.co/spaces/akhaliq/anycoder" target="_blank"
|
| 205 |
-
class="text-xs mono text-gray-500 hover:text-white transition-colors border border-white/10 px-3 py-1 rounded-full">
|
| 206 |
-
Built with anycoder
|
| 207 |
-
</a>
|
| 208 |
-
</header>
|
| 209 |
-
|
| 210 |
-
<!-- Hero Section -->
|
| 211 |
-
<main
|
| 212 |
-
class="flex-grow container mx-auto px-4 py-12 flex flex-col items-center justify-center text-center min-h-[80vh]">
|
| 213 |
-
|
| 214 |
-
<div
|
| 215 |
-
class="inline-block mb-4 px-4 py-1 rounded-full border border-cyan-500/30 bg-cyan-500/10 text-cyan-300 text-xs mono tracking-widest animate-float">
|
| 216 |
-
SYSTEM ONLINE // V.2.0.4
|
| 217 |
-
</div>
|
| 218 |
-
|
| 219 |
-
<h2 class="text-5xl md:text-7xl font-bold mb-6 leading-tight">
|
| 220 |
-
Procedural <br>
|
| 221 |
-
<span class="text-transparent bg-clip-text bg-gradient-to-r from-cyan-400 via-purple-400 to-pink-400 glow-text">Universe Engine</span>
|
| 222 |
-
</h2>
|
| 223 |
-
|
| 224 |
-
<p class="text-gray-400 max-w-2xl text-lg mb-10 leading-relaxed">
|
| 225 |
-
Generate infinite worlds, terrains, and abstract art using mathematical algorithms.
|
| 226 |
-
No assets loaded. Everything is calculated in real-time.
|
| 227 |
-
</p>
|
| 228 |
-
|
| 229 |
-
<div class="flex flex-wrap gap-4 justify-center">
|
| 230 |
-
<button onclick="scrollToSection('generator')" class="group relative px-8 py-4 bg-cyan-500 hover:bg-cyan-400 text-black font-bold rounded-lg overflow-hidden transition-all hover:scale-105 hover:shadow-[0_0_30px_rgba(6,182,212,0.5)]">
|
| 231 |
-
<span class="relative z-10 flex items-center gap-2">
|
| 232 |
-
Initialize Generator
|
| 233 |
-
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 group-hover:translate-x-1 transition-transform" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z" /></svg>
|
| 234 |
-
</span>
|
| 235 |
-
<div class="absolute inset-0 bg-white/20 translate-y-full group-hover:translate-y-0 transition-transform duration-300"></div>
|
| 236 |
-
</button>
|
| 237 |
-
|
| 238 |
-
<button onclick="randomizeSeed()" class="px-8 py-4 glass-panel hover:bg-white/10 text-white font-semibold rounded-lg transition-all border border-white/20 hover:border-white/40">
|
| 239 |
-
Randomize Seed
|
| 240 |
-
</button>
|
| 241 |
-
</div>
|
| 242 |
-
|
| 243 |
-
<!-- Live Stats -->
|
| 244 |
-
<div class="mt-16 grid grid-cols-2 md:grid-cols-4 gap-8 w-full max-w-4xl border-t border-white/10 pt-8">
|
| 245 |
-
<div class="text-center">
|
| 246 |
-
<div class="text-3xl font-bold mono text-cyan-400" id="stat-fps">60</div>
|
| 247 |
-
<div class="text-xs text-gray-500 uppercase tracking-wider mt-1">FPS</div>
|
| 248 |
-
</div>
|
| 249 |
-
<div class="text-center">
|
| 250 |
-
<div class="text-3xl font-bold mono text-purple-400" id="stat-objects">0</div>
|
| 251 |
-
<div class="text-xs text-gray-500 uppercase tracking-wider mt-1">Entities</div>
|
| 252 |
-
</div>
|
| 253 |
-
<div class="text-center">
|
| 254 |
-
<div class="text-3xl font-bold mono text-pink-400" id="stat-seed">8492</div>
|
| 255 |
-
<div class="text-xs text-gray-500 uppercase tracking-wider mt-1">Current Seed</div>
|
| 256 |
-
</div>
|
| 257 |
-
<div class="text-center">
|
| 258 |
-
<div class="text-3xl font-bold mono text-green-400">∞</div>
|
| 259 |
-
<div class="text-xs text-gray-500 uppercase tracking-wider mt-1">Possibilities</div>
|
| 260 |
-
</div>
|
| 261 |
-
</div>
|
| 262 |
-
</main>
|
| 263 |
-
|
| 264 |
-
<!-- Generator Dashboard -->
|
| 265 |
-
<section id="generator" class="py-20 bg-black/40 backdrop-blur-sm border-t border-white/5">
|
| 266 |
-
<div class="container mx-auto px-4">
|
| 267 |
-
<div class="flex flex-col lg:flex-row gap-8 h-[800px]">
|
| 268 |
-
|
| 269 |
-
<!-- Controls Sidebar -->
|
| 270 |
-
<div class="w-full lg:w-1/4 glass-panel rounded-xl p-6 flex flex-col gap-6 overflow-y-auto">
|
| 271 |
-
<div>
|
| 272 |
-
<h3 class="text-xl font-bold mb-1 flex items-center gap-2">
|
| 273 |
-
<span class="w-2 h-2 bg-cyan-400 rounded-full animate-pulse"></span>
|
| 274 |
-
Parameters
|
| 275 |
-
</h3>
|
| 276 |
-
<p class="text-xs text-gray-500 mono">ADJUST SIMULATION VARIABLES</p>
|
| 277 |
-
</div>
|
| 278 |
|
| 279 |
-
|
| 280 |
-
|
| 281 |
-
|
| 282 |
-
|
| 283 |
-
|
| 284 |
-
<span>Flat</span>
|
| 285 |
-
<span>Chaotic</span>
|
| 286 |
-
</div>
|
| 287 |
-
</div>
|
| 288 |
|
| 289 |
-
|
| 290 |
-
|
| 291 |
-
|
| 292 |
-
|
| 293 |
-
<div class="h-2 w-full rounded-full bg-gradient-to-r from-red-500 via-green-500 to-blue-500"></div>
|
| 294 |
-
</div>
|
| 295 |
|
| 296 |
-
|
| 297 |
-
|
| 298 |
-
|
| 299 |
-
|
| 300 |
-
|
|
|
|
|
|
|
|
|
|
| 301 |
|
| 302 |
-
|
| 303 |
-
|
| 304 |
-
|
| 305 |
-
<div class="space-y-3">
|
| 306 |
-
<label class="flex items-center justify-between cursor-pointer group">
|
| 307 |
-
<span class="text-sm text-gray-300 group-hover:text-white">Wireframe Mode</span>
|
| 308 |
-
<input type="checkbox" id="wireframe" class="accent-cyan-500 w-4 h-4" onchange="updateParams()">
|
| 309 |
-
</label>
|
| 310 |
-
<label class="flex items-center justify-between cursor-pointer group">
|
| 311 |
-
<span class="text-sm text-gray-300 group-hover:text-white">Show Nodes</span>
|
| 312 |
-
<input type="checkbox" id="nodes" checked class="accent-cyan-500 w-4 h-4" onchange="updateParams()">
|
| 313 |
-
</label>
|
| 314 |
-
</div>
|
| 315 |
|
| 316 |
-
|
| 317 |
-
|
| 318 |
-
|
| 319 |
-
|
| 320 |
-
</button>
|
| 321 |
-
</div>
|
| 322 |
-
</div>
|
| 323 |
|
| 324 |
-
|
| 325 |
-
|
| 326 |
-
|
| 327 |
-
|
| 328 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 329 |
|
| 330 |
-
|
| 331 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 332 |
|
| 333 |
-
|
| 334 |
-
|
| 335 |
-
|
| 336 |
-
|
| 337 |
-
|
| 338 |
-
|
| 339 |
-
</div>
|
| 340 |
-
</div>
|
| 341 |
-
</div>
|
| 342 |
-
</section>
|
| 343 |
-
|
| 344 |
-
<!-- Feature Grid -->
|
| 345 |
-
<section class="py-20 container mx-auto px-4">
|
| 346 |
-
<h3 class="text-3xl font-bold mb-12 text-center">Procedural Capabilities</h3>
|
| 347 |
-
<div class="grid grid-cols-1 md:grid-cols-3 gap-6">
|
| 348 |
-
|
| 349 |
-
<!-- Card 1 -->
|
| 350 |
-
<div class="glass-card p-8 rounded-2xl relative overflow-hidden group">
|
| 351 |
-
<div
|
| 352 |
-
class="absolute -right-10 -top-10 w-32 h-32 bg-purple-500/20 rounded-full blur-3xl group-hover:bg-purple-500/40 transition-all">
|
| 353 |
-
</div>
|
| 354 |
-
<div class="w-12 h-12 bg-white/5 rounded-lg flex items-center justify-center mb-6 border border-white/10">
|
| 355 |
-
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 text-purple-400" fill="none" viewBox="0 0 24 24"
|
| 356 |
-
stroke="currentColor">
|
| 357 |
-
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
| 358 |
-
d="M19.428 15.428a2 2 0 00-1.022-.547l-2.384-.477a6 6 0 00-3.86.517l-.318.158a6 6 0 01-3.86.517L6.05 15.21a2 2 0 00-1.806.547M8 4h8l-1 1v5.172a2 2 0 00.586 1.414l5 5c1.26 1.26.367 3.414-1.415 3.414H4.828c-1.782 0-2.674-2.154-1.414-3.414l5-5A2 2 0 009 10.172V5L8 4z" />
|
| 359 |
-
</svg>
|
| 360 |
-
</div>
|
| 361 |
-
<h4 class="text-xl font-bold mb-2">Fractal Noise</h4>
|
| 362 |
-
<p class="text-gray-400 text-sm leading-relaxed">
|
| 363 |
-
Utilizes Perlin and Simplex noise algorithms to generate organic-looking textures and terrain that never
|
| 364 |
-
repeat.
|
| 365 |
-
</p>
|
| 366 |
-
</div>
|
| 367 |
|
| 368 |
-
|
| 369 |
-
|
| 370 |
-
<div
|
| 371 |
-
class="absolute -right-10 -top-10 w-32 h-32 bg-cyan-500/20 rounded-full blur-3xl group-hover:bg-cyan-500/40 transition-all">
|
| 372 |
-
</div>
|
| 373 |
-
<div class="w-12 h-12 bg-white/5 rounded-lg flex items-center justify-center mb-6 border border-white/10">
|
| 374 |
-
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 text-cyan-400" fill="none" viewBox="0 0 24 24"
|
| 375 |
-
stroke="currentColor">
|
| 376 |
-
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
| 377 |
-
d="M14 10l-2 1m0 0l-2-1m2 1v2.5M20 7l-2 1m2-1l-2-1m2 1v2.5M14 4l-2-1-2 1M4 7l2-1M4 7l2 1M4 7v2.5M12 21l-2-1m2 1l2-1m-2 1v-2.5M6 18l-2-1v-2.5M18 18l2-1v-2.5" />
|
| 378 |
-
</svg>
|
| 379 |
-
</div>
|
| 380 |
-
<h4 class="text-xl font-bold mb-2">Vector Fields</h4>
|
| 381 |
-
<p class="text-gray-400 text-sm leading-relaxed">
|
| 382 |
-
Simulates fluid dynamics and particle flows using curl noise to create mesmerizing motion patterns.
|
| 383 |
-
</p>
|
| 384 |
-
</div>
|
| 385 |
|
| 386 |
-
|
| 387 |
-
|
| 388 |
-
<div
|
| 389 |
-
class="absolute -right-10 -top-10 w-32 h-32 bg-pink-500/20 rounded-full blur-3xl group-hover:bg-pink-500/40 transition-all">
|
| 390 |
-
</div>
|
| 391 |
-
<div class="w-12 h-12 bg-white/5 rounded-lg flex items-center justify-center mb-6 border border-white/10">
|
| 392 |
-
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 text-pink-400" fill="none" viewBox="0 0 24 24"
|
| 393 |
-
stroke="currentColor">
|
| 394 |
-
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
| 395 |
-
d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" />
|
| 396 |
-
</svg>
|
| 397 |
-
</div>
|
| 398 |
-
<h4 class="text-xl font-bold mb-2">L-Systems</h4>
|
| 399 |
-
<p class="text-gray-400 text-sm leading-relaxed">
|
| 400 |
-
Recursive string rewriting systems used to model the growth processes of plant development and geometric
|
| 401 |
-
structures.
|
| 402 |
-
</p>
|
| 403 |
-
</div>
|
| 404 |
|
| 405 |
-
|
| 406 |
-
<
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 407 |
|
| 408 |
-
|
| 409 |
-
|
| 410 |
-
|
| 411 |
-
|
| 412 |
-
|
| 413 |
-
|
| 414 |
-
|
| 415 |
-
|
| 416 |
-
|
| 417 |
-
|
| 418 |
-
|
| 419 |
-
|
| 420 |
-
|
| 421 |
-
|
| 422 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 423 |
</div>
|
| 424 |
-
|
| 425 |
-
|
| 426 |
-
|
| 427 |
-
|
| 428 |
-
|
| 429 |
-
|
| 430 |
-
/
|
| 431 |
-
|
| 432 |
-
|
| 433 |
-
|
| 434 |
-
|
| 435 |
-
|
| 436 |
-
|
| 437 |
-
|
| 438 |
-
|
| 439 |
-
|
| 440 |
-
|
| 441 |
-
|
| 442 |
-
|
| 443 |
-
|
| 444 |
-
|
| 445 |
-
|
| 446 |
-
|
| 447 |
-
|
| 448 |
-
|
| 449 |
-
|
| 450 |
-
|
| 451 |
-
|
| 452 |
-
|
| 453 |
-
|
| 454 |
-
|
| 455 |
-
|
| 456 |
-
|
| 457 |
-
|
| 458 |
-
|
| 459 |
-
|
| 460 |
-
|
| 461 |
-
|
| 462 |
-
|
| 463 |
-
|
| 464 |
-
|
| 465 |
-
|
| 466 |
-
|
| 467 |
-
|
| 468 |
-
|
| 469 |
-
|
| 470 |
-
|
| 471 |
-
|
| 472 |
-
|
| 473 |
-
|
| 474 |
-
|
| 475 |
-
|
| 476 |
-
|
| 477 |
-
|
| 478 |
-
|
| 479 |
-
|
| 480 |
-
|
| 481 |
-
|
| 482 |
-
|
| 483 |
-
|
| 484 |
-
|
| 485 |
-
|
| 486 |
-
|
| 487 |
-
|
| 488 |
-
|
| 489 |
-
|
| 490 |
-
|
| 491 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 492 |
}
|
| 493 |
-
|
| 494 |
-
|
| 495 |
-
|
| 496 |
-
|
| 497 |
-
|
| 498 |
-
|
| 499 |
-
|
| 500 |
-
|
| 501 |
-
|
| 502 |
-
|
| 503 |
-
|
| 504 |
-
|
| 505 |
-
|
| 506 |
-
|
| 507 |
-
|
| 508 |
-
|
| 509 |
-
|
| 510 |
-
|
| 511 |
-
|
| 512 |
-
|
| 513 |
-
|
| 514 |
-
|
| 515 |
-
|
| 516 |
-
|
| 517 |
-
|
| 518 |
-
|
| 519 |
-
|
| 520 |
-
|
| 521 |
-
|
| 522 |
-
|
| 523 |
-
|
| 524 |
-
|
| 525 |
-
|
| 526 |
-
|
| 527 |
-
|
| 528 |
-
|
| 529 |
-
speed: 10,
|
| 530 |
-
wireframe: false,
|
| 531 |
-
showNodes: true
|
| 532 |
-
};
|
| 533 |
-
|
| 534 |
-
function resizeProcCanvas() {
|
| 535 |
-
const rect = canvas.parentElement.getBoundingClientRect();
|
| 536 |
-
canvas.width = rect.width;
|
| 537 |
-
canvas.height = rect.height;
|
| 538 |
-
width = canvas.width;
|
| 539 |
-
height = canvas.height;
|
| 540 |
-
}
|
| 541 |
-
|
| 542 |
-
function updateParams() {
|
| 543 |
-
params.complexity = parseInt(document.getElementById('complexity').value);
|
| 544 |
-
params.biome = parseInt(document.getElementById('biome').value);
|
| 545 |
-
params.speed = parseInt(document.getElementById('speed').value);
|
| 546 |
-
params.wireframe = document.getElementById('wireframe').checked;
|
| 547 |
-
params.showNodes = document.getElementById('nodes').checked;
|
| 548 |
-
}
|
| 549 |
-
|
| 550 |
-
// Pseudo-3D Projection Logic
|
| 551 |
-
function drawTerrain() {
|
| 552 |
-
// Clear with fade effect for trails
|
| 553 |
-
ctx.fillStyle = 'rgba(5, 5, 16, 0.3)';
|
| 554 |
-
ctx.fillRect(0, 0, width, height);
|
| 555 |
-
|
| 556 |
-
const horizon = height * 0.6;
|
| 557 |
-
const fov = 300;
|
| 558 |
-
const gridSize = 40; // Number of rows
|
| 559 |
-
|
| 560 |
-
// We draw from back to front
|
| 561 |
-
for (let z = gridSize; z > 0; z--) {
|
| 562 |
-
// Perspective scale
|
| 563 |
-
const scale = fov / (fov + z * 10);
|
| 564 |
-
const yBase = horizon + (z * 10) * scale; // Project Y
|
| 565 |
-
|
| 566 |
-
// Color based on depth and biome
|
| 567 |
-
const hue = (params.biome + z * 2) % 360;
|
| 568 |
-
const lightness = 20 + (z / gridSize) * 40;
|
| 569 |
-
|
| 570 |
-
ctx.beginPath();
|
| 571 |
-
|
| 572 |
-
// Draw a horizontal strip
|
| 573 |
-
const stripWidth = width / scale; // How wide the world is at this depth
|
| 574 |
-
|
| 575 |
-
for (let x = -stripWidth/2; x <= stripWidth/2; x += 20) {
|
| 576 |
-
// Calculate Height (Y) based on Sine Waves (Procedural Terrain)
|
| 577 |
-
// Using multiple sine waves for complexity
|
| 578 |
-
const wave1 = Math.sin(x * 0.01 + time * 0.05 + z * 0.1) * (params.complexity * scale);
|
| 579 |
-
const wave2 = Math.cos(x * 0.02 - time * 0.02) * (params.complexity * 0.5 * scale);
|
| 580 |
-
const yOffset = wave1 + wave2;
|
| 581 |
-
|
| 582 |
-
const screenX = width/2 + x * scale;
|
| 583 |
-
const screenY = yBase - yOffset;
|
| 584 |
-
|
| 585 |
-
if (x === -stripWidth/2) {
|
| 586 |
-
ctx.moveTo(screenX, screenY);
|
| 587 |
-
} else {
|
| 588 |
-
ctx.lineTo(screenX, screenY);
|
| 589 |
-
}
|
| 590 |
}
|
| 591 |
|
| 592 |
-
|
| 593 |
-
|
| 594 |
-
|
| 595 |
-
|
| 596 |
-
|
| 597 |
-
|
| 598 |
-
|
| 599 |
-
|
| 600 |
-
|
| 601 |
-
|
| 602 |
-
|
| 603 |
-
|
| 604 |
-
|
| 605 |
-
|
| 606 |
-
|
| 607 |
-
|
| 608 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 609 |
}
|
| 610 |
|
| 611 |
-
|
| 612 |
-
|
| 613 |
-
|
| 614 |
-
|
| 615 |
-
|
| 616 |
-
|
| 617 |
-
|
| 618 |
-
|
| 619 |
-
|
| 620 |
-
|
| 621 |
-
|
| 622 |
-
|
| 623 |
-
|
| 624 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 625 |
}
|
| 626 |
-
|
| 627 |
-
|
| 628 |
-
|
| 629 |
-
|
| 630 |
-
|
| 631 |
-
|
| 632 |
-
|
| 633 |
-
|
| 634 |
-
|
| 635 |
-
|
| 636 |
-
|
| 637 |
-
|
| 638 |
-
|
| 639 |
-
|
| 640 |
-
|
| 641 |
-
|
| 642 |
-
|
| 643 |
-
|
| 644 |
-
|
| 645 |
-
|
| 646 |
-
|
| 647 |
-
|
| 648 |
-
|
| 649 |
-
|
| 650 |
-
|
| 651 |
-
|
| 652 |
-
|
| 653 |
-
|
| 654 |
-
|
| 655 |
-
|
| 656 |
-
|
| 657 |
-
|
| 658 |
-
|
| 659 |
-
|
| 660 |
-
|
| 661 |
-
|
| 662 |
-
|
| 663 |
-
|
| 664 |
-
|
| 665 |
-
|
| 666 |
-
|
| 667 |
-
|
| 668 |
-
|
| 669 |
-
|
| 670 |
-
|
| 671 |
-
|
| 672 |
-
|
| 673 |
-
|
| 674 |
-
|
| 675 |
-
|
| 676 |
-
|
| 677 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
<!DOCTYPE html>
|
| 2 |
<html lang="en">
|
|
|
|
| 3 |
<head>
|
| 4 |
+
<meta charset="UTF-8">
|
| 5 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 6 |
+
<title>ASTRAL COLONIZER - Procedural Space Simulation</title>
|
| 7 |
+
<script src="https://cdn.tailwindcss.com"></script>
|
| 8 |
+
<link href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@300;400;600;700&family=JetBrains+Mono:wght@400;700&display=swap" rel="stylesheet">
|
| 9 |
+
<style>
|
| 10 |
+
:root {
|
| 11 |
+
--neon-cyan: #00f3ff;
|
| 12 |
+
--neon-pink: #ff00ff;
|
| 13 |
+
--neon-amber: #ffaa00;
|
| 14 |
+
--deep-space: #050510;
|
| 15 |
+
--glass-bg: rgba(255, 255, 255, 0.03);
|
| 16 |
+
--glass-border: rgba(255, 255, 255, 0.1);
|
| 17 |
+
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 18 |
|
| 19 |
+
body {
|
| 20 |
+
background-color: var(--deep-space);
|
| 21 |
+
color: white;
|
| 22 |
+
font-family: 'Space Grotesk', sans-serif;
|
| 23 |
+
overflow: hidden;
|
| 24 |
+
user-select: none;
|
| 25 |
+
}
|
| 26 |
|
| 27 |
+
.mono { font-family: 'JetBrains Mono', monospace; }
|
| 28 |
+
|
| 29 |
+
/* Custom Scrollbar */
|
| 30 |
+
::-webkit-scrollbar { width: 6px; }
|
| 31 |
+
::-webkit-scrollbar-track { background: #0a0a1a; }
|
| 32 |
+
::-webkit-scrollbar-thumb { background: #333; border-radius: 3px; }
|
| 33 |
+
::-webkit-scrollbar-thumb:hover { background: var(--neon-cyan); }
|
| 34 |
+
|
| 35 |
+
/* Glassmorphism */
|
| 36 |
+
.glass-panel {
|
| 37 |
+
background: var(--glass-bg);
|
| 38 |
+
backdrop-filter: blur(12px);
|
| 39 |
+
-webkit-backdrop-filter: blur(12px);
|
| 40 |
+
border: 1px solid var(--glass-border);
|
| 41 |
+
box-shadow: 0 8px 32px 0 rgba(0, 0, 0, 0.5);
|
| 42 |
+
}
|
| 43 |
|
| 44 |
+
.glass-card {
|
| 45 |
+
background: linear-gradient(135deg, rgba(255, 255, 255, 0.05) 0%, rgba(255, 255, 255, 0.01) 100%);
|
| 46 |
+
backdrop-filter: blur(10px);
|
| 47 |
+
border: 1px solid rgba(255, 255, 255, 0.05);
|
| 48 |
+
transition: all 0.2s ease;
|
| 49 |
+
}
|
| 50 |
|
| 51 |
+
.glass-card:hover:not(:disabled) {
|
| 52 |
+
border-color: var(--neon-cyan);
|
| 53 |
+
background: rgba(0, 243, 255, 0.05);
|
| 54 |
+
transform: translateY(-2px);
|
| 55 |
+
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 56 |
|
| 57 |
+
/* Animations */
|
| 58 |
+
@keyframes pulse-glow {
|
| 59 |
+
0%, 100% { box-shadow: 0 0 5px var(--neon-cyan); }
|
| 60 |
+
50% { box-shadow: 0 0 20px var(--neon-cyan); }
|
| 61 |
+
}
|
|
|
|
|
|
|
|
|
|
|
|
|
| 62 |
|
| 63 |
+
@keyframes scanline {
|
| 64 |
+
0% { transform: translateY(-100%); }
|
| 65 |
+
100% { transform: translateY(100%); }
|
| 66 |
+
}
|
|
|
|
|
|
|
| 67 |
|
| 68 |
+
.scan-overlay {
|
| 69 |
+
position: absolute;
|
| 70 |
+
top: 0; left: 0; right: 0; bottom: 0;
|
| 71 |
+
background: linear-gradient(to bottom, transparent 50%, rgba(0, 243, 255, 0.02) 51%);
|
| 72 |
+
background-size: 100% 4px;
|
| 73 |
+
pointer-events: none;
|
| 74 |
+
z-index: 40;
|
| 75 |
+
}
|
| 76 |
|
| 77 |
+
.crt-flicker {
|
| 78 |
+
animation: flicker 0.15s infinite;
|
| 79 |
+
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 80 |
|
| 81 |
+
@keyframes flicker {
|
| 82 |
+
0% { opacity: 0.97; }
|
| 83 |
+
100% { opacity: 1; }
|
| 84 |
+
}
|
|
|
|
|
|
|
|
|
|
| 85 |
|
| 86 |
+
/* Game UI Elements */
|
| 87 |
+
.resource-bar-fill {
|
| 88 |
+
transition: width 0.3s ease-out;
|
| 89 |
+
}
|
| 90 |
+
|
| 91 |
+
.btn-action {
|
| 92 |
+
position: relative;
|
| 93 |
+
overflow: hidden;
|
| 94 |
+
}
|
| 95 |
+
.btn-action::after {
|
| 96 |
+
content: '';
|
| 97 |
+
position: absolute;
|
| 98 |
+
top: 0; left: -100%;
|
| 99 |
+
width: 100%; height: 100%;
|
| 100 |
+
background: linear-gradient(90deg, transparent, rgba(255,255,255,0.2), transparent);
|
| 101 |
+
transition: 0.5s;
|
| 102 |
+
}
|
| 103 |
+
.btn-action:hover::after {
|
| 104 |
+
left: 100%;
|
| 105 |
+
}
|
| 106 |
|
| 107 |
+
/* Tooltip */
|
| 108 |
+
#tooltip {
|
| 109 |
+
position: absolute;
|
| 110 |
+
pointer-events: none;
|
| 111 |
+
z-index: 100;
|
| 112 |
+
opacity: 0;
|
| 113 |
+
transition: opacity 0.1s;
|
| 114 |
+
transform: translate(15px, 15px);
|
| 115 |
+
}
|
| 116 |
|
| 117 |
+
canvas {
|
| 118 |
+
image-rendering: pixelated; /* Retro feel */
|
| 119 |
+
}
|
| 120 |
+
</style>
|
| 121 |
+
</head>
|
| 122 |
+
<body class="h-screen w-screen flex flex-col overflow-hidden crt-flicker">
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 123 |
|
| 124 |
+
<!-- Background Starfield Canvas -->
|
| 125 |
+
<canvas id="bgCanvas" class="fixed top-0 left-0 w-full h-full z-0"></canvas>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 126 |
|
| 127 |
+
<!-- CRT Scanline Overlay -->
|
| 128 |
+
<div class="scan-overlay"></div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 129 |
|
| 130 |
+
<!-- Main Game Container -->
|
| 131 |
+
<div class="relative z-10 flex flex-col h-full p-4 gap-4">
|
| 132 |
+
|
| 133 |
+
<!-- Header -->
|
| 134 |
+
<header class="glass-panel rounded-lg p-3 flex justify-between items-center shrink-0">
|
| 135 |
+
<div class="flex items-center gap-3">
|
| 136 |
+
<div class="w-8 h-8 rounded bg-gradient-to-br from-cyan-500 to-blue-600 flex items-center justify-center shadow-[0_0_15px_rgba(6,182,212,0.6)]">
|
| 137 |
+
<svg class="w-5 h-5 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19.428 15.428a2 2 0 00-1.022-.547l-2.384-.477a6 6 0 00-3.86.517l-.318.158a6 6 0 01-3.86.517L6.05 15.21a2 2 0 00-1.806.547M8 4h8l-1 1v5.172a2 2 0 00.586 1.414l5 5c1.26 1.26.367 3.414-1.415 3.414H4.828c-1.782 0-2.674-2.154-1.414-3.414l5-5A2 2 0 009 10.172V5L8 4z"></path></svg>
|
| 138 |
+
</div>
|
| 139 |
+
<div>
|
| 140 |
+
<h1 class="text-xl font-bold tracking-widest uppercase mono text-cyan-400">Astral Colonizer</h1>
|
| 141 |
+
<div class="text-[10px] text-gray-400 mono flex gap-4">
|
| 142 |
+
<span>SECTOR: <span id="sector-id" class="text-white">ALPHA-9</span></span>
|
| 143 |
+
<span>CYCLE: <span id="game-cycle" class="text-white">1</span></span>
|
| 144 |
+
</div>
|
| 145 |
+
</div>
|
| 146 |
+
</div>
|
| 147 |
|
| 148 |
+
<div class="flex gap-6 text-sm mono">
|
| 149 |
+
<div class="flex flex-col items-end w-32">
|
| 150 |
+
<div class="flex justify-between w-full text-xs text-gray-400 mb-1">
|
| 151 |
+
<span>ENERGY</span>
|
| 152 |
+
<span id="val-energy">100</span>
|
| 153 |
+
</div>
|
| 154 |
+
<div class="w-full h-1.5 bg-gray-800 rounded-full overflow-hidden">
|
| 155 |
+
<div id="bar-energy" class="h-full bg-yellow-400 resource-bar-fill" style="width: 100%"></div>
|
| 156 |
+
</div>
|
| 157 |
+
</div>
|
| 158 |
+
<div class="flex flex-col items-end w-32">
|
| 159 |
+
<div class="flex justify-between w-full text-xs text-gray-400 mb-1">
|
| 160 |
+
<span>MATTER</span>
|
| 161 |
+
<span id="val-matter">50</span>
|
| 162 |
+
</div>
|
| 163 |
+
<div class="w-full h-1.5 bg-gray-800 rounded-full overflow-hidden">
|
| 164 |
+
<div id="bar-matter" class="h-full bg-blue-500 resource-bar-fill" style="width: 50%"></div>
|
| 165 |
+
</div>
|
| 166 |
+
</div>
|
| 167 |
+
<div class="flex flex-col items-end w-32">
|
| 168 |
+
<div class="flex justify-between w-full text-xs text-gray-400 mb-1">
|
| 169 |
+
<span>POPULATION</span>
|
| 170 |
+
<span id="val-pop">0</span>
|
| 171 |
+
</div>
|
| 172 |
+
<div class="w-full h-1.5 bg-gray-800 rounded-full overflow-hidden">
|
| 173 |
+
<div id="bar-pop" class="h-full bg-green-500 resource-bar-fill" style="width: 0%"></div>
|
| 174 |
+
</div>
|
| 175 |
+
</div>
|
| 176 |
+
</div>
|
| 177 |
+
|
| 178 |
+
<a href="https://huggingface.co/spaces/akhaliq/anycoder" target="_blank" class="text-[10px] mono text-gray-500 hover:text-cyan-400 transition-colors border border-white/10 px-2 py-1 rounded">
|
| 179 |
+
Built with anycoder
|
| 180 |
+
</a>
|
| 181 |
+
</header>
|
| 182 |
+
|
| 183 |
+
<!-- Game Area -->
|
| 184 |
+
<div class="flex flex-1 gap-4 min-h-0">
|
| 185 |
+
|
| 186 |
+
<!-- Left Panel: Controls & Log -->
|
| 187 |
+
<aside class="w-64 flex flex-col gap-4 shrink-0">
|
| 188 |
+
<!-- Build Menu -->
|
| 189 |
+
<div class="glass-panel rounded-lg p-4 flex-1 flex flex-col gap-2 overflow-y-auto">
|
| 190 |
+
<h3 class="text-xs font-bold text-gray-400 uppercase tracking-wider mb-2 border-b border-white/10 pb-1">Construction</h3>
|
| 191 |
+
|
| 192 |
+
<button onclick="game.selectTool('solar')" id="btn-solar" class="glass-card p-3 rounded flex items-center gap-3 text-left btn-action group">
|
| 193 |
+
<div class="w-8 h-8 rounded bg-yellow-500/20 text-yellow-400 flex items-center justify-center border border-yellow-500/30 group-hover:bg-yellow-500 group-hover:text-black transition-colors">
|
| 194 |
+
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 3v1m0 16v1m9-9h-1M4 12H3m15.364 6.364l-.707-.707M6.343 6.343l-.707-.707m12.728 0l-.707.707M6.343 17.657l-.707.707M16 12a4 4 0 11-8 0 4 4 0 018 0z"></path></svg>
|
| 195 |
+
</div>
|
| 196 |
+
<div>
|
| 197 |
+
<div class="text-sm font-bold text-white">Solar Array</div>
|
| 198 |
+
<div class="text-[10px] text-yellow-500 mono">Cost: 20 M</div>
|
| 199 |
+
</div>
|
| 200 |
+
</button>
|
| 201 |
+
|
| 202 |
+
<button onclick="game.selectTool('mine')" id="btn-mine" class="glass-card p-3 rounded flex items-center gap-3 text-left btn-action group">
|
| 203 |
+
<div class="w-8 h-8 rounded bg-blue-500/20 text-blue-400 flex items-center justify-center border border-blue-500/30 group-hover:bg-blue-500 group-hover:text-black transition-colors">
|
| 204 |
+
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10"></path></svg>
|
| 205 |
+
</div>
|
| 206 |
+
<div>
|
| 207 |
+
<div class="text-sm font-bold text-white">Extractor</div>
|
| 208 |
+
<div class="text-[10px] text-blue-500 mono">Cost: 30 M, 10 E</div>
|
| 209 |
+
</div>
|
| 210 |
+
</button>
|
| 211 |
+
|
| 212 |
+
<button onclick="game.selectTool('habitat')" id="btn-habitat" class="glass-card p-3 rounded flex items-center gap-3 text-left btn-action group">
|
| 213 |
+
<div class="w-8 h-8 rounded bg-green-500/20 text-green-400 flex items-center justify-center border border-green-500/30 group-hover:bg-green-500 group-hover:text-black transition-colors">
|
| 214 |
+
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6"></path></svg>
|
| 215 |
+
</div>
|
| 216 |
+
<div>
|
| 217 |
+
<div class="text-sm font-bold text-white">Habitat</div>
|
| 218 |
+
<div class="text-[10px] text-green-500 mono">Cost: 100 M, 50 E</div>
|
| 219 |
+
</div>
|
| 220 |
+
</button>
|
| 221 |
+
|
| 222 |
+
<button onclick="game.selectTool('defense')" id="btn-defense" class="glass-card p-3 rounded flex items-center gap-3 text-left btn-action group">
|
| 223 |
+
<div class="w-8 h-8 rounded bg-red-500/20 text-red-400 flex items-center justify-center border border-red-500/30 group-hover:bg-red-500 group-hover:text-black transition-colors">
|
| 224 |
+
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z"></path></svg>
|
| 225 |
+
</div>
|
| 226 |
+
<div>
|
| 227 |
+
<div class="text-sm font-bold text-white">Laser Turret</div>
|
| 228 |
+
<div class="text-[10px] text-red-500 mono">Cost: 80 M, 40 E</div>
|
| 229 |
+
</div>
|
| 230 |
+
</button>
|
| 231 |
+
|
| 232 |
+
<div class="mt-auto pt-4 border-t border-white/10">
|
| 233 |
+
<button onclick="game.selectTool('demolish')" id="btn-demolish" class="w-full py-2 border border-red-900/50 bg-red-900/10 text-red-400 text-xs font-bold rounded hover:bg-red-900/30 transition-colors">
|
| 234 |
+
DEMOLISH MODE
|
| 235 |
+
</button>
|
| 236 |
+
</div>
|
| 237 |
+
</div>
|
| 238 |
+
|
| 239 |
+
<!-- Event Log -->
|
| 240 |
+
<div class="glass-panel rounded-lg p-3 h-48 flex flex-col">
|
| 241 |
+
<h3 class="text-[10px] font-bold text-gray-400 uppercase mb-2">System Log</h3>
|
| 242 |
+
<div id="game-log" class="flex-1 overflow-y-auto text-[10px] mono space-y-1 pr-1">
|
| 243 |
+
<div class="text-cyan-400">> System initialized...</div>
|
| 244 |
+
<div class="text-gray-400">> Awaiting commands.</div>
|
| 245 |
+
</div>
|
| 246 |
+
</div>
|
| 247 |
+
</aside>
|
| 248 |
+
|
| 249 |
+
<!-- Center: The World -->
|
| 250 |
+
<main class="flex-1 glass-panel rounded-lg relative overflow-hidden cursor-crosshair" id="canvas-container">
|
| 251 |
+
<canvas id="gameCanvas" class="block w-full h-full"></canvas>
|
| 252 |
+
|
| 253 |
+
<!-- Overlay Info -->
|
| 254 |
+
<div class="absolute top-4 left-4 pointer-events-none">
|
| 255 |
+
<div class="bg-black/60 backdrop-blur px-3 py-1 rounded border border-white/10 text-xs mono">
|
| 256 |
+
<span class="text-gray-400">COORDS:</span> <span id="mouse-coords" class="text-white">0, 0</span>
|
| 257 |
+
</div>
|
| 258 |
+
</div>
|
| 259 |
+
|
| 260 |
+
<!-- Game Over / Win Screen -->
|
| 261 |
+
<div id="overlay-screen" class="absolute inset-0 bg-black/80 backdrop-blur-md z-50 flex flex-col items-center justify-center hidden">
|
| 262 |
+
<h2 id="overlay-title" class="text-4xl font-bold text-white mb-4 tracking-widest uppercase">GAME OVER</h2>
|
| 263 |
+
<p id="overlay-msg" class="text-gray-300 mb-8 mono text-center max-w-md">The colony has fallen.</p>
|
| 264 |
+
<button onclick="location.reload()" class="px-8 py-3 bg-cyan-600 hover:bg-cyan-500 text-white font-bold rounded shadow-[0_0_20px_rgba(8,145,178,0.6)] transition-all">
|
| 265 |
+
REBOOT SYSTEM
|
| 266 |
+
</button>
|
| 267 |
+
</div>
|
| 268 |
+
</main>
|
| 269 |
</div>
|
| 270 |
+
</div>
|
| 271 |
+
|
| 272 |
+
<!-- Tooltip Element -->
|
| 273 |
+
<div id="tooltip" class="glass-panel p-2 rounded border border-cyan-500/30 text-xs max-w-[200px]">
|
| 274 |
+
<div id="tt-title" class="font-bold text-cyan-400 mb-1">Building</div>
|
| 275 |
+
<div id="tt-desc" class="text-gray-300 leading-tight">Description here.</div>
|
| 276 |
+
</div>
|
| 277 |
+
|
| 278 |
+
<script>
|
| 279 |
+
/**
|
| 280 |
+
* UTILITIES & CONFIG
|
| 281 |
+
*/
|
| 282 |
+
const CONSTANTS = {
|
| 283 |
+
TILE_SIZE: 40,
|
| 284 |
+
GRID_W: 20,
|
| 285 |
+
GRID_H: 15,
|
| 286 |
+
COLORS: {
|
| 287 |
+
VOID: '#050510',
|
| 288 |
+
STAR: '#ffffff',
|
| 289 |
+
PLANET_BASE: '#1a1a2e',
|
| 290 |
+
PLANET_HIGHLIGHT: '#2a2a4e',
|
| 291 |
+
SOLAR: '#fbbf24',
|
| 292 |
+
MINE: '#3b82f6',
|
| 293 |
+
HABITAT: '#22c55e',
|
| 294 |
+
DEFENSE: '#ef4444',
|
| 295 |
+
SELECTED: '#00f3ff'
|
| 296 |
+
}
|
| 297 |
+
};
|
| 298 |
+
|
| 299 |
+
// Simple PRNG for deterministic map generation if needed, but we'll use Math.random for gameplay variability
|
| 300 |
+
const rand = (min, max) => Math.random() * (max - min) + min;
|
| 301 |
+
const randInt = (min, max) => Math.floor(rand(min, max));
|
| 302 |
+
|
| 303 |
+
/**
|
| 304 |
+
* AUDIO SYSTEM (Synthesizer)
|
| 305 |
+
* Using Web Audio API to generate sounds without external assets
|
| 306 |
+
*/
|
| 307 |
+
const AudioSys = {
|
| 308 |
+
ctx: null,
|
| 309 |
+
init: function() {
|
| 310 |
+
if (!this.ctx) {
|
| 311 |
+
this.ctx = new (window.AudioContext || window.webkitAudioContext)();
|
| 312 |
+
}
|
| 313 |
+
},
|
| 314 |
+
playTone: function(freq, type, duration, vol = 0.1) {
|
| 315 |
+
if (!this.ctx) return;
|
| 316 |
+
const osc = this.ctx.createOscillator();
|
| 317 |
+
const gain = this.ctx.createGain();
|
| 318 |
+
osc.type = type;
|
| 319 |
+
osc.frequency.setValueAtTime(freq, this.ctx.currentTime);
|
| 320 |
+
gain.gain.setValueAtTime(vol, this.ctx.currentTime);
|
| 321 |
+
gain.gain.exponentialRampToValueAtTime(0.01, this.ctx.currentTime + duration);
|
| 322 |
+
osc.connect(gain);
|
| 323 |
+
gain.connect(this.ctx.destination);
|
| 324 |
+
osc.start();
|
| 325 |
+
osc.stop(this.ctx.currentTime + duration);
|
| 326 |
+
},
|
| 327 |
+
playClick: () => AudioSys.playTone(800, 'sine', 0.1, 0.05),
|
| 328 |
+
playBuild: () => {
|
| 329 |
+
AudioSys.playTone(400, 'triangle', 0.1, 0.1);
|
| 330 |
+
setTimeout(() => AudioSys.playTone(600, 'triangle', 0.2, 0.1), 100);
|
| 331 |
+
},
|
| 332 |
+
playError: () => AudioSys.playTone(150, 'sawtooth', 0.3, 0.1),
|
| 333 |
+
playShoot: () => {
|
| 334 |
+
if(!AudioSys.ctx) return;
|
| 335 |
+
const osc = AudioSys.ctx.createOscillator();
|
| 336 |
+
const gain = AudioSys.ctx.createGain();
|
| 337 |
+
osc.frequency.setValueAtTime(800, AudioSys.ctx.currentTime);
|
| 338 |
+
osc.frequency.exponentialRampToValueAtTime(100, AudioSys.ctx.currentTime + 0.2);
|
| 339 |
+
gain.gain.setValueAtTime(0.1, AudioSys.ctx.currentTime);
|
| 340 |
+
gain.gain.exponentialRampToValueAtTime(0.01, AudioSys.ctx.currentTime + 0.2);
|
| 341 |
+
osc.connect(gain);
|
| 342 |
+
gain.connect(AudioSys.ctx.destination);
|
| 343 |
+
osc.start();
|
| 344 |
+
osc.stop(AudioSys.ctx.currentTime + 0.2);
|
| 345 |
+
}
|
| 346 |
+
};
|
| 347 |
+
|
| 348 |
+
/**
|
| 349 |
+
* GAME CLASSES
|
| 350 |
+
*/
|
| 351 |
+
|
| 352 |
+
class Particle {
|
| 353 |
+
constructor(x, y, color, speed, life) {
|
| 354 |
+
this.x = x;
|
| 355 |
+
this.y = y;
|
| 356 |
+
this.color = color;
|
| 357 |
+
this.vx = (Math.random() - 0.5) * speed;
|
| 358 |
+
this.vy = (Math.random() - 0.5) * speed;
|
| 359 |
+
this.life = life;
|
| 360 |
+
this.maxLife = life;
|
| 361 |
+
this.size = Math.random() * 3 + 1;
|
| 362 |
+
}
|
| 363 |
+
update() {
|
| 364 |
+
this.x += this.vx;
|
| 365 |
+
this.y += this.vy;
|
| 366 |
+
this.life--;
|
| 367 |
+
this.size *= 0.95;
|
| 368 |
+
}
|
| 369 |
+
draw(ctx) {
|
| 370 |
+
ctx.globalAlpha = this.life / this.maxLife;
|
| 371 |
+
ctx.fillStyle = this.color;
|
| 372 |
+
ctx.beginPath();
|
| 373 |
+
ctx.arc(this.x, this.y, this.size, 0, Math.PI*2);
|
| 374 |
+
ctx.fill();
|
| 375 |
+
ctx.globalAlpha = 1;
|
| 376 |
+
}
|
| 377 |
}
|
| 378 |
+
|
| 379 |
+
class Projectile {
|
| 380 |
+
constructor(x, y, target) {
|
| 381 |
+
this.x = x;
|
| 382 |
+
this.y = y;
|
| 383 |
+
this.target = target;
|
| 384 |
+
this.speed = 8;
|
| 385 |
+
this.active = true;
|
| 386 |
+
this.color = '#ff0000';
|
| 387 |
+
}
|
| 388 |
+
update() {
|
| 389 |
+
if (!this.target || this.target.hp <= 0) {
|
| 390 |
+
this.active = false;
|
| 391 |
+
return;
|
| 392 |
+
}
|
| 393 |
+
const dx = this.target.x - this.x;
|
| 394 |
+
const dy = this.target.y - this.y;
|
| 395 |
+
const dist = Math.hypot(dx, dy);
|
| 396 |
+
|
| 397 |
+
if (dist < this.speed) {
|
| 398 |
+
this.target.takeDamage(25);
|
| 399 |
+
this.active = false;
|
| 400 |
+
game.addParticles(this.x, this.y, '#ffaa00', 5);
|
| 401 |
+
} else {
|
| 402 |
+
this.x += (dx / dist) * this.speed;
|
| 403 |
+
this.y += (dy / dist) * this.speed;
|
| 404 |
+
}
|
| 405 |
+
}
|
| 406 |
+
draw(ctx) {
|
| 407 |
+
ctx.strokeStyle = this.color;
|
| 408 |
+
ctx.lineWidth = 2;
|
| 409 |
+
ctx.beginPath();
|
| 410 |
+
ctx.moveTo(this.x, this.y);
|
| 411 |
+
ctx.lineTo(this.x - (this.x - this.target.x)*0.2, this.y - (this.y - this.target.y)*0.2); // Trail
|
| 412 |
+
ctx.stroke();
|
| 413 |
+
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 414 |
}
|
| 415 |
|
| 416 |
+
class Enemy {
|
| 417 |
+
constructor(x, y) {
|
| 418 |
+
this.x = x;
|
| 419 |
+
this.y = y;
|
| 420 |
+
this.hp = 100;
|
| 421 |
+
this.maxHp = 100;
|
| 422 |
+
this.speed = 0.5;
|
| 423 |
+
this.radius = 8;
|
| 424 |
+
this.targetX = 0; // Will be set to colony center
|
| 425 |
+
this.targetY = 0;
|
| 426 |
+
this.angle = 0;
|
| 427 |
+
}
|
| 428 |
+
update() {
|
| 429 |
+
// Move towards center of map (approximate colony center)
|
| 430 |
+
// In a real game, pathfinding would be better, but we move to center (0,0 relative to grid)
|
| 431 |
+
// Actually, let's move towards the nearest building or center
|
| 432 |
+
|
| 433 |
+
const dx = (game.canvas.width/2) - this.x; // Simplified target
|
| 434 |
+
const dy = (game.canvas.height/2) - this.y;
|
| 435 |
+
const dist = Math.hypot(dx, dy);
|
| 436 |
+
|
| 437 |
+
this.x += (dx/dist) * this.speed;
|
| 438 |
+
this.y += (dy/dist) * this.speed;
|
| 439 |
+
|
| 440 |
+
this.angle += 0.05;
|
| 441 |
+
|
| 442 |
+
// Collision with buildings
|
| 443 |
+
// Simplified: if close to center, damage colony
|
| 444 |
+
if (dist < 20) {
|
| 445 |
+
game.resources.energy -= 10;
|
| 446 |
+
game.resources.matter -= 10;
|
| 447 |
+
this.hp = 0; // Kamikaze
|
| 448 |
+
game.addLog("ALERT: Enemy breached defenses!", "red");
|
| 449 |
+
game.addParticles(this.x, this.y, '#ff0000', 10);
|
| 450 |
+
AudioSys.playError();
|
| 451 |
+
}
|
| 452 |
+
}
|
| 453 |
+
takeDamage(amount) {
|
| 454 |
+
this.hp -= amount;
|
| 455 |
+
if (this.hp <= 0) {
|
| 456 |
+
game.addParticles(this.x, this.y, '#aaffaa', 8);
|
| 457 |
+
}
|
| 458 |
+
}
|
| 459 |
+
draw(ctx) {
|
| 460 |
+
ctx.save();
|
| 461 |
+
ctx.translate(this.x, this.y);
|
| 462 |
+
ctx.rotate(this.angle);
|
| 463 |
+
ctx.fillStyle = '#ff0055';
|
| 464 |
+
ctx.shadowBlur = 10;
|
| 465 |
+
ctx.shadowColor = '#ff0055';
|
| 466 |
+
|
| 467 |
+
// Draw spikey shape
|
| 468 |
+
ctx.beginPath();
|
| 469 |
+
for(let i=0; i<5; i++) {
|
| 470 |
+
ctx.lineTo(Math.cos(i*1.25)*this.radius, Math.sin(i*1.25)*this.radius);
|
| 471 |
+
ctx.lineTo(Math.cos(i*1.25+0.6)*(this.radius/2), Math.sin(i*1.25+0.6)*(this.radius/2));
|
| 472 |
+
}
|
| 473 |
+
ctx.closePath();
|
| 474 |
+
ctx.fill();
|
| 475 |
+
|
| 476 |
+
// HP Bar
|
| 477 |
+
ctx.rotate(-this.angle); // Unrotate for bar
|
| 478 |
+
ctx.fillStyle = 'red';
|
| 479 |
+
ctx.fillRect(-10, -15, 20, 3);
|
| 480 |
+
ctx.fillStyle = '#0f0';
|
| 481 |
+
ctx.fillRect(-10, -15, 20 * (this.hp/this.maxHp), 3);
|
| 482 |
+
|
| 483 |
+
ctx.restore();
|
| 484 |
+
}
|
| 485 |
}
|
| 486 |
|
| 487 |
+
class Building {
|
| 488 |
+
constructor(type, gridX, gridY) {
|
| 489 |
+
this.type = type;
|
| 490 |
+
this.gridX = gridX;
|
| 491 |
+
this.gridY = gridY;
|
| 492 |
+
this.level = 1;
|
| 493 |
+
this.timer = 0;
|
| 494 |
+
|
| 495 |
+
// Stats based on type
|
| 496 |
+
switch(type) {
|
| 497 |
+
case 'solar':
|
| 498 |
+
this.production = {e: 2, m: 0};
|
| 499 |
+
this.color = CONSTANTS.COLORS.SOLAR;
|
| 500 |
+
break;
|
| 501 |
+
case 'mine':
|
| 502 |
+
this.production = {e: -1, m: 3};
|
| 503 |
+
this.color = CONSTANTS.COLORS.MINE;
|
| 504 |
+
break;
|
| 505 |
+
case 'habitat':
|
| 506 |
+
this.production = {e: -2, m: 0};
|
| 507 |
+
this.population = 10;
|
| 508 |
+
this.color = CONSTANTS.COLORS.HABITAT;
|
| 509 |
+
break;
|
| 510 |
+
case 'defense':
|
| 511 |
+
this.production = {e: -3, m: 0};
|
| 512 |
+
this.range = 200;
|
| 513 |
+
this.cooldown = 0;
|
| 514 |
+
this.color = CONSTANTS.COLORS.DEFENSE;
|
| 515 |
+
break;
|
| 516 |
+
}
|
| 517 |
+
}
|
| 518 |
+
|
| 519 |
+
update() {
|
| 520 |
+
// Production happens in main loop based on production stats
|
| 521 |
+
// Defense logic
|
| 522 |
+
if (this.type === 'defense') {
|
| 523 |
+
this.cooldown--;
|
| 524 |
+
if (this.cooldown <= 0) {
|
| 525 |
+
// Find target
|
| 526 |
+
const target = game.findEnemyInRange(this.x, this.y, this.range);
|
| 527 |
+
if (target) {
|
| 528 |
+
game.projectiles.push(new Projectile(this.x, this.y, target));
|
| 529 |
+
this.cooldown = 60; // 1 sec at 60fps
|
| 530 |
+
AudioSys.playShoot();
|
| 531 |
+
}
|
| 532 |
+
}
|
| 533 |
+
}
|
| 534 |
+
}
|
| 535 |
+
|
| 536 |
+
draw(ctx, screenX, screenY) {
|
| 537 |
+
this.x = screenX;
|
| 538 |
+
this.y = screenY;
|
| 539 |
+
|
| 540 |
+
const size = CONSTANTS.TILE_SIZE * 0.8;
|
| 541 |
+
|
| 542 |
+
ctx.save();
|
| 543 |
+
ctx.translate(screenX, screenY);
|
| 544 |
+
|
| 545 |
+
// Glow
|
| 546 |
+
ctx.shadowBlur = 15;
|
| 547 |
+
ctx.shadowColor = this.color;
|
| 548 |
+
|
| 549 |
+
// Base
|
| 550 |
+
ctx.fillStyle = 'rgba(0,0,0,0.5)';
|
| 551 |
+
ctx.fillRect(-size/2, -size/2, size, size);
|
| 552 |
+
|
| 553 |
+
ctx.strokeStyle = this.color;
|
| 554 |
+
ctx.lineWidth = 2;
|
| 555 |
+
ctx.strokeRect(-size/2, -size/2, size, size);
|
| 556 |
+
|
| 557 |
+
// Icon/Detail
|
| 558 |
+
ctx.fillStyle = this.color;
|
| 559 |
+
if (this.type === 'solar') {
|
| 560 |
+
// Sun symbol
|
| 561 |
+
ctx.beginPath();
|
| 562 |
+
ctx.arc(0, 0, size/4, 0, Math.PI*2);
|
| 563 |
+
ctx.fill();
|
| 564 |
+
// Rays
|
| 565 |
+
for(let i=0; i<4; i++) {
|
| 566 |
+
ctx.rotate(Math.PI/2);
|
| 567 |
+
ctx.fillRect(-2, -size/3, 4, size/6);
|
| 568 |
+
}
|
| 569 |
+
} else if (this.type === 'mine') {
|
| 570 |
+
// Drill
|
| 571 |
+
ctx.beginPath();
|
| 572 |
+
ctx.moveTo(0, -size/3);
|
| 573 |
+
ctx.lineTo(size/3, size/3);
|
| 574 |
+
ctx.lineTo(-size/3, size/3);
|
| 575 |
+
ctx.fill();
|
| 576 |
+
} else if (this.type === 'habitat') {
|
| 577 |
+
// Dome
|
| 578 |
+
ctx.beginPath();
|
| 579 |
+
ctx.arc(0, size/4, size/3, Math.PI, 0);
|
| 580 |
+
ctx.fill();
|
| 581 |
+
ctx.fillRect(-size/3, size/4, size/1.5, size/4);
|
| 582 |
+
} else if (this.type === 'defense') {
|
| 583 |
+
// Turret
|
| 584 |
+
ctx.fillRect(-size/4, -size/4, size/2, size/2);
|
| 585 |
+
ctx.fillStyle = '#fff';
|
| 586 |
+
ctx.beginPath();
|
| 587 |
+
ctx.arc(0, 0, size/6, 0, Math.PI*2);
|
| 588 |
+
ctx.fill();
|
| 589 |
+
|
| 590 |
+
// Range indicator (faint)
|
| 591 |
+
if (game.selectedTool === 'defense') {
|
| 592 |
+
ctx.beginPath();
|
| 593 |
+
ctx.arc(0, 0, this.range, 0, Math.PI*2);
|
| 594 |
+
ctx.strokeStyle = 'rgba(255,0,0,0.1)';
|
| 595 |
+
ctx.stroke();
|
| 596 |
+
}
|
| 597 |
+
}
|
| 598 |
+
|
| 599 |
+
// Level indicator
|
| 600 |
+
if (this.level > 1) {
|
| 601 |
+
ctx.fillStyle = '#fff';
|
| 602 |
+
ctx.font = '10px monospace';
|
| 603 |
+
ctx.fillText('+' + (this.level-1), -size/2 + 2, -size/2 + 10);
|
| 604 |
+
}
|
| 605 |
+
|
| 606 |
+
ctx.restore();
|
| 607 |
+
}
|
| 608 |
}
|
| 609 |
+
|
| 610 |
+
/**
|
| 611 |
+
* MAIN GAME ENGINE
|
| 612 |
+
*/
|
| 613 |
+
class Game {
|
| 614 |
+
constructor() {
|
| 615 |
+
this.canvas = document.getElementById('gameCanvas');
|
| 616 |
+
this.ctx = this.canvas.getContext('2d');
|
| 617 |
+
this.bgCanvas = document.getElementById('bgCanvas');
|
| 618 |
+
this.bgCtx = this.bgCanvas.getContext('2d');
|
| 619 |
+
|
| 620 |
+
this.resize();
|
| 621 |
+
window.addEventListener('resize', () => this.resize());
|
| 622 |
+
|
| 623 |
+
this.grid = []; // 2D array
|
| 624 |
+
this.buildings = [];
|
| 625 |
+
this.enemies = [];
|
| 626 |
+
this.projectiles = [];
|
| 627 |
+
this.particles = [];
|
| 628 |
+
|
| 629 |
+
this.resources = {
|
| 630 |
+
energy: 100,
|
| 631 |
+
matter: 200,
|
| 632 |
+
population: 0,
|
| 633 |
+
maxPop: 0
|
| 634 |
+
};
|
| 635 |
+
|
| 636 |
+
this.selectedTool = null;
|
| 637 |
+
this.cycle = 1;
|
| 638 |
+
this.gameOver = false;
|
| 639 |
+
|
| 640 |
+
// Mouse interaction
|
| 641 |
+
this.mouse = { x: 0, y: 0, gridX: 0, gridY: 0 };
|
| 642 |
+
this.canvas.addEventListener('mousemove', e => this.handleMouseMove(e));
|
| 643 |
+
this.canvas.addEventListener('click', e => this.handleClick(e));
|
| 644 |
+
this.canvas.addEventListener('mouseleave', () => {
|
| 645 |
+
document.getElementById('tooltip').style.opacity = 0;
|
| 646 |
+
});
|
| 647 |
+
|
| 648 |
+
// Start loops
|
| 649 |
+
this.lastTime = 0;
|
| 650 |
+
this.spawnTimer = 0;
|
| 651 |
+
this.productionTimer = 0;
|
| 652 |
+
|
| 653 |
+
this.initGrid();
|
| 654 |
+
this.generateStars();
|
| 655 |
+
requestAnimationFrame(t => this.loop(t));
|
| 656 |
+
|
| 657 |
+
// Initial Log
|
| 658 |
+
this.addLog("Colony vessel landed.");
|
| 659 |
+
this.addLog("Mission: Survive.");
|
| 660 |
+
}
|
| 661 |
+
|
| 662 |
+
resize() {
|
| 663 |
+
const container = document.getElementById('canvas-container');
|
| 664 |
+
this.canvas.width = container.clientWidth;
|
| 665 |
+
this.canvas.height = container.clientHeight;
|
| 666 |
+
|
| 667 |
+
this.bgCanvas.width = window.innerWidth;
|
| 668 |
+
this.bgCanvas.height = window.innerHeight;
|
| 669 |
+
}
|
| 670 |
+
|
| 671 |
+
initGrid() {
|
| 672 |
+
// Initialize empty grid
|
| 673 |
+
// Center the grid on screen
|
| 674 |
+
this.offsetX = (this.canvas.width - (CONSTANTS.GRID_W * CONSTANTS.TILE_SIZE)) / 2;
|
| 675 |
+
this.offsetY = (this.canvas.height - (CONSTANTS.GRID_H * CONSTANTS.TILE_SIZE)) / 2;
|
| 676 |
+
}
|
| 677 |
+
|
| 678 |
+
generateStars() {
|
| 679 |
+
this.bgCtx.fillStyle = '#050510';
|
| 680 |
+
this.bgCtx.fillRect(0, 0, this.bgCanvas.width, this.bgCanvas.height);
|
| 681 |
+
|
| 682 |
+
for(let i=0; i<200; i++) {
|
| 683 |
+
const x = Math.random() * this.bgCanvas.width;
|
| 684 |
+
const y = Math.random() * this.bgCanvas.height;
|
| 685 |
+
const size = Math.random() * 2;
|
| 686 |
+
const opacity = Math.random();
|
| 687 |
+
|
| 688 |
+
this.bgCtx.fillStyle = `rgba(255, 255, 255, ${opacity})`;
|
| 689 |
+
this.bgCtx.beginPath();
|
| 690 |
+
this.bgCtx.arc(x, y, size, 0, Math.PI*2);
|
| 691 |
+
this.bgCtx.fill();
|
| 692 |
+
}
|
| 693 |
+
}
|
| 694 |
+
|
| 695 |
+
selectTool(tool) {
|
| 696 |
+
AudioSys.init(); // Ensure audio context is started on user interaction
|
| 697 |
+
AudioSys.playClick();
|
| 698 |
+
this.selectedTool = tool;
|
| 699 |
+
|
| 700 |
+
// UI Update
|
| 701 |
+
document.querySelectorAll('.glass-card').forEach(el => {
|
| 702 |
+
el.classList.remove('border-cyan-400', 'bg-cyan-900/20');
|
| 703 |
+
});
|
| 704 |
+
|
| 705 |
+
if (tool) {
|
| 706 |
+
const btn = document.getElementById('btn-' + tool);
|
| 707 |
+
if (btn) btn.classList.add('border-cyan-400', 'bg-cyan-900/20');
|
| 708 |
+
}
|
| 709 |
+
}
|
| 710 |
+
|
| 711 |
+
handleMouseMove(e) {
|
| 712 |
+
const rect = this.canvas.getBoundingClientRect();
|
| 713 |
+
this.mouse.x = e.clientX - rect.left;
|
| 714 |
+
this.mouse.y = e.clientY - rect.top;
|
| 715 |
+
|
| 716 |
+
// Calculate Grid Coords
|
| 717 |
+
this.mouse.gridX = Math.floor((this.mouse.x - this.offsetX) / CONSTANTS.TILE_SIZE);
|
| 718 |
+
this.mouse.gridY = Math.floor((this.mouse.y - this.offsetY) / CONSTANTS.TILE_SIZE);
|
| 719 |
+
|
| 720 |
+
document.getElementById('mouse-coords').innerText = `${this.mouse.gridX}, ${this.mouse.gridY}`;
|
| 721 |
+
|
| 722 |
+
// Tooltip logic
|
| 723 |
+
const b = this.getBuildingAt(this.mouse.gridX, this.mouse.gridY);
|
| 724 |
+
const tt = document.getElementById('tooltip');
|
| 725 |
+
if (b) {
|
| 726 |
+
tt.style.opacity = 1;
|
| 727 |
+
tt.style.left = (e.pageX + 15) + 'px';
|
| 728 |
+
tt.style.top = (e.pageY + 15) + 'px';
|
| 729 |
+
document.getElementById('tt-title').innerText = b.type.toUpperCase() + (b.level > 1 ? ' MK.'+b.level : '');
|
| 730 |
+
|
| 731 |
+
let desc = '';
|
| 732 |
+
if(b.type === 'solar') desc = `Generates +${b.production.e} Energy/cycle.`;
|
| 733 |
+
if(b.type === 'mine') desc = `Generates +${b.production.m} Matter/cycle. Consumes ${Math.abs(b.production.e)} Energy.`;
|
| 734 |
+
if(b.type === 'habitat') desc = `Houses ${b.population} colonists. Consumes Energy.`;
|
| 735 |
+
if(b.type === 'defense') desc = `Automated defense turret. High Energy upkeep.`;
|
| 736 |
+
document.getElementById('tt-desc').innerText = desc;
|
| 737 |
+
} else {
|
| 738 |
+
tt.style.opacity = 0;
|
| 739 |
+
}
|
| 740 |
+
}
|
| 741 |
+
|
| 742 |
+
handleClick(e) {
|
| 743 |
+
if (this.gameOver) return;
|
| 744 |
+
AudioSys.init();
|
| 745 |
+
|
| 746 |
+
const gx = this.mouse.gridX;
|
| 747 |
+
const gy = this.mouse.gridY;
|
| 748 |
+
|
| 749 |
+
// Bounds check
|
| 750 |
+
if (gx < 0 || gx >= CONSTANTS.GRID_W || gy < 0 || gy >= CONSTANTS.GRID_H) return;
|
| 751 |
+
|
| 752 |
+
const existing = this.getBuildingAt(gx, gy);
|
| 753 |
+
|
| 754 |
+
if (this.selectedTool === 'demolish') {
|
| 755 |
+
if (existing) {
|
| 756 |
+
this.buildings = this.buildings.filter(b => b !== existing);
|
| 757 |
+
this.addLog(`Structure demolished at ${gx},${gy}`);
|
| 758 |
+
this.addParticles(this.offsetX + gx*CONSTANTS.TILE_SIZE + CONSTANTS.TILE_SIZE/2, this.offsetY + gy*CONSTANTS.TILE_SIZE + CONSTANTS.TILE_SIZE/2, '#fff', 10);
|
| 759 |
+
AudioSys.playClick();
|
| 760 |
+
}
|
| 761 |
+
return;
|
| 762 |
+
}
|
| 763 |
+
|
| 764 |
+
if (!existing && this.selectedTool) {
|
| 765 |
+
this.attemptBuild(this.selectedTool, gx, gy);
|
| 766 |
+
} else if (existing && this.selectedTool === existing.type) {
|
| 767 |
+
// Upgrade logic could go here
|
| 768 |
+
// For now, just log
|
| 769 |
+
// this.addLog("Upgrade logic not implemented in demo.");
|
| 770 |
+
}
|
| 771 |
+
}
|
| 772 |
+
|
| 773 |
+
attemptBuild(type, gx, gy) {
|
| 774 |
+
let costE = 0, costM = 0;
|
| 775 |
+
|
| 776 |
+
if (type === 'solar') { costM = 20; }
|
| 777 |
+
else if (type === 'mine') { costM = 30; costE = 10; }
|
| 778 |
+
else if (type === 'habitat') { costM = 100; costE = 50; }
|
| 779 |
+
else if (type === 'defense') { costM = 80; costE = 40; }
|
| 780 |
+
|
| 781 |
+
if (this.resources.energy >= costE && this.resources.matter >= costM) {
|
| 782 |
+
this.resources.energy -= costE;
|
| 783 |
+
this.resources.matter -= costM;
|
| 784 |
+
|
| 785 |
+
const b = new Building(type, gx, gy);
|
| 786 |
+
this.buildings.push(b);
|
| 787 |
+
|
| 788 |
+
// Visuals
|
| 789 |
+
const sx = this.offsetX + gx * CONSTANTS.TILE_SIZE + CONSTANTS.TILE_SIZE/2;
|
| 790 |
+
const sy = this.offsetY + gy * CONSTANTS.TILE_SIZE + CONSTANTS.TILE_SIZE/2;
|
| 791 |
+
this.addParticles(sx, sy, b.color, 15);
|
| 792 |
+
|
| 793 |
+
AudioSys.playBuild();
|
| 794 |
+
this.addLog(`Constructed ${type.toUpperCase()}`);
|
| 795 |
+
} else {
|
| 796 |
+
AudioSys.playError();
|
| 797 |
+
this.addLog("Insufficient resources!", "red");
|
| 798 |
+
}
|
| 799 |
+
}
|
| 800 |
+
|
| 801 |
+
getBuildingAt(gx, gy) {
|
| 802 |
+
return this.buildings.find(b => b.gridX === gx && b.gridY === gy);
|
| 803 |
+
}
|
| 804 |
+
|
| 805 |
+
findEnemyInRange(x, y, range) {
|
| 806 |
+
// Simple distance check
|
| 807 |
+
let closest = null;
|
| 808 |
+
let minDst = range;
|
| 809 |
+
|
| 810 |
+
for (let e of this.enemies) {
|
| 811 |
+
const d = Math.hypot(e.x - x, e.y - y);
|
| 812 |
+
if (d < minDst) {
|
| 813 |
+
minDst = d;
|
| 814 |
+
closest = e;
|
| 815 |
+
}
|
| 816 |
+
}
|
| 817 |
+
return closest;
|
| 818 |
+
}
|
| 819 |
+
|
| 820 |
+
addParticles(x, y, color, count) {
|
| 821 |
+
for(let i=0; i<count; i++) {
|
| 822 |
+
this.particles.push(new Particle(x, y, color, 3, 30));
|
| 823 |
+
}
|
| 824 |
+
}
|
| 825 |
+
|
| 826 |
+
addLog(msg, color="gray") {
|
| 827 |
+
const log = document.getElementById('game-log');
|
| 828 |
+
const div = document.createElement('div');
|
| 829 |
+
div.className = `text-${color}-400`;
|
| 830 |
+
div.innerText = `> ${msg}`;
|
| 831 |
+
log.appendChild(div);
|
| 832 |
+
log.scrollTop = log.scrollHeight;
|
| 833 |
+
}
|
| 834 |
+
|
| 835 |
+
update(dt) {
|
| 836 |
+
if (this.gameOver) return;
|
| 837 |
+
|
| 838 |
+
// Production Loop (Every 2 seconds approx)
|
| 839 |
+
this.productionTimer += dt;
|
| 840 |
+
if (this.productionTimer > 2000) {
|
| 841 |
+
this.productionTimer = 0;
|
| 842 |
+
this.cycle++;
|
| 843 |
+
document.getElementById('game-cycle').innerText = this.cycle;
|
| 844 |
+
|
| 845 |
+
// Calculate Net Production
|
| 846 |
+
let netE = 0;
|
| 847 |
+
let netM = 0;
|
| 848 |
+
let maxPop = 0;
|
| 849 |
+
|
| 850 |
+
this.buildings.forEach(b => {
|
| 851 |
+
netE += b.production.e;
|
| 852 |
+
netM += b.production.m;
|
| 853 |
+
if (b.type === 'habitat') maxPop += b.population;
|
| 854 |
+
});
|
| 855 |
+
|
| 856 |
+
// Population Growth
|
| 857 |
+
if (this.resources.energy > 0 && this.resources.matter > 0 && this.resources.population < maxPop) {
|
| 858 |
+
if (Math.random() > 0.5) this.resources.population++;
|
| 859 |
+
}
|
| 860 |
+
|
| 861 |
+
// Consume resources for population
|
| 862 |
+
if (this.resources.population > 0) {
|
| 863 |
+
netE -= Math.floor(this.resources.population / 5); // Pop consumes energy
|
| 864 |
+
}
|
| 865 |
+
|
| 866 |
+
this.resources.energy += netE;
|
| 867 |
+
this.resources.matter += netM;
|
| 868 |
+
this.resources.maxPop = maxPop;
|
| 869 |
+
|
| 870 |
+
// Cap resources
|
| 871 |
+
if (this.resources.matter > 999) this.resources.matter = 999;
|
| 872 |
+
if (this.resources.energy > 999) this.resources.energy = 999;
|
| 873 |
+
|
| 874 |
+
// Check Loss
|
| 875 |
+
if (this.resources.energy < 0) {
|
| 876 |
+
this.resources.energy = 0;
|
| 877 |
+
// Penalty?
|
| 878 |
+
if (this.resources.population > 0 && Math.random() > 0.7) {
|
| 879 |
+
this.resources.population--;
|
| 880 |
+
this.addLog("Colonists lost due to energy shortage!", "red");
|
| 881 |
+
}
|
| 882 |
+
}
|
| 883 |
+
|
| 884 |
+
// Win/Loss Check
|
| 885 |
+
if (this.resources.population >= 100) {
|
| 886 |
+
this.triggerEnd("VICTORY", "Colony population reached sustainable levels. The future is secured.");
|
| 887 |
+
} else if (this.resources.population <= 0 && this.cycle > 10 && this.buildings.length > 0) {
|
| 888 |
+
// If you have buildings but no people for a while, you lose?
|
| 889 |
+
// Let's make it stricter: if pop is 0 and you have habitats, you are failing.
|
| 890 |
+
// If pop is 0 and no habitats, just starting.
|
| 891 |
+
const hasHabs = this.buildings.some(b => b.type === 'habitat');
|
| 892 |
+
if (hasHabs) {
|
| 893 |
+
this.triggerEnd("COLONY FAILED", "All colonists have perished.");
|
| 894 |
+
}
|
| 895 |
+
}
|
| 896 |
+
}
|
| 897 |
+
|
| 898 |
+
// Spawning Logic
|
| 899 |
+
this.spawnTimer += dt;
|
| 900 |
+
// Spawn rate increases with cycle
|
| 901 |
+
const spawnRate = Math.max(500, 5000 - (this.cycle * 200));
|
| 902 |
+
|
| 903 |
+
if (this.spawnTimer > spawnRate) {
|
| 904 |
+
this.spawnTimer = 0;
|
| 905 |
+
this.spawnEnemy();
|
| 906 |
+
}
|
| 907 |
+
|
| 908 |
+
// Updates
|
| 909 |
+
this.buildings.forEach(b => b.update());
|
| 910 |
+
|
| 911 |
+
this.enemies.forEach(e => e.update());
|
| 912 |
+
this.enemies = this.enemies.filter(e => e.hp > 0);
|
| 913 |
+
|
| 914 |
+
this.projectiles.forEach(p => p.update());
|
| 915 |
+
this.projectiles = this.projectiles.filter(p => p.active);
|
| 916 |
+
|
| 917 |
+
this.particles.forEach(p => p.update());
|
| 918 |
+
this.particles = this.particles.filter(p => p.life > 0);
|
| 919 |
+
|
| 920 |
+
// UI Updates
|
| 921 |
+
this.updateUI();
|
| 922 |
+
}
|
| 923 |
+
|
| 924 |
+
spawnEnemy() {
|
| 925 |
+
// Spawn at edge
|
| 926 |
+
const edge = randInt(0, 4); // 0:top, 1:right, 2:bottom, 3:left
|
| 927 |
+
let x, y;
|
| 928 |
+
const margin = 50;
|
| 929 |
+
|
| 930 |
+
if (edge === 0) { x = rand(0, this.canvas.width); y = -margin; }
|
| 931 |
+
else if (edge === 1) { x = this.canvas.width + margin; y = rand(0, this.canvas.height); }
|
| 932 |
+
else if (edge === 2) { x = rand(0, this.canvas.width); y = this.canvas.height + margin; }
|
| 933 |
+
else { x = -margin; y = rand(0, this.canvas.height); }
|
| 934 |
+
|
| 935 |
+
this.enemies.push(new Enemy(x, y));
|
| 936 |
+
// this.addLog("Incoming signature detected...");
|
| 937 |
+
}
|
| 938 |
+
|
| 939 |
+
updateUI() {
|
| 940 |
+
document.getElementById('val-energy').innerText = Math.floor(this.resources.energy);
|
| 941 |
+
document.getElementById('bar-energy').style.width = Math.min(100, (this.resources.energy / 200) * 100) + '%';
|
| 942 |
+
|
| 943 |
+
document.getElementById('val-matter').innerText = Math.floor(this.resources.matter);
|
| 944 |
+
document.getElementById('bar-matter').style.width = Math.min(100, (this.resources.matter / 500) * 100) + '%';
|
| 945 |
+
|
| 946 |
+
document.getElementById('val-pop').innerText = this.resources.population + '/' + this.resources.maxPop;
|
| 947 |
+
document.getElementById('bar-pop').style.width = this.resources.maxPop > 0 ? (this.resources.population / this.resources.maxPop) * 100 + '%' : '0%';
|
| 948 |
+
}
|
| 949 |
+
|
| 950 |
+
triggerEnd(title, msg) {
|
| 951 |
+
this.gameOver = true;
|
| 952 |
+
document.getElementById('overlay-title').innerText = title;
|
| 953 |
+
document.getElementById('overlay-msg').innerText = msg;
|
| 954 |
+
document.getElementById('overlay-screen').classList.remove('hidden');
|
| 955 |
+
AudioSys.playTone(200, 'sawtooth', 1, 0.2);
|
| 956 |
+
}
|
| 957 |
+
|
| 958 |
+
draw() {
|
| 959 |
+
// Clear
|
| 960 |
+
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
|
| 961 |
+
|
| 962 |
+
// Draw Grid
|
| 963 |
+
this.ctx.strokeStyle = 'rgba(255, 255, 255, 0.05)';
|
| 964 |
+
this.ctx.lineWidth = 1;
|
| 965 |
+
|
| 966 |
+
for (let x = 0; x <= CONSTANTS.GRID_W
|