HAL1993 commited on
Commit
28c55c9
·
verified ·
1 Parent(s): b95020a

Upload folder using huggingface_hub

Browse files
Files changed (1) hide show
  1. index.html +1048 -1087
index.html CHANGED
@@ -2,1220 +2,1181 @@
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;
61
  width: 100%;
62
  height: 100%;
 
63
  display: flex;
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
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2
  <html lang="en">
3
  <head>
4
  <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
6
+ <title>Bitcoin Dino: Endless Runner</title>
 
7
  <style>
8
  :root {
9
+ --primary-color: #f7931a; /* Bitcoin Orange */
10
+ --accent-color: #00ff00; /* Dino Green */
11
+ --dark-bg: #1a1a2e;
12
+ --ui-bg: rgba(0, 0, 0, 0.85);
13
+ --text-color: #ffffff;
14
+ --font-main: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
 
 
 
15
  }
16
 
17
  * {
 
 
18
  box-sizing: border-box;
19
+ user-select: none;
20
+ -webkit-user-select: none;
21
+ touch-action: none; /* Prevent browser zooming/scrolling */
22
  }
23
 
24
+ body, html {
25
+ margin: 0;
26
+ padding: 0;
27
+ width: 100%;
28
+ height: 100%;
29
  overflow: hidden;
30
+ background-color: var(--dark-bg);
31
+ font-family: var(--font-main);
32
+ color: var(--text-color);
33
  }
34
 
35
+ #game-container {
36
  position: relative;
37
  width: 100%;
38
+ height: 100%;
39
+ display: flex;
40
+ justify-content: center;
41
+ align-items: center;
 
 
 
 
42
  }
43
 
44
+ canvas {
 
 
45
  display: block;
46
+ box-shadow: 0 0 50px rgba(0,0,0,0.5);
47
  }
48
 
49
+ /* UI OVERLAYS */
50
+ .ui-layer {
51
  position: absolute;
52
  top: 0;
53
  left: 0;
54
  width: 100%;
55
  height: 100%;
56
+ pointer-events: none; /* Let clicks pass through to canvas when playing */
57
  display: flex;
58
  flex-direction: column;
59
  justify-content: center;
60
  align-items: center;
61
+ transition: opacity 0.3s ease;
 
 
 
62
  }
63
 
64
+ .hidden {
65
  opacity: 0;
66
+ pointer-events: none !important;
 
 
 
 
 
 
 
 
 
 
 
 
 
67
  }
68
 
69
+ .visible {
70
+ opacity: 1;
71
+ pointer-events: auto !important;
72
+ background: rgba(0,0,0,0.6);
73
+ backdrop-filter: blur(5px);
 
 
 
 
 
 
 
 
 
 
 
74
  }
75
 
76
+ /* HUD */
77
+ #hud {
78
+ justify-content: space-between;
79
+ flex-direction: row;
80
+ align-items: flex-start;
81
+ padding: 20px;
82
+ height: auto;
83
+ z-index: 10;
84
  }
85
 
86
+ .hud-panel {
87
+ background: rgba(0, 0, 0, 0.5);
88
+ padding: 10px 20px;
89
+ border-radius: 15px;
90
+ border: 2px solid var(--primary-color);
91
+ box-shadow: 0 4px 10px rgba(0,0,0,0.3);
92
+ display: flex;
93
+ flex-direction: column;
94
+ gap: 5px;
95
+ min-width: 150px;
96
  }
97
 
98
+ .hud-label {
99
+ font-size: 0.8rem;
100
+ color: #aaa;
101
+ text-transform: uppercase;
102
+ letter-spacing: 1px;
103
  }
104
 
105
+ .hud-value {
106
+ font-size: 1.5rem;
107
+ font-weight: bold;
108
+ color: var(--text-color);
109
+ font-variant-numeric: tabular-nums;
110
  }
111
 
112
+ #btc-value {
113
+ color: var(--primary-color);
114
+ font-size: 1.8rem;
115
  }
116
 
117
+ .powerup-indicator {
118
+ position: absolute;
119
+ top: 80px;
120
+ right: 20px;
121
+ display: flex;
122
+ gap: 10px;
123
  }
124
 
125
+ .active-powerup {
126
+ background: var(--primary-color);
127
+ color: white;
128
+ padding: 5px 10px;
129
+ border-radius: 5px;
130
+ font-size: 0.9rem;
131
+ animation: pulse 1s infinite;
132
  }
133
 
134
+ @keyframes pulse {
135
+ 0% { transform: scale(1); box-shadow: 0 0 0 0 rgba(247, 147, 26, 0.7); }
136
+ 70% { transform: scale(1.1); box-shadow: 0 0 0 10px rgba(247, 147, 26, 0); }
137
+ 100% { transform: scale(1); box-shadow: 0 0 0 0 rgba(247, 147, 26, 0); }
 
 
138
  }
139
 
140
+ /* MENUS */
141
+ .menu-content {
142
+ background: var(--ui-bg);
143
+ padding: 40px;
 
 
144
  border-radius: 20px;
145
+ text-align: center;
146
+ border: 4px solid var(--primary-color);
147
+ box-shadow: 0 0 50px var(--primary-color);
148
+ max-width: 500px;
149
+ width: 90%;
150
+ animation: float 3s ease-in-out infinite;
151
  }
152
 
153
+ @keyframes float {
154
+ 0% { transform: translateY(0px); }
155
+ 50% { transform: translateY(-20px); }
156
+ 100% { transform: translateY(0px); }
 
 
 
157
  }
158
 
159
+ h1 {
160
+ font-size: 3rem;
161
+ margin: 0 0 20px 0;
162
+ color: var(--accent-color);
163
+ text-shadow: 3px 3px 0px #000;
164
+ line-height: 1.1;
 
165
  }
166
 
167
+ h2 {
168
+ color: var(--primary-color);
169
+ margin-bottom: 30px;
 
 
170
  }
171
 
172
+ p {
173
+ font-size: 1.1rem;
174
+ line-height: 1.6;
175
+ color: #ddd;
176
+ margin-bottom: 30px;
177
  }
178
 
179
+ .btn {
180
+ background: linear-gradient(45deg, var(--primary-color), #ff7f00);
181
+ border: none;
182
+ padding: 15px 40px;
183
+ font-size: 1.5rem;
184
+ color: white;
185
+ font-weight: bold;
186
+ border-radius: 50px;
187
+ cursor: pointer;
188
+ transition: transform 0.2s, box-shadow 0.2s;
189
+ font-family: var(--font-main);
190
+ text-transform: uppercase;
191
+ box-shadow: 0 5px 15px rgba(247, 147, 26, 0.4);
192
+ margin-top: 20px;
193
  }
194
 
195
+ .btn:hover {
196
+ transform: scale(1.05);
197
+ box-shadow: 0 8px 20px rgba(247, 147, 26, 0.6);
 
 
 
 
 
198
  }
199
 
200
+ .btn:active {
201
+ transform: scale(0.95);
 
 
202
  }
203
 
204
+ .controls-hint {
205
+ margin-top: 30px;
206
+ display: grid;
207
+ grid-template-columns: 1fr 1fr;
208
+ gap: 15px;
209
+ text-align: left;
210
+ font-size: 0.9rem;
 
 
 
211
  }
212
 
213
+ .key {
214
+ background: #333;
215
+ padding: 5px 10px;
216
+ border-radius: 5px;
217
+ font-family: monospace;
218
+ border-bottom: 2px solid #111;
219
  }
220
 
221
+ /* Mobile Controls Overlay */
222
+ #mobile-controls {
223
  position: absolute;
224
  bottom: 20px;
225
+ width: 100%;
226
+ height: 150px;
227
+ pointer-events: none;
228
+ display: none; /* Shown via JS on touch devices */
229
+ z-index: 20;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
230
  }
231
 
232
+ .touch-zone {
233
+ position: absolute;
234
+ bottom: 0;
235
+ width: 33.33%;
236
+ height: 100%;
237
+ pointer-events: auto;
238
+ /* background: rgba(255,255,255,0.1); Debugging */
239
  }
240
+
241
+ .touch-label {
242
+ position: absolute;
243
+ bottom: 20px;
244
+ width: 100%;
245
+ text-align: center;
246
+ color: rgba(255,255,255,0.3);
247
+ font-size: 2rem;
248
+ pointer-events: none;
249
  }
250
 
251
+ #zone-left { left: 0; }
252
+ #zone-jump { left: 33.33%; }
253
+ #zone-slide { right: 33.33%; }
 
 
 
 
 
 
 
 
 
 
254
 
255
+ @media (max-width: 768px) {
256
+ h1 { font-size: 2rem; }
257
+ .hud-panel { min-width: 100px; padding: 5px 10px; }
258
+ .hud-value { font-size: 1.2rem; }
259
+ #btc-value { font-size: 1.4rem; }
260
+ #mobile-controls { display: block; }
261
  }
262
  </style>
263
  </head>
264
  <body>
265
+
266
+ <div id="game-container">
267
+ <canvas id="gameCanvas"></canvas>
268
+
269
+ <!-- HUD -->
270
+ <div id="hud" class="ui-layer">
271
+ <div class="hud-panel">
272
+ <span class="hud-label">Distance</span>
273
+ <span class="hud-value" id="score-dist">0 m</span>
 
 
 
 
 
 
 
 
274
  </div>
275
 
276
+ <div class="powerup-indicator" id="powerup-container">
277
+ <!-- Active powerups appear here -->
 
 
 
278
  </div>
279
+
280
+ <div class="hud-panel" style="align-items: flex-end;">
281
+ <span class="hud-label">BTC Balance</span>
282
+ <span class="hud-value" id="btc-value">0.00000000</span>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
283
  </div>
284
+ </div>
285
+
286
+ <!-- Start Screen -->
287
+ <div id="start-screen" class="ui-layer visible">
288
+ <div class="menu-content">
289
+ <h1>BITCOIN DINO</h1>
290
+ <p>Run endless, mine BTC, and survive the obstacles!</p>
291
+
292
+ <div class="controls-hint">
293
+ <div><span class="key">←</span> <span class="key"></span> Move Lanes</div>
294
+ <div><span class="key">↑</span> or <span class="key">Space</span> Jump</div>
295
+ <div><span class="key">↓</span> Slide</div>
296
+ <div><span class="key">P</span> Pause Game</div>
297
  </div>
298
+
299
+ <button class="btn" id="start-btn">START MINING</button>
300
  </div>
301
  </div>
302
 
303
+ <!-- Game Over Screen -->
304
+ <div id="game-over-screen" class="ui-layer hidden">
305
+ <div class="menu-content">
306
+ <h1 style="color: #ff4444;">CRASHED!</h1>
307
+ <p>Your BTC mining session has ended.</p>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
308
 
309
+ <div class="hud-panel" style="margin: 20px auto; width: auto;">
310
+ <span class="hud-label">Final Distance</span>
311
+ <span class="hud-value" id="final-score-dist">0 m</span>
312
+ <span class="hud-label" style="margin-top:10px;">BTC Earned</span>
313
+ <span class="hud-value" id="final-btc" style="color: var(--primary-color);">0.00000000</span>
314
+ </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
315
 
316
+ <button class="btn" id="restart-btn">CONTINUE MINING</button>
317
+ </div>
318
+ </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
319
 
320
+ <!-- Pause Screen -->
321
+ <div id="pause-screen" class="ui-layer hidden">
322
+ <div class="menu-content">
323
+ <h1>PAUSED</h1>
324
+ <p>Take a breather, miner.</p>
325
+ <button class="btn" id="resume-btn">RESUME</button>
326
+ </div>
327
+ </div>
 
 
 
 
 
 
 
328
 
329
+ <!-- Mobile Controls -->
330
+ <div id="mobile-controls">
331
+ <div id="zone-left" class="touch-zone">
332
+ <div class="touch-label">←</div>
333
+ </div>
334
+ <div id="zone-jump" class="touch-zone">
335
+ <div class="touch-label">↑</div>
336
+ </div>
337
+ <div id="zone-slide" class="touch-zone">
338
+ <div class="touch-label">↓</div>
339
+ </div>
340
+ </div>
341
+ </div>
342
+
343
+ <script>
344
+ /**
345
+ * BITCOIN DINO - ENDLESS RUNNER
346
+ * Production Grade Implementation
347
+ */
348
+
349
+ // --- CONFIGURATION & CONSTANTS ---
350
+ const CONFIG = {
351
+ canvasWidth: 800,
352
+ canvasHeight: 450,
353
+ laneWidth: 100, // Base width, scaled later
354
+ gravity: 0.6,
355
+ jumpForce: -12,
356
+ speedBase: 8,
357
+ speedMax: 25,
358
+ speedIncrement: 0.005, // Speed increase per frame
359
+ colors: {
360
+ dino: '#4CAF50', // Green
361
+ dinoDark: '#388E3C',
362
+ skin: '#81C784',
363
+ headband: '#111',
364
+ coin: '#f7931a',
365
+ coinInner: '#FFB900',
366
+ obstacle: '#555',
367
+ obstacleTop: '#777',
368
+ skyTop: '#2b32b2',
369
+ skyBottom: '#1488cc',
370
+ ground: '#222',
371
+ grid: '#333'
372
+ }
373
+ };
374
+
375
+ // --- UTILITIES ---
376
+ const Utils = {
377
+ randomRange: (min, max) => Math.random() * (max - min) + min,
378
+ checkAABB: (rect1, rect2) => {
379
+ return (
380
+ rect1.x < rect2.x + rect2.w &&
381
+ rect1.x + rect1.w > rect2.x &&
382
+ rect1.y < rect2.y + rect2.h &&
383
+ rect1.y + rect1.h > rect2.y
384
+ );
385
+ },
386
+ formatBTC: (amount) => {
387
+ // Ensure exactly 8 decimal places
388
+ return amount.toFixed(8);
389
+ }
390
+ };
391
+
392
+ // --- INPUT HANDLING ---
393
+ class InputHandler {
394
+ constructor(game) {
395
+ this.game = game;
396
+ this.keys = {
397
+ left: false,
398
+ right: false,
399
+ up: false,
400
+ down: false
401
+ };
402
+
403
+ // Keyboard
404
+ window.addEventListener('keydown', (e) => this.handleKey(e, true));
405
+ window.addEventListener('keyup', (e) => this.handleKey(e, false));
406
+
407
+ // Mobile Touch
408
+ const zoneLeft = document.getElementById('zone-left');
409
+ const zoneJump = document.getElementById('zone-jump');
410
+ const zoneSlide = document.getElementById('zone-slide');
411
+
412
+ const addTouch = (element, action) => {
413
+ element.addEventListener('touchstart', (e) => { e.preventDefault(); this.triggerAction(action); });
414
+ element.addEventListener('mousedown', (e) => { e.preventDefault(); this.triggerAction(action); });
415
+ };
416
 
417
+ addTouch(zoneLeft, 'left');
418
+ addTouch(zoneJump, 'jump');
419
+ addTouch(zoneSlide, 'slide');
420
+
421
+ // Pause Toggle
422
+ window.addEventListener('keydown', (e) => {
423
+ if (e.key.toLowerCase() === 'p') this.game.togglePause();
424
+ });
425
+ }
426
+
427
+ handleKey(e, isDown) {
428
+ const key = e.key.toLowerCase();
429
+ if (key === 'arrowleft' || key === 'a') this.keys.left = isDown;
430
+ if (key === 'arrowright' || key === 'd') this.keys.right = isDown;
431
+ if (key === 'arrowup' || key === 'w' || key === ' ') this.keys.up = isDown;
432
+ if (key === 'arrowdown' || key === 's') this.keys.down = isDown;
433
+
434
+ if (isDown && this.keys.up) this.game.player.jump();
435
+ if (isDown && this.keys.down) this.game.player.slide(true);
436
+ if (!isDown && this.keys.down) this.game.player.slide(false);
437
+ }
438
 
439
+ triggerAction(action) {
440
+ if (this.game.gameState !== 'PLAYING') return;
441
+
442
+ if (action === 'left') this.game.player.moveLane(-1);
443
+ if (action === 'right') this.game.player.moveLane(1);
444
+ if (action === 'jump') this.game.player.jump();
445
+ if (action === 'slide') this.game.player.slide(true);
446
+ }
447
+ }
448
+
449
+ // --- PARTICLE SYSTEM ---
450
+ class Particle {
451
+ constructor(x, y, color, type = 'coin') {
452
+ this.x = x;
453
+ this.y = y;
454
+ this.color = color;
455
+ this.type = type;
456
+ this.size = Utils.randomRange(3, 6);
457
+ this.speedX = Utils.randomRange(-3, 3);
458
+ this.speedY = Utils.randomRange(-3, 3);
459
+ this.life = 1.0; // Opacity
460
+ this.decay = Utils.randomRange(0.02, 0.05);
461
+ }
462
+
463
+ update() {
464
+ this.x += this.speedX;
465
+ this.y += this.speedY;
466
+ this.life -= this.decay;
467
+ this.size *= 0.95;
468
+ }
469
+
470
+ draw(ctx) {
471
+ ctx.save();
472
+ ctx.globalAlpha = this.life;
473
+ ctx.fillStyle = this.color;
474
+ ctx.beginPath();
475
+ ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2);
476
+ ctx.fill();
477
+ ctx.restore();
478
+ }
479
+ }
480
+
481
+ // --- GAME ENTITIES ---
482
+
483
+ class Coin {
484
+ constructor(lane, x) {
485
+ this.lane = lane; // 0, 1, 2
486
+ this.x = 0; // Calculated in update
487
+ this.y = 0;
488
+ this.w = 30;
489
+ this.h = 30;
490
+ this.active = true;
491
+ this.btcValue = Utils.randomRange(0.00000001, 0.00000005);
492
+ this.bonusChance = 0.1;
493
+ if (Math.random() < this.bonusChance) this.btcValue *= 5;
494
+
495
+ // Animation vars
496
+ this.rotation = 0;
497
+ this.baseY = 0; // Set during spawn
498
+ }
499
+
500
+ update(speed, laneCenterX, laneY) {
501
+ this.x = laneCenterX(this.lane) - this.w / 2;
502
+ this.y = laneY + 20;
503
+ this.rotation += 0.1;
504
+ this.x -= speed;
505
+ }
506
+
507
+ draw(ctx) {
508
+ if (!this.active) return;
509
+
510
+ ctx.save();
511
+ ctx.translate(this.x + this.w/2, this.y + this.h/2);
512
+
513
+ // Spin effect
514
+ const scaleX = Math.abs(Math.cos(this.rotation));
515
+ ctx.scale(scaleX, 1);
516
+
517
+ // Glow
518
+ ctx.shadowBlur = 15;
519
+ ctx.shadowColor = CONFIG.colors.coin;
520
+
521
+ // Coin Body
522
+ ctx.fillStyle = CONFIG.colors.coin;
523
+ ctx.beginPath();
524
+ ctx.arc(0, 0, this.w/2, 0, Math.PI * 2);
525
+ ctx.fill();
526
+ ctx.lineWidth = 2;
527
+ ctx.strokeStyle = '#b35900';
528
+ ctx.stroke();
529
+
530
+ // BTC Symbol
531
+ ctx.fillStyle = '#000';
532
+ ctx.font = 'bold 16px Arial';
533
+ ctx.textAlign = 'center';
534
+ ctx.textBaseline = 'middle';
535
+ ctx.fillText('₿', 0, 1);
536
+
537
+ ctx.restore();
538
+ }
539
+ }
540
+
541
+ class Obstacle {
542
+ constructor(lane, x, type) {
543
+ this.lane = lane;
544
+ this.x = x;
545
+ this.y = 0;
546
+ this.w = 60;
547
+ this.h = 60;
548
+ this.type = type; // 'barrier', 'low_barrier', 'high_hazard'
549
+ this.active = true;
550
+
551
+ // Setup dimensions based on type
552
+ if (type === 'low_barrier') {
553
+ this.h = 30;
554
+ this.y = 0; // On ground
555
+ this.color = '#e74c3c';
556
+ } else if (type === 'high_hazard') {
557
+ this.w = 50;
558
+ this.h = 50;
559
+ this.y = 110; // In air (requires slide)
560
+ this.color = '#8e44ad';
561
+ } else {
562
+ // Standard barrier
563
+ this.w = 60;
564
+ this.h = 60;
565
+ this.y = 0;
566
+ this.color = '#34495e';
567
  }
568
+ }
569
 
570
+ update(speed) {
571
+ this.x -= speed;
572
+ }
 
573
 
574
+ draw(ctx) {
575
+ if (!this.active) return;
576
+
577
+ // Draw Shadow
578
+ ctx.fillStyle = 'rgba(0,0,0,0.3)';
579
+ ctx.fillRect(this.x + 5, this.y + 5, this.w, this.h);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
580
 
581
+ // Draw Obstacle
582
+ ctx.fillStyle = this.color;
583
+ ctx.fillRect(this.x, this.y, this.w, this.h);
584
+
585
+ // Detail
586
+ ctx.strokeStyle = '#2c3e50';
587
+ ctx.lineWidth = 3;
588
+ ctx.strokeRect(this.x + 5, this.y + 5, this.w - 10, this.h - 10);
589
+
590
+ // Warning stripes
591
+ ctx.fillStyle = 'rgba(255,255,255,0.2)';
592
+ if (this.type === 'barrier') {
593
+ ctx.beginPath();
594
+ ctx.moveTo(this.x, this.y);
595
+ ctx.lineTo(this.x + 20, this.y);
596
+ ctx.lineTo(this.x, this.y + 20);
597
+ ctx.fill();
598
  }
599
+ }
600
+ }
601
+
602
+ class PowerUp {
603
+ constructor(lane, x, type) {
604
+ this.lane = lane;
605
+ this.x = x;
606
+ this.y = 0;
607
+ this.w = 40;
608
+ this.h = 40;
609
+ this.type = type; // 'magnet', 'shield', 'multiplier'
610
+ this.active = true;
611
+ this.oscillation = 0;
612
+ }
613
+
614
+ update(speed, laneCenterX, laneY) {
615
+ this.x = laneCenterX(this.lane) - this.w / 2;
616
+ this.y = laneY - 40; // Floating above
617
+ this.oscillation += 0.1;
618
+ this.y += Math.sin(this.oscillation) * 10;
619
+ this.x -= speed;
620
+ }
621
+
622
+ draw(ctx) {
623
+ if (!this.active) return;
624
+
625
+ const cx = this.x + this.w/2;
626
+ const cy = this.y + this.h/2;
627
+
628
+ // Glow
629
+ ctx.shadowBlur = 20;
630
+
631
+ if (this.type === 'shield') {
632
+ ctx.shadowColor = '#00bcd4';
633
+ ctx.fillStyle = '#00bcd4';
634
+ ctx.beginPath();
635
+ ctx.arc(cx, cy, 15, 0, Math.PI*2);
636
+ ctx.fill();
637
+ ctx.fillStyle = '#fff';
638
+ ctx.font = '12px Arial';
639
+ ctx.textAlign = 'center';
640
+ ctx.textBaseline = 'middle';
641
+ ctx.fillText('S', cx, cy);
642
+ } else if (this.type === 'magnet') {
643
+ ctx.shadowColor = '#ffeb3b';
644
+ ctx.fillStyle = '#ffeb3b';
645
+ ctx.fillRect(cx - 15, cy - 10, 30, 20);
646
+ ctx.fillStyle = '#333';
647
+ ctx.font = '12px Arial';
648
+ ctx.textAlign = 'center';
649
+ ctx.textBaseline = 'middle';
650
+ ctx.fillText('M', cx, cy);
651
+ } else if (this.type === 'multiplier') {
652
+ ctx.shadowColor = '#e91e63';
653
+ ctx.fillStyle = '#e91e63';
654
+ ctx.beginPath();
655
+ ctx.moveTo(cx, cy - 15);
656
+ ctx.lineTo(cx + 15, cy + 15);
657
+ ctx.lineTo(cx - 15, cy + 15);
658
+ ctx.fill();
659
+ ctx.fillStyle = '#fff';
660
+ ctx.font = 'bold 16px Arial';
661
+ ctx.textAlign = 'center';
662
+ ctx.textBaseline = 'middle';
663
+ ctx.fillText('2x', cx, cy);
 
 
 
 
 
 
 
 
 
664
  }
665
+
666
+ ctx.shadowBlur = 0;
667
+ }
668
+ }
669
+
670
+ class Dino {
671
+ constructor(game) {
672
+ this.game = game;
673
+ this.lane = 1; // Start middle
674
+ this.x = 0;
675
+ this.y = 0;
676
+ this.vy = 0;
677
+ this.width = 50;
678
+ this.height = 60;
679
+ this.isJumping = false;
680
+ this.isSliding = false;
681
+ this.slideTimer = 0;
682
+ this.color = CONFIG.colors.dino;
683
+ this.animTimer = 0;
684
+ }
685
+
686
+ update() {
687
+ // Calculate X position based on lane
688
+ const laneWidth = this.game.laneWidth;
689
+ const centerX = (this.game.width / 2) - (laneWidth * 1.5); // Offset to start of lanes
690
+ this.x = centerX + (this.lane * laneWidth) + (laneWidth - this.width) / 2;
691
+
692
+ // Physics
693
+ if (this.isJumping) {
694
+ this.y += this.vy;
695
+ this.vy += CONFIG.gravity;
696
+
697
+ // Ground collision
698
+ if (this.y >= this.game.groundY - this.height) {
699
+ this.y = this.game.groundY - this.height;
700
+ this.isJumping = false;
701
+ this.vy = 0;
702
+ this.game.spawnParticles(this.x + this.width/2, this.y + this.height, '#fff', 5);
703
  }
704
+ } else {
705
+ this.y = this.game.groundY - this.height;
706
+ }
707
 
708
+ // Slide Logic
709
+ if (this.isSliding) {
710
+ this.height = 35;
711
+ this.width = 60;
712
+ this.slideTimer--;
713
+ if (this.slideTimer <= 0) {
714
+ this.slide(false);
715
  }
716
+ } else {
717
+ this.height = 60;
718
+ this.width = 50;
719
+ }
720
 
721
+ // Animation
722
+ this.animTimer += 0.2;
723
+ }
 
 
 
724
 
725
+ moveLane(dir) {
726
+ const newLane = this.lane + dir;
727
+ if (newLane >= 0 && newLane <= 2) {
728
+ this.lane = newLane;
729
+ this.game.spawnParticles(this.x + this.width/2, this.y + this.height, '#fff', 3);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
730
  }
731
+ }
732
 
733
+ jump() {
734
+ if (!this.isJumping) {
735
+ this.vy = CONFIG.jumpForce;
736
+ this.isJumping = true;
737
+ }
738
+ }
739
+
740
+ slide(active) {
741
+ this.isSliding = active;
742
+ this.slideTimer = 60; // Frames
743
+ }
744
+
745
+ draw(ctx) {
746
+ const cx = this.x + this.width / 2;
747
+ const cy = this.y + this.height / 2;
748
+
749
+ // Body
750
+ ctx.fillStyle = this.color;
751
+ ctx.beginPath();
752
+ // Rounded rect for body
753
+ ctx.roundRect(this.x, this.y, this.width, this.height, 10);
754
+ ctx.fill();
755
+
756
+ // Headband
757
+ ctx.fillStyle = CONFIG.colors.headband;
758
+ ctx.fillRect(this.x, this.y + 5, this.width, 10);
759
+
760
+ // Bitcoin Symbol on Headband
761
+ ctx.fillStyle = CONFIG.colors.coin;
762
+ ctx.font = 'bold 12px Arial';
763
+ ctx.textAlign = 'center';
764
+ ctx.textBaseline = 'middle';
765
+ ctx.fillText('₿', cx, this.y + 10);
766
+
767
+ // Eyes
768
+ ctx.fillStyle = '#fff';
769
+ const eyeOffset = this.isSliding ? 10 : 12;
770
+ const eyeY = this.isSliding ? this.y + 15 : this.y + 18;
771
+
772
+ // Left Eye
773
+ ctx.beginPath();
774
+ ctx.arc(cx - eyeOffset, eyeY, 8, 0, Math.PI * 2);
775
+ ctx.fill();
776
+ // Right Eye
777
+ ctx.beginPath();
778
+ ctx.arc(cx + eyeOffset, eyeY, 8, 0, Math.PI * 2);
779
+ ctx.fill();
780
+
781
+ // Pupils (Look direction based on lane if needed, but static for now)
782
+ ctx.fillStyle = '#000';
783
+ ctx.beginPath();
784
+ ctx.arc(cx - eyeOffset, eyeY, 3, 0, Math.PI * 2);
785
+ ctx.arc(cx + eyeOffset, eyeY, 3, 0, Math.PI * 2);
786
+ ctx.fill();
787
+
788
+ // Legs (Simple animation)
789
+ if (!this.isJumping) {
790
+ ctx.fillStyle = CONFIG.colors.dinoDark;
791
+ const legOffset = Math.sin(this.animTimer) * 10;
792
+
793
+ // Left Leg
794
+ ctx.fillRect(this.x + 10 + legOffset, this.y + this.height, 10, 10);
795
+ // Right Leg
796
+ ctx.fillRect(this.x + 30 - legOffset, this.y + this.height, 10, 10);
797
+ }
798
+ }
799
+ }
800
+
801
+ // --- BACKGROUND & ENVIRONMENT ---
802
+ class Background {
803
+ constructor(width, height) {
804
+ this.width = width;
805
+ this.height = height;
806
+ this.groundY = height - 50;
807
+ this.offsetX = 0;
808
+ this.speed = 0;
809
+ }
810
+
811
+ update(speed) {
812
+ this.offsetX -= speed;
813
+ if (this.offsetX <= -this.width) this.offsetX = 0;
814
+ }
815
+
816
+ draw(ctx) {
817
+ // Sky Gradient
818
+ const gradient = ctx.createLinearGradient(0, 0, 0, this.height);
819
+ gradient.addColorStop(0, CONFIG.colors.skyTop);
820
+ gradient.addColorStop(1, CONFIG.colors.skyBottom);
821
+ ctx.fillStyle = gradient;
822
+ ctx.fillRect(0, 0, this.width, this.height);
823
+
824
+ // Distant Mountains (Parallax)
825
+ ctx.fillStyle = 'rgba(0,0,0,0.2)';
826
+ ctx.beginPath();
827
+ for(let i=0; i<this.width/100 + 2; i++) {
828
+ const h = 100 + Math.random() * 50; // Static noise for variety if we used time, but let's keep it simple shapes
829
+ const x = (i * 100) + this.offsetX * 0.2;
830
+ ctx.moveTo(x, this.height - 50);
831
+ ctx.lineTo(x + 50, this.height - 50 - h);
832
+ ctx.lineTo(x + 100, this.height - 50);
833
+ }
834
+ ctx.fill();
835
 
836
+ // Grid Floor (Synthwave style)
837
+ const groundY = this.groundY;
838
+ ctx.fillStyle = CONFIG.colors.ground;
839
+ ctx.fillRect(0, groundY, this.width, 50);
 
 
840
 
841
+ // Moving Grid Lines
842
+ ctx.strokeStyle = CONFIG.colors.grid;
843
+ ctx.lineWidth = 2;
844
+ ctx.beginPath();
845
+
846
+ // Horizontal lines
847
+ for (let i = 0; i < 10; i++) {
848
+ const y = groundY + (i * 10) + (this.offsetX * 0.5 % 10);
849
+ if (y < groundY) continue;
850
+ ctx.moveTo(0, y);
851
+ ctx.lineTo(this.width, y);
852
+ }
853
 
854
+ // Vertical lines (Perspective effect)
855
+ const centerX = this.width / 2;
856
+ for (let i = -10; i < 10; i++) {
857
+ const x = centerX + (i * 100) + (this.offsetX % 100);
858
+ ctx.moveTo(x, groundY);
859
+ ctx.lineTo(centerX + (i * 300), this.height);
860
+ }
861
+ ctx.stroke();
862
+
863
+ // Ground Top Border
864
+ ctx.fillStyle = '#444';
865
+ ctx.fillRect(0, groundY, this.width, 5);
866
+ }
867
+ }
868
+
869
+ // --- MAIN GAME ENGINE ---
870
+ class Game {
871
+ constructor() {
872
+ this.canvas = document.getElementById('gameCanvas');
873
+ this.ctx = this.canvas.getContext('2d');
874
+ this.width = window.innerWidth;
875
+ this.height = window.innerHeight;
876
+
877
+ // Adjust canvas for high DPI
878
+ const dpr = window.devicePixelRatio || 1;
879
+ this.canvas.width = this.width * dpr;
880
+ this.canvas.height = this.height * dpr;
881
+ this.ctx.scale(dpr, dpr);
882
+ this.width = this.width;
883
+ this.height = this.height;
884
+
885
+ this.laneWidth = this.width / 3;
886
+ this.groundY = this.height - 80;
887
+
888
+ this.state = 'START'; // START, PLAYING, PAUSED, GAMEOVER
889
+ this.score = 0;
890
+ this.btcBalance = 0.00000000;
891
+ this.gameSpeed = CONFIG.speedBase;
892
+ this.distance = 0;
893
+
894
+ this.entities = {
895
+ coins: [],
896
+ obstacles: [],
897
+ powerups: [],
898
+ particles: []
899
+ };
900
 
901
+ this.dino = new Dino(this);
902
+ this.background = new Background(this.width, this.height);
903
+ this.input = new InputHandler(this);
904
+
905
+ this.lastSpawnTime = 0;
906
+ this.spawnInterval = 1500; // ms
907
+
908
+ this.shieldActive = false;
909
+ this.multiplierActive = false;
910
+
911
+ // UI Elements
912
+ this.ui = {
913
+ start: document.getElementById('start-screen'),
914
+ gameOver: document.getElementById('game-over-screen'),
915
+ pause: document.getElementById('pause-screen'),
916
+ hud: document.getElementById('hud'),
917
+ dist: document.getElementById('score-dist'),
918
+ btc: document.getElementById('btc-value'),
919
+ finalDist: document.getElementById('final-score-dist'),
920
+ finalBtc: document.getElementById('final-btc'),
921
+ powerupContainer: document.getElementById('powerup-container')
922
+ };
923
 
924
+ this.bindUI();
925
+ this.loop = this.loop.bind(this);
926
+ requestAnimationFrame(this.loop);
927
+ }
928
+
929
+ bindUI() {
930
+ document.getElementById('start-btn').addEventListener('click', () => this.start());
931
+ document.getElementById('restart-btn').addEventListener('click', () => this.start());
932
+ document.getElementById('resume-btn').addEventListener('click', () => this.togglePause());
933
+ }
934
+
935
+ start() {
936
+ this.state = 'PLAYING';
937
+ this.score = 0;
938
+ this.distance = 0;
939
+ this.btcBalance = 0.00000000;
940
+ this.gameSpeed = CONFIG.speedBase;
941
+ this.entities = { coins: [], obstacles: [], powerups: [], particles: [] };
942
+ this.dino = new Dino(this);
943
+ this.shieldActive = false;
944
+ this.multiplierActive = false;
945
+
946
+ this.ui.start.classList.add('hidden');
947
+ this.ui.start.classList.remove('visible');
948
+ this.ui.gameOver.classList.add('hidden');
949
+ this.ui.gameOver.classList.remove('visible');
950
+ this.ui.pause.classList.add('hidden');
951
+ this.ui.pause.classList.remove('visible');
952
+ this.ui.hud.classList.remove('hidden');
953
+ this.updatePowerupUI();
954
+ }
955
+
956
+ togglePause() {
957
+ if (this.state === 'PLAYING') {
958
+ this.state = 'PAUSED';
959
+ this.ui.pause.classList.remove('hidden');
960
+ this.ui.pause.classList.add('visible');
961
+ } else if (this.state === 'PAUSED') {
962
+ this.state = 'PLAYING';
963
+ this.ui.pause.classList.add('hidden');
964
+ this.ui.pause.classList.remove('visible');
965
+ }
966
+ }
 
 
 
 
 
 
 
 
 
 
967
 
968
+ gameOver() {
969
+ this.state = 'GAMEOVER';
970
+ this.ui.hud.classList.add('hidden');
971
+ this.ui.gameOver.classList.remove('hidden');
972
+ this.ui.gameOver.classList.add('visible');
973
+
974
+ this.ui.finalDist.innerText = Math.floor(this.distance) + " m";
975
+ this.ui.finalBtc.innerText = Utils.formatBTC(this.btcBalance);
976
+ }
977
+
978
+ spawnEntities() {
979
+ const now = Date.now();
980
+ if (now - this.lastSpawnTime > this.spawnInterval) {
981
+ this.lastSpawnTime = now;
982
+
983
+ // Randomly choose what to spawn
984
+ const rand = Math.random();
985
+
986
+ // Determine lane
987
+ const lane = Math.floor(Math.random() * 3);
988
+ const spawnX = this.width + 100;
989
+
990
+ if (rand < 0.6) {
991
+ // Spawn Obstacle
992
+ const type = Math.random() < 0.33 ? 'low_barrier' : (Math.random() < 0.5 ? 'high_hazard' : 'barrier');
993
+ this.entities.obstacles.push(new Obstacle(lane, spawnX, type));
994
 
995
+ // Sometimes spawn coins around obstacles
996
+ if (Math.random() > 0.5) {
997
+ const coinLane = (lane + 1) % 3;
998
+ this.entities.coins.push(new Coin(coinLane, spawnX + 100));
 
 
 
 
 
999
  }
 
 
 
 
 
 
1000
 
1001
+ } else if (rand < 0.9) {
1002
+ // Spawn Coins
1003
+ const coinCount = Math.floor(Utils.randomRange(3, 8));
1004
+ for (let i = 0; i < coinCount; i++) {
1005
+ this.entities.coins.push(new Coin(lane, spawnX + (i * 60)));
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1006
  }
1007
+ } else {
1008
+ // Spawn Powerup
1009
+ const pType = Math.random() < 0.4 ? 'shield' : (Math.random() < 0.7 ? 'magnet' : 'multiplier');
1010
+ this.entities.powerups.push(new PowerUp(lane, spawnX, pType));
1011
  }
1012
 
1013
+ // Decrease spawn interval slightly as speed increases
1014
+ this.spawnInterval = Math.max(600, 1500 - (this.gameSpeed * 20));
 
 
 
 
 
 
 
1015
  }
1016
+ }
1017
 
1018
+ spawnParticles(x, y, color, count) {
1019
+ for(let i=0; i<count; i++) {
1020
+ this.entities.particles.push(new Particle(x, y, color));
1021
+ }
1022
+ }
1023
+
1024
+ checkCollisions() {
1025
+ const dinoRect = { x: this.dino.x, y: this.dino.y, w: this.dino.width, h: this.dino.height };
1026
+
1027
+ // Coins
1028
+ this.entities.coins.forEach(coin => {
1029
+ if (coin.active && Utils.checkAABB(dinoRect, {x: coin.x, y: coin.y, w: coin.w, h: coin.h})) {
1030
+ coin.active = false;
1031
+ const value = this.multiplierActive ? coin.btcValue * 2 : coin.btcValue;
1032
+ this.btcBalance += value;
1033
+ this.spawnParticles(coin.x, coin.y, CONFIG.colors.coin, 5);
1034
  }
1035
+ });
1036
+
1037
+ // Obstacles
1038
+ this.entities.obstacles.forEach(obs => {
1039
+ if (obs.active && Utils.checkAABB(dinoRect, {x: obs.x, y: obs.y, w: obs.w, h: obs.h})) {
1040
+ if (this.shieldActive) {
1041
+ this.shieldActive = false;
1042
+ obs.active = false;
1043
+ this.spawnParticles(obs.x, obs.y, '#00bcd4', 10);
1044
+ this.spawnParticles(this.dino.x, this.dino.y, '#00bcd4', 10);
1045
+ } else {
1046
+ this.spawnParticles(this.dino.x, this.dino.y, CONFIG.colors.dino, 20);
1047
+ this.gameOver();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1048
  }
1049
  }
1050
+ });
1051
+
1052
+ // Powerups
1053
+ this.entities.powerups.forEach(p => {
1054
+ if (p.active && Utils.checkAABB(dinoRect, {x: p.x, y: p.y, w: p.w, h: p.h})) {
1055
+ p.active = false;
1056
+ this.activatePowerUp(p.type);
1057
+ this.spawnParticles(p.x, p.y, '#fff', 10);
1058
  }
1059
+ });
1060
+ }
1061
+
1062
+ activatePowerUp(type) {
1063
+ if (type === 'shield') {
1064
+ this.shieldActive = true;
1065
+ this.updatePowerupUI();
1066
+ setTimeout(() => {
1067
+ this.shieldActive = false;
1068
+ this.updatePowerupUI();
1069
+ }, 5000);
1070
+ } else if (type === 'multiplier') {
1071
+ this.multiplierActive = true;
1072
+ this.updatePowerupUI();
1073
+ setTimeout(() => {
1074
+ this.multiplierActive = false;
1075
+ this.updatePowerupUI();
1076
+ }, 5000);
1077
+ } else if (type === 'magnet') {
1078
+ // Simple implementation: attract coins for 5 seconds
1079
+ const magnetTime = 5000;
1080
+ const magnetInterval = setInterval(() => {
1081
+ if (this.state !== 'PLAYING') {
1082
+ clearInterval(magnetInterval);
1083
+ return;
1084
  }
1085
+ this.entities.coins.forEach(coin => {
1086
+ if (coin.active && coin.lane === this.dino.lane) {
1087
+ const dist = Math.abs((this.dino.x + this.dino.width/2) - (coin.x + coin.w/2));
1088
+ if (dist < 200) {
1089
+ coin.x += (this.dino.x - coin.x) * 0.2; // Move towards dino
1090
+ }
1091
+ }
1092
+ });
1093
+ }, 50);
1094
+ setTimeout(() => clearInterval(magnetInterval), magnetTime);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1095
  }
1096
+ }
1097
+
1098
+ updatePowerupUI() {
1099
+ this.ui.powerupContainer.innerHTML = '';
1100
+ if (this.shieldActive) {
1101
+ const el = document.createElement('div');
1102
+ el.className = 'active-powerup';
1103
+ el.innerText = 'SHIELD';
1104
+ this.ui.powerupContainer.appendChild(el);
1105
+ }
1106
+ if (this.multiplierActive) {
1107
+ const el = document.createElement('div');
1108
+ el.className = 'active-powerup';
1109
+ el.innerText = '2X BTC';
1110
+ this.ui.powerupContainer.appendChild(el);
1111
+ }
1112
+ }
1113
+
1114
+ update() {
1115
+ if (this.state !== 'PLAYING') return;
1116
+
1117
+ // Difficulty scaling
1118
+ this.gameSpeed = Math.min(CONFIG.speedMax, this.gameSpeed + CONFIG.speedIncrement);
1119
+ this.distance += this.gameSpeed * 0.05;
1120
+ this.score = Math.floor(this.distance);
1121
+
1122
+ this.background.update(this.gameSpeed);
1123
+ this.dino.update();
1124
+ this.spawnEntities();
1125
+
1126
+ // Update Entities
1127
+ this.entities.coins.forEach(c => c.update(this.gameSpeed, (lane) => (this.width/2) - (this.laneWidth*1.5) + (lane * this.laneWidth) + (this.laneWidth/2), this.groundY));
1128
+ this.entities.obstacles.forEach(o => o.update(this.gameSpeed));
1129
+ this.entities.powerups.forEach(p => p.update(this.gameSpeed, (lane) => (this.width/2) - (this.laneWidth*1.5) + (lane * this.laneWidth) + (this.laneWidth/2), this.groundY));
1130
+ this.entities.particles.forEach(p => p.update());
1131
+
1132
+ // Cleanup off-screen entities
1133
+ this.entities.coins = this.entities.coins.filter(c => c.x + c.w > -50);
1134
+ this.entities.obstacles = this.entities.obstacles.filter(o => o.x + o.w > -50);
1135
+ this.entities.powerups = this.entities.powerups.filter(p => p.x + p.w > -50);
1136
+ this.entities.particles = this.entities.particles.filter(p => p.life > 0);
1137
+
1138
+ this.checkCollisions();
1139
+
1140
+ // Update HUD
1141
+ this.ui.dist.innerText = Math.floor(this.distance) + " m";
1142
+ this.ui.btc.innerText = Utils.formatBTC(this.btcBalance);
1143
+ }
1144
+
1145
+ draw() {
1146
+ this.ctx.clearRect(0, 0, this.width, this.height);
1147
+
1148
+ this.background.draw(this.ctx);
1149
+
1150
+ // Draw Lanes (Subtle)
1151
+ this.ctx.strokeStyle = 'rgba(255,255,255,0.1)';
1152
+ this.ctx.lineWidth = 2;
1153
+ this.ctx.beginPath();
1154
+ for(let i=1; i<3; i++) {
1155
+ const x = (this.width/2) - (this.laneWidth * 1.5) + (i * this.laneWidth);
1156
+ this.ctx.moveTo(x, 0);
1157
+ this.ctx.lineTo(x, this.height);
1158
+ }
1159
+ this.ctx.stroke();
1160
+
1161
+ this.entities.powerups.forEach(p => p.draw(this.ctx));
1162
+ this.entities.obstacles.forEach(o => o.draw(this.ctx));
1163
+ this.entities.coins.forEach(c => c.draw(this.ctx));
1164
+ this.dino.draw(this.ctx);
1165
+ this.entities.particles.forEach(p => p.draw(this.ctx));
1166
+ }
1167
+
1168
+ loop() {
1169
+ this.update();
1170
+ this.draw();
1171
+ requestAnimationFrame(this.loop);
1172
+ }
1173
+ }
1174
+
1175
+ // Initialize Game
1176
+ window.onload = () => {
1177
+ const game = new Game();
1178
+ };
1179
+
1180
+ </script>
1181
+ </body>
1182
+ </html>