HAL1993 commited on
Commit
b95020a
·
verified ·
1 Parent(s): 172e467

Upload folder using huggingface_hub

Browse files
Files changed (1) hide show
  1. index.html +1057 -999
index.html CHANGED
@@ -2,119 +2,59 @@
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>Dino Runner - Bitcoin Edition</title>
 
7
  <style>
8
- @import url('https://fonts.googleapis.com/css2?family=Orbitron:wght@400;700;900&family=Rajdhani:wght@500;700&display=swap');
9
-
 
 
 
 
 
 
 
 
 
 
10
  * {
11
  margin: 0;
12
  padding: 0;
13
  box-sizing: border-box;
14
  }
15
-
16
- :root {
17
- --primary: #F7931A;
18
- --secondary: #1a1a2e;
19
- --accent: #00ff88;
20
- --danger: #ff4757;
21
- --gold: #ffd700;
22
- }
23
-
24
  body {
25
- background: linear-gradient(135deg, #0f0c29 0%, #302b63 50%, #24243e 100%);
 
26
  min-height: 100vh;
 
27
  display: flex;
28
  justify-content: center;
29
  align-items: center;
30
- font-family: 'Rajdhani', sans-serif;
31
- overflow: hidden;
32
  }
33
-
34
  #gameContainer {
35
  position: relative;
36
  width: 100%;
37
  max-width: 800px;
38
  aspect-ratio: 16/9;
39
- background: linear-gradient(180deg, #87CEEB 0%, #98D8C8 50%, #7CB342 100%);
40
  border-radius: 16px;
41
  overflow: hidden;
42
- box-shadow: 0 0 60px rgba(247, 147, 26, 0.3), 0 20px 60px rgba(0,0,0,0.5);
 
 
 
43
  }
44
-
45
- canvas {
46
- display: block;
47
- width: 100%;
48
- height: 100%;
49
- }
50
-
51
- .ui-overlay {
52
- position: absolute;
53
- top: 0;
54
- left: 0;
55
  width: 100%;
56
  height: 100%;
57
- pointer-events: none;
58
- display: flex;
59
- flex-direction: column;
60
- }
61
-
62
- .hud {
63
- display: flex;
64
- justify-content: space-between;
65
- padding: 15px 25px;
66
- background: linear-gradient(180deg, rgba(0,0,0,0.6) 0%, transparent 100%);
67
- }
68
-
69
- .hud-item {
70
- color: white;
71
- text-shadow: 2px 2px 4px rgba(0,0,0,0.8);
72
- font-family: 'Orbitron', monospace;
73
- }
74
-
75
- .hud-label {
76
- font-size: 10px;
77
- color: var(--primary);
78
- letter-spacing: 2px;
79
- margin-bottom: 2px;
80
- }
81
-
82
- .hud-value {
83
- font-size: 18px;
84
- font-weight: 700;
85
- }
86
-
87
- .hud-value.btc {
88
- color: var(--primary);
89
- font-size: 20px;
90
- }
91
-
92
- .powerup-bar {
93
- display: flex;
94
- gap: 10px;
95
- margin-top: 5px;
96
- }
97
-
98
- .powerup-indicator {
99
- width: 30px;
100
- height: 30px;
101
- border-radius: 50%;
102
- border: 2px solid white;
103
- display: flex;
104
- align-items: center;
105
- justify-content: center;
106
- font-size: 16px;
107
- opacity: 0.3;
108
- transition: all 0.3s;
109
- }
110
-
111
- .powerup-indicator.active {
112
- opacity: 1;
113
- transform: scale(1.1);
114
- box-shadow: 0 0 15px currentColor;
115
  }
116
-
117
- .screen {
118
  position: absolute;
119
  top: 0;
120
  left: 0;
@@ -124,1040 +64,1158 @@
124
  flex-direction: column;
125
  justify-content: center;
126
  align-items: center;
127
- background: rgba(0,0,0,0.85);
128
  backdrop-filter: blur(10px);
129
- pointer-events: auto;
130
- transition: opacity 0.5s;
131
  }
132
-
133
- .screen.hidden {
134
  opacity: 0;
 
135
  pointer-events: none;
136
  }
137
-
138
  .game-title {
139
- font-family: 'Orbitron', monospace;
140
- font-size: 48px;
141
- font-weight: 900;
142
- color: white;
143
- text-shadow: 0 0 30px var(--primary), 0 0 60px var(--primary);
144
- margin-bottom: 10px;
145
- animation: pulse 2s infinite;
146
- }
147
-
148
- .game-subtitle {
149
- font-size: 24px;
150
- color: var(--primary);
151
- margin-bottom: 40px;
152
- letter-spacing: 4px;
153
  }
154
-
155
- @keyframes pulse {
156
  0%, 100% { transform: scale(1); }
157
- 50% { transform: scale(1.05); }
158
  }
159
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
160
  .btn {
161
- padding: 18px 50px;
162
- font-family: 'Orbitron', monospace;
163
- font-size: 18px;
164
- font-weight: 700;
165
  border: none;
166
  border-radius: 50px;
167
  cursor: pointer;
168
- transition: all 0.3s;
169
- text-transform: uppercase;
170
- letter-spacing: 3px;
171
- margin: 10px;
172
  }
173
-
174
  .btn-primary {
175
- background: linear-gradient(135deg, var(--primary) 0%, #ff9f43 100%);
176
- color: white;
177
- box-shadow: 0 10px 30px rgba(247, 147, 26, 0.4);
178
  }
179
-
180
  .btn-primary:hover {
181
  transform: translateY(-3px) scale(1.05);
182
- box-shadow: 0 15px 40px rgba(247, 147, 26, 0.6);
183
  }
184
-
 
 
 
 
185
  .btn-secondary {
186
- background: transparent;
187
- color: white;
188
- border: 2px solid white;
 
189
  }
190
-
191
  .btn-secondary:hover {
192
- background: white;
193
- color: var(--secondary);
194
  }
195
-
196
- .controls-hint {
197
- margin-top: 40px;
198
- color: rgba(255,255,255,0.6);
199
- font-size: 14px;
200
- text-align: center;
201
- line-height: 2;
202
  }
203
-
204
- .controls-hint span {
205
- display: inline-block;
206
- background: rgba(255,255,255,0.1);
207
- padding: 5px 15px;
208
- border-radius: 5px;
209
- margin: 0 5px;
210
- font-family: 'Orbitron', monospace;
211
- font-size: 12px;
 
212
  }
213
-
214
- .dino-preview {
215
- width: 120px;
216
- height: 120px;
217
- margin-bottom: 30px;
218
- animation: bounce 1s infinite;
 
 
219
  }
220
-
221
- @keyframes bounce {
222
- 0%, 100% { transform: translateY(0); }
223
- 50% { transform: translateY(-20px); }
 
 
 
 
224
  }
225
-
226
- .final-score {
227
- font-family: 'Orbitron', monospace;
228
- font-size: 60px;
229
- color: var(--primary);
230
- text-shadow: 0 0 30px var(--primary);
231
- margin: 20px 0;
232
  }
233
-
234
- .final-stats {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
235
  color: white;
236
- font-size: 20px;
237
- margin-bottom: 30px;
238
- text-align: center;
239
- line-height: 1.8;
 
240
  }
241
-
242
- .combo-popup {
 
 
 
 
 
 
243
  position: absolute;
244
- font-family: 'Orbitron', monospace;
245
- font-weight: 900;
246
- font-size: 32px;
247
- color: var(--gold);
248
- text-shadow: 0 0 20px var(--gold), 2px 2px 0 #000;
249
- pointer-events: none;
250
- animation: comboFade 1s forwards;
 
251
  }
252
-
253
- @keyframes comboFade {
254
- 0% { opacity: 1; transform: translateY(0) scale(1); }
255
- 100% { opacity: 0; transform: translateY(-50px) scale(1.5); }
256
  }
257
-
258
- .pause-overlay {
 
259
  position: absolute;
260
- top: 50%;
261
- left: 50%;
262
- transform: translate(-50%, -50%);
263
- background: rgba(0,0,0,0.9);
264
- padding: 40px 60px;
265
- border-radius: 20px;
266
- text-align: center;
267
- pointer-events: auto;
268
  display: none;
 
 
 
269
  }
270
-
271
- .pause-overlay.visible {
272
- display: block;
 
 
273
  }
274
-
275
- .pause-title {
276
- font-family: 'Orbitron', monospace;
277
- font-size: 36px;
 
 
 
 
 
 
278
  color: white;
279
- margin-bottom: 30px;
 
 
 
280
  }
281
-
282
- .powered-by {
283
- position: absolute;
284
- bottom: 10px;
285
- left: 50%;
286
- transform: translateX(-50%);
287
- font-family: 'Rajdhani', sans-serif;
288
- font-size: 12px;
289
- color: rgba(255,255,255,0.5);
290
- text-decoration: none;
291
- transition: color 0.3s;
292
  }
293
-
294
- .powered-by:hover {
295
- color: var(--primary);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
296
  }
297
  </style>
298
  </head>
299
  <body>
300
  <div id="gameContainer">
 
 
301
  <canvas id="gameCanvas"></canvas>
302
 
303
- <div class="ui-overlay" id="uiOverlay">
304
- <div class="hud" id="hud" style="display: none;">
305
- <div class="hud-item">
306
- <div class="hud-label">DISTANCE</div>
307
- <div class="hud-value" id="distanceDisplay">0m</div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
308
  </div>
309
- <div class="hud-item">
310
- <div class="hud-label"> BALANCE</div>
311
- <div class="hud-value btc" id="btcDisplay">0.00000000</div>
312
  </div>
313
- <div class="hud-item">
314
- <div class="hud-label">SPEED</div>
315
- <div class="hud-value" id="speedDisplay">0 km/h</div>
316
  </div>
317
- <div class="powerup-bar">
318
- <div class="powerup-indicator" id="magnetInd" style="color: #00ffff;">🧲</div>
319
- <div class="powerup-indicator" id="shieldInd" style="color: #ff6b6b;">🛡️</div>
320
- <div class="powerup-indicator" id="multiInd" style="color: #ffd700;">✖️2</div>
321
  </div>
322
  </div>
 
 
323
  </div>
324
 
325
- <div class="screen" id="startScreen">
326
- <canvas class="dino-preview" id="dinoPreview"></canvas>
327
- <div class="game-title">DINO RUNNER</div>
328
- <div class="game-subtitle">BITCOIN EDITION</div>
329
- <button class="btn btn-primary" id="startBtn">START GAME</button>
330
- <div class="controls-hint">
331
- <span>← →</span> Move &nbsp;&nbsp;
332
- <span>↑</span> Jump &nbsp;&nbsp;
333
- <span>↓</span> Slide<br>
334
- <span>SPACE</span> Pause
335
  </div>
336
- </div>
337
-
338
- <div class="screen hidden" id="gameOverScreen">
339
- <div class="game-title" style="font-size: 36px;">GAME OVER</div>
340
- <div class="final-score" id="finalScore">0m</div>
341
- <div class="final-stats" id="finalStats">
342
- Distance: 0m<br>
343
- Coins: 0 (₿0.00000000)<br>
344
- Best: 0m
345
  </div>
346
- <button class="btn btn-primary" id="restartBtn">PLAY AGAIN</button>
347
- <button class="btn btn-secondary" id="menuBtn">MAIN MENU</button>
348
- </div>
349
-
350
- <div class="pause-overlay" id="pauseOverlay">
351
- <div class="pause-title">PAUSED</div>
352
- <button class="btn btn-primary" id="resumeBtn">RESUME</button>
353
  </div>
354
-
355
- <a href="https://huggingface.co/spaces/akhaliq/anycoder" target="_blank" class="powered-by">Built with anycoder</a>
356
  </div>
357
 
358
  <script>
359
- const canvas = document.getElementById('gameCanvas');
360
- const ctx = canvas.getContext('2d');
361
- const dinoPreviewCanvas = document.getElementById('dinoPreview');
362
- const dinoPreviewCtx = dinoPreviewCanvas.getContext('2d');
363
-
364
- dinoPreviewCanvas.width = 120;
365
- dinoPreviewCanvas.height = 120;
366
-
367
- let width, height;
368
-
369
- function resize() {
370
- const container = document.getElementById('gameContainer');
371
- width = container.clientWidth;
372
- height = container.clientHeight;
373
- canvas.width = width;
374
- canvas.height = height;
375
- }
376
-
377
- resize();
378
- window.addEventListener('resize', resize);
379
-
380
- // Game State
381
- const GameState = {
382
- START: 'start',
383
- PLAYING: 'playing',
384
- PAUSED: 'paused',
385
- GAMEOVER: 'gameover'
386
- };
387
-
388
- let gameState = GameState.START;
389
- let lastTime = 0;
390
- let deltaTime = 0;
391
-
392
- // Game Variables
393
- let distance = 0;
394
- let btcBalance = 0;
395
- let speed = 0;
396
- let baseSpeed = 400;
397
- let maxSpeed = 1200;
398
- let score = 0;
399
- let combo = 0;
400
- let comboTimer = 0;
401
- let bestDistance = parseFloat(localStorage.getItem('dinoBestDistance')) || 0;
402
-
403
- // Lanes
404
- const lanes = [-1, 0, 1];
405
- let currentLane = 1;
406
-
407
- // Player
408
- const player = {
409
- x: 0,
410
- y: 0,
411
- width: 60,
412
- height: 80,
413
- lane: 1,
414
- targetLane: 1,
415
- yVelocity: 0,
416
- isJumping: false,
417
- isSliding: false,
418
- slideTimer: 0,
419
- runFrame: 0,
420
- runTimer: 0,
421
- bounceOffset: 0,
422
- bounceTimer: 0,
423
- hasShield: false,
424
- magnetActive: false,
425
- multiplierActive: false,
426
- speedBoostActive: false,
427
- powerupTimers: {
428
- magnet: 0,
429
- multiplier: 0,
430
- shield: 0,
431
- speedBoost: 0
432
- }
433
- };
434
-
435
- // Collections
436
- let obstacles = [];
437
- let coins = [];
438
- let powerups = [];
439
- let particles = [];
440
- let backgroundLayers = [];
441
-
442
- // Input
443
- const keys = {};
444
-
445
- // Lane positions
446
- function getLaneX(lane) {
447
- const laneWidth = width / 3;
448
- return width / 2 + (lane - 1) * laneWidth;
449
- }
450
-
451
- // Initialize background layers
452
- function initBackground() {
453
- backgroundLayers = [
454
- { speed: 0.1, elements: [], color: '#4a90d9' },
455
- { speed: 0.3, elements: [], color: '#5ba85f' },
456
- { speed: 0.5, elements: [], color: '#7cb342' }
457
- ];
458
 
459
- // Add mountains/clouds
460
- for (let i = 0; i < 5; i++) {
461
- backgroundLayers[0].elements.push({
462
- x: Math.random() * width * 2,
463
- y: height * 0.3,
464
- size: 80 + Math.random() * 60,
465
- type: 'cloud'
466
- });
467
- }
468
 
469
- // Add trees
470
- for (let i = 0; i < 10; i++) {
471
- backgroundLayers[1].elements.push({
472
- x: Math.random() * width * 2,
473
- y: height * 0.6,
474
- size: 40 + Math.random() * 30,
475
- type: Math.random() > 0.5 ? 'tree' : 'bush'
476
- });
477
- }
478
- }
479
-
480
- // Draw parallax background
481
- function drawBackground() {
482
- // Sky gradient
483
- const gradient = ctx.createLinearGradient(0, 0, 0, height);
484
- gradient.addColorStop(0, '#87CEEB');
485
- gradient.addColorStop(0.5, '#98D8C8');
486
- gradient.addColorStop(1, '#7CB342');
487
- ctx.fillStyle = gradient;
488
- ctx.fillRect(0, 0, width, height);
489
 
490
- // Draw background layers
491
- backgroundLayers.forEach((layer, idx) => {
492
- ctx.fillStyle = layer.color;
493
- layer.elements.forEach(el => {
494
- if (el.type === 'cloud') {
495
- drawCloud(el.x, el.y, el.size);
496
- } else if (el.type === 'tree') {
497
- drawTree(el.x, el.y, el.size);
498
- } else if (el.type === 'bush') {
499
- drawBush(el.x, el.y, el.size);
500
- }
501
- });
502
- });
503
 
504
- // Ground
505
- ctx.fillStyle = '#5d4037';
506
- ctx.fillRect(0, height * 0.75, width, height * 0.25);
507
 
508
- // Lane lines
509
- const laneWidth = width / 3;
510
- ctx.strokeStyle = 'rgba(255,255,255,0.3)';
511
- ctx.lineWidth = 2;
512
- ctx.setLineDash([20, 20]);
 
513
 
514
- for (let i = 0; i < 2; i++) {
515
- ctx.beginPath();
516
- ctx.moveTo(laneWidth * (i + 1), height * 0.75);
517
- ctx.lineTo(laneWidth * (i + 1), height);
518
- ctx.stroke();
519
- }
520
- ctx.setLineDash([]);
521
- }
522
-
523
- function drawCloud(x, y, size) {
524
- ctx.fillStyle = 'rgba(255,255,255,0.8)';
525
- ctx.beginPath();
526
- ctx.arc(x, y, size * 0.5, 0, Math.PI * 2);
527
- ctx.arc(x + size * 0.4, y - size * 0.2, size * 0.4, 0, Math.PI * 2);
528
- ctx.arc(x + size * 0.8, y, size * 0.5, 0, Math.PI * 2);
529
- ctx.fill();
530
- }
531
-
532
- function drawTree(x, y, size) {
533
- ctx.fillStyle = '#2e7d32';
534
- ctx.beginPath();
535
- ctx.moveTo(x, y - size);
536
- ctx.lineTo(x - size * 0.5, y);
537
- ctx.lineTo(x + size * 0.5, y);
538
- ctx.fill();
539
- ctx.fillStyle = '#5d4037';
540
- ctx.fillRect(x - size * 0.1, y, size * 0.2, size * 0.3);
541
- }
542
-
543
- function drawBush(x, y, size) {
544
- ctx.fillStyle = '#388e3c';
545
- ctx.beginPath();
546
- ctx.arc(x, y, size * 0.5, 0, Math.PI * 2);
547
- ctx.arc(x - size * 0.3, y + size * 0.2, size * 0.4, 0, Math.PI * 2);
548
- ctx.arc(x + size * 0.3, y + size * 0.2, size * 0.4, 0, Math.PI * 2);
549
- ctx.fill();
550
- }
551
-
552
- // Draw the dinosaur
553
- function drawDinosaur(x, y, w, h, preview = false) {
554
- const c = preview ? dinoPreviewCtx : ctx;
555
- const isSliding = player.isSliding;
556
- const actualH = isSliding ? h * 0.5 : h;
557
- const actualY = isSliding ? y + h * 0.5 : y;
558
-
559
- // Body
560
- c.fillStyle = '#4CAF50';
561
- c.beginPath();
562
- c.ellipse(x, actualY + actualH * 0.4, w * 0.5, actualH * 0.4, 0, 0, Math.PI * 2);
563
- c.fill();
564
 
565
- // Head
566
- c.beginPath();
567
- c.ellipse(x + w * 0.3, actualY + actualH * 0.2, w * 0.4, actualH * 0.3, 0.2, 0, Math.PI * 2);
568
- c.fill();
569
-
570
- // Eyes
571
- const eyeY = actualY + actualH * 0.15;
572
- c.fillStyle = 'white';
573
- c.beginPath();
574
- c.ellipse(x + w * 0.35, eyeY, w * 0.12, h * 0.12, 0, 0, Math.PI * 2);
575
- c.fill();
 
 
 
 
576
 
577
- c.fillStyle = '#1a1a1a';
578
- c.beginPath();
579
- c.ellipse(x + w * 0.38, eyeY, w * 0.06, h * 0.06, 0, 0, Math.PI * 2);
580
- c.fill();
581
 
582
- // Eye shine
583
- c.fillStyle = 'white';
584
- c.beginPath();
585
- c.arc(x + w * 0.4, eyeY - h * 0.02, w * 0.02, 0, Math.PI * 2);
586
- c.fill();
 
587
 
588
- // Mouth
589
- c.strokeStyle = '#2e7d32';
590
- c.lineWidth = 2;
591
- c.beginPath();
592
- if (gameState === GameState.PLAYING) {
593
- c.arc(x + w * 0.4, actualY + actualH * 0.3, w * 0.1, 0, Math.PI);
594
- } else {
595
- c.moveTo(x + w * 0.3, actualY + actualH * 0.28);
596
- c.lineTo(x + w * 0.5, actualY + actualH * 0.28);
597
  }
598
- c.stroke();
599
-
600
- // Headband
601
- c.fillStyle = '#f44336';
602
- c.fillRect(x + w * 0.1, actualY + actualH * 0.05, w * 0.6, h * 0.08);
603
-
604
- // Bitcoin symbol on headband
605
- c.fillStyle = '#F7931A';
606
- c.font = `bold ${h * 0.15}px Arial`;
607
- c.textAlign = 'center';
608
- c.fillText('₿', x + w * 0.4, actualY + actualH * 0.15);
609
-
610
- // Tail
611
- c.fillStyle = '#4CAF50';
612
- c.beginPath();
613
- c.moveTo(x - w * 0.4, actualY + actualH * 0.5);
614
- c.quadraticCurveTo(x - w * 0.7, actualY + actualH * 0.3, x - w * 0.5, actualY + actualH * 0.6);
615
- c.quadraticCurveTo(x - w * 0.3, actualY + actualH * 0.7, x - w * 0.4, actualY + actualH * 0.5);
616
- c.fill();
617
-
618
- // Legs (animated)
619
- const legOffset = Math.sin(player.runTimer * 15) * 10;
620
-
621
- if (!player.isJumping && !isSliding) {
622
- // Front leg
623
- c.fillStyle = '#388e3c';
624
- c.beginPath();
625
- c.ellipse(x + w * 0.1, actualY + actualH * 0.7 + legOffset, w * 0.12, h * 0.15, 0, 0, Math.PI * 2);
626
- c.fill();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
627
 
628
- // Back leg
629
- c.beginPath();
630
- c.ellipse(x - w * 0.1, actualY + actualH * 0.7 - legOffset, w * 0.12, h * 0.15, 0, 0, Math.PI * 2);
631
- c.fill();
632
  }
633
-
634
- // Shield effect
635
- if (player.hasShield) {
636
- c.strokeStyle = '#ff6b6b';
637
- c.lineWidth = 3;
638
- c.globalAlpha = 0.5 + Math.sin(Date.now() * 0.01) * 0.3;
639
- c.beginPath();
640
- c.arc(x, actualY + actualH * 0.4, w * 0.8, 0, Math.PI * 2);
641
- c.stroke();
642
- c.globalAlpha = 1;
643
  }
644
-
645
- // Magnet effect
646
- if (player.magnetActive) {
647
- c.strokeStyle = '#00ffff';
648
- c.lineWidth = 2;
649
- c.globalAlpha = 0.5 + Math.sin(Date.now() * 0.015) * 0.3;
650
- c.beginPath();
651
- c.arc(x, actualY + actualH * 0.4, w * 1.2, 0, Math.PI * 2);
652
- c.stroke();
653
- c.globalAlpha = 1;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
654
  }
655
  }
656
-
657
- // Draw obstacles
658
- function drawObstacle(obs) {
659
- const x = getLaneX(obs.lane);
660
- const y = height * 0.75 - obs.height;
661
-
662
- ctx.fillStyle = obs.color || '#e74c3c';
663
-
664
- if (obs.type === 'barrier') {
665
- // Barrier with stripes
666
- ctx.fillRect(x - 30, y, 60, obs.height);
667
 
668
- ctx.fillStyle = '#f39c12';
669
- for (let i = 0; i < 3; i++) {
670
- ctx.fillRect(x - 30 + i * 20, y + i * 15, 10, obs.height / 3);
 
 
 
 
671
  }
 
 
 
 
 
 
672
 
673
- // Top light
674
- ctx.fillStyle = obs.passed ? '#27ae60' : '#e74c3c';
675
- ctx.beginPath();
676
- ctx.arc(x, y - 10, 8, 0, Math.PI * 2);
677
- ctx.fill();
 
 
 
 
 
 
 
 
 
 
 
 
 
678
 
679
- } else if (obs.type === 'low') {
680
- // Low barrier - need to jump
681
- ctx.fillStyle = '#9b59b6';
682
- ctx.fillRect(x - 25, y + 20, 50, obs.height - 20);
 
 
 
683
 
684
- // Warning stripes
685
- ctx.fillStyle = '#f1c40f';
686
- ctx.fillRect(x - 25, y + 20, 50, 8);
 
687
 
688
- } else if (obs.type === 'high') {
689
- // High barrier - need to slide
690
- ctx.fillStyle = '#3498db';
691
- ctx.fillRect(x - 25, y, 50, obs.height - 30);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
692
 
693
- // Hanging effect
694
- ctx.strokeStyle = '#3498db';
695
  ctx.lineWidth = 3;
696
  ctx.beginPath();
697
- ctx.moveTo(x - 20, y);
698
- ctx.lineTo(x - 20, y - 30);
699
- ctx.moveTo(x + 20, y);
700
- ctx.lineTo(x + 20, y - 30);
701
  ctx.stroke();
702
  }
703
  }
704
-
705
- // Draw Bitcoin coin
706
- function drawCoin(coin) {
707
- const x = coin.x;
708
- const y = coin.y;
709
- const size = 25;
710
-
711
- // Glow effect
712
- const glow = ctx.createRadialGradient(x, y, 0, x, y, size * 1.5);
713
- glow.addColorStop(0, 'rgba(247, 147, 26, 0.4)');
714
- glow.addColorStop(1, 'transparent');
715
- ctx.fillStyle = glow;
716
- ctx.beginPath();
717
- ctx.arc(x, y, size * 1.5, 0, Math.PI * 2);
718
- ctx.fill();
719
-
720
- // Spinning effect (scale X)
721
- const spin = Math.cos(coin.rotation || 0);
722
-
723
- ctx.save();
724
- ctx.translate(x, y);
725
- ctx.scale(Math.abs(spin), 1);
726
-
727
- // Coin body
728
- const coinGrad = ctx.createLinearGradient(-size, -size, size, size);
729
- coinGrad.addColorStop(0, '#ffd700');
730
- coinGrad.addColorStop(0.5, '#ffec8b');
731
- coinGrad.addColorStop(1, '#daa520');
732
- ctx.fillStyle = coinGrad;
733
-
734
- ctx.beginPath();
735
- ctx.arc(0, 0, size, 0, Math.PI * 2);
736
- ctx.fill();
737
-
738
- // Bitcoin symbol
739
- if (Math.abs(spin) > 0.3) {
740
- ctx.fillStyle = '#F7931A';
741
- ctx.font = 'bold 20px Arial';
742
- ctx.textAlign = 'center';
743
- ctx.textBaseline = 'middle';
744
- ctx.fillText('₿', 0, 0);
745
- }
746
-
747
- ctx.restore();
748
- }
749
-
750
- // Draw powerups
751
- function drawPowerup(pup) {
752
- const x = pup.x;
753
- const y = pup.y;
754
-
755
- ctx.save();
756
-
757
- // Pulsing effect
758
- const pulse = 1 + Math.sin(Date.now() * 0.005) * 0.2;
759
- ctx.translate(x, y);
760
- ctx.scale(pulse, pulse);
761
-
762
- // Background circle
763
- let color;
764
- let symbol;
765
- switch(pup.type) {
766
- case 'magnet': color = '#00ffff'; symbol = '🧲'; break;
767
- case 'shield': color = '#ff6b6b'; symbol = '🛡️'; break;
768
- case 'multiplier': color = '#ffd700'; symbol = '✖️2'; break;
769
- case 'speedBoost': color = '#ff9f43'; symbol = '⚡'; break;
770
- }
771
-
772
- ctx.fillStyle = color;
773
- ctx.globalAlpha = 0.3;
774
- ctx.beginPath();
775
- ctx.arc(0, 0, 25, 0, Math.PI * 2);
776
- ctx.fill();
777
-
778
- ctx.globalAlpha = 1;
779
- ctx.strokeStyle = color;
780
- ctx.lineWidth = 3;
781
- ctx.beginPath();
782
- ctx.arc(0, 0, 25, 0, Math.PI * 2);
783
- ctx.stroke();
784
-
785
- ctx.font = '20px Arial';
786
- ctx.textAlign = 'center';
787
- ctx.textBaseline = 'middle';
788
- ctx.fillText(symbol, 0, 0);
789
-
790
- ctx.restore();
791
- }
792
-
793
- // Draw particles
794
- function drawParticles() {
795
- particles.forEach((p, i) => {
796
- ctx.globalAlpha = p.life;
797
- ctx.fillStyle = p.color;
798
- ctx.beginPath();
799
- ctx.arc(p.x, p.y, p.size, 0, Math.PI * 2);
800
- ctx.fill();
801
- ctx.globalAlpha = 1;
802
- });
803
- }
804
-
805
- // Spawn obstacles
806
- function spawnObstacle() {
807
- const types = ['barrier', 'low', 'high'];
808
- const type = types[Math.floor(Math.random() * types.length)];
809
- const lane = Math.floor(Math.random() * 3);
810
-
811
- let height = 60;
812
- if (type === 'barrier') height = 60;
813
- else if (type === 'low') height = 40;
814
- else if (type === 'high') height = 100;
815
-
816
- obstacles.push({
817
- lane: lane,
818
- x: getLaneX(lane),
819
- y: height * 0.75,
820
- height: height,
821
- type: type,
822
- passed: false,
823
- color: type === 'barrier' ? '#e74c3c' : type === 'low' ? '#9b59b6' : '#3498db'
824
- });
825
- }
826
-
827
- // Spawn coin
828
- function spawnCoin() {
829
- const lane = Math.floor(Math.random() * 3);
830
- const x = getLaneX(lane);
831
-
832
- // Random pattern: straight line, arc, or scattered
833
- const pattern = Math.floor(Math.random() * 3);
834
- const count = 3 + Math.floor(Math.random() * 5);
835
-
836
- for (let i = 0; i < count; i++) {
837
- let y = height * 0.65;
838
- let offsetX = 0;
839
 
840
- if (pattern === 1) {
841
- // Arc
842
- offsetX = Math.sin(i / count * Math.PI) * 60 - 30;
843
- } else if (pattern === 2) {
844
- // Scattered
845
- y = height * 0.55 - Math.random() * 100;
846
- } else {
847
- // Line
848
- offsetX = i * 40;
849
- }
850
 
851
- coins.push({
852
- x: x + offsetX,
853
- y: y + i * 35,
854
- lane: lane,
855
- value: (0.00000001 + Math.random() * 0.00000009).toFixed(8),
856
- rotation: Math.random() * Math.PI * 2,
857
- collected: false
858
- });
 
 
 
 
 
 
 
 
859
  }
860
- }
861
-
862
- // Spawn powerup
863
- function spawnPowerup() {
864
- const types = ['magnet', 'shield', 'multiplier', 'speedBoost'];
865
- const type = types[Math.floor(Math.random() * types.length)];
866
- const lane = Math.floor(Math.random() * 3);
867
-
868
- powerups.push({
869
- x: getLaneX(lane),
870
- y: height * 0.5,
871
- lane: lane,
872
- type: type
873
- });
874
- }
875
-
876
- // Create particles
877
- function createParticles(x, y, color, count = 10) {
878
- for (let i = 0; i < count; i++) {
879
- particles.push({
880
- x: x,
881
- y: y,
882
- vx: (Math.random() - 0.5) * 200,
883
- vy: (Math.random() - 0.5) * 200 - 100,
884
- size: Math.random() * 5 + 2,
885
- color: color,
886
- life: 1
887
- });
888
  }
889
- }
890
-
891
- // Update game
892
- function update(dt) {
893
- if (gameState !== GameState.PLAYING) return;
894
-
895
- // Update speed
896
- const targetSpeed = player.speedBoostActive ? maxSpeed : Math.min(baseSpeed + distance * 0.1, maxSpeed);
897
- speed += (targetSpeed - speed) * 0.01;
898
-
899
- // Update distance
900
- distance += speed * dt / 1000;
901
- score = Math.floor(distance);
902
-
903
- // Update player
904
- updatePlayer(dt);
905
-
906
- // Update background
907
- backgroundLayers.forEach(layer => {
908
- layer.elements.forEach(el => {
909
- el.x -= speed * dt / 1000 * layer.speed;
910
- if (el.x < -100) {
911
- el.x = width + 100 + Math.random() * 200;
912
- }
913
- });
914
- });
915
-
916
- // Spawn obstacles
917
- if (Math.random() < 0.02 + distance * 0.0001) {
918
- const minDist = 300;
919
- const lastObs = obstacles[obstacles.length - 1];
920
- if (!lastObs || lastObs.x < width - minDist - speed * 0.3) {
921
- spawnObstacle();
922
  }
923
  }
924
-
925
- // Spawn coins
926
- if (Math.random() < 0.015) {
927
- const lastCoin = coins[coins.length - 1];
928
- if (!lastCoin || lastCoin.x < width - 200) {
929
- spawnCoin();
 
930
  }
931
  }
932
-
933
- // Spawn powerups (rare)
934
- if (Math.random() < 0.002) {
935
- const lastPup = powerups[powerups.length - 1];
936
- if (!lastPup || lastPup.x < width - 300) {
937
- spawnPowerup();
 
938
  }
939
  }
940
-
941
- // Update obstacles
942
- obstacles.forEach((obs, i) => {
943
- obs.x -= speed * dt / 1000;
 
 
 
 
 
 
 
944
 
945
- // Check collision
946
- const playerLane = player.targetLane;
947
- const inSameLane = obs.lane === playerLane;
948
 
949
- if (inSameLane && !obs.passed) {
950
- const playerY = height * 0.75 - player.height;
951
- const obsTop = height * 0.75 - obs.height;
 
952
 
953
- let collision = false;
954
-
955
- if (obs.type === 'barrier') {
956
- collision = playerY + player.height > obsTop + 10;
957
- } else if (obs.type === 'low') {
958
- // Low obstacles - need to jump
959
- collision = playerY + player.height > obsTop + 10 && !player.isJumping;
960
- } else if (obs.type === 'high') {
961
- // High obstacles - need to slide
962
- const slideHit = player.isSliding && playerY + player.height * 0.5 > obsTop;
963
- const standHit = !player.isSliding && playerY + player.height > obsTop + 30;
964
- collision = slideHit || standHit;
965
  }
966
-
967
- if (collision) {
968
- if (player.hasShield) {
969
- player.hasShield = false;
970
- player.powerupTimers.shield = 0;
971
- createParticles(obs.x, obs.y, '#ff6b6b', 20);
972
- obs.passed = true;
973
- } else {
974
- gameOver();
975
- }
976
  }
977
  }
978
 
979
- if (obs.x < -50) {
980
- obs.passed = true;
 
 
 
981
  }
982
- });
983
-
984
- // Update coins
985
- coins.forEach((coin, i) => {
986
- // Magnet effect
987
- if (player.magnetActive && Math.abs(coin.x - player.x) < 300) {
988
- const dx = player.x - coin.x;
989
- const dy = player.y - coin.y;
990
- const dist = Math.sqrt(dx * dx + dy * dy);
991
- coin.x += (dx / dist) * 400 * dt / 1000;
992
- coin.y += (dy / dist) * 400 * dt / 1000;
993
  }
994
 
995
- coin.x -= speed * dt / 1000;
996
- coin.rotation += dt * 0.005;
 
 
 
 
 
 
 
997
 
998
- // Collection
999
- const dx = coin.x - player.x;
1000
- const dy = coin.y - (player.y + player.height * 0.3);
1001
- const dist = Math.sqrt(dx * dx + dy * dy);
1002
 
1003
- if (dist < 40 && !coin.collected) {
1004
- coin.collected = true;
1005
- let value = parseFloat(coin.value);
1006
- if (player.multiplierActive) {
1007
- value *= 2;
1008
- }
1009
- btcBalance += value;
1010
-
1011
- combo++;
1012
- comboTimer = 2;
1013
-
1014
- createParticles(coin.x, coin.y, '#F7931A', 15);
1015
- showComboPopup(`+₿${coin.value}`);
1016
  }
1017
- });
1018
-
1019
- // Update powerups
1020
- powerups.forEach((pup, i) => {
1021
- pup.x -= speed * dt / 1000;
1022
 
1023
- const dx = pup.x - player.x;
1024
- const dy = pup.y - (player.y + player.height * 0.3);
1025
- const dist = Math.sqrt(dx * dx + dy * dy);
1026
 
1027
- if (dist < 50) {
1028
- activatePowerup(pup.type);
1029
- powerups.splice(i, 1);
1030
- createParticles(pup.x, pup.y, '#fff', 20);
1031
  }
1032
- });
1033
-
1034
- // Update particles
1035
- particles.forEach((p, i) => {
1036
- p.x += p.vx * dt / 1000;
1037
- p.y += p.vy * dt / 1000;
1038
- p.vy += 500 * dt / 1000;
1039
- p.life -= dt / 1000;
1040
 
1041
- if (p.life <= 0) {
1042
- particles.splice(i, 1);
 
 
 
 
 
 
 
1043
  }
1044
- });
1045
-
1046
- // Clean up off-screen objects
1047
- obstacles = obstacles.filter(obs => obs.x > -100);
1048
- coins = coins.filter(c => c.x > -50 && !c.collected);
1049
- powerups = powerups.filter(p => p.x > -50);
1050
-
1051
- // Update powerup timers
1052
- Object.keys(player.powerupTimers).forEach(key => {
1053
- if (player.powerupTimers[key] > 0) {
1054
- player.powerupTimers[key] -= dt / 1000;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1055
 
1056
- if (key === 'shield') {
1057
- player.hasShield = player.powerupTimers.shield > 0;
1058
- }
1059
- if (player.powerupTimers[key] <= 0) {
1060
- player[key === 'multiplier' ? 'multiplierActive' :
1061
- key === 'speedBoost' ? 'speedBoostActive' :
1062
- key + 'Active'] = false;
1063
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1064
  }
1065
- });
1066
-
1067
- // Update combo
1068
- if (comboTimer > 0) {
1069
- comboTimer -= dt / 1000;
1070
- } else {
1071
- combo = 0;
1072
  }
1073
-
1074
- // Update HUD
1075
- updateHUD();
 
 
 
 
 
 
 
1076
  }
1077
-
1078
- function updatePlayer(dt) {
1079
- // Lane movement
1080
- const targetX = getLaneX(player.targetLane);
1081
- player.x += (targetX - player.x) * 0.15;
1082
-
1083
- // Jump physics
1084
- if (player.isJumping) {
1085
- player.yVelocity -= 1800 * dt / 1000;
1086
- player.y += player.yVelocity * dt / 1000;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1087
 
1088
- const groundY = height * 0.75 - player.height;
1089
- if (player.y >= groundY) {
1090
- player.y = groundY;
1091
- player.isJumping = false;
1092
- player.yVelocity = 0;
 
 
 
 
 
 
 
 
 
 
 
1093
  }
1094
- } else {
1095
- player.y = height * 0.75 - player.height;
1096
  }
1097
-
1098
- // Slide
1099
- if (player.isSliding) {
1100
- player.slideTimer -= dt / 1000;
1101
- if (player.slideTimer <= 0) {
1102
- player.isSliding = false;
 
 
 
 
 
 
 
1103
  }
 
 
 
 
1104
  }
1105
-
1106
- // Running animation
1107
- player.runTimer += dt / 1000;
1108
-
1109
- // Bounce
1110
- if (!player.isJumping && !player.isSliding) {
1111
- player.bounceTimer += dt / 1000;
1112
- player.bounceOffset = Math.sin(player.bounceTimer * 15) * 3;
 
 
 
 
 
1113
  }
1114
- }
1115
-
1116
- function activatePowerup(type) {
1117
- switch(type) {
1118
- case 'magnet':
1119
- player.magnetActive = true;
1120
- player.powerupTimers.magnet = 8;
1121
- break;
1122
- case 'shield':
1123
- player.hasShield = true;
1124
- player.powerupTimers.shield = 10;
1125
- break;
1126
- case 'multiplier':
1127
- player.multiplierActive = true;
1128
- player.powerupTimers.multiplier = 15;
1129
- break;
1130
- case 'speedBoost':
1131
- player.speedBoostActive = true;
1132
- player.powerupTimers.speedBoost = 5;
1133
- break;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1134
  }
1135
  }
1136
-
1137
- function showComboPopup(text) {
1138
- const popup = document.createElement('div');
1139
- popup.className = 'combo-popup';
1140
- popup.textContent = text;
1141
- popup.style.left = (player.x + 50) + 'px';
1142
- popup.style.top = (player.y - 20) + 'px';
1143
- document.getElementById('gameContainer').appendChild(popup);
1144
-
1145
- setTimeout(() => popup.remove(), 1000);
1146
- }
1147
-
1148
- function updateHUD() {
1149
- document.getElementById('distanceDisplay').textContent = Math.floor(distance) + 'm';
1150
- document.getElementById('btcDisplay').textContent = '₿ ' + btcBalance.toFixed(8);
1151
- document.getElementById('speedDisplay').textContent = Math.floor(speed * 0.36) + ' km/h';
1152
-
1153
- // Powerup indicators
1154
- document.getElementById('magnetInd').classList.toggle('active', player.magnetActive);
1155
- document.getElementById('shieldInd').classList.toggle('active', player.hasShield);
1156
- document.getElementById('multiInd').classList.toggle('active', player.multiplierActive);
1157
- }
1158
-
1159
- // Draw everything
1160
- function draw() {
1161
- ctx.clearRect(0, 0, width, height);
1162
-
1163
- drawBackground
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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>BTC Dino Runner</title>
7
+ <link href="https://fonts.googleapis.com/css2?family=Fredoka+One&family=Nunito:wght@400;700;900&display=swap" rel="stylesheet">
8
  <style>
9
+ :root {
10
+ --bg-primary: #1a1a2e;
11
+ --bg-secondary: #16213e;
12
+ --accent-gold: #f7931a;
13
+ --accent-green: #00d26a;
14
+ --accent-cyan: #00d4ff;
15
+ --text-primary: #ffffff;
16
+ --text-secondary: #a0a0c0;
17
+ --danger: #ff4757;
18
+ --success: #2ed573;
19
+ }
20
+
21
  * {
22
  margin: 0;
23
  padding: 0;
24
  box-sizing: border-box;
25
  }
26
+
 
 
 
 
 
 
 
 
27
  body {
28
+ font-family: 'Nunito', sans-serif;
29
+ background: linear-gradient(135deg, var(--bg-primary) 0%, var(--bg-secondary) 100%);
30
  min-height: 100vh;
31
+ overflow: hidden;
32
  display: flex;
33
  justify-content: center;
34
  align-items: center;
 
 
35
  }
36
+
37
  #gameContainer {
38
  position: relative;
39
  width: 100%;
40
  max-width: 800px;
41
  aspect-ratio: 16/9;
 
42
  border-radius: 16px;
43
  overflow: hidden;
44
+ box-shadow:
45
+ 0 0 60px rgba(247, 147, 26, 0.2),
46
+ 0 0 120px rgba(0, 210, 106, 0.1),
47
+ 0 25px 50px rgba(0, 0, 0, 0.5);
48
  }
49
+
50
+ #gameCanvas {
 
 
 
 
 
 
 
 
 
51
  width: 100%;
52
  height: 100%;
53
+ display: block;
54
+ background: #0f0f23;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
55
  }
56
+
57
+ .screen-overlay {
58
  position: absolute;
59
  top: 0;
60
  left: 0;
 
64
  flex-direction: column;
65
  justify-content: center;
66
  align-items: center;
67
+ background: rgba(15, 15, 35, 0.95);
68
  backdrop-filter: blur(10px);
69
+ transition: opacity 0.5s ease, visibility 0.5s ease;
70
+ z-index: 10;
71
  }
72
+
73
+ .screen-overlay.hidden {
74
  opacity: 0;
75
+ visibility: hidden;
76
  pointer-events: none;
77
  }
78
+
79
  .game-title {
80
+ font-family: 'Fredoka One', cursive;
81
+ font-size: clamp(2rem, 8vw, 4rem);
82
+ background: linear-gradient(135deg, var(--accent-gold) 0%, #ffd700 50%, var(--accent-gold) 100%);
83
+ -webkit-background-clip: text;
84
+ -webkit-text-fill-color: transparent;
85
+ background-clip: text;
86
+ text-shadow: none;
87
+ margin-bottom: 0.5rem;
88
+ animation: titlePulse 2s ease-in-out infinite;
 
 
 
 
 
89
  }
90
+
91
+ @keyframes titlePulse {
92
  0%, 100% { transform: scale(1); }
93
+ 50% { transform: scale(1.02); }
94
  }
95
+
96
+ .subtitle {
97
+ font-size: clamp(0.9rem, 3vw, 1.2rem);
98
+ color: var(--text-secondary);
99
+ margin-bottom: 2rem;
100
+ }
101
+
102
+ .start-dino-container {
103
+ width: 150px;
104
+ height: 150px;
105
+ margin-bottom: 2rem;
106
+ animation: dinoBounce 1s ease-in-out infinite;
107
+ }
108
+
109
+ @keyframes dinoBounce {
110
+ 0%, 100% { transform: translateY(0) rotate(-2deg); }
111
+ 50% { transform: translateY(-15px) rotate(2deg); }
112
+ }
113
+
114
  .btn {
115
+ font-family: 'Fredoka One', cursive;
116
+ font-size: clamp(1rem, 4vw, 1.4rem);
117
+ padding: 1rem 3rem;
 
118
  border: none;
119
  border-radius: 50px;
120
  cursor: pointer;
121
+ transition: all 0.3s ease;
122
+ position: relative;
123
+ overflow: hidden;
 
124
  }
125
+
126
  .btn-primary {
127
+ background: linear-gradient(135deg, var(--accent-gold) 0%, #ff9500 100%);
128
+ color: #1a1a2e;
129
+ box-shadow: 0 8px 30px rgba(247, 147, 26, 0.4);
130
  }
131
+
132
  .btn-primary:hover {
133
  transform: translateY(-3px) scale(1.05);
134
+ box-shadow: 0 12px 40px rgba(247, 147, 26, 0.6);
135
  }
136
+
137
+ .btn-primary:active {
138
+ transform: translateY(0) scale(0.98);
139
+ }
140
+
141
  .btn-secondary {
142
+ background: rgba(255, 255, 255, 0.1);
143
+ color: var(--text-primary);
144
+ border: 2px solid rgba(255, 255, 255, 0.2);
145
+ margin-top: 1rem;
146
  }
147
+
148
  .btn-secondary:hover {
149
+ background: rgba(255, 255, 255, 0.2);
150
+ border-color: rgba(255, 255, 255, 0.4);
151
  }
152
+
153
+ .controls-info {
154
+ margin-top: 2rem;
155
+ display: flex;
156
+ gap: 1rem;
157
+ flex-wrap: wrap;
158
+ justify-content: center;
159
  }
160
+
161
+ .control-item {
162
+ display: flex;
163
+ align-items: center;
164
+ gap: 0.5rem;
165
+ background: rgba(255, 255, 255, 0.05);
166
+ padding: 0.5rem 1rem;
167
+ border-radius: 20px;
168
+ font-size: 0.85rem;
169
+ color: var(--text-secondary);
170
  }
171
+
172
+ .key-badge {
173
+ background: rgba(255, 255, 255, 0.15);
174
+ padding: 0.25rem 0.6rem;
175
+ border-radius: 6px;
176
+ font-weight: 700;
177
+ color: var(--text-primary);
178
+ font-size: 0.75rem;
179
  }
180
+
181
+ .stats-container {
182
+ display: grid;
183
+ grid-template-columns: repeat(2, 1fr);
184
+ gap: 1rem;
185
+ margin: 1.5rem 0;
186
+ width: 100%;
187
+ max-width: 300px;
188
  }
189
+
190
+ .stat-item {
191
+ background: rgba(255, 255, 255, 0.05);
192
+ padding: 1rem;
193
+ border-radius: 12px;
194
+ text-align: center;
 
195
  }
196
+
197
+ .stat-label {
198
+ font-size: 0.75rem;
199
+ color: var(--text-secondary);
200
+ text-transform: uppercase;
201
+ letter-spacing: 1px;
202
+ }
203
+
204
+ .stat-value {
205
+ font-family: 'Fredoka One', cursive;
206
+ font-size: 1.2rem;
207
+ color: var(--accent-gold);
208
+ margin-top: 0.25rem;
209
+ }
210
+
211
+ .high-score-badge {
212
+ background: linear-gradient(135deg, var(--accent-green) 0%, #00a854 100%);
213
  color: white;
214
+ padding: 0.5rem 1.5rem;
215
+ border-radius: 20px;
216
+ font-weight: 700;
217
+ margin-bottom: 1rem;
218
+ animation: badgePop 0.5s ease;
219
  }
220
+
221
+ @keyframes badgePop {
222
+ 0% { transform: scale(0); }
223
+ 50% { transform: scale(1.2); }
224
+ 100% { transform: scale(1); }
225
+ }
226
+
227
+ .built-with {
228
  position: absolute;
229
+ top: 10px;
230
+ left: 10px;
231
+ font-size: 0.7rem;
232
+ color: var(--text-secondary);
233
+ z-index: 100;
234
+ text-decoration: none;
235
+ opacity: 0.7;
236
+ transition: opacity 0.3s;
237
  }
238
+
239
+ .built-with:hover {
240
+ opacity: 1;
241
+ color: var(--accent-cyan);
242
  }
243
+
244
+ /* Mobile touch controls */
245
+ .touch-controls {
246
  position: absolute;
247
+ bottom: 20px;
248
+ left: 0;
249
+ right: 0;
 
 
 
 
 
250
  display: none;
251
+ justify-content: space-between;
252
+ padding: 0 20px;
253
+ z-index: 5;
254
  }
255
+
256
+ @media (pointer: coarse) {
257
+ .touch-controls {
258
+ display: flex;
259
+ }
260
  }
261
+
262
+ .touch-btn {
263
+ width: 60px;
264
+ height: 60px;
265
+ border-radius: 50%;
266
+ background: rgba(255, 255, 255, 0.15);
267
+ border: 2px solid rgba(255, 255, 255, 0.3);
268
+ display: flex;
269
+ justify-content: center;
270
+ align-items: center;
271
  color: white;
272
+ font-size: 1.5rem;
273
+ backdrop-filter: blur(5px);
274
+ transition: all 0.2s;
275
+ -webkit-tap-highlight-color: transparent;
276
  }
277
+
278
+ .touch-btn:active {
279
+ background: rgba(255, 255, 255, 0.3);
280
+ transform: scale(0.95);
 
 
 
 
 
 
 
281
  }
282
+
283
+ .touch-btn-group {
284
+ display: flex;
285
+ gap: 15px;
286
+ }
287
+
288
+ /* Responsive adjustments */
289
+ @media (max-width: 600px) {
290
+ #gameContainer {
291
+ border-radius: 0;
292
+ max-width: 100%;
293
+ height: 100vh;
294
+ aspect-ratio: auto;
295
+ }
296
+
297
+ .stats-container {
298
+ grid-template-columns: 1fr 1fr;
299
+ padding: 0 1rem;
300
+ }
301
+
302
+ .controls-info {
303
+ display: none;
304
+ }
305
  }
306
  </style>
307
  </head>
308
  <body>
309
  <div id="gameContainer">
310
+ <a href="https://huggingface.co/spaces/akhaliq/anycoder" target="_blank" class="built-with">Built with anycoder</a>
311
+
312
  <canvas id="gameCanvas"></canvas>
313
 
314
+ <!-- Start Screen -->
315
+ <div id="startScreen" class="screen-overlay">
316
+ <h1 class="game-title">BTC DINO RUN</h1>
317
+ <p class="subtitle">Collect Satoshi, Dodge Obstacles!</p>
318
+ <canvas id="startDinoCanvas" class="start-dino-container" width="150" height="150"></canvas>
319
+ <button id="startBtn" class="btn btn-primary">START RUNNING</button>
320
+ <div class="controls-info">
321
+ <div class="control-item"><span class="key-badge">A/D</span> Move</div>
322
+ <div class="control-item"><span class="key-badge">W/SPACE</span> Jump</div>
323
+ <div class="control-item"><span class="key-badge">S</span> Slide</div>
324
+ <div class="control-item"><span class="key-badge">P/ESC</span> Pause</div>
325
+ </div>
326
+ </div>
327
+
328
+ <!-- Pause Screen -->
329
+ <div id="pauseScreen" class="screen-overlay hidden">
330
+ <h2 class="game-title" style="font-size: 2.5rem;">PAUSED</h2>
331
+ <button id="resumeBtn" class="btn btn-primary">RESUME</button>
332
+ <button id="quitBtn" class="btn btn-secondary">QUIT TO MENU</button>
333
+ </div>
334
+
335
+ <!-- Game Over Screen -->
336
+ <div id="gameOverScreen" class="screen-overlay hidden">
337
+ <h2 class="game-title" style="font-size: 2.5rem;">GAME OVER</h2>
338
+ <div id="newHighScore" class="high-score-badge" style="display: none;">NEW HIGH SCORE!</div>
339
+ <div class="stats-container">
340
+ <div class="stat-item">
341
+ <div class="stat-label">Distance</div>
342
+ <div class="stat-value" id="finalDistance">0m</div>
343
  </div>
344
+ <div class="stat-item">
345
+ <div class="stat-label">BTC Collected</div>
346
+ <div class="stat-value" id="finalBTC">0.00000000</div>
347
  </div>
348
+ <div class="stat-item">
349
+ <div class="stat-label">Top Speed</div>
350
+ <div class="stat-value" id="finalSpeed">0</div>
351
  </div>
352
+ <div class="stat-item">
353
+ <div class="stat-label">Best Combo</div>
354
+ <div class="stat-value" id="finalCombo">x0</div>
 
355
  </div>
356
  </div>
357
+ <button id="restartBtn" class="btn btn-primary">PLAY AGAIN</button>
358
+ <button id="menuBtn" class="btn btn-secondary">MAIN MENU</button>
359
  </div>
360
 
361
+ <!-- Touch Controls -->
362
+ <div class="touch-controls" id="touchControls">
363
+ <div class="touch-btn-group">
364
+ <button class="touch-btn" id="touchLeft">◀</button>
365
+ <button class="touch-btn" id="touchRight"></button>
 
 
 
 
 
366
  </div>
367
+ <div class="touch-btn-group">
368
+ <button class="touch-btn" id="touchSlide">▼</button>
369
+ <button class="touch-btn" id="touchJump">▲</button>
 
 
 
 
 
 
370
  </div>
 
 
 
 
 
 
 
371
  </div>
 
 
372
  </div>
373
 
374
  <script>
375
+ // ==========================================
376
+ // GAME CONFIGURATION
377
+ // ==========================================
378
+ const CONFIG = {
379
+ // Canvas dimensions
380
+ CANVAS_WIDTH: 800,
381
+ CANVAS_HEIGHT: 450,
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
382
 
383
+ // Lane system
384
+ LANE_COUNT: 3,
385
+ LANE_WIDTH: 120,
 
 
 
 
 
 
386
 
387
+ // Dinosaur properties
388
+ DINO_WIDTH: 60,
389
+ DINO_HEIGHT: 80,
390
+ DINO_JUMP_FORCE: -15,
391
+ DINO_GRAVITY: 0.6,
392
+ DINO_SLIDE_SCALE: 0.4,
 
 
 
 
 
 
 
 
 
 
 
 
 
 
393
 
394
+ // Game speed
395
+ INITIAL_SPEED: 8,
396
+ MAX_SPEED: 25,
397
+ SPEED_INCREMENT: 0.002,
 
 
 
 
 
 
 
 
 
398
 
399
+ // Obstacles
400
+ OBSTACLE_SPAWN_RATE: 0.025,
401
+ MIN_OBSTACLE_GAP: 300,
402
 
403
+ // Coins
404
+ COIN_SPAWN_RATE: 0.04,
405
+ COIN_VALUE_MIN: 0.00000001,
406
+ COIN_VALUE_MAX: 0.00000010,
407
+ BONUS_COIN_CHANCE: 0.1,
408
+ BONUS_COIN_MULTIPLIER: 5,
409
 
410
+ // Power-ups
411
+ POWERUP_SPAWN_RATE: 0.008,
412
+ POWERUP_DURATION: 8000,
413
+ MAGNET_RANGE: 150,
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
414
 
415
+ // Visual
416
+ PARALLAX_LAYERS: 4,
417
+ PARTICLE_COUNT: 50
418
+ };
419
+
420
+ // ==========================================
421
+ // UTILITY FUNCTIONS
422
+ // ==========================================
423
+ const Utils = {
424
+ lerp: (a, b, t) => a + (b - a) * t,
425
+ clamp: (val, min, max) => Math.max(min, Math.min(max, val)),
426
+ random: (min, max) => Math.random() * (max - min) + min,
427
+ randomInt: (min, max) => Math.floor(Utils.random(min, max + 1)),
428
+ easeOut: (t) => 1 - Math.pow(1 - t, 3),
429
+ easeInOut: (t) => t < 0.5 ? 4 * t * t * t : 1 - Math.pow(-2 * t + 2, 3) / 2,
430
 
431
+ formatBTC: (value) => {
432
+ return value.toFixed(8);
433
+ },
 
434
 
435
+ formatDistance: (value) => {
436
+ if (value >= 1000) {
437
+ return (value / 1000).toFixed(2) + 'km';
438
+ }
439
+ return Math.floor(value) + 'm';
440
+ },
441
 
442
+ hexToRgba: (hex, alpha) => {
443
+ const r = parseInt(hex.slice(1, 3), 16);
444
+ const g = parseInt(hex.slice(3, 5), 16);
445
+ const b = parseInt(hex.slice(5, 7), 16);
446
+ return `rgba(${r},${g},${b},${alpha})`;
 
 
 
 
447
  }
448
+ };
449
+
450
+ // ==========================================
451
+ // PARTICLE SYSTEM
452
+ // ==========================================
453
+ class Particle {
454
+ constructor(x, y, color, velocity, size, life) {
455
+ this.x = x;
456
+ this.y = y;
457
+ this.color = color;
458
+ this.vx = velocity.x;
459
+ this.vy = velocity.y;
460
+ this.size = size;
461
+ this.life = life;
462
+ this.maxLife = life;
463
+ this.gravity = 0.1;
464
+ }
465
+
466
+ update(dt) {
467
+ this.x += this.vx * dt;
468
+ this.y += this.vy * dt;
469
+ this.vy += this.gravity * dt;
470
+ this.life -= dt;
471
+ }
472
+
473
+ draw(ctx) {
474
+ const alpha = Utils.clamp(this.life / this.maxLife, 0, 1);
475
+ ctx.save();
476
+ ctx.globalAlpha = alpha;
477
+ ctx.fillStyle = this.color;
478
+ ctx.beginPath();
479
+ ctx.arc(this.x, this.y, this.size * alpha, 0, Math.PI * 2);
480
+ ctx.fill();
481
+ ctx.restore();
482
+ }
483
+
484
+ get isDead() {
485
+ return this.life <= 0;
486
+ }
487
+ }
488
+
489
+ class ParticleSystem {
490
+ constructor() {
491
+ this.particles = [];
492
+ }
493
+
494
+ emit(x, y, count, config) {
495
+ for (let i = 0; i < count; i++) {
496
+ const angle = Utils.random(0, Math.PI * 2);
497
+ const speed = Utils.random(config.minSpeed || 1, config.maxSpeed || 5);
498
+ const velocity = {
499
+ x: Math.cos(angle) * speed,
500
+ y: Math.sin(angle) * speed - 2
501
+ };
502
+ const particle = new Particle(
503
+ x + Utils.random(-5, 5),
504
+ y + Utils.random(-5, 5),
505
+ config.colors[Utils.randomInt(0, config.colors.length - 1)],
506
+ velocity,
507
+ Utils.random(config.minSize || 2, config.maxSize || 6),
508
+ Utils.random(config.minLife || 20, config.maxLife || 40)
509
+ );
510
+ this.particles.push(particle);
511
+ }
512
+ }
513
+
514
+ update(dt) {
515
+ this.particles.forEach(p => p.update(dt));
516
+ this.particles = this.particles.filter(p => !p.isDead);
517
+ }
518
+
519
+ draw(ctx) {
520
+ this.particles.forEach(p => p.draw(ctx));
521
+ }
522
+ }
523
+
524
+ // ==========================================
525
+ // BACKGROUND SYSTEM
526
+ // ==========================================
527
+ class BackgroundLayer {
528
+ constructor(speed, y, height, color1, color2, hasDetails = false) {
529
+ this.speed = speed;
530
+ this.y = y;
531
+ this.height = height;
532
+ this.color1 = color1;
533
+ this.color2 = color2;
534
+ this.offset = 0;
535
+ this.hasDetails = hasDetails;
536
+ this.details = [];
537
 
538
+ if (hasDetails) {
539
+ this.generateDetails();
540
+ }
 
541
  }
542
+
543
+ generateDetails() {
544
+ for (let i = 0; i < 20; i++) {
545
+ this.details.push({
546
+ x: Utils.random(0, CONFIG.CANVAS_WIDTH * 3),
547
+ type: Utils.randomInt(0, 2),
548
+ size: Utils.random(20, 60)
549
+ });
550
+ }
 
551
  }
552
+
553
+ update(gameSpeed, dt) {
554
+ this.offset += this.speed * gameSpeed * dt;
555
+ if (this.offset > CONFIG.CANVAS_WIDTH) {
556
+ this.offset -= CONFIG.CANVAS_WIDTH;
557
+ }
558
+ }
559
+
560
+ draw(ctx, canvasWidth, canvasHeight) {
561
+ // Draw gradient background
562
+ const gradient = ctx.createLinearGradient(0, this.y, 0, this.y + this.height);
563
+ gradient.addColorStop(0, this.color1);
564
+ gradient.addColorStop(1, this.color2);
565
+ ctx.fillStyle = gradient;
566
+ ctx.fillRect(0, this.y, canvasWidth, this.height);
567
+
568
+ if (this.hasDetails) {
569
+ this.drawDetails(ctx, canvasWidth);
570
+ }
571
+ }
572
+
573
+ drawDetails(ctx, canvasWidth) {
574
+ ctx.fillStyle = 'rgba(255,255,255,0.03)';
575
+ this.details.forEach(detail => {
576
+ let x = (detail.x - this.offset * 0.5) % (canvasWidth * 2);
577
+ if (x < -100) x += canvasWidth * 2;
578
+
579
+ if (detail.type === 0) {
580
+ // Building silhouette
581
+ ctx.fillRect(x, this.y + this.height - detail.size, detail.size * 0.6, detail.size);
582
+ } else if (detail.type === 1) {
583
+ // Tree silhouette
584
+ ctx.beginPath();
585
+ ctx.moveTo(x, this.y + this.height);
586
+ ctx.lineTo(x + detail.size / 2, this.y + this.height - detail.size);
587
+ ctx.lineTo(x + detail.size, this.y + this.height);
588
+ ctx.fill();
589
+ } else {
590
+ // Hill
591
+ ctx.beginPath();
592
+ ctx.arc(x, this.y + this.height, detail.size, Math.PI, 0);
593
+ ctx.fill();
594
+ }
595
+ });
596
  }
597
  }
598
+
599
+ // ==========================================
600
+ // TRACK/GROUND SYSTEM
601
+ // ==========================================
602
+ class Track {
603
+ constructor() {
604
+ this.lanes = [];
605
+ this.lanePositions = [];
606
+ this.calculateLanePositions();
607
+ this.groundOffset = 0;
608
+ this.trackLines = [];
609
 
610
+ // Generate track line patterns
611
+ for (let i = 0; i < 50; i++) {
612
+ this.trackLines.push({
613
+ x: Utils.random(0, CONFIG.CANVAS_WIDTH * 2),
614
+ width: Utils.random(2, 8),
615
+ alpha: Utils.random(0.1, 0.3)
616
+ });
617
  }
618
+ }
619
+
620
+ calculateLanePositions() {
621
+ const centerX = CONFIG.CANVAS_WIDTH / 2;
622
+ const totalWidth = CONFIG.LANE_COUNT * CONFIG.LANE_WIDTH;
623
+ const startX = centerX - totalWidth / 2 + CONFIG.LANE_WIDTH / 2;
624
 
625
+ for (let i = 0; i < CONFIG.LANE_COUNT; i++) {
626
+ this.lanePositions.push(startX + i * CONFIG.LANE_WIDTH);
627
+ }
628
+ }
629
+
630
+ getLanePosition(laneIndex) {
631
+ return this.lanePositions[Utils.clamp(laneIndex, 0, CONFIG.LANE_COUNT - 1)];
632
+ }
633
+
634
+ update(gameSpeed, dt) {
635
+ this.groundOffset += gameSpeed * dt;
636
+ if (this.groundOffset > 100) {
637
+ this.groundOffset -= 100;
638
+ }
639
+ }
640
+
641
+ draw(ctx, canvasWidth, canvasHeight) {
642
+ const groundY = canvasHeight - 100;
643
 
644
+ // Main ground
645
+ const groundGradient = ctx.createLinearGradient(0, groundY, 0, canvasHeight);
646
+ groundGradient.addColorStop(0, '#2d4a3e');
647
+ groundGradient.addColorStop(0.3, '#1e3a2e');
648
+ groundGradient.addColorStop(1, '#0f1f1a');
649
+ ctx.fillStyle = groundGradient;
650
+ ctx.fillRect(0, groundY, canvasWidth, canvasHeight - groundY);
651
 
652
+ // Lane dividers
653
+ ctx.strokeStyle = 'rgba(255,255,255,0.15)';
654
+ ctx.lineWidth = 2;
655
+ ctx.setLineDash([20, 20]);
656
 
657
+ for (let i = 0; i <= CONFIG.LANE_COUNT; i++) {
658
+ const x = this.lanePositions[0] - CONFIG.LANE_WIDTH / 2 + i * CONFIG.LANE_WIDTH;
659
+ ctx.beginPath();
660
+ ctx.moveTo(x, groundY);
661
+ ctx.lineTo(x, canvasHeight);
662
+ ctx.stroke();
663
+ }
664
+
665
+ ctx.setLineDash([]);
666
+
667
+ // Track lines for motion effect
668
+ this.trackLines.forEach(line => {
669
+ let x = (line.x - this.groundOffset * 3) % (canvasWidth * 2);
670
+ if (x < -50) x += canvasWidth * 2;
671
+
672
+ ctx.fillStyle = `rgba(255,255,255,${line.alpha})`;
673
+ ctx.fillRect(x, groundY + 5, line.width, 2);
674
+ ctx.fillRect(x, groundY + 15, line.width * 0.5, 1);
675
+ });
676
 
677
+ // Ground top edge highlight
678
+ ctx.strokeStyle = 'rgba(0,210,106,0.5)';
679
  ctx.lineWidth = 3;
680
  ctx.beginPath();
681
+ ctx.moveTo(0, groundY);
682
+ ctx.lineTo(canvasWidth, groundY);
 
 
683
  ctx.stroke();
684
  }
685
  }
686
+
687
+ // ==========================================
688
+ // DINOSAUR PLAYER
689
+ // ==========================================
690
+ class Dinosaur {
691
+ constructor(track) {
692
+ this.track = track;
693
+ this.lane = 1; // Start in middle lane
694
+ this.targetLane = 1;
695
+ this.x = track.getLanePosition(this.lane);
696
+ this.y = CONFIG.CANVAS_HEIGHT - 100 - CONFIG.DINO_HEIGHT;
697
+ this.groundY = this.y;
698
+ this.width = CONFIG.DINO_WIDTH;
699
+ this.height = CONFIG.DINO_HEIGHT;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
700
 
701
+ // Physics
702
+ this.vy = 0;
703
+ this.isJumping = false;
704
+ this.isSliding = false;
705
+ this.slideTimer = 0;
706
+ this.laneTransition = 0;
707
+ this.targetX = this.x;
 
 
 
708
 
709
+ // Animation
710
+ this.runFrame = 0;
711
+ this.runTimer = 0;
712
+ this.bounceOffset = 0;
713
+ this.bounceTimer = 0;
714
+ this.tiltAngle = 0;
715
+
716
+ // Power-up states
717
+ this.hasShield = false;
718
+ this.hasMagnet = false;
719
+ this.hasMultiplier = false;
720
+ this.hasSpeedBoost = false;
721
+
722
+ // Visual effects
723
+ this.invincibleTimer = 0;
724
+ this.flashTimer = 0;
725
  }
726
+
727
+ jump() {
728
+ if (!this.isJumping && !this.isSliding) {
729
+ this.isJumping = true;
730
+ this.vy = CONFIG.DINO_JUMP_FORCE;
731
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
732
  }
733
+
734
+ slide() {
735
+ if (!this.isJumping && !this.isSliding) {
736
+ this.isSliding = true;
737
+ this.slideTimer = 600; // ms
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
738
  }
739
  }
740
+
741
+ moveLeft() {
742
+ if (this.targetLane > 0) {
743
+ this.targetLane--;
744
+ this.targetX = this.track.getLanePosition(this.targetLane);
745
+ this.laneTransition = 1;
746
+ this.tiltAngle = -0.2;
747
  }
748
  }
749
+
750
+ moveRight() {
751
+ if (this.targetLane < CONFIG.LANE_COUNT - 1) {
752
+ this.targetLane++;
753
+ this.targetX = this.track.getLanePosition(this.targetLane);
754
+ this.laneTransition = 1;
755
+ this.tiltAngle = 0.2;
756
  }
757
  }
758
+
759
+ update(dt) {
760
+ // Lane transition
761
+ if (this.laneTransition > 0) {
762
+ this.laneTransition -= dt * 0.008;
763
+ if (this.laneTransition <= 0) {
764
+ this.laneTransition = 0;
765
+ this.lane = this.targetLane;
766
+ }
767
+ this.x = Utils.lerp(this.x, this.targetX, 0.15);
768
+ }
769
 
770
+ // Tilt recovery
771
+ this.tiltAngle = Utils.lerp(this.tiltAngle, 0, 0.1);
 
772
 
773
+ // Jump physics
774
+ if (this.isJumping) {
775
+ this.vy += CONFIG.DINO_GRAVITY * dt;
776
+ this.y += this.vy * dt;
777
 
778
+ if (this.y >= this.groundY) {
779
+ this.y = this.groundY;
780
+ this.isJumping = false;
781
+ this.vy = 0;
 
 
 
 
 
 
 
 
782
  }
783
+ }
784
+
785
+ // Slide timer
786
+ if (this.isSliding) {
787
+ this.slideTimer -= dt;
788
+ if (this.slideTimer <= 0) {
789
+ this.isSliding = false;
 
 
 
790
  }
791
  }
792
 
793
+ // Running animation
794
+ this.runTimer += dt;
795
+ if (this.runTimer > 80) {
796
+ this.runTimer = 0;
797
+ this.runFrame = (this.runFrame + 1) % 4;
798
  }
799
+
800
+ // Bounce effect
801
+ if (!this.isJumping && !this.isSliding) {
802
+ this.bounceTimer += dt * 0.01;
803
+ this.bounceOffset = Math.sin(this.bounceTimer * 2) * 3;
 
 
 
 
 
 
804
  }
805
 
806
+ // Invincibility flash
807
+ if (this.invincibleTimer > 0) {
808
+ this.invincibleTimer -= dt;
809
+ this.flashTimer += dt;
810
+ }
811
+ }
812
+
813
+ draw(ctx) {
814
+ ctx.save();
815
 
816
+ // Position
817
+ const drawX = this.x;
818
+ let drawY = this.y + this.bounceOffset;
819
+ let scale = 1;
820
 
821
+ // Slide transformation
822
+ if (this.isSliding) {
823
+ drawY = this.groundY + this.height * 0.5;
824
+ scale = CONFIG.DINO_SLIDE_SCALE;
 
 
 
 
 
 
 
 
 
825
  }
 
 
 
 
 
826
 
827
+ ctx.translate(drawX, drawY);
828
+ ctx.rotate(this.tiltAngle);
 
829
 
830
+ // Flash effect for invincibility
831
+ if (this.invincibleTimer > 0 && Math.floor(this.flashTimer / 50) % 2 === 0) {
832
+ ctx.globalAlpha = 0.5;
 
833
  }
 
 
 
 
 
 
 
 
834
 
835
+ // Shield effect
836
+ if (this.hasShield) {
837
+ ctx.beginPath();
838
+ ctx.arc(0, -this.height * scale / 2, this.width * 0.8, 0, Math.PI * 2);
839
+ ctx.strokeStyle = 'rgba(0,212,255,0.6)';
840
+ ctx.lineWidth = 3;
841
+ ctx.stroke();
842
+ ctx.fillStyle = 'rgba(0,212,255,0.1)';
843
+ ctx.fill();
844
  }
845
+
846
+ // Draw dinosaur body
847
+ this.drawBody(ctx, scale);
848
+
849
+ ctx.restore();
850
+ }
851
+
852
+ drawBody(ctx, scale) {
853
+ const s = scale;
854
+
855
+ // Body (main oval)
856
+ ctx.fillStyle = '#2ecc71';
857
+ ctx.beginPath();
858
+ ctx.ellipse(0, -35 * s, 25 * s, 35 * s, 0, 0, Math.PI * 2);
859
+ ctx.fill();
860
+
861
+ // Belly highlight
862
+ ctx.fillStyle = '#58d68d';
863
+ ctx.beginPath();
864
+ ctx.ellipse(5 * s, -30 * s, 15 * s, 25 * s, 0.2, 0, Math.PI * 2);
865
+ ctx.fill();
866
+
867
+ // Head
868
+ ctx.fillStyle = '#2ecc71';
869
+ ctx.beginPath();
870
+ ctx.ellipse(15 * s, -60 * s, 20 * s, 18 * s, 0.3, 0, Math.PI * 2);
871
+ ctx.fill();
872
+
873
+ // Head highlight
874
+ ctx.fillStyle = '#58d68d';
875
+ ctx.beginPath();
876
+ ctx.ellipse(18 * s, -62 * s, 12 * s, 10 * s, 0.3, 0, Math.PI * 2);
877
+ ctx.fill();
878
+
879
+ // Eyes (big expressive eyes)
880
+ // Left eye white
881
+ ctx.fillStyle = '#ffffff';
882
+ ctx.beginPath();
883
+ ctx.ellipse(22 * s, -65 * s, 8 * s, 10 * s, 0, 0, Math.PI * 2);
884
+ ctx.fill();
885
+
886
+ // Right eye white
887
+ ctx.beginPath();
888
+ ctx.ellipse(35 * s, -63 * s, 7 * s, 9 * s, 0, 0, Math.PI * 2);
889
+ ctx.fill();
890
+
891
+ // Pupils
892
+ ctx.fillStyle = '#1a1a2e';
893
+ ctx.beginPath();
894
+ ctx.arc(24 * s, -64 * s, 4 * s, 0, Math.PI * 2);
895
+ ctx.fill();
896
+ ctx.beginPath();
897
+ ctx.arc(36 * s, -62 * s, 3.5 * s, 0, Math.PI * 2);
898
+ ctx.fill();
899
+
900
+ // Eye shine
901
+ ctx.fillStyle = '#ffffff';
902
+ ctx.beginPath();
903
+ ctx.arc(25 * s, -66 * s, 1.5 * s, 0, Math.PI * 2);
904
+ ctx.fill();
905
+ ctx.beginPath();
906
+ ctx.arc(37 * s, -64 * s, 1.2 * s, 0, Math.PI * 2);
907
+ ctx.fill();
908
+
909
+ // Headband
910
+ ctx.fillStyle = '#e74c3c';
911
+ ctx.beginPath();
912
+ ctx.moveTo(-5 * s, -75 * s);
913
+ ctx.lineTo(35 * s, -70 * s);
914
+ ctx.lineTo(33 * s, -65 * s);
915
+ ctx.lineTo(-7 * s, -70 * s);
916
+ ctx.closePath();
917
+ ctx.fill();
918
+
919
+ // Bitcoin symbol on headband
920
+ ctx.fillStyle = '#f7931a';
921
+ ctx.beginPath();
922
+ ctx.arc(15 * s, -72 * s, 8 * s, 0, Math.PI * 2);
923
+ ctx.fill();
924
+
925
+ // Draw ₿ symbol
926
+ ctx.fillStyle = '#ffffff';
927
+ ctx.font = `bold ${10 * s}px Arial`;
928
+ ctx.textAlign = 'center';
929
+ ctx.textBaseline = 'middle';
930
+ ctx.fillText('₿', 15 * s, -72 * s);
931
+
932
+ // Mouth (smile)
933
+ ctx.strokeStyle = '#1a5a3a';
934
+ ctx.lineWidth = 2 * s;
935
+ ctx.beginPath();
936
+ ctx.arc(30 * s, -52 * s, 6 * s, 0.2, Math.PI - 0.2);
937
+ ctx.stroke();
938
+
939
+ // Arms
940
+ ctx.fillStyle = '#2ecc71';
941
+ const armAngle = this.isSliding ? 0.5 : Math.sin(this.runTimer * 0.05) * 0.3;
942
+ ctx.save();
943
+ ctx.translate(-20 * s, -40 * s);
944
+ ctx.rotate(armAngle);
945
+ ctx.beginPath();
946
+ ctx.ellipse(0, 10 * s, 6 * s, 15 * s, 0, 0, Math.PI * 2);
947
+ ctx.fill();
948
+ ctx.restore();
949
+
950
+ // Legs
951
+ if (!this.isSliding) {
952
+ const legOffset = this.runFrame % 2 === 0 ? 5 : -5;
953
 
954
+ // Left leg
955
+ ctx.fillStyle = '#27ae60';
956
+ ctx.beginPath();
957
+ ctx.ellipse(-10 * s + legOffset * s, -5 * s, 8 * s, 12 * s, legOffset * 0.02, 0, Math.PI * 2);
958
+ ctx.fill();
959
+
960
+ // Right leg
961
+ ctx.beginPath();
962
+ ctx.ellipse(10 * s - legOffset * s, -5 * s, 8 * s, 12 * s, -legOffset * 0.02, 0, Math.PI * 2);
963
+ ctx.fill();
964
+ }
965
+
966
+ // Tail
967
+ ctx.fillStyle = '#2ecc71';
968
+ ctx.beginPath();
969
+ ctx.moveTo(-25 * s, -35 * s);
970
+ ctx.quadraticCurveTo(-45 * s, -30 * s, -50 * s, -45 * s);
971
+ ctx.quadraticCurveTo(-40 * s, -50 * s, -25 * s, -45 * s);
972
+ ctx.fill();
973
+
974
+ // Magnet effect indicator
975
+ if (this.hasMagnet) {
976
+ ctx.strokeStyle = 'rgba(255,215,0,0.5)';
977
+ ctx.lineWidth = 2;
978
+ ctx.beginPath();
979
+ ctx.arc(0, -35 * s, 40 * s, 0, Math.PI * 2);
980
+ ctx.stroke();
981
  }
 
 
 
 
 
 
 
982
  }
983
+
984
+ getHitbox() {
985
+ const slideOffset = this.isSliding ? this.height * 0.6 : 0;
986
+ return {
987
+ x: this.x - this.width / 2 + 10,
988
+ y: this.y + slideOffset,
989
+ width: this.width - 20,
990
+ height: this.height * (this.isSliding ? CONFIG.DINO_SLIDE_SCALE : 1) - 10
991
+ };
992
+ }
993
  }
994
+
995
+ // ==========================================
996
+ // OBSTACLE SYSTEM
997
+ // ==========================================
998
+ class Obstacle {
999
+ constructor(lane, type, track, groundY) {
1000
+ this.lane = lane;
1001
+ this.type = type;
1002
+ this.x = track.getLanePosition(lane);
1003
+ this.y = groundY;
1004
+ this.width = 50;
1005
+ this.height = 60;
1006
+ this.needsJump = true;
1007
+ this.needsSlide = false;
1008
+
1009
+ this.setupByType();
1010
+ }
1011
+
1012
+ setupByType() {
1013
+ switch (this.type) {
1014
+ case 'barrier':
1015
+ this.width = 50;
1016
+ this.height = 70;
1017
+ this.needsJump = true;
1018
+ this.needsSlide = false;
1019
+ break;
1020
+ case 'lowBarrier':
1021
+ this.width = 60;
1022
+ this.height = 35;
1023
+ this.needsJump = false;
1024
+ this.needsSlide = true;
1025
+ break;
1026
+ case 'doubleBarrier':
1027
+ this.width = 45;
1028
+ this.height = 90;
1029
+ this.needsJump = true;
1030
+ this.needsSlide = false;
1031
+ break;
1032
+ case 'spike':
1033
+ this.width = 40;
1034
+ this.height = 50;
1035
+ this.needsJump = true;
1036
+ this.needsSlide = false;
1037
+ break;
1038
+ case 'crate':
1039
+ this.width = 55;
1040
+ this.height = 55;
1041
+ this.needsJump = true;
1042
+ this.needsSlide = false;
1043
+ break;
1044
+ }
1045
+ }
1046
+
1047
+ update(gameSpeed, dt) {
1048
+ this.x -= gameSpeed * dt;
1049
+ }
1050
+
1051
+ draw(ctx) {
1052
+ ctx.save();
1053
+ ctx.translate(this.x, this.y);
1054
 
1055
+ switch (this.type) {
1056
+ case 'barrier':
1057
+ this.drawBarrier(ctx);
1058
+ break;
1059
+ case 'lowBarrier':
1060
+ this.drawLowBarrier(ctx);
1061
+ break;
1062
+ case 'doubleBarrier':
1063
+ this.drawDoubleBarrier(ctx);
1064
+ break;
1065
+ case 'spike':
1066
+ this.drawSpike(ctx);
1067
+ break;
1068
+ case 'crate':
1069
+ this.drawCrate(ctx);
1070
+ break;
1071
  }
1072
+
1073
+ ctx.restore();
1074
  }
1075
+
1076
+ drawBarrier(ctx) {
1077
+ // Main barrier body
1078
+ const gradient = ctx.createLinearGradient(0, -this.height, 0, 0);
1079
+ gradient.addColorStop(0, '#c0392b');
1080
+ gradient.addColorStop(1, '#922b21');
1081
+ ctx.fillStyle = gradient;
1082
+ ctx.fillRect(-this.width / 2, -this.height, this.width, this.height);
1083
+
1084
+ // Warning stripes
1085
+ ctx.fillStyle = '#f1c40f';
1086
+ for (let i = 0; i < 3; i++) {
1087
+ ctx.fillRect(-this.width / 2 + 5, -this.height + 15 + i * 25, this.width - 10, 8);
1088
  }
1089
+
1090
+ // Top cap
1091
+ ctx.fillStyle = '#e74c3c';
1092
+ ctx.fillRect(-this.width / 2 - 5, -this.height - 5, this.width + 10, 10);
1093
  }
1094
+
1095
+ drawLowBarrier(ctx) {
1096
+ // Low pipe/barrier
1097
+ const gradient = ctx.createLinearGradient(0, -this.height, 0, 0);
1098
+ gradient.addColorStop(0, '#7f8c8d');
1099
+ gradient.addColorStop(0.5, '#95a5a6');
1100
+ gradient.addColorStop(1, '#7f8c8d');
1101
+ ctx.fillStyle = gradient;
1102
+ ctx.fillRect(-this.width / 2, -this.height, this.width, this.height);
1103
+
1104
+ // Highlights
1105
+ ctx.fillStyle = 'rgba(255,255,255,0.3)';
1106
+ ctx.fillRect(-this.width / 2 + 5, -this.height + 3, 10, this.height - 6);
1107
  }
1108
+
1109
+ drawDoubleBarrier(ctx) {
1110
+ // Two stacked barriers
1111
+ ctx.fillStyle = '#8e44ad';
1112
+ ctx.fillRect(-this.width / 2, -this.height * 0.55, this.width, this.height * 0.55);
1113
+ ctx.fillRect(-this.width / 2, -this.height, this.width, this.height * 0.45);
1114
+
1115
+ // Warning symbols
1116
+ ctx.fillStyle = '#f39c12';
1117
+ ctx.font = 'bold 20px Arial';
1118
+ ctx.textAlign = 'center';
1119
+ ctx.fillText('!', 0, -this.height * 0.3);
1120
+ ctx.fillText('!', 0, -this.height * 0.8);
1121
+ }
1122
+
1123
+ drawSpike(ctx) {
1124
+ ctx.fillStyle = '#7f8c8d';
1125
+ ctx.beginPath();
1126
+ ctx.moveTo(-this.width / 2, 0);
1127
+ ctx.lineTo(0, -this.height);
1128
+ ctx.lineTo(this.width / 2, 0);
1129
+ ctx.closePath();
1130
+ ctx.fill();
1131
+
1132
+ // Shine
1133
+ ctx.fillStyle = 'rgba(255,255,255,0.3)';
1134
+ ctx.beginPath();
1135
+ ctx.moveTo(-this.width / 4, -this.height * 0.3);
1136
+ ctx.lineTo(0, -this.height * 0.8);
1137
+ ctx.lineTo(this.width / 8, -this.height * 0.3);
1138
+ ctx.closePath();
1139
+ ctx.fill();
1140
+ }
1141
+
1142
+ drawCrate(ctx) {
1143
+ // Wooden crate
1144
+ const gradient = ctx.createLinearGradient(0, -this.height, 0, 0);
1145
+ gradient.addColorStop(0, '#d68910');
1146
+ gradient.addColorStop(1, '#b9770e');
1147
+ ctx.fillStyle = gradient;
1148
+ ctx.fillRect(-this.width / 2, -this.height, this.width, this.height);
1149
+
1150
+ // Cross planks
1151
+ ctx.strokeStyle = '#7e5109';
1152
+ ctx.lineWidth = 3;
1153
+ ctx.beginPath();
1154
+ ctx.moveTo(-this.width / 2, -this.height);
1155
+ ctx.lineTo(this.width / 2, 0);
1156
+ ctx.moveTo(this.width / 2, -this.height);
1157
+ ctx.lineTo(-this.width / 2, 0);
1158
+ ctx.stroke();
1159
+
1160
+ // Border
1161
+ ctx.strokeStyle = '#6e4a09';
1162
+ ctx.lineWidth = 4;
1163
+ ctx.strokeRect(-this.width / 2, -this.height, this.width, this.height);
1164
+ }
1165
+
1166
+ getHitbox() {
1167
+ return {
1168
+ x: this.x - this.width / 2 + 5,
1169
+ y: this.y - this.height + 5,
1170
+ width: this.width - 10,
1171
+ height: this.height - 10
1172
+ };
1173
+ }
1174
+
1175
+ get isOffScreen() {
1176
+ return this.x < -100;
1177
  }
1178
  }
1179
+
1180
+ // ==========================================
1181
+ // COIN SYSTEM
1182
+ // ==========================================
1183
+ class Coin {
1184
+ constructor(lane, track, groundY, isBonus = false) {
1185
+ this.lane = lane;
1186
+ this.x = track.getLanePosition(lane);
1187
+ this.y = groundY - 60;
1188
+ this.baseY = this.y;
1189
+ this.radius = 18;
1190
+ this.isBonus = isBonus;
1191
+ this.rotation = 0;
1192
+ this.bobOffset = Utils.random(0, Math.PI * 2);
1193
+ this.glowIntensity = 0;
1194
+ this.glowDirection = 1;
1195
+
1196
+ // BTC value
1197
+ if (isBonus) {
1198
+ this.value = Utils.random(CONFIG.COIN_VALUE_MIN, CONFIG.COIN_VALUE_MAX) * CONFIG.BONUS_COIN_MULTIPLIER;
1199
+ } else {
1200
+ this.value = Utils.random(CONFIG.COIN_VALUE_MIN, CONFIG.COIN_VALUE_MAX);
1201
+ }
1202
+ }
1203
+
1204
+ update(gameSpeed, dt) {
1205
+ this.x -= gameSpeed * dt;
1206
+ this.rotation += dt * 0.01;
1207
+ this.bobOffset += dt * 0.005;
1208
+ this.y = this.baseY + Math.sin(this.bobOffset) * 8;
1209
+
1210
+ // Glow pulse
1211
+ this.glowIntensity += this.glowDirection * dt * 0.003;
1212
+ if (this.glowIntensity > 1) {
1213
+ this.glowDirection = -1;
1214
+ } else if (this.glowIntensity < 0) {
1215
+ this.glowDirection = 1;
1216
+ }
1217
+ }
1218
+
1219
+ draw(ctx) {
1220
+ ctx.save();
1221
+ ctx.translate