GuangyuanSD commited on
Commit
4899a72
·
verified ·
1 Parent(s): b703bc7

Add 2 files

Browse files
Files changed (2) hide show
  1. README.md +7 -5
  2. index.html +726 -19
README.md CHANGED
@@ -1,10 +1,12 @@
1
  ---
2
- title: Ejectionface
3
- emoji:
4
- colorFrom: green
5
- colorTo: indigo
6
  sdk: static
7
  pinned: false
 
 
8
  ---
9
 
10
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
1
  ---
2
+ title: ejectionface
3
+ emoji: 🐳
4
+ colorFrom: gray
5
+ colorTo: red
6
  sdk: static
7
  pinned: false
8
+ tags:
9
+ - deepsite
10
  ---
11
 
12
+ Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
index.html CHANGED
@@ -1,19 +1,726 @@
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>Ejection FACE!</title>
7
+ <script src="https://cdn.tailwindcss.com"></script>
8
+ <style>
9
+ @import url('https://fonts.googleapis.com/css2?family=Press+Start+2P&display=swap');
10
+
11
+ body {
12
+ margin: 0;
13
+ padding: 0;
14
+ overflow: hidden;
15
+ font-family: 'Press Start 2P', cursive;
16
+ background-color: #000;
17
+ color: white;
18
+ cursor: none;
19
+ }
20
+
21
+ #game-container {
22
+ position: relative;
23
+ width: 100vw;
24
+ height: 100vh;
25
+ overflow: hidden;
26
+ }
27
+
28
+ #starfield {
29
+ position: absolute;
30
+ width: 100%;
31
+ height: 100%;
32
+ z-index: 1;
33
+ }
34
+
35
+ .star {
36
+ position: absolute;
37
+ width: 2px;
38
+ height: 2px;
39
+ background-color: white;
40
+ border-radius: 50%;
41
+ }
42
+
43
+ .target {
44
+ position: absolute;
45
+ width: 250px;
46
+ height: 250px;
47
+ background-size: contain;
48
+ background-repeat: no-repeat;
49
+ background-position: center;
50
+ transition: transform 0.1s ease-out;
51
+ z-index: 2;
52
+ }
53
+
54
+ .hit-effect {
55
+ position: absolute;
56
+ width: 100%;
57
+ height: 100%;
58
+ background-size: contain;
59
+ background-repeat: no-repeat;
60
+ background-position: center;
61
+ opacity: 0;
62
+ transition: opacity 0.3s;
63
+ z-index: 3;
64
+ }
65
+
66
+ .hit-effect.active {
67
+ opacity: 1;
68
+ }
69
+
70
+ #custom-cursor {
71
+ position: absolute;
72
+ width: 40px;
73
+ height: 40px;
74
+ background-size: contain;
75
+ background-repeat: no-repeat;
76
+ pointer-events: none;
77
+ z-index: 999;
78
+ transform-origin: center center;
79
+ transform: rotate(0deg);
80
+ }
81
+
82
+ #score-display {
83
+ position: absolute;
84
+ top: 20px;
85
+ left: 0;
86
+ right: 0;
87
+ text-align: center;
88
+ font-size: 24px;
89
+ color: #ff0;
90
+ text-shadow: 0 0 10px #ff0, 0 0 20px #ff0;
91
+ z-index: 10;
92
+ opacity: 0;
93
+ transition: opacity 0.3s;
94
+ }
95
+
96
+ #score-display.active {
97
+ opacity: 1;
98
+ }
99
+
100
+ #hud {
101
+ position: absolute;
102
+ bottom: 20px;
103
+ left: 0;
104
+ right: 0;
105
+ display: flex;
106
+ justify-content: center;
107
+ align-items: center;
108
+ gap: 20px;
109
+ z-index: 10;
110
+ }
111
+
112
+ .weapon {
113
+ width: 60px;
114
+ height: 60px;
115
+ background-size: contain;
116
+ background-repeat: no-repeat;
117
+ background-position: center;
118
+ border: 2px solid #555;
119
+ border-radius: 5px;
120
+ cursor: pointer;
121
+ transition: all 0.2s;
122
+ position: relative;
123
+ }
124
+
125
+ .weapon:hover {
126
+ transform: scale(1.1);
127
+ border-color: #ff0;
128
+ }
129
+
130
+ .weapon.locked {
131
+ filter: grayscale(100%);
132
+ opacity: 0.5;
133
+ }
134
+
135
+ .weapon.selected {
136
+ border-color: #ff0;
137
+ box-shadow: 0 0 10px #ff0;
138
+ }
139
+
140
+ .weapon-price {
141
+ position: absolute;
142
+ top: -15px;
143
+ right: -10px;
144
+ background-color: #000;
145
+ color: #ff0;
146
+ padding: 2px 5px;
147
+ border-radius: 10px;
148
+ font-size: 10px;
149
+ border: 1px solid #ff0;
150
+ }
151
+
152
+ .projectile {
153
+ position: absolute;
154
+ width: 10px;
155
+ height: 10px;
156
+ border-radius: 50%;
157
+ z-index: 5;
158
+ transform-origin: center;
159
+ }
160
+
161
+ #hit-sound, #shoot-sound, #bg-music {
162
+ display: none;
163
+ }
164
+
165
+ .hit-marker {
166
+ position: absolute;
167
+ width: 30px;
168
+ height: 30px;
169
+ background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="%23ff0000"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z"/></svg>');
170
+ background-size: contain;
171
+ opacity: 0;
172
+ animation: hitMarker 1s forwards;
173
+ z-index: 4;
174
+ }
175
+
176
+ @keyframes hitMarker {
177
+ 0% { transform: scale(0.5); opacity: 0; }
178
+ 50% { transform: scale(1.5); opacity: 1; }
179
+ 100% { transform: scale(1); opacity: 0; }
180
+ }
181
+
182
+ .ejection-text {
183
+ position: absolute;
184
+ font-size: 48px;
185
+ color: #ff0;
186
+ text-shadow: 0 0 10px #ff0, 0 0 20px #ff0;
187
+ animation: ejectionText 1.5s forwards;
188
+ z-index: 10;
189
+ opacity: 0;
190
+ }
191
+
192
+ @keyframes ejectionText {
193
+ 0% { transform: scale(0.5); opacity: 0; }
194
+ 20% { transform: scale(1.2); opacity: 1; }
195
+ 40% { transform: scale(0.9); }
196
+ 60% { transform: scale(1.1); }
197
+ 80% { transform: scale(0.95); }
198
+ 100% { transform: scale(1); opacity: 0; }
199
+ }
200
+ </style>
201
+ </head>
202
+ <body>
203
+ <div id="game-container">
204
+ <div id="starfield"></div>
205
+ <div id="targets-container"></div>
206
+ <div id="projectiles-container"></div>
207
+ <div id="hit-markers-container"></div>
208
+ <div id="score-display">Ejection FACE!</div>
209
+ <div id="hud">
210
+ <div class="score-box">Hits: <span id="hit-count">0</span> | Score: <span id="score">0</span></div>
211
+ <div class="weapon selected" data-weapon="basic" data-price="0">
212
+ <div class="weapon-price">FREE</div>
213
+ </div>
214
+ <div class="weapon locked" data-weapon="rapid" data-price="50">
215
+ <div class="weapon-price">50</div>
216
+ </div>
217
+ <div class="weapon locked" data-weapon="double" data-price="150">
218
+ <div class="weapon-price">150</div>
219
+ </div>
220
+ <div class="weapon locked" data-weapon="shotgun" data-price="300">
221
+ <div class="weapon-price">300</div>
222
+ </div>
223
+ <div class="weapon locked" data-weapon="bouncy" data-price="500">
224
+ <div class="weapon-price">500</div>
225
+ </div>
226
+ </div>
227
+ <div id="custom-cursor"></div>
228
+ </div>
229
+
230
+ <audio id="hit-sound" preload="auto">
231
+ <source src="https://assets.mixkit.co/sfx/preview/mixkit-positive-interface-beep-221.mp3" type="audio/mpeg">
232
+ </audio>
233
+ <audio id="shoot-sound" preload="auto">
234
+ <source src="https://assets.mixkit.co/sfx/preview/mixkit-short-laser-gun-shot-1670.mp3" type="audio/mpeg">
235
+ </audio>
236
+ <audio id="bg-music" loop>
237
+ <source src="https://assets.mixkit.co/music/preview/mixkit-game-show-suspense-waiting-668.mp3" type="audio/mpeg">
238
+ </audio>
239
+
240
+ <script>
241
+ document.addEventListener('DOMContentLoaded', () => {
242
+ // Game state
243
+ const state = {
244
+ score: 0,
245
+ hits: 0,
246
+ targets: [],
247
+ projectiles: [],
248
+ stars: [],
249
+ currentWeapon: 'basic',
250
+ weapons: {
251
+ basic: { price: 0, fireRate: 500, projectileCount: 1, spread: 0, bouncy: false },
252
+ rapid: { price: 50, fireRate: 250, projectileCount: 1, spread: 0, bouncy: false },
253
+ double: { price: 150, fireRate: 500, projectileCount: 2, spread: 10, bouncy: false },
254
+ shotgun: { price: 300, fireRate: 800, projectileCount: 5, spread: 20, bouncy: false },
255
+ bouncy: { price: 500, fireRate: 400, projectileCount: 1, spread: 0, bouncy: true }
256
+ },
257
+ lastShot: 0,
258
+ colors: ['#ff0000', '#00ff00', '#0000ff', '#ffff00', '#ff00ff', '#00ffff'],
259
+ currentColorIndex: 0,
260
+ targetSpeed: 2,
261
+ targetCount: 1
262
+ };
263
+
264
+ // DOM elements
265
+ const gameContainer = document.getElementById('game-container');
266
+ const starfield = document.getElementById('starfield');
267
+ const targetsContainer = document.getElementById('targets-container');
268
+ const projectilesContainer = document.getElementById('projectiles-container');
269
+ const hitMarkersContainer = document.getElementById('hit-markers-container');
270
+ const scoreDisplay = document.getElementById('score-display');
271
+ const hitCountElement = document.getElementById('hit-count');
272
+ const scoreElement = document.getElementById('score');
273
+ const customCursor = document.getElementById('custom-cursor');
274
+ const hitSound = document.getElementById('hit-sound');
275
+ const shootSound = document.getElementById('shoot-sound');
276
+ const bgMusic = document.getElementById('bg-music');
277
+ const weaponElements = document.querySelectorAll('.weapon');
278
+
279
+ // Initialize game
280
+ initStarfield();
281
+ createTargets(state.targetCount);
282
+ setupCursor();
283
+ setupWeapons();
284
+ playMusic();
285
+
286
+ // Event listeners
287
+ gameContainer.addEventListener('mousemove', handleMouseMove);
288
+ gameContainer.addEventListener('click', handleShoot);
289
+
290
+ // Game loop
291
+ function gameLoop() {
292
+ updateTargets();
293
+ updateProjectiles();
294
+ checkCollisions();
295
+ requestAnimationFrame(gameLoop);
296
+ }
297
+
298
+ gameLoop();
299
+
300
+ // Initialize starfield
301
+ function initStarfield() {
302
+ const starCount = Math.floor(window.innerWidth * window.innerHeight / 1000);
303
+
304
+ for (let i = 0; i < starCount; i++) {
305
+ createStar();
306
+ }
307
+ }
308
+
309
+ function createStar() {
310
+ const star = document.createElement('div');
311
+ star.className = 'star';
312
+
313
+ const x = Math.random() * window.innerWidth;
314
+ const y = Math.random() * window.innerHeight;
315
+ const size = Math.random() * 3;
316
+ const opacity = Math.random();
317
+
318
+ star.style.left = `${x}px`;
319
+ star.style.top = `${y}px`;
320
+ star.style.width = `${size}px`;
321
+ star.style.height = `${size}px`;
322
+ star.style.opacity = opacity;
323
+
324
+ starfield.appendChild(star);
325
+
326
+ // Animate star (parallax effect)
327
+ const speed = 0.5 + Math.random() * 2;
328
+
329
+ function animateStar() {
330
+ const currentX = parseFloat(star.style.left);
331
+ const currentY = parseFloat(star.style.top);
332
+
333
+ // Move star outward from center
334
+ const centerX = window.innerWidth / 2;
335
+ const centerY = window.innerHeight / 2;
336
+
337
+ let dx = currentX - centerX;
338
+ let dy = currentY - centerY;
339
+
340
+ // Normalize direction vector
341
+ const distance = Math.sqrt(dx * dx + dy * dy);
342
+ dx /= distance;
343
+ dy /= distance;
344
+
345
+ // Move star
346
+ star.style.left = `${currentX + dx * speed}px`;
347
+ star.style.top = `${currentY + dy * speed}px`;
348
+
349
+ // Reset star if it goes out of bounds
350
+ if (parseFloat(star.style.left) < 0 || parseFloat(star.style.left) > window.innerWidth ||
351
+ parseFloat(star.style.top) < 0 || parseFloat(star.style.top) > window.innerHeight) {
352
+ star.style.left = `${Math.random() * window.innerWidth}px`;
353
+ star.style.top = `${Math.random() * window.innerHeight}px`;
354
+ }
355
+
356
+ requestAnimationFrame(animateStar);
357
+ }
358
+
359
+ animateStar();
360
+ }
361
+
362
+ // Create targets
363
+ function createTargets(count) {
364
+ targetsContainer.innerHTML = '';
365
+ state.targets = [];
366
+
367
+ for (let i = 0; i < count; i++) {
368
+ createTarget(i);
369
+ }
370
+ }
371
+
372
+ function createTarget(index) {
373
+ const target = document.createElement('div');
374
+ target.className = 'target';
375
+
376
+ // Use huggingface emoji as target
377
+ target.style.backgroundImage = 'url("https://huggingface.co/front/assets/huggingface_logo-noborder.svg")';
378
+
379
+ // Position randomly but ensure it's fully visible
380
+ const maxX = window.innerWidth - 250;
381
+ const maxY = window.innerHeight - 250;
382
+
383
+ const x = Math.max(0, Math.min(maxX, Math.random() * maxX));
384
+ const y = Math.max(0, Math.min(maxY, Math.random() * maxY));
385
+
386
+ target.style.left = `${x}px`;
387
+ target.style.top = `${y}px`;
388
+
389
+ // Add hit effect
390
+ const hitEffect = document.createElement('div');
391
+ hitEffect.className = 'hit-effect';
392
+ hitEffect.style.backgroundImage = 'url("data:image/svg+xml;utf8,<svg xmlns=\'http://www.w3.org/2000/svg\' viewBox=\'0 0 100 100\'><circle cx=\'50\' cy=\'50\' r=\'40\' fill=\'' + state.colors[state.currentColorIndex] + '\' opacity=\'0.7\'/></svg>")';
393
+
394
+ target.appendChild(hitEffect);
395
+ targetsContainer.appendChild(target);
396
+
397
+ // Store target data
398
+ state.targets.push({
399
+ element: target,
400
+ hitEffect: hitEffect,
401
+ x: x,
402
+ y: y,
403
+ vx: (Math.random() - 0.5) * state.targetSpeed,
404
+ vy: (Math.random() - 0.5) * state.targetSpeed,
405
+ index: index
406
+ });
407
+ }
408
+
409
+ // Update targets position
410
+ function updateTargets() {
411
+ state.targets.forEach(target => {
412
+ // Update position
413
+ target.x += target.vx;
414
+ target.y += target.vy;
415
+
416
+ // Bounce off edges
417
+ if (target.x <= 0 || target.x >= window.innerWidth - 250) {
418
+ target.vx = -target.vx * (0.9 + Math.random() * 0.2);
419
+ }
420
+
421
+ if (target.y <= 0 || target.y >= window.innerHeight - 250) {
422
+ target.vy = -target.vy * (0.9 + Math.random() * 0.2);
423
+ }
424
+
425
+ // Random direction changes
426
+ if (Math.random() < 0.02) {
427
+ target.vx += (Math.random() - 0.5) * 0.5;
428
+ target.vy += (Math.random() - 0.5) * 0.5;
429
+
430
+ // Limit speed
431
+ const speed = Math.sqrt(target.vx * target.vx + target.vy * target.vy);
432
+ const maxSpeed = state.targetSpeed * 1.5;
433
+
434
+ if (speed > maxSpeed) {
435
+ target.vx = (target.vx / speed) * maxSpeed;
436
+ target.vy = (target.vy / speed) * maxSpeed;
437
+ }
438
+ }
439
+
440
+ // Apply position
441
+ target.element.style.left = `${target.x}px`;
442
+ target.element.style.top = `${target.y}px`;
443
+ });
444
+ }
445
+
446
+ // Setup custom cursor
447
+ function setupCursor() {
448
+ customCursor.style.backgroundImage = 'url("data:image/svg+xml;utf8,<svg xmlns=\'http://www.w3.org/2000/svg\' viewBox=\'0 0 100 100\'><path d=\'M50 10 L50 90 M10 50 L90 50\' stroke=\'' + state.colors[state.currentColorIndex] + '\' stroke-width=\'5\' stroke-linecap=\'round\'/></svg>")';
449
+ }
450
+
451
+ function handleMouseMove(e) {
452
+ customCursor.style.left = `${e.clientX - 20}px`;
453
+ customCursor.style.top = `${e.clientY - 20}px`;
454
+
455
+ // Rotate cursor slightly for visual feedback
456
+ const rotation = Math.sin(Date.now() / 200) * 5;
457
+ customCursor.style.transform = `rotate(${rotation}deg)`;
458
+ }
459
+
460
+ // Handle shooting
461
+ function handleShoot(e) {
462
+ const now = Date.now();
463
+ const weapon = state.weapons[state.currentWeapon];
464
+
465
+ if (now - state.lastShot < weapon.fireRate) return;
466
+
467
+ state.lastShot = now;
468
+
469
+ // Play shoot sound
470
+ shootSound.currentTime = 0;
471
+ shootSound.play();
472
+
473
+ // Create projectiles based on weapon type
474
+ for (let i = 0; i < weapon.projectileCount; i++) {
475
+ createProjectile(e.clientX, e.clientY, i);
476
+ }
477
+
478
+ // Animate cursor
479
+ customCursor.style.transform = 'scale(0.8)';
480
+ setTimeout(() => {
481
+ customCursor.style.transform = 'scale(1)';
482
+ }, 100);
483
+ }
484
+
485
+ function createProjectile(mouseX, mouseY, index) {
486
+ const projectile = document.createElement('div');
487
+ projectile.className = 'projectile';
488
+
489
+ // Calculate direction from center to mouse
490
+ const centerX = window.innerWidth / 2;
491
+ const centerY = window.innerHeight / 2;
492
+
493
+ let dx = mouseX - centerX;
494
+ let dy = mouseY - centerY;
495
+
496
+ // Normalize direction vector
497
+ const distance = Math.sqrt(dx * dx + dy * dy);
498
+ dx /= distance;
499
+ dy /= distance;
500
+
501
+ // Apply weapon spread
502
+ const weapon = state.weapons[state.currentWeapon];
503
+ const spreadAngle = (index - (weapon.projectileCount - 1) / 2) * weapon.spread * (Math.PI / 180);
504
+
505
+ // Rotate direction by spread angle
506
+ const cos = Math.cos(spreadAngle);
507
+ const sin = Math.sin(spreadAngle);
508
+ const tx = dx * cos - dy * sin;
509
+ const ty = dx * sin + dy * cos;
510
+
511
+ dx = tx;
512
+ dy = ty;
513
+
514
+ // Set projectile color
515
+ projectile.style.backgroundColor = state.colors[state.currentColorIndex];
516
+
517
+ // Set initial position at center
518
+ projectile.style.left = `${centerX}px`;
519
+ projectile.style.top = `${centerY}px`;
520
+
521
+ projectilesContainer.appendChild(projectile);
522
+
523
+ // Store projectile data
524
+ const projectileData = {
525
+ element: projectile,
526
+ x: centerX,
527
+ y: centerY,
528
+ vx: dx * 10,
529
+ vy: dy * 10,
530
+ bounces: 0,
531
+ maxBounces: weapon.bouncy ? 3 : 0
532
+ };
533
+
534
+ state.projectiles.push(projectileData);
535
+ }
536
+
537
+ // Update projectiles position
538
+ function updateProjectiles() {
539
+ state.projectiles.forEach((projectile, index) => {
540
+ // Update position
541
+ projectile.x += projectile.vx;
542
+ projectile.y += projectile.vy;
543
+
544
+ // Bounce off edges if bouncy
545
+ if (projectile.maxBounces > 0) {
546
+ if (projectile.x <= 0 || projectile.x >= window.innerWidth) {
547
+ projectile.vx = -projectile.vx;
548
+ projectile.bounces++;
549
+ projectile.x = Math.max(0, Math.min(window.innerWidth, projectile.x));
550
+ }
551
+
552
+ if (projectile.y <= 0 || projectile.y >= window.innerHeight) {
553
+ projectile.vy = -projectile.vy;
554
+ projectile.bounces++;
555
+ projectile.y = Math.max(0, Math.min(window.innerHeight, projectile.y));
556
+ }
557
+ }
558
+
559
+ // Apply position
560
+ projectile.element.style.left = `${projectile.x}px`;
561
+ projectile.element.style.top = `${projectile.y}px`;
562
+
563
+ // Remove if out of bounds or max bounces reached
564
+ if (projectile.x < 0 || projectile.x > window.innerWidth ||
565
+ projectile.y < 0 || projectile.y > window.innerHeight ||
566
+ projectile.bounces > projectile.maxBounces) {
567
+ projectile.element.remove();
568
+ state.projectiles.splice(index, 1);
569
+ }
570
+ });
571
+ }
572
+
573
+ // Check for collisions
574
+ function checkCollisions() {
575
+ state.projectiles.forEach((projectile, pIndex) => {
576
+ state.targets.forEach(target => {
577
+ // Simple circle-rectangle collision detection
578
+ const targetRect = {
579
+ x: target.x,
580
+ y: target.y,
581
+ width: 250,
582
+ height: 250
583
+ };
584
+
585
+ // Find closest point on rectangle to circle
586
+ const closestX = Math.max(targetRect.x, Math.min(projectile.x, targetRect.x + targetRect.width));
587
+ const closestY = Math.max(targetRect.y, Math.min(projectile.y, targetRect.y + targetRect.height));
588
+
589
+ // Calculate distance
590
+ const distanceX = projectile.x - closestX;
591
+ const distanceY = projectile.y - closestY;
592
+ const distance = Math.sqrt(distanceX * distanceX + distanceY * distanceY);
593
+
594
+ if (distance < 10) { // Projectile radius
595
+ // Hit!
596
+ handleHit(target, projectile);
597
+
598
+ // Remove projectile
599
+ projectile.element.remove();
600
+ state.projectiles.splice(pIndex, 1);
601
+ }
602
+ });
603
+ });
604
+ }
605
+
606
+ function handleHit(target, projectile) {
607
+ // Play hit sound
608
+ hitSound.currentTime = 0;
609
+ hitSound.play();
610
+
611
+ // Show hit effect
612
+ target.hitEffect.classList.add('active');
613
+ setTimeout(() => {
614
+ target.hitEffect.classList.remove('active');
615
+ }, 500);
616
+
617
+ // Create hit marker
618
+ const hitMarker = document.createElement('div');
619
+ hitMarker.className = 'hit-marker';
620
+ hitMarker.style.left = `${projectile.x - 15}px`;
621
+ hitMarker.style.top = `${projectile.y - 15}px`;
622
+ hitMarkersContainer.appendChild(hitMarker);
623
+
624
+ setTimeout(() => {
625
+ hitMarker.remove();
626
+ }, 1000);
627
+
628
+ // Update score
629
+ state.hits++;
630
+ state.score += 10;
631
+
632
+ hitCountElement.textContent = state.hits;
633
+ scoreElement.textContent = state.score;
634
+
635
+ // Show "Ejection FACE!" text
636
+ const ejectionText = document.createElement('div');
637
+ ejectionText.className = 'ejection-text';
638
+ ejectionText.textContent = 'Ejection FACE!';
639
+ ejectionText.style.left = `${window.innerWidth / 2 - 150}px`;
640
+ ejectionText.style.top = `${window.innerHeight / 2 - 50}px`;
641
+
642
+ gameContainer.appendChild(ejectionText);
643
+ setTimeout(() => {
644
+ ejectionText.remove();
645
+ }, 1500);
646
+
647
+ // Change target direction on hit
648
+ target.vx = (Math.random() - 0.5) * state.targetSpeed * 2;
649
+ target.vy = (Math.random() - 0.5) * state.targetSpeed * 2;
650
+
651
+ // Increase difficulty every 10 hits
652
+ if (state.hits % 10 === 0) {
653
+ state.targetSpeed += 0.2;
654
+
655
+ // Make targets change direction more often
656
+ state.targets.forEach(t => {
657
+ t.vx *= 1.1;
658
+ t.vy *= 1.1;
659
+ });
660
+ }
661
+
662
+ // Add new target every 100 hits
663
+ if (state.hits % 100 === 0) {
664
+ state.targetCount++;
665
+ state.currentColorIndex = (state.currentColorIndex + 1) % state.colors.length;
666
+ setupCursor();
667
+ createTargets(state.targetCount);
668
+ }
669
+
670
+ // Unlock weapons if enough score
671
+ weaponElements.forEach(weapon => {
672
+ const weaponType = weapon.dataset.weapon;
673
+ const price = parseInt(weapon.dataset.price);
674
+
675
+ if (state.score >= price) {
676
+ weapon.classList.remove('locked');
677
+ }
678
+ });
679
+ }
680
+
681
+ // Setup weapon selection
682
+ function setupWeapons() {
683
+ weaponElements.forEach(weapon => {
684
+ const weaponType = weapon.dataset.weapon;
685
+
686
+ // Set weapon icons
687
+ switch (weaponType) {
688
+ case 'basic':
689
+ weapon.style.backgroundImage = 'url("data:image/svg+xml;utf8,<svg xmlns=\'http://www.w3.org/2000/svg\' viewBox=\'0 0 100 100\'><path d=\'M50 10 L50 90 M10 50 L90 50\' stroke=\'' + state.colors[0] + '\' stroke-width=\'5\' stroke-linecap=\'round\'/></svg>")';
690
+ break;
691
+ case 'rapid':
692
+ weapon.style.backgroundImage = 'url("data:image/svg+xml;utf8,<svg xmlns=\'http://www.w3.org/2000/svg\' viewBox=\'0 0 100 100\'><path d=\'M50 10 L50 90 M10 50 L90 50\' stroke=\'' + state.colors[1] + '\' stroke-width=\'5\' stroke-linecap=\'round\'/><circle cx=\'50\' cy=\'50\' r=\'15\' fill=\'none\' stroke=\'' + state.colors[1] + '\' stroke-width=\'3\'/></svg>")';
693
+ break;
694
+ case 'double':
695
+ weapon.style.backgroundImage = 'url("data:image/svg+xml;utf8,<svg xmlns=\'http://www.w3.org/2000/svg\' viewBox=\'0 0 100 100\'><path d=\'M50 10 L50 90 M10 50 L90 50\' stroke=\'' + state.colors[2] + '\' stroke-width=\'5\' stroke-linecap=\'round\'/><path d=\'M30 30 L70 70 M30 70 L70 30\' stroke=\'' + state.colors[2] + '\' stroke-width=\'3\'/></svg>")';
696
+ break;
697
+ case 'shotgun':
698
+ weapon.style.backgroundImage = 'url("data:image/svg+xml;utf8,<svg xmlns=\'http://www.w3.org/2000/svg\' viewBox=\'0 0 100 100\'><path d=\'M50 10 L50 90 M10 50 L90 50\' stroke=\'' + state.colors[3] + '\' stroke-width=\'5\' stroke-linecap=\'round\'/><path d=\'M50 30 L30 50 M50 30 L70 50 M50 70 L30 50 M50 70 L70 50\' stroke=\'' + state.colors[3] + '\' stroke-width=\'3\'/></svg>")';
699
+ break;
700
+ case 'bouncy':
701
+ weapon.style.backgroundImage = 'url("data:image/svg+xml;utf8,<svg xmlns=\'http://www.w3.org/2000/svg\' viewBox=\'0 0 100 100\'><path d=\'M50 10 L50 90 M10 50 L90 50\' stroke=\'' + state.colors[4] + '\' stroke-width=\'5\' stroke-linecap=\'round\'/><path d=\'M20 20 Q50 30 80 20 Q85 50 80 80 Q50 70 20 80 Q15 50 20 20\' fill=\'none\' stroke=\'' + state.colors[4] + '\' stroke-width=\'3\'/></svg>")';
702
+ break;
703
+ }
704
+
705
+ weapon.addEventListener('click', () => {
706
+ if (weapon.classList.contains('locked')) return;
707
+
708
+ // Select weapon
709
+ weaponElements.forEach(w => w.classList.remove('selected'));
710
+ weapon.classList.add('selected');
711
+
712
+ state.currentWeapon = weaponType;
713
+ setupCursor();
714
+ });
715
+ });
716
+ }
717
+
718
+ // Play background music
719
+ function playMusic() {
720
+ bgMusic.volume = 0.3;
721
+ bgMusic.play();
722
+ }
723
+ });
724
+ </script>
725
+ <p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=GuangyuanSD/ejectionface" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body>
726
+ </html>