Matanga commited on
Commit
9cecdf5
·
verified ·
1 Parent(s): 5caceea

Upload folder using huggingface_hub

Browse files
Files changed (1) hide show
  1. index.html +940 -651
index.html CHANGED
@@ -1,677 +1,966 @@
1
  <!DOCTYPE html>
2
  <html lang="en">
3
-
4
  <head>
5
- <meta charset="UTF-8">
6
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
- <title>Procedural Universe Explorer</title>
8
- <script src="https://cdn.tailwindcss.com"></script>
9
- <link
10
- href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@300;400;600;700&family=JetBrains+Mono:wght@400;700&display=swap"
11
- rel="stylesheet">
12
- <style>
13
- :root {
14
- --neon-cyan: #00f3ff;
15
- --neon-pink: #ff00ff;
16
- --deep-space: #050510;
17
- --glass-bg: rgba(255, 255, 255, 0.03);
18
- --glass-border: rgba(255, 255, 255, 0.1);
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
- <body class="min-h-screen relative selection:bg-cyan-500 selection:text-black">
 
 
 
 
 
 
182
 
183
- <!-- Background Canvas -->
184
- <canvas id="starfield" class="fixed top-0 left-0 w-full h-full z-0"></canvas>
185
- <div class="fixed top-0 left-0 w-full h-full bg-grid z-0 pointer-events-none"></div>
186
- <div class="fixed top-0 left-0 w-full h-full crt-overlay z-50 pointer-events-none opacity-30"></div>
 
 
 
 
 
 
 
 
 
 
 
 
187
 
188
- <!-- Main Content Wrapper -->
189
- <div class="relative z-10 flex flex-col min-h-screen">
 
 
 
 
190
 
191
- <!-- Header -->
192
- <header class="w-full p-6 flex justify-between items-center glass-panel sticky top-0 z-50 border-b border-white/10">
193
- <div class="flex items-center gap-3 group cursor-pointer" onclick="window.location.reload()">
194
- <div
195
- class="w-8 h-8 rounded-full bg-gradient-to-tr from-cyan-500 to-purple-600 animate-pulse group-hover:rotate-180 transition-transform duration-700">
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
- <!-- Control Group: Terrain -->
280
- <div class="space-y-4">
281
- <label class="text-sm font-semibold text-gray-300 uppercase tracking-wider">Terrain Complexity</label>
282
- <input type="range" id="complexity" min="1" max="100" value="50" oninput="updateParams()">
283
- <div class="flex justify-between text-xs text-gray-500 mono">
284
- <span>Flat</span>
285
- <span>Chaotic</span>
286
- </div>
287
- </div>
288
 
289
- <!-- Control Group: Color -->
290
- <div class="space-y-4">
291
- <label class="text-sm font-semibold text-gray-300 uppercase tracking-wider">Biome Shift</label>
292
- <input type="range" id="biome" min="0" max="360" value="180" oninput="updateParams()">
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
- <!-- Control Group: Speed -->
297
- <div class="space-y-4">
298
- <label class="text-sm font-semibold text-gray-300 uppercase tracking-wider">Time Dilation</label>
299
- <input type="range" id="speed" min="0" max="50" value="10" oninput="updateParams()">
300
- </div>
 
 
 
301
 
302
- <hr class="border-white/10">
303
-
304
- <!-- Toggles -->
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
- <div class="mt-auto">
317
- <button onclick="downloadCanvas()" class="w-full py-3 border border-cyan-500/50 text-cyan-400 hover:bg-cyan-500/10 rounded-lg transition-colors flex items-center justify-center gap-2 text-sm font-bold uppercase">
318
- <svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4" /></svg>
319
- Export Render
320
- </button>
321
- </div>
322
- </div>
323
 
324
- <!-- Visualization Area -->
325
- <div class="w-full lg:w-3/4 glass-panel rounded-xl p-1 relative overflow-hidden group">
326
- <div class="absolute top-4 left-4 z-10 bg-black/50 backdrop-blur px-3 py-1 rounded border border-white/10">
327
- <span class="text-xs mono text-cyan-400">VIEWPORT: ACTIVE</span>
328
- </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
329
 
330
- <!-- The Main Procedural Canvas -->
331
- <canvas id="procCanvas" class="w-full h-full rounded-lg bg-black cursor-crosshair"></canvas>
 
 
 
 
 
 
 
332
 
333
- <!-- Overlay UI -->
334
- <div class="absolute bottom-4 right-4 flex gap-2">
335
- <div class="w-3 h-3 rounded-full bg-red-500 animate-pulse"></div>
336
- <div class="w-3 h-3 rounded-full bg-yellow-500 animate-pulse delay-75"></div>
337
- <div class="w-3 h-3 rounded-full bg-green-500 animate-pulse delay-150"></div>
338
- </div>
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
- <!-- Card 2 -->
369
- <div class="glass-card p-8 rounded-2xl relative overflow-hidden group">
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
- <!-- Card 3 -->
387
- <div class="glass-card p-8 rounded-2xl relative overflow-hidden group">
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
- </div>
406
- </section>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
407
 
408
- <!-- Footer -->
409
- <footer class="border-t border-white/10 bg-black/80 py-12">
410
- <div class="container mx-auto px-4 flex flex-col md:flex-row justify-between items-center gap-6">
411
- <div class="text-center md:text-left">
412
- <h2 class="text-2xl font-bold tracking-tighter uppercase mono">NEXUS<span class="text-cyan-400">GEN</span>
413
- </h2>
414
- <p class="text-gray-500 text-sm mt-2">Infinite possibilities in a finite space.</p>
415
- </div>
416
- <div class="flex gap-6">
417
- <a href="#" class="text-gray-400 hover:text-white transition-colors">Github</a>
418
- <a href="#" class="text-gray-400 hover:text-white transition-colors">Twitter</a>
419
- <a href="#" class="text-gray-400 hover:text-white transition-colors">Discord</a>
420
- </div>
421
- <div class="text-gray-600 text-xs mono">
422
- &copy; 2023 PROCEDURAL SYSTEMS
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
423
  </div>
424
- </div>
425
- </footer>
426
-
427
- </div>
428
-
429
- <script>
430
- /**
431
- * UTILITIES
432
- */
433
- function scrollToSection(id) {
434
- document.getElementById(id).scrollIntoView({ behavior: 'smooth' });
435
- }
436
-
437
- // Seeded Random Number Generator (Mulberry32)
438
- function mulberry32(a) {
439
- return function() {
440
- var t = a += 0x6D2B79F5;
441
- t = Math.imul(t ^ t >>> 15, t | 1);
442
- t ^= t + Math.imul(t ^ t >>> 7, t | 61);
443
- return ((t ^ t >>> 14) >>> 0) / 4294967296;
444
- }
445
- }
446
-
447
- let seed = Math.floor(Math.random() * 10000);
448
- let rand = mulberry32(seed);
449
-
450
- function randomizeSeed() {
451
- seed = Math.floor(Math.random() * 100000);
452
- rand = mulberry32(seed);
453
- document.getElementById('stat-seed').innerText = seed;
454
- initStars(); // Re-init stars
455
- time = 0; // Reset procedural anim
456
- }
457
-
458
- /**
459
- * BACKGROUND STARFIELD
460
- */
461
- const starCanvas = document.getElementById('starfield');
462
- const starCtx = starCanvas.getContext('2d');
463
- let stars = [];
464
- const numStars = 200;
465
-
466
- function resizeStarCanvas() {
467
- starCanvas.width = window.innerWidth;
468
- starCanvas.height = window.innerHeight;
469
- }
470
-
471
- class Star {
472
- constructor() {
473
- this.reset();
474
- }
475
- reset() {
476
- this.x = rand() * starCanvas.width;
477
- this.y = rand() * starCanvas.height;
478
- this.z = rand() * 2 + 0.5; // Depth/Speed
479
- this.size = rand() * 1.5;
480
- this.opacity = rand();
481
- this.fadeDir = 0.01;
482
- }
483
- update() {
484
- this.y -= this.z * 0.2; // Move up slowly
485
- this.opacity += this.fadeDir;
486
-
487
- if (this.opacity > 1 || this.opacity < 0.2) this.fadeDir *= -1;
488
-
489
- if (this.y < 0) {
490
- this.y = starCanvas.height;
491
- this.x = rand() * starCanvas.width;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
492
  }
493
- }
494
- draw() {
495
- starCtx.fillStyle = `rgba(255, 255, 255, ${this.opacity})`;
496
- starCtx.beginPath();
497
- starCtx.arc(this.x, this.y, this.size, 0, Math.PI * 2);
498
- starCtx.fill();
499
- }
500
- }
501
-
502
- function initStars() {
503
- stars = [];
504
- for(let i=0; i<numStars; i++) stars.push(new Star());
505
- }
506
-
507
- function animateStars() {
508
- starCtx.clearRect(0, 0, starCanvas.width, starCanvas.height);
509
- stars.forEach(star => {
510
- star.update();
511
- star.draw();
512
- });
513
- requestAnimationFrame(animateStars);
514
- }
515
-
516
- /**
517
- * MAIN PROCEDURAL VISUALIZATION (The "Cool" Part)
518
- * Simulating a 3D Terrain/Topography using 2D Canvas
519
- */
520
- const canvas = document.getElementById('procCanvas');
521
- const ctx = canvas.getContext('2d');
522
- let width, height;
523
- let time = 0;
524
-
525
- // Parameters controlled by UI
526
- let params = {
527
- complexity: 50,
528
- biome: 180,
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
- // Close the shape to fill it
593
- ctx.lineTo(width/2 + stripWidth/2 * scale, yBase + 100 * scale);
594
- ctx.lineTo(width/2 - stripWidth/2 * scale, yBase + 100 * scale);
595
- ctx.closePath();
596
-
597
- if (params.wireframe) {
598
- ctx.strokeStyle = `hsla(${hue}, 80%, 60%, 0.8)`;
599
- ctx.lineWidth = 1;
600
- ctx.stroke();
601
- } else {
602
- // Gradient fill for depth
603
- const gradient = ctx.createLinearGradient(0, yBase - params.complexity * scale, 0, yBase + 50 * scale);
604
- gradient.addColorStop(0, `hsla(${hue}, 70%, ${lightness + 20}%, 0.9)`);
605
- gradient.addColorStop(0.5, `hsla(${hue}, 70%, ${lightness}%, 0.6)`);
606
- gradient.addColorStop(1, `hsla(${hue}, 70%, ${lightness - 10}%, 0.2)`);
607
- ctx.fillStyle = gradient;
608
- ctx.fill();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
609
  }
610
 
611
- // Draw nodes if enabled
612
- if (params.showNodes && z % 5 === 0) {
613
- for (let x = -stripWidth/2; x <= stripWidth/2; x += 60) {
614
- const wave1 = Math.sin(x * 0.01 + time * 0.05 + z * 0.1) * (params.complexity * scale);
615
- const wave2 = Math.cos(x * 0.02 - time * 0.02) * (params.complexity * 0.5 * scale);
616
- const yOffset = wave1 + wave2;
617
- const screenX = width/2 + x * scale;
618
- const screenY = yBase - yOffset;
619
-
620
- ctx.fillStyle = `hsla(${(hue + 180) % 360}, 100%, 70%, 0.8)`;
621
- ctx.beginPath();
622
- ctx.arc(screenX, screenY, 2 * scale, 0, Math.PI * 2);
623
- ctx.fill();
624
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
625
  }
626
- }
627
-
628
- // Update stats
629
- document.getElementById('stat-objects').innerText = gridSize * 20;
630
-
631
- time += params.speed * 0.01;
632
- requestAnimationFrame(drawTerrain);
633
- }
634
-
635
- // FPS Counter
636
- let lastTime = performance.now();
637
- let frameCount = 0;
638
- function updateFPS() {
639
- const now = performance.now();
640
- frameCount++;
641
- if (now - lastTime >= 1000) {
642
- document.getElementById('stat-fps').innerText = frameCount;
643
- frameCount = 0;
644
- lastTime = now;
645
- }
646
- requestAnimationFrame(updateFPS);
647
- }
648
-
649
- // Download canvas image
650
- function downloadCanvas() {
651
- const link = document.createElement('a');
652
- link.download = `nexusgen-terrain-${seed}-${Date.now()}.png`;
653
- link.href = canvas.toDataURL();
654
- link.click();
655
- }
656
-
657
- // Initialization
658
- window.addEventListener('resize', () => {
659
- resizeStarCanvas();
660
- resizeProcCanvas();
661
- });
662
-
663
- window.addEventListener('DOMContentLoaded', () => {
664
- resizeStarCanvas();
665
- resizeProcCanvas();
666
- initStars();
667
- animateStars();
668
- drawTerrain();
669
- updateFPS();
670
-
671
- // Set initial seed display
672
- document.getElementById('stat-seed').innerText = seed;
673
- });
674
- </script>
675
- </body>
676
-
677
- </html>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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