HAL1993 commited on
Commit
e887326
·
verified ·
1 Parent(s): a1788c2

Upload folder using huggingface_hub

Browse files
Files changed (1) hide show
  1. index.html +1188 -19
index.html CHANGED
@@ -1,19 +1,1188 @@
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, user-scalable=no">
6
+ <title>Dino Run - Bitcoin Edition</title>
7
+ <link href="https://fonts.googleapis.com/css2?family=Fredoka+One&family=Orbitron:wght@400;700;900&display=swap" rel="stylesheet">
8
+ <style>
9
+ :root {
10
+ --primary: #f7931a;
11
+ --primary-light: #ffb347;
12
+ --secondary: #4ade80;
13
+ --dark: #1a1a2e;
14
+ --darker: #0f0f1a;
15
+ --light: #ffffff;
16
+ --danger: #ef4444;
17
+ }
18
+
19
+ * {
20
+ margin: 0;
21
+ padding: 0;
22
+ box-sizing: border-box;
23
+ }
24
+
25
+ body {
26
+ font-family: 'Fredoka One', cursive;
27
+ background: var(--darker);
28
+ overflow: hidden;
29
+ touch-action: none;
30
+ user-select: none;
31
+ -webkit-user-select: none;
32
+ }
33
+
34
+ #gameCanvas {
35
+ display: block;
36
+ width: 100vw;
37
+ height: 100vh;
38
+ }
39
+
40
+ .screen {
41
+ position: fixed;
42
+ top: 0;
43
+ left: 0;
44
+ width: 100%;
45
+ height: 100%;
46
+ display: flex;
47
+ flex-direction: column;
48
+ align-items: center;
49
+ justify-content: center;
50
+ background: linear-gradient(135deg, var(--darker) 0%, #16213e 50%, var(--darker) 100%);
51
+ z-index: 100;
52
+ opacity: 0;
53
+ pointer-events: none;
54
+ transition: opacity 0.5s ease;
55
+ }
56
+
57
+ .screen.active {
58
+ opacity: 1;
59
+ pointer-events: all;
60
+ }
61
+
62
+ .title {
63
+ font-family: 'Orbitron', sans-serif;
64
+ font-size: clamp(2rem, 8vw, 5rem);
65
+ font-weight: 900;
66
+ background: linear-gradient(180deg, var(--primary) 0%, var(--primary-light) 100%);
67
+ -webkit-background-clip: text;
68
+ -webkit-text-fill-color: transparent;
69
+ background-clip: text;
70
+ text-shadow: 0 0 40px rgba(247, 147, 26, 0.5);
71
+ margin-bottom: 1rem;
72
+ animation: pulse 2s ease-in-out infinite;
73
+ }
74
+
75
+ @keyframes pulse {
76
+ 0%, 100% { transform: scale(1); }
77
+ 50% { transform: scale(1.05); }
78
+ }
79
+
80
+ .subtitle {
81
+ font-family: 'Orbitron', sans-serif;
82
+ font-size: clamp(0.8rem, 3vw, 1.5rem);
83
+ color: var(--secondary);
84
+ margin-bottom: 2rem;
85
+ letter-spacing: 0.3em;
86
+ }
87
+
88
+ .btn {
89
+ font-family: 'Fredoka One', cursive;
90
+ font-size: clamp(1rem, 4vw, 1.5rem);
91
+ padding: 1rem 3rem;
92
+ border: none;
93
+ border-radius: 50px;
94
+ cursor: pointer;
95
+ transition: all 0.3s ease;
96
+ position: relative;
97
+ overflow: hidden;
98
+ }
99
+
100
+ .btn-primary {
101
+ background: linear-gradient(135deg, var(--primary) 0%, var(--primary-light) 100%);
102
+ color: var(--dark);
103
+ box-shadow: 0 10px 30px rgba(247, 147, 26, 0.4);
104
+ }
105
+
106
+ .btn-primary:hover {
107
+ transform: translateY(-5px) scale(1.05);
108
+ box-shadow: 0 20px 40px rgba(247, 147, 26, 0.6);
109
+ }
110
+
111
+ .btn-primary:active {
112
+ transform: translateY(0) scale(0.98);
113
+ }
114
+
115
+ .dino-preview {
116
+ width: clamp(120px, 30vw, 200px);
117
+ height: clamp(120px, 30vw, 200px);
118
+ margin: 2rem 0;
119
+ animation: bounce 1s ease-in-out infinite;
120
+ }
121
+
122
+ @keyframes bounce {
123
+ 0%, 100% { transform: translateY(0); }
124
+ 50% { transform: translateY(-20px); }
125
+ }
126
+
127
+ .hud {
128
+ position: fixed;
129
+ top: 0;
130
+ left: 0;
131
+ width: 100%;
132
+ padding: 1rem;
133
+ display: flex;
134
+ justify-content: space-between;
135
+ align-items: flex-start;
136
+ z-index: 50;
137
+ pointer-events: none;
138
+ }
139
+
140
+ .hud-item {
141
+ background: rgba(15, 15, 26, 0.85);
142
+ backdrop-filter: blur(10px);
143
+ border: 2px solid rgba(247, 147, 26, 0.3);
144
+ border-radius: 15px;
145
+ padding: 0.75rem 1.25rem;
146
+ color: var(--light);
147
+ }
148
+
149
+ .hud-label {
150
+ font-size: 0.7rem;
151
+ color: rgba(255, 255, 255, 0.6);
152
+ text-transform: uppercase;
153
+ letter-spacing: 0.1em;
154
+ }
155
+
156
+ .hud-value {
157
+ font-family: 'Orbitron', sans-serif;
158
+ font-size: clamp(1rem, 3vw, 1.5rem);
159
+ font-weight: 700;
160
+ }
161
+
162
+ .hud-value.btc {
163
+ color: var(--primary);
164
+ }
165
+
166
+ .hud-value.distance {
167
+ color: var(--secondary);
168
+ }
169
+
170
+ .powerup-indicator {
171
+ position: fixed;
172
+ top: 50%;
173
+ left: 50%;
174
+ transform: translate(-50%, -50%);
175
+ font-family: 'Orbitron', sans-serif;
176
+ font-size: 2rem;
177
+ font-weight: 900;
178
+ color: var(--primary);
179
+ text-shadow: 0 0 20px var(--primary);
180
+ opacity: 0;
181
+ z-index: 60;
182
+ pointer-events: none;
183
+ }
184
+
185
+ .powerup-indicator.active {
186
+ animation: powerupPopup 1s ease-out forwards;
187
+ }
188
+
189
+ @keyframes powerupPopup {
190
+ 0% { opacity: 0; transform: translate(-50%, -50%) scale(0.5); }
191
+ 20% { opacity: 1; transform: translate(-50%, -50%) scale(1.2); }
192
+ 80% { opacity: 1; transform: translate(-50%, -60%) scale(1); }
193
+ 100% { opacity: 0; transform: translate(-50%, -80%) scale(0.8); }
194
+ }
195
+
196
+ .controls-hint {
197
+ position: fixed;
198
+ bottom: 2rem;
199
+ left: 50%;
200
+ transform: translateX(-50%);
201
+ display: flex;
202
+ gap: 1rem;
203
+ z-index: 50;
204
+ opacity: 0;
205
+ transition: opacity 0.5s;
206
+ }
207
+
208
+ .controls-hint.visible {
209
+ opacity: 1;
210
+ }
211
+
212
+ .control-key {
213
+ width: 60px;
214
+ height: 60px;
215
+ background: rgba(15, 15, 26, 0.8);
216
+ border: 2px solid rgba(255, 255, 255, 0.2);
217
+ border-radius: 15px;
218
+ display: flex;
219
+ align-items: center;
220
+ justify-content: center;
221
+ font-size: 1.5rem;
222
+ color: var(--light);
223
+ }
224
+
225
+ .game-over-stats {
226
+ display: flex;
227
+ flex-direction: column;
228
+ gap: 1rem;
229
+ margin: 2rem 0;
230
+ }
231
+
232
+ .stat-row {
233
+ display: flex;
234
+ justify-content: space-between;
235
+ gap: 2rem;
236
+ font-size: clamp(1rem, 3vw, 1.3rem);
237
+ }
238
+
239
+ .stat-label {
240
+ color: rgba(255, 255, 255, 0.6);
241
+ }
242
+
243
+ .stat-value {
244
+ font-family: 'Orbitron', sans-serif;
245
+ font-weight: 700;
246
+ }
247
+
248
+ .stat-value.btc {
249
+ color: var(--primary);
250
+ }
251
+
252
+ .high-score {
253
+ color: var(--primary-light);
254
+ }
255
+
256
+ .paused-overlay {
257
+ position: fixed;
258
+ top: 0;
259
+ left: 0;
260
+ width: 100%;
261
+ height: 100%;
262
+ background: rgba(15, 15, 26, 0.9);
263
+ display: flex;
264
+ align-items: center;
265
+ justify-content: center;
266
+ z-index: 90;
267
+ opacity: 0;
268
+ pointer-events: none;
269
+ transition: opacity 0.3s;
270
+ }
271
+
272
+ .paused-overlay.active {
273
+ opacity: 1;
274
+ pointer-events: all;
275
+ }
276
+
277
+ .paused-text {
278
+ font-family: 'Orbitron', sans-serif;
279
+ font-size: 3rem;
280
+ color: var(--light);
281
+ animation: pausePulse 1.5s ease-in-out infinite;
282
+ }
283
+
284
+ @keyframes pausePulse {
285
+ 0%, 100% { opacity: 1; }
286
+ 50% { opacity: 0.5; }
287
+ }
288
+
289
+ /* Built with anycoder */
290
+ .branding {
291
+ position: fixed;
292
+ bottom: 0.5rem;
293
+ left: 50%;
294
+ transform: translateX(-50%);
295
+ font-size: 0.7rem;
296
+ color: rgba(255, 255, 255, 0.3);
297
+ z-index: 200;
298
+ text-decoration: none;
299
+ transition: color 0.3s;
300
+ }
301
+
302
+ .branding:hover {
303
+ color: var(--primary);
304
+ }
305
+ </style>
306
+ </head>
307
+ <body>
308
+ <canvas id="gameCanvas"></canvas>
309
+
310
+ <!-- Start Screen -->
311
+ <div id="startScreen" class="screen active">
312
+ <div class="title">DINO RUN</div>
313
+ <div class="subtitle">BITCOIN EDITION</div>
314
+ <canvas id="dinoPreview" class="dino-preview" width="200" height="200"></canvas>
315
+ <button class="btn btn-primary" id="startBtn">START GAME</button>
316
+ <div style="margin-top: 2rem; color: rgba(255,255,255,0.4); font-size: 0.9rem;">
317
+ Use Arrow Keys or Touch to Play
318
+ </div>
319
+ </div>
320
+
321
+ <!-- HUD -->
322
+ <div id="hud" class="hud" style="display: none;">
323
+ <div class="hud-item">
324
+ <div class="hud-label">Balance</div>
325
+ <div class="hud-value btc" id="btcBalance">₿ 0.00000000</div>
326
+ </div>
327
+ <div class="hud-item">
328
+ <div class="hud-label">Distance</div>
329
+ <div class="hud-value distance" id="distanceDisplay">0m</div>
330
+ </div>
331
+ <div class="hud-item">
332
+ <div class="hud-label">Speed</div>
333
+ <div class="hud-value" id="speedDisplay">100%</div>
334
+ </div>
335
+ </div>
336
+
337
+ <!-- Power-up Indicator -->
338
+ <div id="powerupIndicator" class="powerup-indicator"></div>
339
+
340
+ <!-- Paused Overlay -->
341
+ <div id="pausedOverlay" class="paused-overlay">
342
+ <div class="paused-text">PAUSED</div>
343
+ </div>
344
+
345
+ <!-- Game Over Screen -->
346
+ <div id="gameOverScreen" class="screen">
347
+ <div class="title" style="font-size: clamp(2rem, 6vw, 3.5rem);">GAME OVER</div>
348
+ <div class="game-over-stats">
349
+ <div class="stat-row">
350
+ <span class="stat-label">Distance</span>
351
+ <span class="stat-value" id="finalDistance">0m</span>
352
+ </div>
353
+ <div class="stat-row">
354
+ <span class="stat-label">BTC Earned</span>
355
+ <span class="stat-value btc" id="finalBtc">₿ 0.00000000</span>
356
+ </div>
357
+ <div class="stat-row">
358
+ <span class="stat-label">High Score</span>
359
+ <span class="stat-value high-score" id="highScore">0m</span>
360
+ </div>
361
+ </div>
362
+ <button class="btn btn-primary" id="restartBtn">PLAY AGAIN</button>
363
+ </div>
364
+
365
+ <a href="https://huggingface.co/spaces/akhaliq/anycoder" target="_blank" class="branding">Built with anycoder</a>
366
+
367
+ <script>
368
+ // =====================================================
369
+ // GAME CONFIGURATION
370
+ // =====================================================
371
+ const CONFIG = {
372
+ LANES: 3,
373
+ LANE_WIDTH: 120,
374
+ INITIAL_SPEED: 400,
375
+ MAX_SPEED: 1200,
376
+ SPEED_INCREMENT: 5,
377
+ GRAVITY: 1800,
378
+ JUMP_FORCE: 700,
379
+ GROUND_Y: 0,
380
+
381
+ // Bitcoin values
382
+ BASE_COIN_VALUE: 0.00000001,
383
+ BONUS_COIN_VALUE: 0.00000005,
384
+ COIN_SPAWN_CHANCE: 0.7,
385
+ BONUS_COIN_CHANCE: 0.1,
386
+
387
+ // Power-up durations (seconds)
388
+ MAGNET_DURATION: 8,
389
+ SHIELD_DURATION: 5,
390
+ MULTIPLIER_DURATION: 10,
391
+ SPEEDBOOST_DURATION: 5,
392
+
393
+ // Spawn distances
394
+ MIN_OBSTACLE_DISTANCE: 300,
395
+ MAX_OBSTACLE_DISTANCE: 600,
396
+
397
+ // Colors
398
+ COLORS: {
399
+ primary: '#f7931a',
400
+ primaryLight: '#ffb347',
401
+ secondary: '#4ade80',
402
+ danger: '#ef4444',
403
+ sky: ['#1a1a2e', '#16213e', '#0f0f1a'],
404
+ ground: '#2d2d44',
405
+ laneMarker: '#3d3d5c'
406
+ }
407
+ };
408
+
409
+ // =====================================================
410
+ // GAME STATE
411
+ // =====================================================
412
+ const gameState = {
413
+ screen: 'start', // start, playing, paused, gameover
414
+ distance: 0,
415
+ btcBalance: 0,
416
+ speed: CONFIG.INITIAL_SPEED,
417
+ currentLane: 1,
418
+ targetLane: 1,
419
+ isJumping: false,
420
+ isSliding: false,
421
+ jumpVelocity: 0,
422
+ playerY: 0,
423
+ slideTimer: 0,
424
+ highScore: parseInt(localStorage.getItem('dinoRunHighScore')) || 0,
425
+
426
+ // Power-ups
427
+ activePowerups: {
428
+ magnet: 0,
429
+ shield: 0,
430
+ multiplier: 1,
431
+ speedboost: 0
432
+ },
433
+
434
+ // Game objects
435
+ obstacles: [],
436
+ coins: [],
437
+ powerups: [],
438
+ particles: [],
439
+
440
+ // Timing
441
+ lastTime: 0,
442
+ spawnTimer: 0,
443
+ obstacleSpawnDistance: 0,
444
+
445
+ // Screen shake
446
+ shakeIntensity: 0,
447
+ shakeDuration: 0
448
+ };
449
+
450
+ // =====================================================
451
+ // CANVAS SETUP
452
+ // =====================================================
453
+ const canvas = document.getElementById('gameCanvas');
454
+ const ctx = canvas.getContext('2d');
455
+ let canvasWidth, canvasHeight, centerX, groundY;
456
+
457
+ function resizeCanvas() {
458
+ canvasWidth = canvas.width = window.innerWidth;
459
+ canvasHeight = canvas.height = window.innerHeight;
460
+ centerX = canvasWidth / 2;
461
+ groundY = canvasHeight * 0.75;
462
+ CONFIG.LANE_WIDTH = Math.min(120, canvasWidth / 6);
463
+ CONFIG.GROUND_Y = groundY;
464
+ }
465
+
466
+ resizeCanvas();
467
+ window.addEventListener('resize', resizeCanvas);
468
+
469
+ // =====================================================
470
+ // UTILITY FUNCTIONS
471
+ // =====================================================
472
+ function lerp(a, b, t) {
473
+ return a + (b - a) * t;
474
+ }
475
+
476
+ function randomRange(min, max) {
477
+ return Math.random() * (max - min) + min;
478
+ }
479
+
480
+ function randomInt(min, max) {
481
+ return Math.floor(randomRange(min, max + 1));
482
+ }
483
+
484
+ function formatBTC(value) {
485
+ return '₿ ' + value.toFixed(8);
486
+ }
487
+
488
+ function screenShake(intensity, duration) {
489
+ gameState.shakeIntensity = intensity;
490
+ gameState.shakeDuration = duration;
491
+ }
492
+
493
+ // =====================================================
494
+ // PARTICLE SYSTEM
495
+ // =====================================================
496
+ class Particle {
497
+ constructor(x, y, color, type = 'default') {
498
+ this.x = x;
499
+ this.y = y;
500
+ this.color = color;
501
+ this.type = type;
502
+ this.life = 1;
503
+ this.decay = randomRange(0.02, 0.05);
504
+ this.vx = randomRange(-100, 100);
505
+ this.vy = randomRange(-200, -50);
506
+ this.size = randomRange(3, 8);
507
+
508
+ if (type === 'coin') {
509
+ this.vy = randomRange(-150, -50);
510
+ this.decay = 0.03;
511
+ } else if (type === 'trail') {
512
+ this.vx = randomRange(-30, 30);
513
+ this.vy = 0;
514
+ this.decay = 0.1;
515
+ this.size = randomRange(2, 5);
516
+ } else if (type === 'shield') {
517
+ this.decay = 0.02;
518
+ this.size = randomRange(4, 10);
519
+ }
520
+ }
521
+
522
+ update(dt) {
523
+ this.x += this.vx * dt;
524
+ this.y += this.vy * dt;
525
+ this.vy += 400 * dt; // gravity
526
+ this.life -= this.decay;
527
+ return this.life > 0;
528
+ }
529
+
530
+ draw() {
531
+ ctx.save();
532
+ ctx.globalAlpha = this.life;
533
+ ctx.fillStyle = this.color;
534
+ ctx.beginPath();
535
+ ctx.arc(this.x, this.y, this.size * this.life, 0, Math.PI * 2);
536
+ ctx.fill();
537
+ ctx.restore();
538
+ }
539
+ }
540
+
541
+ function createParticleBurst(x, y, color, count = 10, type = 'default') {
542
+ for (let i = 0; i < count; i++) {
543
+ gameState.particles.push(new Particle(x, y, color, type));
544
+ }
545
+ }
546
+
547
+ // =====================================================
548
+ // DINOSAUR PLAYER
549
+ // =====================================================
550
+ const dinosaur = {
551
+ x: 0,
552
+ y: 0,
553
+ width: 60,
554
+ height: 80,
555
+ lane: 1,
556
+ targetX: 0,
557
+ animFrame: 0,
558
+ animTimer: 0,
559
+ bounceOffset: 0,
560
+
561
+ draw() {
562
+ const targetX = (gameState.targetLane - 1) * CONFIG.LANE_WIDTH - CONFIG.LANE_WIDTH;
563
+ this.x = lerp(this.x, targetX, 0.15);
564
+
565
+ const bounce = gameState.isJumping ? 0 : Math.sin(this.animFrame * 0.3) * 3;
566
+ const slideScale = gameState.isSliding ? 0.6 : 1;
567
+ const jumpOffset = gameState.playerY;
568
+
569
+ const drawX = centerX + this.x;
570
+ const drawY = groundY - this.height / 2 - 20 - jumpOffset + bounce;
571
+
572
+ // Running animation
573
+ this.animTimer += 1/60;
574
+ if (this.animTimer > 0.1) {
575
+ this.animTimer = 0;
576
+ this.animFrame++;
577
+ }
578
+
579
+ ctx.save();
580
+ ctx.translate(drawX, drawY);
581
+ ctx.scale(slideScale, slideScale);
582
+
583
+ // Shield effect
584
+ if (gameState.activePowerups.shield > 0) {
585
+ const shieldPulse = Math.sin(Date.now() * 0.01) * 0.2 + 0.8;
586
+ ctx.save();
587
+ ctx.globalAlpha = 0.3 * shieldPulse;
588
+ ctx.fillStyle = CONFIG.COLORS.secondary;
589
+ ctx.beginPath();
590
+ ctx.arc(0, 0, 60, 0, Math.PI * 2);
591
+ ctx.fill();
592
+ ctx.restore();
593
+
594
+ ctx.save();
595
+ ctx.globalAlpha = shieldPulse;
596
+ ctx.strokeStyle = CONFIG.COLORS.secondary;
597
+ ctx.lineWidth = 3;
598
+ ctx.beginPath();
599
+ ctx.arc(0, 0, 55, 0, Math.PI * 2);
600
+ ctx.stroke();
601
+ ctx.restore();
602
+ }
603
+
604
+ // Magnet effect - draw attraction line
605
+ if (gameState.activePowerups.magnet > 0) {
606
+ ctx.save();
607
+ ctx.strokeStyle = CONFIG.COLORS.primary;
608
+ ctx.globalAlpha = 0.3;
609
+ ctx.lineWidth = 2;
610
+ ctx.setLineDash([5, 5]);
611
+ ctx.beginPath();
612
+ ctx.arc(0, 0, 150, 0, Math.PI * 2);
613
+ ctx.stroke();
614
+ ctx.restore();
615
+ }
616
+
617
+ // Shadow
618
+ ctx.save();
619
+ ctx.fillStyle = 'rgba(0,0,0,0.3)';
620
+ ctx.beginPath();
621
+ ctx.ellipse(0, this.height/2 + 5 - jumpOffset, 25, 8, 0, 0, Math.PI * 2);
622
+ ctx.fill();
623
+ ctx.restore();
624
+
625
+ // Body
626
+ ctx.fillStyle = '#22c55e';
627
+ ctx.beginPath();
628
+ ctx.ellipse(0, 0, 28, 35, 0, 0, Math.PI * 2);
629
+ ctx.fill();
630
+
631
+ // Belly
632
+ ctx.fillStyle = '#86efac';
633
+ ctx.beginPath();
634
+ ctx.ellipse(0, 8, 18, 22, 0, 0, Math.PI * 2);
635
+ ctx.fill();
636
+
637
+ // Head
638
+ ctx.fillStyle = '#22c55e';
639
+ ctx.beginPath();
640
+ ctx.ellipse(0, -35, 22, 20, 0, 0, Math.PI * 2);
641
+ ctx.fill();
642
+
643
+ // Snout
644
+ ctx.fillStyle = '#86efac';
645
+ ctx.beginPath();
646
+ ctx.ellipse(8, -32, 12, 10, 0.2, 0, Math.PI * 2);
647
+ ctx.fill();
648
+
649
+ // Nostril
650
+ ctx.fillStyle = '#166534';
651
+ ctx.beginPath();
652
+ ctx.arc(14, -34, 2, 0, Math.PI * 2);
653
+ ctx.fill();
654
+
655
+ // Eye white
656
+ ctx.fillStyle = '#ffffff';
657
+ ctx.beginPath();
658
+ ctx.ellipse(-5, -40, 10, 12, 0, 0, Math.PI * 2);
659
+ ctx.fill();
660
+
661
+ // Eye pupil
662
+ ctx.fillStyle = '#1a1a2e';
663
+ ctx.beginPath();
664
+ ctx.ellipse(-3, -38, 6, 7, 0, 0, Math.PI * 2);
665
+ ctx.fill();
666
+
667
+ // Eye shine
668
+ ctx.fillStyle = '#ffffff';
669
+ ctx.beginPath();
670
+ ctx.arc(-6, -42, 3, 0, Math.PI * 2);
671
+ ctx.fill();
672
+
673
+ // Headband
674
+ ctx.fillStyle = '#dc2626';
675
+ ctx.fillRect(-24, -52, 48, 8);
676
+
677
+ // Bitcoin symbol on headband
678
+ ctx.fillStyle = CONFIG.COLORS.primary;
679
+ ctx.font = 'bold 12px Arial';
680
+ ctx.textAlign = 'center';
681
+ ctx.fillText('₿', 0, -45);
682
+
683
+ // Legs
684
+ const legOffset = gameState.isJumping ? 0 : Math.sin(this.animFrame * 0.4) * 8;
685
+ const legOffset2 = gameState.isJumping ? 0 : Math.sin(this.animFrame * 0.4 + Math.PI) * 8;
686
+
687
+ // Back leg
688
+ ctx.fillStyle = '#16a34a';
689
+ ctx.beginPath();
690
+ ctx.ellipse(-12 + legOffset2, 30, 8, 15, -0.2, 0, Math.PI * 2);
691
+ ctx.fill();
692
+
693
+ // Front leg
694
+ ctx.fillStyle = '#16a34a';
695
+ ctx.beginPath();
696
+ ctx.ellipse(12 + legOffset, 30, 8, 15, 0.2, 0, Math.PI * 2);
697
+ ctx.fill();
698
+
699
+ // Arms
700
+ const armOffset = gameState.isJumping ? 0 : Math.sin(this.animFrame * 0.4 + Math.PI/2) * 5;
701
+ ctx.fillStyle = '#22c55e';
702
+
703
+ // Left arm
704
+ ctx.beginPath();
705
+ ctx.ellipse(-25 + armOffset, -5, 6, 12, -0.5, 0, Math.PI * 2);
706
+ ctx.fill();
707
+
708
+ // Right arm
709
+ ctx.beginPath();
710
+ ctx.ellipse(25 - armOffset, -5, 6, 12, 0.5, 0, Math.PI * 2);
711
+ ctx.fill();
712
+
713
+ ctx.restore();
714
+
715
+ // Running particles
716
+ if (!gameState.isJumping && Math.random() > 0.7) {
717
+ gameState.particles.push(new Particle(
718
+ drawX + randomRange(-10, 10),
719
+ groundY - 10,
720
+ 'rgba(100, 100, 150, 0.5)',
721
+ 'trail'
722
+ ));
723
+ }
724
+ }
725
+ };
726
+
727
+ // =====================================================
728
+ // OBSTACLES
729
+ // =====================================================
730
+ class Obstacle {
731
+ constructor(lane, type) {
732
+ this.lane = lane;
733
+ this.type = type;
734
+ this.x = (lane - 1) * CONFIG.LANE_WIDTH - CONFIG.LANE_WIDTH;
735
+ this.z = 2000; // Far away
736
+ this.passed = false;
737
+
738
+ if (type === 'low') {
739
+ this.height = 40;
740
+ this.width = 50;
741
+ } else if (type === 'high') {
742
+ this.height = 70;
743
+ this.width = 50;
744
+ } else if (type === 'gap') {
745
+ this.height = 20;
746
+ this.width = CONFIG.LANE_WIDTH * 0.8;
747
+ } else {
748
+ this.height = 60;
749
+ this.width = 40;
750
+ }
751
+ }
752
+
753
+ update(dt, speed) {
754
+ this.z -= speed * dt;
755
+ return this.z > -100;
756
+ }
757
+
758
+ draw() {
759
+ const perspective = 1 - (this.z / 2000);
760
+ const scale = perspective * 0.8 + 0.2;
761
+ const drawX = centerX + this.x * scale;
762
+ const drawY = groundY - this.height * scale * 0.5;
763
+ const drawWidth = this.width * scale;
764
+ const drawHeight = this.height * scale;
765
+
766
+ if (this.type === 'gap') {
767
+ // Draw gap/hole
768
+ ctx.fillStyle = '#0f0f1a';
769
+ ctx.fillRect(drawX - drawWidth/2, drawY, drawWidth, 10 * scale);
770
+ return;
771
+ }
772
+
773
+ // Shadow
774
+ ctx.fillStyle = 'rgba(0,0,0,0.3)';
775
+ ctx.beginPath();
776
+ ctx.ellipse(drawX, groundY - 5, drawWidth/2, 8 * scale, 0, 0, Math.PI * 2);
777
+ ctx.fill();
778
+
779
+ // Main body
780
+ if (this.type === 'low') {
781
+ // Barrier (duck under)
782
+ ctx.fillStyle = '#dc2626';
783
+ ctx.fillRect(drawX - drawWidth/2, drawY - drawHeight/2, drawWidth, drawHeight);
784
+
785
+ // Stripe
786
+ ctx.fillStyle = '#fbbf24';
787
+ ctx.fillRect(drawX - drawWidth/2, drawY - drawHeight/4, drawWidth, drawHeight/4);
788
+
789
+ // Warning symbol
790
+ ctx.fillStyle = '#ffffff';
791
+ ctx.font = `bold ${14 * scale}px Arial`;
792
+ ctx.textAlign = 'center';
793
+ ctx.fillText('!', drawX, drawY + 5 * scale);
794
+ } else if (this.type === 'high') {
795
+ // High barrier (jump over)
796
+ ctx.fillStyle = '#7c3aed';
797
+ ctx.fillRect(drawX - drawWidth/2, drawY - drawHeight, drawWidth, drawHeight);
798
+
799
+ // Top cone
800
+ ctx.fillStyle = '#a78bfa';
801
+ ctx.beginPath();
802
+ ctx.moveTo(drawX - drawWidth/2, drawY - drawHeight);
803
+ ctx.lineTo(drawX + drawWidth/2, drawY - drawHeight);
804
+ ctx.lineTo(drawX, drawY - drawHeight - 20 * scale);
805
+ ctx.fill();
806
+ } else {
807
+ // Regular obstacle
808
+ ctx.fillStyle = '#ef4444';
809
+ ctx.beginPath();
810
+ ctx.roundRect(drawX - drawWidth/2, drawY - drawHeight, drawWidth, drawHeight, 8 * scale);
811
+ ctx.fill();
812
+
813
+ // Detail
814
+ ctx.fillStyle = '#fca5a5';
815
+ ctx.fillRect(drawX - drawWidth/4, drawY - drawHeight + 10*scale, drawWidth/2, 10*scale);
816
+ }
817
+ }
818
+ }
819
+
820
+ // =====================================================
821
+ // BITCOIN COINS
822
+ // =====================================================
823
+ class Coin {
824
+ constructor(lane, value, isBonus = false) {
825
+ this.lane = lane;
826
+ this.x = (lane - 1) * CONFIG.LANE_WIDTH - CONFIG.LANE_WIDTH;
827
+ this.z = 2000;
828
+ this.value = value;
829
+ this.isBonus = isBonus;
830
+ this.rotation = 0;
831
+ this.collected = false;
832
+ this.size = isBonus ? 25 : 20;
833
+ this.glowIntensity = 0;
834
+ }
835
+
836
+ update(dt, speed) {
837
+ this.z -= speed * dt;
838
+ this.rotation += dt * 5;
839
+ this.glowIntensity = (Math.sin(Date.now() * 0.005) + 1) / 2;
840
+
841
+ // Magnet effect
842
+ if (gameState.activePowerups.magnet > 0 && !this.collected) {
843
+ const playerX = (gameState.targetLane - 1) * CONFIG.LANE_WIDTH - CONFIG.LANE_WIDTH;
844
+ const dx = playerX - this.x;
845
+ if (Math.abs(dx) < CONFIG.LANE_WIDTH) {
846
+ this.x += dx * dt * 5;
847
+ }
848
+ }
849
+
850
+ return this.z > -50 && !this.collected;
851
+ }
852
+
853
+ draw() {
854
+ const perspective = 1 - (this.z / 2000);
855
+ const scale = perspective * 0.8 + 0.2;
856
+ const drawX = centerX + this.x * scale;
857
+ const drawY = groundY - 40 * scale;
858
+
859
+ // Spin effect (scale X based on rotation)
860
+ const spinScale = Math.abs(Math.cos(this.rotation));
861
+ const drawWidth = this.size * scale * spinScale;
862
+ const drawHeight = this.size * scale;
863
+
864
+ if (drawWidth < 2) return; // Don't draw when coin is edge-on
865
+
866
+ // Glow
867
+ if (this.isBonus) {
868
+ ctx.save();
869
+ ctx.shadowColor = CONFIG.COLORS.primary;
870
+ ctx.shadowBlur = 20 + this.glowIntensity * 10;
871
+ ctx.fillStyle = CONFIG.COLORS.primary;
872
+ ctx.beginPath();
873
+ ctx.ellipse(drawX, drawY, drawWidth, drawHeight, 0, 0, Math.PI * 2);
874
+ ctx.fill();
875
+ ctx.restore();
876
+ } else {
877
+ ctx.save();
878
+ ctx.globalAlpha = 0.5 + this.glowIntensity * 0.3;
879
+ ctx.fillStyle = CONFIG.COLORS.primary;
880
+ ctx.beginPath();
881
+ ctx.ellipse(drawX, drawY, drawWidth + 5, drawHeight + 5, 0, 0, Math.PI * 2);
882
+ ctx.fill();
883
+ ctx.restore();
884
+ }
885
+
886
+ // Main coin
887
+ ctx.fillStyle = this.isBonus ? '#ffd700' : CONFIG.COLORS.primary;
888
+ ctx.beginPath();
889
+ ctx.ellipse(drawX, drawY, drawWidth, drawHeight, 0, 0, Math.PI * 2);
890
+ ctx.fill();
891
+
892
+ // Inner circle
893
+ ctx.fillStyle = this.isBonus ? '#b8860b' : '#d97706';
894
+ ctx.beginPath();
895
+ ctx.ellipse(drawX, drawY, drawWidth * 0.75, drawHeight * 0.75, 0, 0, Math.PI * 2);
896
+ ctx.fill();
897
+
898
+ // Bitcoin symbol
899
+ if (drawWidth > 8) {
900
+ ctx.fillStyle = this.isBonus ? '#ffd700' : '#f7931a';
901
+ ctx.font = `bold ${10 * scale * spinScale}px Arial`;
902
+ ctx.textAlign = 'center';
903
+ ctx.textBaseline = 'middle';
904
+ ctx.fillText('₿', drawX, drawY);
905
+ }
906
+ }
907
+
908
+ collect() {
909
+ if (this.collected) return false;
910
+ this.collected = true;
911
+
912
+ const multiplier = gameState.activePowerups.multiplier;
913
+ const earned = this.value * multiplier;
914
+ gameState.btcBalance += earned;
915
+
916
+ // Particles
917
+ createParticleBurst(
918
+ centerX + this.x * 0.8,
919
+ groundY - 40,
920
+ this.isBonus ? '#ffd700' : CONFIG.COLORS.primary,
921
+ 15,
922
+ 'coin'
923
+ );
924
+
925
+ return true;
926
+ }
927
+ }
928
+
929
+ // =====================================================
930
+ // POWER-UPS
931
+ // =====================================================
932
+ class PowerUp {
933
+ constructor(lane, type) {
934
+ this.lane = lane;
935
+ this.x = (lane - 1) * CONFIG.LANE_WIDTH - CONFIG.LANE_WIDTH;
936
+ this.z = 2000;
937
+ this.type = type;
938
+ this.collected = false;
939
+ this.bobOffset = 0;
940
+ }
941
+
942
+ update(dt, speed) {
943
+ this.z -= speed * dt;
944
+ this.bobOffset = Math.sin(Date.now() * 0.005) * 10;
945
+ return this.z > -50 && !this.collected;
946
+ }
947
+
948
+ draw() {
949
+ const perspective = 1 - (this.z / 2000);
950
+ const scale = perspective * 0.8 + 0.2;
951
+ const drawX = centerX + this.x * scale;
952
+ const drawY = groundY - 50 * scale + this.bobOffset * scale;
953
+ const size = 30 * scale;
954
+
955
+ // Glow
956
+ ctx.save();
957
+ ctx.shadowColor = this.getColor();
958
+ ctx.shadowBlur = 20;
959
+ ctx.fillStyle = this.getColor();
960
+ ctx.beginPath();
961
+ ctx.arc(drawX, drawY, size, 0, Math.PI * 2);
962
+ ctx.fill();
963
+ ctx.restore();
964
+
965
+ // Inner circle
966
+ ctx.fillStyle = '#ffffff';
967
+ ctx.beginPath();
968
+ ctx.arc(drawX, drawY, size * 0.7, 0, Math.PI * 2);
969
+ ctx.fill();
970
+
971
+ // Icon
972
+ ctx.fillStyle = this.getColor();
973
+ ctx.font = `bold ${18 * scale}px Arial`;
974
+ ctx.textAlign = 'center';
975
+ ctx.textBaseline = 'middle';
976
+ ctx.fillText(this.getIcon(), drawX, drawY);
977
+ }
978
+
979
+ getColor() {
980
+ switch(this.type) {
981
+ case 'magnet': return CONFIG.COLORS.primary;
982
+ case 'shield': return CONFIG.COLORS.secondary;
983
+ case 'multiplier': return '#a855f7';
984
+ case 'speedboost': return '#3b82f6';
985
+ default: return '#ffffff';
986
+ }
987
+ }
988
+
989
+ getIcon() {
990
+ switch(this.type) {
991
+ case 'magnet': return '🧲';
992
+ case 'shield': return '🛡️';
993
+ case 'multiplier': return '✖️';
994
+ case 'speedboost': return '⚡';
995
+ default: return '?';
996
+ }
997
+ }
998
+
999
+ collect() {
1000
+ if (this.collected) return;
1001
+ this.collected = true;
1002
+
1003
+ // Activate power-up
1004
+ switch(this.type) {
1005
+ case 'magnet':
1006
+ gameState.activePowerups.magnet = CONFIG.MAGNET_DURATION;
1007
+ break;
1008
+ case 'shield':
1009
+ gameState.activePowerups.shield = CONFIG.SHIELD_DURATION;
1010
+ break;
1011
+ case 'multiplier':
1012
+ gameState.activePowerups.multiplier = 2;
1013
+ gameState.activePowerups.multiplier = CONFIG.MULTIPLIER_DURATION;
1014
+ break;
1015
+ case 'speedboost':
1016
+ gameState.activePowerups.speedboost = CONFIG.SPEEDBOOST_DURATION;
1017
+ break;
1018
+ }
1019
+
1020
+ showPowerupText(this.getText());
1021
+ createParticleBurst(centerX, groundY - 50, this.getColor(), 20, 'shield');
1022
+ }
1023
+
1024
+ getText() {
1025
+ switch(this.type) {
1026
+ case 'magnet': return 'MAGNET!';
1027
+ case 'shield': return 'SHIELD!';
1028
+ case 'multiplier': return '2X BTC!';
1029
+ case 'speedboost': return 'SPEED BOOST!';
1030
+ default: return 'POWER UP!';
1031
+ }
1032
+ }
1033
+ }
1034
+
1035
+ // =====================================================
1036
+ // BACKGROUND & ENVIRONMENT
1037
+ // =====================================================
1038
+ function drawBackground() {
1039
+ // Sky gradient
1040
+ const gradient = ctx.createLinearGradient(0, 0, 0, canvasHeight);
1041
+ gradient.addColorStop(0, '#0f0f1a');
1042
+ gradient.addColorStop(0.5, '#1a1a2e');
1043
+ gradient.addColorStop(1, '#16213e');
1044
+ ctx.fillStyle = gradient;
1045
+ ctx.fillRect(0, 0, canvasWidth, canvasHeight);
1046
+
1047
+ // Stars
1048
+ ctx.fillStyle = '#ffffff';
1049
+ for (let i = 0; i < 50; i++) {
1050
+ const x = (i * 137.5) % canvasWidth;
1051
+ const y = (i * 73.3) % (canvasHeight * 0.5);
1052
+ const size = (i % 3) + 1;
1053
+ const alpha = 0.3 + Math.sin(Date.now() * 0.001 + i) * 0.2;
1054
+ ctx.globalAlpha = alpha;
1055
+ ctx.beginPath();
1056
+ ctx.arc(x, y, size, 0, Math.PI * 2);
1057
+ ctx.fill();
1058
+ }
1059
+ ctx.globalAlpha = 1;
1060
+
1061
+ // Distant mountains (parallax layer 1)
1062
+ const mountainOffset = (gameState.distance * 0.1) % canvasWidth;
1063
+ ctx.fillStyle = '#1e1e3f';
1064
+ ctx.beginPath();
1065
+ ctx.moveTo(0, groundY);
1066
+ for (let x = 0; x <= canvasWidth; x += 50) {
1067
+ const height = Math.sin((x + mountainOffset) * 0.01) * 80 +
1068
+ Math.sin((x + mountainOffset) * 0.02) * 40;
1069
+ ctx.lineTo(x, groundY - 100 - height);
1070
+ }
1071
+ ctx.lineTo(canvasWidth, groundY);
1072
+ ctx.fill();
1073
+
1074
+ // Mid mountains (parallax layer 2)
1075
+ const midOffset = (gameState.distance * 0.3) % canvasWidth;
1076
+ ctx.fillStyle = '#252547';
1077
+ ctx.beginPath();
1078
+ ctx.moveTo(0, groundY);
1079
+ for (let x = 0; x <= canvasWidth; x += 30) {
1080
+ const height = Math.sin((x + midOffset) * 0.015) * 60 +
1081
+ Math.sin((x + midOffset) * 0.03) * 30;
1082
+ ctx.lineTo(x, groundY - 60 - height);
1083
+ }
1084
+ ctx.lineTo(canvasWidth, groundY);
1085
+ ctx.fill();
1086
+ }
1087
+
1088
+ function drawGround() {
1089
+ // Main ground
1090
+ ctx.fillStyle = CONFIG.COLORS.ground;
1091
+ ctx.fillRect(0, groundY, canvasWidth, canvasHeight - groundY);
1092
+
1093
+ // Lane lines
1094
+ const lineOffset = (gameState.distance * 2) % CONFIG.LANE_WIDTH;
1095
+ ctx.strokeStyle = CONFIG.COLORS.laneMarker;
1096
+ ctx.lineWidth = 2;
1097
+
1098
+ for (let i = -1; i <= 3; i++) {
1099
+ const x = centerX + (i - 1) * CONFIG.LANE_WIDTH - lineOffset;
1100
+ ctx.beginPath();
1101
+ ctx.moveTo(x, groundY);
1102
+ ctx.lineTo(x + CONFIG.LANE_WIDTH * 0.5, canvasHeight);
1103
+ ctx.stroke();
1104
+ }
1105
+
1106
+ // Ground line
1107
+ ctx.strokeStyle = CONFIG.COLORS.primary;
1108
+ ctx.lineWidth = 3;
1109
+ ctx.beginPath();
1110
+ ctx.moveTo(0, groundY);
1111
+ ctx.lineTo(canvasWidth, groundY);
1112
+ ctx.stroke();
1113
+
1114
+ // Speed lines
1115
+ if (gameState.speed > 600) {
1116
+ const intensity = (gameState.speed - 600) / 600;
1117
+ ctx.strokeStyle = `rgba(247, 147, 26, ${intensity * 0.3})`;
1118
+ ctx.lineWidth = 2;
1119
+ for (let i = 0; i < 10; i++) {
1120
+ const x = randomRange(0, canvasWidth);
1121
+ const y = randomRange(groundY, canvasHeight);
1122
+ const length = randomRange(20, 50) * intensity;
1123
+ ctx.beginPath();
1124
+ ctx.moveTo(x, y);
1125
+ ctx.lineTo(x, y + length);
1126
+ ctx.stroke();
1127
+ }
1128
+ }
1129
+ }
1130
+
1131
+ // =====================================================
1132
+ // SPAWNING SYSTEM
1133
+ // =====================================================
1134
+ function spawnObstacle() {
1135
+ const lane = randomInt(0, 2);
1136
+ const types = ['low', 'high', 'regular'];
1137
+ const type = types[randomInt(0, types.length - 1)];
1138
+
1139
+ gameState.obstacles.push(new Obstacle(lane, type));
1140
+
1141
+ // Spawn coins in other lanes
1142
+ for (let l = 0; l < 3; l++) {
1143
+ if (l !== lane && Math.random() < CONFIG.COIN_SPAWN_CHANCE) {
1144
+ const isBonus = Math.random() < CONFIG.BONUS_COIN_CHANCE;
1145
+ const value = isBonus ? CONFIG.BONUS_COIN_VALUE : CONFIG.BASE_COIN_VALUE;
1146
+
1147
+ // Spawn multiple coins in a row
1148
+ const coinCount = isBonus ? randomInt(3, 5) : randomInt(3, 7);
1149
+ for (let c = 0; c < coinCount; c++) {
1150
+ const coin = new Coin(l, value, isBonus);
1151
+ coin.z = 1800 - c * 40;
1152
+ gameState.coins.push(coin);
1153
+ }
1154
+ }
1155
+ }
1156
+
1157
+ // Chance to spawn power-up
1158
+ if (Math.random() < 0.08) {
1159
+ const powerTypes = ['magnet', 'shield', 'multiplier', 'speedboost'];
1160
+ const pType = powerTypes[randomInt(0, powerTypes.length - 1)];
1161
+
1162
+ // Find a lane without obstacle
1163
+ let pLane = lane;
1164
+ while (pLane === lane) {
1165
+ pLane = randomInt(0, 2);
1166
+ }
1167
+
1168
+ gameState.powerups.push(new PowerUp(pLane, pType));
1169
+ }
1170
+ }
1171
+
1172
+ // =====================================================
1173
+ // COLLISION DETECTION
1174
+ // =====================================================
1175
+ function checkCollisions() {
1176
+ const playerLane = gameState.targetLane;
1177
+
1178
+ // Check obstacles
1179
+ for (const obstacle of gameState.obstacles) {
1180
+ if (obstacle.z < 100 && obstacle.z > -50 &&
1181
+ obstacle.lane === playerLane && !obstacle.passed) {
1182
+
1183
+ // Check collision based on player state
1184
+ let collided = false;
1185
+
1186
+ if (obstacle.type === 'low' && !gameState.isSliding) {
1187
+ collided = true;
1188
+ } else if (obstacle.type === 'high' && !gameState.is