F555 commited on
Commit
c799e7d
·
verified ·
1 Parent(s): 8e01e80

Upload folder using huggingface_hub

Browse files
Files changed (1) hide show
  1. index.html +821 -19
index.html CHANGED
@@ -1,19 +1,821 @@
1
- <!doctype html>
2
- <html>
3
- <head>
4
- <meta charset="utf-8" />
5
- <meta name="viewport" content="width=device-width" />
6
- <title>My static Space</title>
7
- <link rel="stylesheet" href="style.css" />
8
- </head>
9
- <body>
10
- <div class="card">
11
- <h1>Welcome to your static Space!</h1>
12
- <p>You can modify this app directly by editing <i>index.html</i> in the Files and versions tab.</p>
13
- <p>
14
- Also don't forget to check the
15
- <a href="https://huggingface.co/docs/hub/spaces" target="_blank">Spaces documentation</a>.
16
- </p>
17
- </div>
18
- </body>
19
- </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>Neon Void: Hyper Space Shooter</title>
7
+ <!-- Importing a futuristic font -->
8
+ <link href="https://fonts.googleapis.com/css2?family=Orbitron:wght@400;700;900&display=swap" rel="stylesheet">
9
+ <!-- Importing FontAwesome for UI Icons -->
10
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
11
+
12
+ <style>
13
+ :root {
14
+ --primary-color: #00f3ff;
15
+ --secondary-color: #bc13fe;
16
+ --danger-color: #ff2a6d;
17
+ --bg-color: #050505;
18
+ --glass-bg: rgba(255, 255, 255, 0.05);
19
+ --glass-border: rgba(255, 255, 255, 0.1);
20
+ }
21
+
22
+ * {
23
+ margin: 0;
24
+ padding: 0;
25
+ box-sizing: border-box;
26
+ user-select: none;
27
+ }
28
+
29
+ body {
30
+ overflow: hidden;
31
+ background-color: var(--bg-color);
32
+ font-family: 'Orbitron', sans-serif;
33
+ color: white;
34
+ height: 100vh;
35
+ width: 100vw;
36
+ }
37
+
38
+ /* --- Canvas Layer --- */
39
+ #gameCanvas {
40
+ position: absolute;
41
+ top: 0;
42
+ left: 0;
43
+ width: 100%;
44
+ height: 100%;
45
+ z-index: 1;
46
+ }
47
+
48
+ /* --- UI Layer --- */
49
+ #ui-layer {
50
+ position: absolute;
51
+ top: 0;
52
+ left: 0;
53
+ width: 100%;
54
+ height: 100%;
55
+ z-index: 10;
56
+ pointer-events: none; /* Let clicks pass through to canvas when playing */
57
+ display: flex;
58
+ flex-direction: column;
59
+ justify-content: space-between;
60
+ padding: 20px;
61
+ }
62
+
63
+ /* --- Header / HUD --- */
64
+ .hud-top {
65
+ display: flex;
66
+ justify-content: space-between;
67
+ align-items: flex-start;
68
+ }
69
+
70
+ .brand-link {
71
+ pointer-events: auto;
72
+ color: var(--primary-color);
73
+ text-decoration: none;
74
+ font-size: 0.8rem;
75
+ opacity: 0.7;
76
+ transition: opacity 0.3s;
77
+ text-shadow: 0 0 10px var(--primary-color);
78
+ }
79
+ .brand-link:hover {
80
+ opacity: 1;
81
+ text-shadow: 0 0 20px var(--primary-color);
82
+ }
83
+
84
+ .score-board {
85
+ text-align: right;
86
+ text-shadow: 0 0 10px rgba(255,255,255,0.5);
87
+ }
88
+
89
+ .score-label {
90
+ font-size: 0.8rem;
91
+ color: #aaa;
92
+ letter-spacing: 2px;
93
+ }
94
+
95
+ .score-value {
96
+ font-size: 2.5rem;
97
+ font-weight: 900;
98
+ color: white;
99
+ }
100
+
101
+ /* --- Health Bar --- */
102
+ .health-container {
103
+ position: absolute;
104
+ bottom: 30px;
105
+ left: 30px;
106
+ width: 300px;
107
+ pointer-events: auto;
108
+ }
109
+
110
+ .health-label {
111
+ margin-bottom: 5px;
112
+ font-size: 0.9rem;
113
+ color: var(--danger-color);
114
+ text-transform: uppercase;
115
+ letter-spacing: 1px;
116
+ }
117
+
118
+ .health-bar-bg {
119
+ width: 100%;
120
+ height: 10px;
121
+ background: rgba(255, 255, 255, 0.1);
122
+ border-radius: 5px;
123
+ overflow: hidden;
124
+ border: 1px solid var(--glass-border);
125
+ }
126
+
127
+ .health-bar-fill {
128
+ height: 100%;
129
+ width: 100%;
130
+ background: linear-gradient(90deg, var(--danger-color), #ff8c00);
131
+ box-shadow: 0 0 15px var(--danger-color);
132
+ transition: width 0.3s cubic-bezier(0.4, 0, 0.2, 1);
133
+ }
134
+
135
+ /* --- Screens (Start / Game Over) --- */
136
+ .screen {
137
+ position: absolute;
138
+ top: 0;
139
+ left: 0;
140
+ width: 100%;
141
+ height: 100%;
142
+ display: flex;
143
+ flex-direction: column;
144
+ justify-content: center;
145
+ align-items: center;
146
+ background: rgba(5, 5, 5, 0.6);
147
+ backdrop-filter: blur(10px);
148
+ z-index: 20;
149
+ pointer-events: auto;
150
+ transition: opacity 0.5s ease;
151
+ }
152
+
153
+ .hidden {
154
+ opacity: 0;
155
+ pointer-events: none;
156
+ }
157
+
158
+ h1 {
159
+ font-size: 4rem;
160
+ text-transform: uppercase;
161
+ background: linear-gradient(to bottom, #fff, var(--primary-color));
162
+ -webkit-background-clip: text;
163
+ -webkit-text-fill-color: transparent;
164
+ margin-bottom: 10px;
165
+ text-shadow: 0 0 30px rgba(0, 243, 255, 0.5);
166
+ text-align: center;
167
+ }
168
+
169
+ h2 {
170
+ font-size: 2.5rem;
171
+ color: var(--danger-color);
172
+ margin-bottom: 20px;
173
+ text-shadow: 0 0 20px var(--danger-color);
174
+ }
175
+
176
+ p.instructions {
177
+ margin-bottom: 40px;
178
+ color: #ddd;
179
+ font-size: 1.1rem;
180
+ line-height: 1.6;
181
+ text-align: center;
182
+ max-width: 600px;
183
+ }
184
+
185
+ .key {
186
+ display: inline-block;
187
+ padding: 5px 10px;
188
+ border: 1px solid var(--primary-color);
189
+ border-radius: 4px;
190
+ color: var(--primary-color);
191
+ font-size: 0.8rem;
192
+ margin: 0 5px;
193
+ box-shadow: 0 0 5px var(--primary-color);
194
+ }
195
+
196
+ /* --- Buttons --- */
197
+ .btn {
198
+ background: transparent;
199
+ color: white;
200
+ font-family: 'Orbitron', sans-serif;
201
+ font-size: 1.2rem;
202
+ font-weight: bold;
203
+ padding: 15px 50px;
204
+ border: 2px solid var(--primary-color);
205
+ border-radius: 50px;
206
+ cursor: pointer;
207
+ position: relative;
208
+ overflow: hidden;
209
+ transition: all 0.3s ease;
210
+ text-transform: uppercase;
211
+ letter-spacing: 2px;
212
+ box-shadow: 0 0 15px rgba(0, 243, 255, 0.2);
213
+ }
214
+
215
+ .btn:hover {
216
+ background: var(--primary-color);
217
+ color: black;
218
+ box-shadow: 0 0 40px rgba(0, 243, 255, 0.8);
219
+ transform: scale(1.05);
220
+ }
221
+
222
+ .btn::before {
223
+ content: '';
224
+ position: absolute;
225
+ top: 0;
226
+ left: -100%;
227
+ width: 100%;
228
+ height: 100%;
229
+ background: linear-gradient(90deg, transparent, rgba(255,255,255,0.4), transparent);
230
+ transition: 0.5s;
231
+ }
232
+
233
+ .btn:hover::before {
234
+ left: 100%;
235
+ }
236
+
237
+ /* --- Mobile Controls --- */
238
+ .mobile-controls {
239
+ display: none; /* Shown via JS on touch devices */
240
+ position: absolute;
241
+ bottom: 20px;
242
+ width: 100%;
243
+ justify-content: space-between;
244
+ padding: 0 20px;
245
+ pointer-events: none;
246
+ }
247
+
248
+ .control-zone {
249
+ width: 120px;
250
+ height: 120px;
251
+ border: 2px solid rgba(255,255,255,0.2);
252
+ border-radius: 50%;
253
+ pointer-events: auto;
254
+ display: flex;
255
+ justify-content: center;
256
+ align-items: center;
257
+ color: rgba(255,255,255,0.5);
258
+ font-size: 1.5rem;
259
+ backdrop-filter: blur(4px);
260
+ }
261
+
262
+ .control-zone:active {
263
+ background: rgba(255,255,255,0.1);
264
+ border-color: var(--primary-color);
265
+ color: var(--primary-color);
266
+ }
267
+
268
+ @media (max-width: 768px) {
269
+ h1 { font-size: 2.5rem; }
270
+ .score-value { font-size: 1.8rem; }
271
+ .health-container { width: 200px; bottom: 160px; }
272
+ .mobile-controls { display: flex; }
273
+ .instructions { font-size: 0.9rem; padding: 0 20px; }
274
+ }
275
+ </style>
276
+ </head>
277
+ <body>
278
+
279
+ <!-- Canvas for 3D Rendering -->
280
+ <canvas id="gameCanvas"></canvas>
281
+
282
+ <!-- UI Overlay -->
283
+ <div id="ui-layer">
284
+ <div class="hud-top">
285
+ <a href="https://huggingface.co/spaces/akhaliq/anycoder" target="_blank" class="brand-link">
286
+ Built with anycoder <i class="fas fa-external-link-alt"></i>
287
+ </a>
288
+ <div class="score-board">
289
+ <div class="score-label">SCORE</div>
290
+ <div class="score-value" id="scoreDisplay">0</div>
291
+ </div>
292
+ </div>
293
+
294
+ <div class="health-container">
295
+ <div class="health-label">Shield Integrity</div>
296
+ <div class="health-bar-bg">
297
+ <div class="health-bar-fill" id="healthFill"></div>
298
+ </div>
299
+ </div>
300
+
301
+ <div class="mobile-controls">
302
+ <div class="control-zone" id="btnLeft"><i class="fas fa-arrow-left"></i></div>
303
+ <div class="control-zone" id="btnRight"><i class="fas fa-arrow-right"></i></div>
304
+ </div>
305
+ </div>
306
+
307
+ <!-- Start Screen -->
308
+ <div id="startScreen" class="screen">
309
+ <h1>Neon Void</h1>
310
+ <p class="instructions">
311
+ Pilot your ship through the hyper-speed corridor.<br>
312
+ Avoid asteroids and destroy enemy drones.<br><br>
313
+ <span class="key">←</span> <span class="key">→</span> to Move<br>
314
+ <span class="key">SPACE</span> or <span class="key">CLICK</span> to Shoot
315
+ </p>
316
+ <button class="btn" id="startBtn">Initiate Launch</button>
317
+ </div>
318
+
319
+ <!-- Game Over Screen -->
320
+ <div id="gameOverScreen" class="screen hidden">
321
+ <h2>CRITICAL FAILURE</h2>
322
+ <p class="instructions">Final Score: <span id="finalScore" style="color:var(--primary-color); font-weight:bold;">0</span></p>
323
+ <button class="btn" id="restartBtn">Reboot System</button>
324
+ </div>
325
+
326
+ <script>
327
+ /**
328
+ * 3D Space Shooter Logic
329
+ * Uses HTML5 Canvas for pseudo-3D rendering.
330
+ */
331
+
332
+ const canvas = document.getElementById('gameCanvas');
333
+ const ctx = canvas.getContext('2d', { alpha: false }); // Optimize for no transparency on bg
334
+
335
+ // UI Elements
336
+ const scoreEl = document.getElementById('scoreDisplay');
337
+ const finalScoreEl = document.getElementById('finalScore');
338
+ const healthFillEl = document.getElementById('healthFill');
339
+ const startScreen = document.getElementById('startScreen');
340
+ const gameOverScreen = document.getElementById('gameOverScreen');
341
+ const startBtn = document.getElementById('startBtn');
342
+ const restartBtn = document.getElementById('restartBtn');
343
+ const btnLeft = document.getElementById('btnLeft');
344
+ const btnRight = document.getElementById('btnRight');
345
+
346
+ // Game State
347
+ let isPlaying = false;
348
+ let score = 0;
349
+ let health = 100;
350
+ let frameCount = 0;
351
+ let speed = 15; // Base speed
352
+ let speedMultiplier = 1;
353
+
354
+ // Input State
355
+ const keys = { ArrowLeft: false, ArrowRight: false, Space: false };
356
+ let touchInput = 0; // -1 left, 1 right, 0 none
357
+
358
+ // Dimensions
359
+ let width, height, cx, cy;
360
+
361
+ function resize() {
362
+ width = canvas.width = window.innerWidth;
363
+ height = canvas.height = window.innerHeight;
364
+ cx = width / 2;
365
+ cy = height / 2;
366
+ }
367
+ window.addEventListener('resize', resize);
368
+ resize();
369
+
370
+ // --- Classes ---
371
+
372
+ // 1. Starfield (Speed Effect)
373
+ class Star {
374
+ constructor() {
375
+ this.init();
376
+ }
377
+
378
+ init() {
379
+ this.x = (Math.random() - 0.5) * width * 2; // Spread wide
380
+ this.y = (Math.random() - 0.5) * height * 2;
381
+ this.z = Math.random() * 2000 + 500; // Depth
382
+ this.pz = this.z; // Previous z for trail effect
383
+ }
384
+
385
+ update(currentSpeed) {
386
+ this.z -= currentSpeed;
387
+ if (this.z <= 1) {
388
+ this.init();
389
+ this.z = 2000;
390
+ this.pz = 2000;
391
+ }
392
+ }
393
+
394
+ draw() {
395
+ // Perspective projection
396
+ const sx = (this.x / this.z) * width + cx;
397
+ const sy = (this.y / this.z) * height + cy;
398
+
399
+ // Previous position for trail (Speed Lines)
400
+ const px = (this.x / this.pz) * width + cx;
401
+ const py = (this.y / this.pz) * height + cy;
402
+
403
+ this.pz = this.z;
404
+
405
+ // Size based on proximity
406
+ const size = (1 - this.z / 2000) * 3;
407
+ const alpha = (1 - this.z / 2000);
408
+
409
+ ctx.beginPath();
410
+ ctx.moveTo(px, py);
411
+ ctx.lineTo(sx, sy);
412
+ ctx.strokeStyle = `rgba(200, 240, 255, ${alpha})`;
413
+ ctx.lineWidth = size;
414
+ ctx.stroke();
415
+ }
416
+ }
417
+
418
+ // 2. Player Ship
419
+ class Player {
420
+ constructor() {
421
+ this.x = 0; // Horizontal position (-1 to 1 range)
422
+ this.y = 0.8; // Vertical fixed (near bottom)
423
+ this.width = 0.15; // Relative width
424
+ this.color = '#00f3ff';
425
+ this.bullets = [];
426
+ this.cooldown = 0;
427
+ }
428
+
429
+ update() {
430
+ // Movement smoothing
431
+ if (keys.ArrowLeft || touchInput === -1) this.x -= 0.04;
432
+ if (keys.ArrowRight || touchInput === 1) this.x += 0.04;
433
+
434
+ // Clamp
435
+ if (this.x < -1) this.x = -1;
436
+ if (this.x > 1) this.x = 1;
437
+
438
+ // Shooting
439
+ if (this.cooldown > 0) this.cooldown--;
440
+ if ((keys.Space || touchInput === 2) && this.cooldown <= 0) {
441
+ this.shoot();
442
+ this.cooldown = 10; // Fire rate
443
+ }
444
+
445
+ // Update Bullets
446
+ for (let i = this.bullets.length - 1; i >= 0; i--) {
447
+ let b = this.bullets[i];
448
+ b.z -= speed * 2.5; // Bullets faster than ship
449
+ if (b.z < 0) this.bullets.splice(i, 1);
450
+ }
451
+ }
452
+
453
+ shoot() {
454
+ // Dual guns
455
+ this.bullets.push({ x: this.x - 0.05, y: this.y, z: 100 });
456
+ this.bullets.push({ x: this.x + 0.05, y: this.y, z: 100 });
457
+ }
458
+
459
+ draw() {
460
+ // Draw Bullets
461
+ ctx.shadowBlur = 10;
462
+ ctx.shadowColor = this.color;
463
+ ctx.fillStyle = '#fff';
464
+
465
+ this.bullets.forEach(b => {
466
+ const sx = (b.x / b.z) * width + cx;
467
+ const sy = (b.y / b.z) * height + cy; // Note: y is inverted in 3D usually, but simplified here
468
+ // Adjust perspective y to be relative to center
469
+ const screenY = height - (b.y * height * 0.5); // Rough approximation for gameplay feel
470
+
471
+ // Better projection for gameplay objects
472
+ const projX = (b.x * width * 0.8) + cx;
473
+ const projY = height * 0.8; // Shoot from bottom plane
474
+
475
+ // Scale bullet size
476
+ const scale = 500 / b.z;
477
+ if(scale > 0) {
478
+ ctx.beginPath();
479
+ ctx.arc(projX, projY - (b.z * 0.5), 3 * scale, 0, Math.PI * 2);
480
+ ctx.fill();
481
+ }
482
+ });
483
+
484
+ // Draw Ship (Simple 3D wireframe-ish look)
485
+ const shipX = this.x * width * 0.4 + cx;
486
+ const shipY = height * 0.85;
487
+ const size = 40;
488
+
489
+ ctx.shadowBlur = 20;
490
+ ctx.shadowColor = this.color;
491
+ ctx.strokeStyle = this.color;
492
+ ctx.lineWidth = 2;
493
+
494
+ // Main body triangle
495
+ ctx.beginPath();
496
+ ctx.moveTo(shipX, shipY - size);
497
+ ctx.lineTo(shipX - size, shipY + size);
498
+ ctx.lineTo(shipX, shipY + size * 0.7); // Engine indent
499
+ ctx.lineTo(shipX + size, shipY + size);
500
+ ctx.closePath();
501
+ ctx.stroke();
502
+
503
+ // Engine glow
504
+ ctx.fillStyle = `rgba(0, 243, 255, ${Math.random() * 0.5 + 0.5})`;
505
+ ctx.beginPath();
506
+ ctx.moveTo(shipX - size * 0.5, shipY + size * 0.8);
507
+ ctx.lineTo(shipX, shipY + size * 1.5 + Math.random() * 20);
508
+ ctx.lineTo(shipX + size * 0.5, shipY + size * 0.8);
509
+ ctx.fill();
510
+
511
+ ctx.shadowBlur = 0;
512
+ }
513
+ }
514
+
515
+ // 3. Enemies & Obstacles
516
+ class Object3D {
517
+ constructor(type) {
518
+ this.type = type; // 'asteroid' or 'enemy'
519
+ this.x = (Math.random() - 0.5) * 2.5; // Random X
520
+ this.y = (Math.random() - 0.5) * 1; // Random Y spread
521
+ this.z = 2000;
522
+ this.active = true;
523
+ this.rotation = Math.random() * Math.PI;
524
+ this.rotSpeed = (Math.random() - 0.5) * 0.1;
525
+ this.size = Math.random() * 0.5 + 0.5;
526
+ }
527
+
528
+ update() {
529
+ this.z -= speed;
530
+ this.rotation += this.rotSpeed;
531
+ if (this.z < 10) {
532
+ this.active = false;
533
+ // If it passes player, maybe damage?
534
+ if (Math.abs(this.x - player.x) < 0.3 && this.type === 'asteroid') {
535
+ takeDamage(10);
536
+ createExplosion(0, 0, 0, 'red'); // Screen shake effect essentially
537
+ }
538
+ }
539
+ }
540
+
541
+ draw() {
542
+ const scale = 500 / this.z;
543
+ const screenX = (this.x * width * 0.8) + cx;
544
+ const screenY = cy + (this.y * height * 0.5); // Center projection
545
+
546
+ ctx.save();
547
+ ctx.translate(screenX, screenY);
548
+ ctx.scale(scale * this.size, scale * this.size);
549
+ ctx.rotate(this.rotation);
550
+
551
+ if (this.type === 'asteroid') {
552
+ ctx.strokeStyle = '#888';
553
+ ctx.fillStyle = '#111';
554
+ ctx.lineWidth = 2;
555
+ ctx.beginPath();
556
+ // Draw a rough polygon
557
+ for(let i=0; i<6; i++) {
558
+ const angle = (i / 6) * Math.PI * 2;
559
+ const r = 30 + Math.sin(i * 3) * 10;
560
+ const px = Math.cos(angle) * r;
561
+ const py = Math.sin(angle) * r;
562
+ if(i===0) ctx.moveTo(px, py);
563
+ else ctx.lineTo(px, py);
564
+ }
565
+ ctx.closePath();
566
+ ctx.fill();
567
+ ctx.stroke();
568
+ } else if (this.type === 'enemy') {
569
+ ctx.shadowBlur = 15;
570
+ ctx.shadowColor = '#bc13fe';
571
+ ctx.fillStyle = '#bc13fe';
572
+
573
+ // Drone shape
574
+ ctx.beginPath();
575
+ ctx.moveTo(0, -30);
576
+ ctx.lineTo(20, 10);
577
+ ctx.lineTo(0, 0);
578
+ ctx.lineTo(-20, 10);
579
+ ctx.closePath();
580
+ ctx.fill();
581
+
582
+ // Core
583
+ ctx.fillStyle = '#fff';
584
+ ctx.beginPath();
585
+ ctx.arc(0, 0, 5, 0, Math.PI*2);
586
+ ctx.fill();
587
+ ctx.shadowBlur = 0;
588
+ }
589
+
590
+ ctx.restore();
591
+ }
592
+ }
593
+
594
+ // 4. Particles (Explosions)
595
+ class Particle {
596
+ constructor(x, y, color) {
597
+ this.x = x;
598
+ this.y = y;
599
+ this.vx = (Math.random() - 0.5) * 10;
600
+ this.vy = (Math.random() - 0.5) * 10;
601
+ this.life = 1.0;
602
+ this.color = color;
603
+ this.size = Math.random() * 5 + 2;
604
+ }
605
+ update() {
606
+ this.x += this.vx;
607
+ this.y += this.vy;
608
+ this.life -= 0.03;
609
+ this.size *= 0.95;
610
+ }
611
+ draw() {
612
+ ctx.globalAlpha = this.life;
613
+ ctx.fillStyle = this.color;
614
+ ctx.beginPath();
615
+ ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2);
616
+ ctx.fill();
617
+ ctx.globalAlpha = 1.0;
618
+ }
619
+ }
620
+
621
+ // --- Game Logic Setup ---
622
+
623
+ const stars = Array.from({ length: 300 }, () => new Star());
624
+ let player = new Player();
625
+ let objects = [];
626
+ let particles = [];
627
+ let animationId;
628
+
629
+ function createExplosion(x, y, color) {
630
+ for(let i=0; i<15; i++) {
631
+ particles.push(new Particle(x, y, color));
632
+ }
633
+ }
634
+
635
+ function takeDamage(amount) {
636
+ health -= amount;
637
+ healthFillEl.style.width = `${health}%`;
638
+
639
+ // Screen shake effect
640
+ canvas.style.transform = `translate(${Math.random()*20-10}px, ${Math.random()*20-10}px)`;
641
+ setTimeout(() => canvas.style.transform = 'none', 50);
642
+
643
+ if (health <= 0) {
644
+ endGame();
645
+ }
646
+ }
647
+
648
+ function spawnObject() {
649
+ // Difficulty curve
650
+ const spawnRate = Math.max(20, 60 - Math.floor(score / 500));
651
+
652
+ if (frameCount % spawnRate === 0) {
653
+ const type = Math.random() > 0.7 ? 'enemy' : 'asteroid';
654
+ objects.push(new Object3D(type));
655
+ }
656
+ }
657
+
658
+ function checkCollisions() {
659
+ objects.forEach(obj => {
660
+ if (!obj.active) return;
661
+
662
+ // Bullet Collision
663
+ player.bullets.forEach((b, bIndex) => {
664
+ if (b.z < obj.z + 50 && b.z > obj.z - 50) {
665
+ // Check X distance roughly
666
+ // Map bullet relative X (-1 to 1) to Object X range
667
+ // Simplified 3D distance check
668
+ const dx = b.x - obj.x;
669
+ const dy = (player.y - 0.5) - obj.y; // Adjust for player plane
670
+
671
+ if (Math.sqrt(dx*dx + dy*dy) < 0.3) {
672
+ // Hit
673
+ obj.active = false;
674
+ player.bullets.splice(bIndex, 1);
675
+
676
+ // Visuals
677
+ const sx = (obj.x * width * 0.8) + cx;
678
+ const sy = cy + (obj.y * height * 0.5);
679
+ createExplosion(sx, sy, obj.type === 'enemy' ? '#bc13fe' : '#aaa');
680
+
681
+ // Score
682
+ if (obj.type === 'enemy') {
683
+ score += 200;
684
+ scoreEl.innerText = score;
685
+ } else {
686
+ score += 50;
687
+ scoreEl.innerText = score;
688
+ }
689
+ }
690
+ }
691
+ });
692
+
693
+ // Player Collision (Direct hit)
694
+ if (obj.z < 150 && obj.z > 50) {
695
+ const dx = player.x - obj.x;
696
+ if (Math.abs(dx) < 0.2) {
697
+ obj.active = false;
698
+ createExplosion(cx, height * 0.8, '#ff2a6d');
699
+ takeDamage(20);
700
+ }
701
+ }
702
+ });
703
+ }
704
+
705
+ function loop() {
706
+ if (!isPlaying) return;
707
+
708
+ // Clear Canvas
709
+ ctx.fillStyle = 'rgba(5, 5, 5, 0.4)'; // Trail effect for motion blur
710
+ ctx.fillRect(0, 0, width, height);
711
+
712
+ frameCount++;
713
+
714
+ // Increase speed gradually
715
+ speed = 15 + (score / 1000);
716
+
717
+ // Update Stars
718
+ stars.forEach(star => {
719
+ star.update(speed);
720
+ star.draw();
721
+ });
722
+
723
+ // Update Player
724
+ player.update();
725
+ player.draw();
726
+
727
+ // Update Objects
728
+ objects = objects.filter(obj => obj.active);
729
+ objects.forEach(obj => {
730
+ obj.update();
731
+ obj.draw();
732
+ });
733
+
734
+ // Update Particles
735
+ particles = particles.filter(p => p.life > 0);
736
+ particles.forEach(p => {
737
+ p.update();
738
+ p.draw();
739
+ });
740
+
741
+ spawnObject();
742
+ checkCollisions();
743
+
744
+ animationId = requestAnimationFrame(loop);
745
+ }
746
+
747
+ function startGame() {
748
+ isPlaying = true;
749
+ score = 0;
750
+ health = 100;
751
+ speed = 15;
752
+ objects = [];
753
+ particles = [];
754
+ player.bullets = [];
755
+ player.x = 0;
756
+
757
+ scoreEl.innerText = '0';
758
+ healthFillEl.style.width = '100%';
759
+
760
+ startScreen.classList.add('hidden');
761
+ gameOverScreen.classList.add('hidden');
762
+
763
+ loop();
764
+ }
765
+
766
+ function endGame() {
767
+ isPlaying = false;
768
+ cancelAnimationFrame(animationId);
769
+ finalScoreEl.innerText = score;
770
+ gameOverScreen.classList.remove('hidden');
771
+ }
772
+
773
+ // --- Event Listeners ---
774
+
775
+ window.addEventListener('keydown', e => {
776
+ if (e.code === 'ArrowLeft') keys.ArrowLeft = true;
777
+ if (e.code === 'ArrowRight') keys.ArrowRight = true;
778
+ if (e.code === 'Space') keys.Space = true;
779
+ });
780
+
781
+ window.addEventListener('keyup', e => {
782
+ if (e.code === 'ArrowLeft') keys.ArrowLeft = false;
783
+ if (e.code === 'ArrowRight') keys.ArrowRight = false;
784
+ if (e.code === 'Space') keys.Space = false;
785
+ });
786
+
787
+ // Mouse/Touch Drag for movement
788
+ window.addEventListener('mousedown', () => keys.Space = true);
789
+ window.addEventListener('mouseup', () => keys.Space = false);
790
+
791
+ // Touch Logic for Mobile
792
+ btnLeft.addEventListener('touchstart', (e) => { e.preventDefault(); touchInput = -1; });
793
+ btnLeft.addEventListener('touchend', (e) => { e.preventDefault(); touchInput = 0; });
794
+
795
+ btnRight.addEventListener('touchstart', (e) => { e.preventDefault(); touchInput = 1; });
796
+ btnRight.addEventListener('touchend', (e) => { e.preventDefault(); touchInput = 0; });
797
+
798
+ // Tap center to shoot on mobile (simplified)
799
+ canvas.addEventListener('touchstart', (e) => {
800
+ if(e.touches.length > 0) {
801
+ const t = e.touches[0];
802
+ if(t.clientX > width * 0.3 && t.clientX < width * 0.7) {
803
+ touchInput = 2; // Shoot code
804
+ }
805
+ }
806
+ });
807
+ canvas.addEventListener('touchend', (e) => {
808
+ touchInput = 0;
809
+ });
810
+
811
+ startBtn.addEventListener('click', startGame);
812
+ restartBtn.addEventListener('click', startGame);
813
+
814
+ // Initial render for background
815
+ ctx.fillStyle = '#050505';
816
+ ctx.fillRect(0, 0, width, height);
817
+ stars.forEach(star => star.draw());
818
+
819
+ </script>
820
+ </body>
821
+ </html>