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

Upload folder using huggingface_hub

Browse files
Files changed (1) hide show
  1. index.html +892 -1072
index.html CHANGED
@@ -3,1180 +3,1000 @@
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>
 
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>₿itcoin Dino Run</title>
7
+ <script src="https://cdn.tailwindcss.com"></script>
8
+ <link href="https://fonts.googleapis.com/css2?family=Fredoka+One&family=VT323&display=swap" rel="stylesheet">
9
  <style>
10
+ body {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
11
  margin: 0;
 
 
 
12
  overflow: hidden;
13
+ background-color: #1a202c;
14
+ font-family: 'Fredoka One', cursive;
15
+ touch-action: none; /* Prevent pull-to-refresh on mobile */
16
  }
 
 
 
 
 
 
 
 
 
 
17
  canvas {
18
  display: block;
 
19
  }
 
 
20
  .ui-layer {
21
  position: absolute;
22
  top: 0;
23
  left: 0;
24
  width: 100%;
25
  height: 100%;
26
+ pointer-events: none;
27
  display: flex;
28
  flex-direction: column;
29
+ justify-content: space-between;
30
+ padding: 20px;
31
+ box-sizing: border-box;
32
+ }
33
+ .interactive {
34
+ pointer-events: auto;
35
  }
 
36
  .hidden {
37
+ display: none !important;
38
+ }
39
+ /* Retro Scanline Effect */
40
+ .scanlines {
41
+ background: linear-gradient(
42
+ to bottom,
43
+ rgba(255,255,255,0),
44
+ rgba(255,255,255,0) 50%,
45
+ rgba(0,0,0,0.1) 50%,
46
+ rgba(0,0,0,0.1)
47
+ );
48
+ background-size: 100% 4px;
49
+ position: absolute;
50
+ top: 0; left: 0; right: 0; bottom: 0;
51
+ pointer-events: none;
52
+ z-index: 10;
53
  }
54
+
55
+ /* Glitch Text Animation */
56
+ @keyframes glitch {
57
+ 0% { transform: translate(0) }
58
+ 20% { transform: translate(-2px, 2px) }
59
+ 40% { transform: translate(-2px, -2px) }
60
+ 60% { transform: translate(2px, 2px) }
61
+ 80% { transform: translate(2px, -2px) }
62
+ 100% { transform: translate(0) }
63
  }
64
+ .glitch-hover:hover {
65
+ animation: glitch 0.3s cubic-bezier(.25, .46, .45, .94) both infinite;
66
+ color: #F7931A;
 
 
 
 
 
 
67
  }
68
 
69
+ .hud-text {
70
+ font-family: 'VT323', monospace;
71
+ text-shadow: 2px 2px 0px #000;
 
 
 
 
 
 
 
72
  }
73
+ </style>
74
+ </head>
75
+ <body>
76
 
77
+ <!-- Canvas Layer -->
78
+ <canvas id="gameCanvas"></canvas>
79
+ <div class="scanlines"></div>
 
 
 
80
 
81
+ <!-- UI Layer -->
82
+ <div id="uiLayer" class="ui-layer">
83
+
84
+ <!-- HUD (Top) -->
85
+ <div id="hud" class="hidden w-full flex justify-between items-start z-20">
86
+ <div class="flex flex-col gap-1">
87
+ <div class="text-yellow-400 text-3xl hud-text tracking-wider">
88
+ <span class="text-white">SCORE:</span> <span id="scoreDisplay">0</span>
89
+ </div>
90
+ <div class="text-orange-500 text-2xl hud-text tracking-wider flex items-center gap-2">
91
+ <span class="text-white">BTC:</span> <span id="btcDisplay">0.00000000</span> ₿
92
+ </div>
93
+ </div>
94
+ <div class="flex flex-col items-end gap-1">
95
+ <div class="text-cyan-400 text-2xl hud-text">
96
+ SPEED: <span id="speedDisplay">0</span> km/h
97
+ </div>
98
+ <div id="powerupStatus" class="flex gap-2">
99
+ <!-- Icons injected via JS -->
100
+ </div>
101
+ </div>
102
+ </div>
103
 
104
+ <!-- Start Screen -->
105
+ <div id="startScreen" class="absolute inset-0 flex flex-col items-center justify-center bg-black/80 backdrop-blur-sm z-30 interactive">
106
+ <h1 class="text-6xl md:text-8xl text-green-400 mb-2 tracking-tighter drop-shadow-[0_5px_5px_rgba(0,0,0,0.8)]" style="font-family: 'Fredoka One'">
107
+ DINO <span class="text-orange-500">₿</span> RUN
108
+ </h1>
109
+ <p class="text-gray-300 mb-8 text-xl font-sans">Collect Sats. Dodge Obstacles. To the Moon.</p>
110
+
111
+ <div class="flex gap-4 mb-8">
112
+ <div class="flex flex-col items-center bg-gray-800 p-4 rounded-lg border border-gray-700">
113
+ <div class="w-8 h-8 border-2 border-white rounded mb-2 flex items-center justify-center text-white">←</div>
114
+ <span class="text-xs text-gray-400">LEFT</span>
115
+ </div>
116
+ <div class="flex flex-col items-center bg-gray-800 p-4 rounded-lg border border-gray-700">
117
+ <div class="w-8 h-8 border-2 border-white rounded mb-2 flex items-center justify-center text-white">→</div>
118
+ <span class="text-xs text-gray-400">RIGHT</span>
119
+ </div>
120
+ <div class="flex flex-col items-center bg-gray-800 p-4 rounded-lg border border-gray-700">
121
+ <div class="w-8 h-8 border-2 border-white rounded mb-2 flex items-center justify-center text-white">↑</div>
122
+ <span class="text-xs text-gray-400">JUMP</span>
123
+ </div>
124
+ <div class="flex flex-col items-center bg-gray-800 p-4 rounded-lg border border-gray-700">
125
+ <div class="w-8 h-8 border-2 border-white rounded mb-2 flex items-center justify-center text-white">↓</div>
126
+ <span class="text-xs text-gray-400">SLIDE</span>
127
+ </div>
128
+ </div>
129
 
130
+ <button id="startBtn" class="glitch-hover bg-green-500 hover:bg-green-400 text-black text-2xl px-12 py-4 rounded-full shadow-[0_0_20px_rgba(74,222,128,0.6)] transition-all transform hover:scale-105 font-bold border-4 border-green-700">
131
+ START RUNNING
132
+ </button>
133
+ </div>
 
 
 
134
 
135
+ <!-- Game Over Screen -->
136
+ <div id="gameOverScreen" class="hidden absolute inset-0 flex flex-col items-center justify-center bg-red-900/90 backdrop-blur-md z-40 interactive">
137
+ <h2 class="text-6xl text-white mb-2 drop-shadow-lg">CRASHED!</h2>
138
+ <div class="bg-black/50 p-6 rounded-xl border border-red-500 mb-8 text-center min-w-[300px]">
139
+ <div class="text-gray-300 text-lg mb-1">Distance</div>
140
+ <div class="text-4xl text-white mb-4" id="finalScore">0m</div>
141
+
142
+ <div class="text-gray-300 text-lg mb-1">Total BTC Mined</div>
143
+ <div class="text-4xl text-orange-400 font-mono" id="finalBTC">0.00000000</div>
144
+ </div>
145
+ <button id="restartBtn" class="glitch-hover bg-white text-red-600 text-2xl px-10 py-3 rounded-full shadow-lg hover:bg-gray-100 transition-all font-bold">
146
+ TRY AGAIN
147
+ </button>
148
+ </div>
149
 
150
+ <!-- Mobile Controls (Visible only on touch devices via logic, but here for structure) -->
151
+ <div id="mobileControls" class="hidden w-full h-full absolute top-0 left-0 z-10">
152
+ <div class="w-1/2 h-full absolute left-0" id="touchLeft"></div>
153
+ <div class="w-1/2 h-full absolute right-0" id="touchRight"></div>
154
+ <div class="absolute bottom-10 left-10 w-20 h-20 bg-white/10 rounded-full backdrop-blur" id="btnJump">JUMP</div>
155
+ <div class="absolute bottom-10 right-10 w-20 h-20 bg-white/10 rounded-full backdrop-blur" id="btnSlide">SLIDE</div>
156
+ </div>
157
+ </div>
158
 
159
+ <script>
160
+ /**
161
+ * BITCOIN DINO RUN
162
+ * A high-performance HTML5 Canvas Endless Runner
163
+ */
164
+
165
+ // --- Configuration ---
166
+ const CONFIG = {
167
+ lanes: 3,
168
+ laneWidth: 0, // Calculated dynamically
169
+ baseSpeed: 600, // Pixels per second
170
+ maxSpeed: 1400,
171
+ acceleration: 5, // Speed increase per second
172
+ gravity: 2500,
173
+ jumpForce: -900,
174
+ colors: {
175
+ dino: '#4ade80', // Tailwind green-400
176
+ dinoDark: '#166534',
177
+ headband: '#ef4444', // Red
178
+ btc: '#F7931A',
179
+ ground: '#374151',
180
+ groundLight: '#4b5563'
181
+ }
182
+ };
183
 
184
+ // --- State Management ---
185
+ const state = {
186
+ screen: 'start', // start, playing, gameover
187
+ lastTime: 0,
188
+ score: 0, // Distance
189
+ btc: 0.00000000,
190
+ speed: 0,
191
+ lane: 1, // 0, 1, 2
192
+ isJumping: false,
193
+ isSliding: false,
194
+ jumpVelocity: 0,
195
+ y: 0, // Vertical position (0 is ground)
196
+ slideTimer: 0,
197
+ magnetTimer: 0,
198
+ multiplierTimer: 0,
199
+ shieldTimer: 0,
200
+ multiplier: 1,
201
+ frame: 0,
202
+ cameraShake: 0
203
+ };
204
 
205
+ // --- Assets & Objects ---
206
+ const canvas = document.getElementById('gameCanvas');
207
+ const ctx = canvas.getContext('2d');
208
+
209
+ let entities = {
210
+ obstacles: [],
211
+ coins: [],
212
+ particles: [],
213
+ bgLayers: []
214
+ };
215
 
216
+ // --- Input Handling ---
217
+ const keys = { ArrowUp: false, ArrowDown: false, ArrowLeft: false, ArrowRight: false, w: false, s: false, a: false, d: false, Space: false };
218
+
219
+ window.addEventListener('keydown', (e) => {
220
+ if(keys.hasOwnProperty(e.key) || e.code === 'Space') keys[e.key] = true;
221
+ handleInput(e.key, true);
222
+ });
223
+
224
+ window.addEventListener('keyup', (e) => {
225
+ if(keys.hasOwnProperty(e.key) || e.code === 'Space') keys[e.key] = false;
226
+ handleInput(e.key, false);
227
+ });
228
 
229
+ // Touch controls
230
+ let touchStartX = 0;
231
+ let touchStartY = 0;
232
+ canvas.addEventListener('touchstart', e => {
233
+ touchStartX = e.changedTouches[0].screenX;
234
+ touchStartY = e.changedTouches[0].screenY;
235
+ }, {passive: false});
236
+
237
+ canvas.addEventListener('touchend', e => {
238
+ const touchEndX = e.changedTouches[0].screenX;
239
+ const touchEndY = e.changedTouches[0].screenY;
240
+ handleSwipe(touchStartX, touchStartY, touchEndX, touchEndY);
241
+ }, {passive: false});
242
+
243
+ function handleSwipe(sx, sy, ex, ey) {
244
+ const dx = ex - sx;
245
+ const dy = ey - sy;
246
+ if (Math.abs(dx) > Math.abs(dy)) {
247
+ // Horizontal
248
+ if (Math.abs(dx) > 30) {
249
+ if (dx > 0) changeLane(1);
250
+ else changeLane(-1);
251
+ }
252
+ } else {
253
+ // Vertical
254
+ if (Math.abs(dy) > 30) {
255
+ if (dy < 0) jump();
256
+ else slide();
257
+ }
258
+ }
259
  }
260
 
261
+ function handleInput(key, isDown) {
262
+ if (state.screen !== 'playing') return;
263
+
264
+ if (isDown) {
265
+ if (key === 'ArrowLeft' || key === 'a') changeLane(-1);
266
+ if (key === 'ArrowRight' || key === 'd') changeLane(1);
267
+ if (key === 'ArrowUp' || key === 'w' || key === ' ') jump();
268
+ if (key === 'ArrowDown' || key === 's') slide();
269
+ }
 
 
 
 
 
270
  }
271
 
272
+ function changeLane(dir) {
273
+ const newLane = state.lane + dir;
274
+ if (newLane >= 0 && newLane < CONFIG.lanes) {
275
+ state.lane = newLane;
276
+ createDust(state.lane, 0, 5);
277
+ }
278
  }
279
 
280
+ function jump() {
281
+ if (!state.isJumping && !state.isSliding) {
282
+ state.isJumping = true;
283
+ state.jumpVelocity = CONFIG.jumpForce;
284
+ createDust(state.lane, 0, 10);
285
+ }
286
  }
287
 
288
+ function slide() {
289
+ if (!state.isSliding && !state.isJumping) {
290
+ state.isSliding = true;
291
+ state.slideTimer = 0.8; // seconds
292
+ }
 
 
293
  }
294
 
295
+ // --- Game Loop ---
296
+ function resize() {
297
+ canvas.width = window.innerWidth;
298
+ canvas.height = window.innerHeight;
299
+ CONFIG.laneWidth = canvas.width / CONFIG.lanes;
300
+ }
301
+ window.addEventListener('resize', resize);
302
+ resize();
303
+
304
+ function initGame() {
305
+ state.score = 0;
306
+ state.btc = 0.00000000;
307
+ state.speed = CONFIG.baseSpeed;
308
+ state.lane = 1;
309
+ state.isJumping = false;
310
+ state.isSliding = false;
311
+ state.y = 0;
312
+ state.multiplier = 1;
313
+ state.magnetTimer = 0;
314
+ state.shieldTimer = 0;
315
+ state.multiplierTimer = 0;
316
+
317
+ entities.obstacles = [];
318
+ entities.coins = [];
319
+ entities.particles = [];
320
+
321
+ // Initialize Background Layers
322
+ entities.bgLayers = [
323
+ { speedMod: 0.1, color: '#111827', elements: generateBgElements(20, 50) }, // Far
324
+ { speedMod: 0.3, color: '#1f2937', elements: generateBgElements(10, 100) }, // Mid
325
+ { speedMod: 0.6, color: '#374151', elements: generateBgElements(5, 200) } // Near
326
+ ];
327
+
328
+ state.screen = 'playing';
329
+ state.lastTime = performance.now();
330
+ requestAnimationFrame(loop);
331
+ }
332
+
333
+ function generateBgElements(count, heightVar) {
334
+ let arr = [];
335
+ for(let i=0; i<count; i++) {
336
+ arr.push({
337
+ x: Math.random() * canvas.width * 2,
338
+ y: canvas.height - 150 - Math.random() * heightVar,
339
+ w: 50 + Math.random() * 100,
340
+ h: 50 + Math.random() * heightVar
341
+ });
342
+ }
343
+ return arr;
344
  }
345
 
346
+ function loop(timestamp) {
347
+ if (state.screen !== 'playing') return;
 
 
 
 
 
 
 
 
348
 
349
+ const dt = Math.min((timestamp - state.lastTime) / 1000, 0.1); // Cap dt
350
+ state.lastTime = timestamp;
351
+ state.frame++;
352
+
353
+ update(dt);
354
+ draw();
355
+
356
+ requestAnimationFrame(loop);
357
  }
358
+
359
+ // --- Update Logic ---
360
+ function update(dt) {
361
+ // Speed progression
362
+ if (state.speed < CONFIG.maxSpeed) {
363
+ state.speed += CONFIG.acceleration * dt;
364
+ }
365
+
366
+ // Score
367
+ state.score += (state.speed * dt) / 100;
368
+
369
+ // Physics
370
+ if (state.isJumping) {
371
+ state.y += state.jumpVelocity * dt;
372
+ state.jumpVelocity += CONFIG.gravity * dt;
373
+ if (state.y >= 0) {
374
+ state.y = 0;
375
+ state.isJumping = false;
376
+ createDust(state.lane, 0, 5);
377
+ }
378
+ }
379
+
380
+ if (state.isSliding) {
381
+ state.slideTimer -= dt;
382
+ if (state.slideTimer <= 0) state.isSliding = false;
383
+ }
384
+
385
+ // Timers
386
+ if (state.magnetTimer > 0) state.magnetTimer -= dt;
387
+ if (state.multiplierTimer > 0) state.multiplierTimer -= dt;
388
+ if (state.shieldTimer > 0) state.shieldTimer -= dt;
389
+ else state.multiplier = 1;
390
+
391
+ // Camera Shake decay
392
+ if (state.cameraShake > 0) state.cameraShake *= 0.9;
393
+ if (state.cameraShake < 0.5) state.cameraShake = 0;
394
+
395
+ // Spawning
396
+ spawnManager(dt);
397
+
398
+ // Entity Updates & Collision
399
+ updateEntities(dt);
400
+
401
+ // Particles
402
+ updateParticles(dt);
403
+
404
+ // UI Update
405
+ document.getElementById('scoreDisplay').innerText = Math.floor(state.score);
406
+ document.getElementById('speedDisplay').innerText = Math.floor(state.speed / 10);
407
+ document.getElementById('btcDisplay').innerText = state.btc.toFixed(8);
408
+
409
+ // Update Powerup Icons
410
+ const pContainer = document.getElementById('powerupStatus');
411
+ pContainer.innerHTML = '';
412
+ if (state.magnetTimer > 0) pContainer.innerHTML += '<div class="w-6 h-6 rounded-full bg-blue-500 border-2 border-white shadow-lg animate-pulse"></div>';
413
+ if (state.multiplierTimer > 0) pContainer.innerHTML += '<div class="w-6 h-6 rounded-full bg-yellow-500 border-2 border-white shadow-lg animate-pulse">x2</div>';
414
+ if (state.shieldTimer > 0) pContainer.innerHTML += '<div class="w-6 h-6 rounded-full bg-purple-500 border-2 border-white shadow-lg animate-pulse">S</div>';
415
+ }
416
+
417
+ function spawnManager(dt) {
418
+ // Simple spawn logic based on distance traveled
419
+ // We track a virtual "distance since last spawn"
420
+ if (!state.spawnTimer) state.spawnTimer = 0;
421
+ state.spawnTimer -= state.speed * dt;
422
+
423
+ if (state.spawnTimer <= 0) {
424
+ // Determine what to spawn
425
+ const r = Math.random();
426
+ const lane = Math.floor(Math.random() * 3);
427
+
428
+ // 30% chance for coin row, 40% obstacle, 30% mixed
429
+ if (r < 0.3) {
430
+ spawnCoinRow(lane);
431
+ } else if (r < 0.7) {
432
+ spawnObstacle(lane);
433
+ } else {
434
+ spawnCoinRow(lane);
435
+ // Spawn obstacle in different lane
436
+ let otherLane = (lane + 1) % 3;
437
+ if(Math.random() > 0.5) otherLane = (lane + 2) % 3;
438
+ spawnObstacle(otherLane);
439
+ }
440
+
441
+ // Reset timer (random interval based on speed)
442
+ state.spawnTimer = 400 + Math.random() * 600;
443
+ }
444
  }
445
 
446
+ function spawnObstacle(lane) {
447
+ const type = Math.random() > 0.5 ? 'barrier' : 'train'; // simplified types
448
+ entities.obstacles.push({
449
+ x: canvas.width + 100,
450
+ lane: lane,
451
+ type: type,
452
+ width: type === 'train' ? 300 : 80,
453
+ height: type === 'train' ? 140 : 80,
454
+ z: 0, // Ground level
455
+ active: true
456
+ });
457
+ }
458
+
459
+ function spawnCoinRow(lane) {
460
+ const count = 5 + Math.floor(Math.random() * 5);
461
+ const startX = canvas.width + 100;
462
+ for (let i = 0; i < count; i++) {
463
+ // Check for powerup spawn
464
+ let isPowerup = Math.random() < 0.05;
465
+ let type = 'btc';
466
+ if (isPowerup) {
467
+ const pType = Math.random();
468
+ if (pType < 0.33) type = 'magnet';
469
+ else if (pType < 0.66) type = 'multiplier';
470
+ else type = 'shield';
471
+ }
472
 
473
+ entities.coins.push({
474
+ x: startX + (i * 80),
475
+ lane: lane,
476
+ y: 0, // Ground relative
477
+ type: type,
478
+ active: true,
479
+ rot: 0
480
+ });
481
+ }
482
  }
 
 
 
483
 
484
+ function updateEntities(dt) {
485
+ const moveDist = state.speed * dt;
486
 
487
+ // Update Obstacles
488
+ entities.obstacles.forEach(obs => {
489
+ obs.x -= moveDist;
490
+
491
+ // Collision
492
+ if (obs.active && obs.x < getLaneCenter(state.lane) + 40 && obs.x > getLaneCenter(state.lane) - 40) {
493
+ // Check Z depth (simple Y check here)
494
+ // Obstacles are on ground (y=0) unless we had aerial ones.
495
+ // Player Y is negative when jumping.
496
+
497
+ let hit = false;
498
+ if (state.isJumping) {
499
+ // Jumping over barrier is okay, train is too high? Let's say train hits jumpers.
500
+ if (obs.type === 'train' && state.y > -100) hit = true;
501
+ } else if (state.isSliding) {
502
+ // Sliding under train is okay? Let's say yes.
503
+ if (obs.type === 'barrier') hit = true;
504
+ } else {
505
+ // Running
506
+ hit = true;
507
+ }
508
 
509
+ if (hit) {
510
+ if (state.shieldTimer > 0) {
511
+ obs.active = false; // Break obstacle
512
+ createExplosion(obs.x, canvas.height/2 + 50, '#fff');
513
+ } else {
514
+ gameOver();
515
+ }
516
+ }
517
+ }
518
+ });
519
+
520
+ // Cleanup Obstacles
521
+ entities.obstacles = entities.obstacles.filter(o => o.x > -200);
522
+
523
+ // Update Coins
524
+ const playerX = getLaneCenter(state.lane);
525
+ const playerY = canvas.height/2 + 100 + state.y; // Approx player screen Y
526
+
527
+ entities.coins.forEach(coin => {
528
+ coin.x -= moveDist;
529
+ coin.rot += dt * 5;
530
+
531
+ // Magnet Logic
532
+ let magnetRange = 300;
533
+ let cx = coin.x;
534
+ let cy = getLaneY(coin.lane) - 50; // Coin height offset
535
+
536
+ // Simple magnet pull
537
+ if (state.magnetTimer > 0) {
538
+ const dist = Math.hypot(cx - playerX, cy - playerY);
539
+ if (dist < magnetRange) {
540
+ coin.x += (playerX - cx) * dt * 5;
541
+ // Adjust lane visually for magnet effect? Hard with lane system.
542
+ // Just pull X for now.
543
+ }
544
+ }
545
 
546
+ // Collection
547
+ if (coin.active && Math.abs(coin.x - playerX) < 50 && coin.lane === state.lane) {
548
+ // Check height (can collect while jumping)
549
+ if (Math.abs(state.y) < 100) {
550
+ collectCoin(coin);
551
+ }
552
+ }
553
+ });
554
+ entities.coins = entities.coins.filter(c => c.x > -100);
555
+ }
556
+
557
+ function collectCoin(coin) {
558
+ coin.active = false;
559
 
560
+ if (coin.type === 'btc') {
561
+ const val = 0.00000001 * state.multiplier;
562
+ state.btc += val;
563
+ createFloatingText(coin.x, getLaneY(coin.lane) - 50, `+${val.toFixed(8)}`);
564
+ createSparkle(coin.x, getLaneY(coin.lane) - 50, CONFIG.colors.btc);
565
+ } else if (coin.type === 'magnet') {
566
+ state.magnetTimer = 10;
567
+ createFloatingText(coin.x, getLaneY(coin.lane) - 50, "MAGNET!");
568
+ } else if (coin.type === 'multiplier') {
569
+ state.multiplierTimer = 10;
570
+ state.multiplier = 2;
571
+ createFloatingText(coin.x, getLaneY(coin.lane) - 50, "x2 BTC!");
572
+ } else if (coin.type === 'shield') {
573
+ state.shieldTimer = 10;
574
+ createFloatingText(coin.x, getLaneY(coin.lane) - 50, "SHIELD!");
575
+ }
576
+ }
577
 
578
+ // --- Rendering ---
579
+ function draw() {
580
+ // Clear
581
+ ctx.fillStyle = '#111827'; // Dark bg
582
+ ctx.fillRect(0, 0, canvas.width, canvas.height);
583
 
584
+ // Camera Shake
585
+ ctx.save();
586
+ if (state.cameraShake > 0) {
587
+ const dx = (Math.random() - 0.5) * state.cameraShake;
588
+ const dy = (Math.random() - 0.5) * state.cameraShake;
589
+ ctx.translate(dx, dy);
590
+ }
 
 
 
 
 
591
 
592
+ // Draw Background (Parallax)
593
+ drawBackground();
 
594
 
595
+ // Draw Ground
596
+ drawGround();
 
 
 
 
 
 
597
 
598
+ // Draw Entities
599
+ // Sort by Y for pseudo-depth
600
+ const renderList = [
601
+ ...entities.obstacles.map(o => ({...o, t: 'obs', y: 0})),
602
+ ...entities.coins.map(c => ({...c, t: 'coin', y: 0}))
603
+ ];
604
+
605
+ // Player Y is relative to ground. Ground is at canvas.height/2 + 100.
606
+ const groundY = canvas.height / 2 + 100;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
607
 
608
+ // Draw Player Shadow
609
+ const playerX = getLaneCenter(state.lane);
610
+ const playerY = groundY + state.y;
611
+
612
+ ctx.fillStyle = 'rgba(0,0,0,0.3)';
613
+ ctx.beginPath();
614
+ ctx.ellipse(playerX, groundY, 30 * (1 - Math.abs(state.y)/500), 10 * (1 - Math.abs(state.y)/500), 0, 0, Math.PI*2);
615
+ ctx.fill();
616
 
617
+ // Draw Player
618
+ drawDino(playerX, playerY, state.isSliding);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
619
 
620
+ // Draw Coins & Obstacles
621
+ // We need to interleave them based on their "depth" (which is just lane order for now, or simple Z)
622
+ // Since it's a runner, things just come towards camera.
623
+ // Draw order: Back lane -> Front lane? Or just Y position.
624
+ // Let's draw everything at their lane Y.
625
+
626
+ entities.obstacles.forEach(obs => {
627
+ const x = obs.x;
628
+ const y = groundY;
629
+ drawObstacle(x, y, obs);
630
+ });
631
+
632
+ entities.coins.forEach(coin => {
633
+ const x = coin.x;
634
+ const y = groundY - 50; // Float
635
+ drawCoin(x, y, coin);
636
+ });
637
+
638
+ // Particles
639
+ drawParticles();
640
+
641
+ ctx.restore();
642
+ }
643
+
644
+ function drawBackground() {
645
+ // Sky gradient
646
+ const grad = ctx.createLinearGradient(0, 0, 0, canvas.height);
647
+ grad.addColorStop(0, '#0f172a');
648
+ grad.addColorStop(1, '#1e293b');
649
+ ctx.fillStyle = grad;
650
+ ctx.fillRect(0,0, canvas.width, canvas.height);
651
+
652
+ // Parallax Layers
653
+ entities.bgLayers.forEach(layer => {
654
+ ctx.fillStyle = layer.color;
655
+ layer.elements.forEach(el => {
656
+ // Move element
657
+ el.x -= (state.speed * 0.1) * layer.speedMod; // Slow movement
658
+ if (el.x < -200) el.x = canvas.width + Math.random() * 200;
659
+
660
+ // Draw simple building/rock shape
661
+ ctx.beginPath();
662
+ ctx.roundRect(el.x, el.y, el.w, canvas.height - el.y, 10);
663
+ ctx.fill();
664
+ });
665
+ });
666
+ }
667
+
668
+ function drawGround() {
669
+ const horizon = canvas.height / 2 + 100;
670
+
671
+ // Road
672
+ ctx.fillStyle = '#374151';
673
+ ctx.fillRect(0, horizon, canvas.width, canvas.height - horizon);
674
+
675
+ // Lane markings
676
+ ctx.strokeStyle = '#4b5563';
677
+ ctx.lineWidth = 4;
678
+ ctx.setLineDash([40, 40]);
679
+ ctx.lineDashOffset = -state.score * 2; // Animate lines
680
+
681
+ for(let i=1; i<CONFIG.lanes; i++) {
682
+ const x = i * CONFIG.laneWidth;
683
+ ctx.beginPath();
684
+ ctx.moveTo(x, horizon);
685
+ ctx.lineTo(x, canvas.height);
686
+ ctx.stroke();
687
+ }
688
+ ctx.setLineDash([]);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
689
  }
 
690
 
691
+ function drawDino(x, y, sliding) {
692
+ ctx.save();
693
+ ctx.translate(x, y);
694
+
695
+ // Bounce animation
696
+ if (!state.isJumping && !sliding) {
697
+ const bounce = Math.sin(state.frame * 0.2) * 5;
698
+ ctx.translate(0, -bounce);
699
+ }
700
+
701
+ if (sliding) {
702
+ ctx.rotate(-Math.PI / 2); // Rotate to slide
703
+ ctx.translate(0, -20);
704
+ }
705
 
706
+ // Shadow/Glow
707
+ ctx.shadowColor = 'rgba(74, 222, 128, 0.4)';
708
+ ctx.shadowBlur = 20;
 
 
 
709
 
710
+ // Body (Rounded Rect)
711
+ ctx.fillStyle = CONFIG.colors.dino;
 
 
 
 
 
 
 
 
 
 
712
  ctx.beginPath();
713
+ ctx.roundRect(-30, -80, 60, 70, 15);
 
 
714
  ctx.fill();
715
+
716
+ // Head
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
717
  ctx.beginPath();
718
+ ctx.arc(0, -90, 35, 0, Math.PI * 2);
719
  ctx.fill();
720
+
721
+ // Headband
722
+ ctx.fillStyle = CONFIG.colors.headband;
723
+ ctx.fillRect(-36, -100, 72, 15);
724
+ // Knot
 
 
 
 
 
 
 
 
 
 
 
 
725
  ctx.beginPath();
726
+ ctx.moveTo(30, -95);
727
+ ctx.lineTo(50, -105);
728
+ ctx.lineTo(50, -85);
729
  ctx.fill();
730
+
731
+ // Bitcoin Symbol on Headband
732
+ ctx.fillStyle = '#F7931A';
733
+ ctx.font = 'bold 20px monospace';
734
  ctx.textAlign = 'center';
735
  ctx.textBaseline = 'middle';
736
+ ctx.fillText('', 0, -92);
737
+
738
+ // Eyes
739
+ ctx.fillStyle = 'white';
740
+ ctx.beginPath();
741
+ ctx.ellipse(-10, -95, 10, 12, 0, 0, Math.PI*2);
742
+ ctx.ellipse(10, -95, 10, 12, 0, 0, Math.PI*2);
743
+ ctx.fill();
744
+
745
+ // Pupils (look forward, or at coins?)
746
+ ctx.fillStyle = 'black';
747
+ ctx.beginPath();
748
+ ctx.arc(-8, -95, 4, 0, Math.PI*2);
749
+ ctx.arc(12, -95, 4, 0, Math.PI*2);
750
+ ctx.fill();
751
+
752
+ // Arms
753
+ ctx.fillStyle = CONFIG.colors.dino;
754
+ if (sliding) {
755
+ // Arms tucked
756
+ } else {
757
+ // Running arms
758
+ const armAngle = Math.sin(state.frame * 0.3) * 0.5;
759
+ ctx.beginPath();
760
+ ctx.ellipse(-25, -50, 10, 25, armAngle, 0, Math.PI*2);
761
+ ctx.ellipse(25, -50, 10, 25, -armAngle, 0, Math.PI*2);
762
+ ctx.fill();
 
 
 
 
 
 
 
 
 
 
 
 
 
763
  }
 
 
 
764
 
765
+ // Legs (if not sliding)
766
+ if (!sliding) {
767
+ const legOffset = Math.sin(state.frame * 0.3) * 15;
768
+ ctx.fillStyle = CONFIG.colors.dinoDark;
769
+ // Left Leg
770
+ ctx.beginPath();
771
+ ctx.roundRect(-20 + legOffset, -10, 15, 30, 5);
772
+ ctx.fill();
773
+ // Right Leg
774
+ ctx.beginPath();
775
+ ctx.roundRect(5 - legOffset, -10, 15, 30, 5);
776
+ ctx.fill();
777
  }
 
 
 
 
778
 
779
+ // Shield Effect
780
+ if (state.shieldTimer > 0) {
781
+ ctx.strokeStyle = `rgba(168, 85, 247, ${Math.abs(Math.sin(state.frame * 0.1))})`;
782
+ ctx.lineWidth = 5;
783
+ ctx.beginPath();
784
+ ctx.arc(0, -60, 60, 0, Math.PI*2);
785
+ ctx.stroke();
786
+ }
787
 
788
+ ctx.restore();
 
 
 
 
789
  }
 
790
 
791
+ function drawObstacle(x, y, obs) {
792
+ ctx.save();
793
+ ctx.translate(x, y);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
794
 
795
+ if (obs.type === 'barrier') {
796
+ ctx.fillStyle = '#ef4444'; // Red barrier
797
+ // 3D-ish box
798
+ ctx.fillRect(-40, -obs.height, obs.width, obs.height);
799
+ // Stripe
800
+ ctx.fillStyle = '#fca5a5';
801
+ ctx.beginPath();
802
+ ctx.moveTo(-40, -obs.height);
803
+ ctx.lineTo(40, -obs.height);
804
+ ctx.lineTo(-40, 0);
805
+ ctx.fill();
806
+ // Border
807
+ ctx.strokeStyle = '#7f1d1d';
808
+ ctx.lineWidth = 3;
809
+ ctx.strokeRect(-40, -obs.height, obs.width, obs.height);
810
+ } else {
811
+ // Train
812
+ ctx.fillStyle = '#1e3a8a'; // Blue
813
+ ctx.fillRect(-obs.width/2, -obs.height, obs.width, obs.height);
814
+ // Windows
815
+ ctx.fillStyle = '#93c5fd';
816
+ ctx.fillRect(-obs.width/2 + 10, -obs.height + 10, obs.width - 20, 40);
817
+ // Detail
818
+ ctx.fillStyle = '#172554';
819
+ ctx.fillRect(-obs.width/2, -20, obs.width, 10);
820
+ }
821
+ ctx.restore();
 
 
 
 
 
 
 
 
 
 
 
 
 
822
  }
 
823
 
824
+ function drawCoin(x, y, coin) {
825
+ if (!coin.active) return;
826
+ ctx.save();
827
+ ctx.translate(x, y);
828
+
829
+ // Spin effect (scale X)
830
+ const scaleX = Math.abs(Math.sin(coin.rot));
831
+ ctx.scale(scaleX, 1);
832
+
833
+ if (coin.type === 'btc') {
834
+ ctx.fillStyle = CONFIG.colors.btc;
835
+ ctx.shadowColor = CONFIG.colors.btc;
836
+ ctx.shadowBlur = 15;
837
+ ctx.beginPath();
838
+ ctx.arc(0, 0, 20, 0, Math.PI*2);
839
+ ctx.fill();
840
+ ctx.fillStyle = '#fff';
841
+ ctx.font = 'bold 20px sans-serif';
842
+ ctx.textAlign = 'center';
843
+ ctx.textBaseline = 'middle';
844
+ ctx.fillText('₿', 0, 2);
845
+ } else {
846
+ // Powerups
847
+ ctx.fillStyle = coin.type === 'magnet' ? '#3b82f6' : (coin.type === 'shield' ? '#a855f7' : '#eab308');
848
+ ctx.shadowColor = ctx.fillStyle;
849
+ ctx.shadowBlur = 15;
850
+ ctx.beginPath();
851
+ ctx.arc(0, 0, 15, 0, Math.PI*2);
852
+ ctx.fill();
853
+ ctx.fillStyle = 'white';
854
+ ctx.font = '16px sans-serif';
855
+ ctx.textAlign = 'center';
856
+ ctx.textBaseline = 'middle';
857
+ let icon = '?';
858
+ if(coin.type === 'magnet') icon = 'M';
859
+ if(coin.type === 'shield') icon = 'S';
860
+ if(coin.type === 'multiplier') icon = 'x2';
861
+ ctx.fillText(icon, 0, 2);
862
+ }
863
 
864
+ ctx.restore();
865
+ }
866
+
867
+ // --- Particle System ---
868
+ function createDust(lane, y, count) {
869
+ const cx = getLaneCenter(lane);
870
+ const cy = getLaneY(lane);
871
+ for(let i=0; i<count; i++) {
872
+ entities.particles.push({
873
+ x: cx + (Math.random()-0.5)*40,
874
+ y: cy,
875
+ vx: (Math.random()-0.5)*100,
876
+ vy: -Math.random()*100,
877
+ life: 0.5,
878
+ color: 'rgba(255,255,255,0.5)',
879
+ size: Math.random()*5+2,
880
+ type: 'dust'
881
+ });
882
+ }
883
  }
884
 
885
+ function createSparkle(x, y, color) {
886
+ for(let i=0; i<10; i++) {
887
+ entities.particles.push({
888
+ x: x,
889
+ y: y,
890
+ vx: (Math.random()-0.5)*300,
891
+ vy: (Math.random()-0.5)*300,
892
+ life: 0.8,
893
+ color: color,
894
+ size: Math.random()*4+2,
895
+ type: 'sparkle'
896
+ });
897
+ }
898
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
899
 
900
+ function createExplosion(x, y, color) {
901
+ for(let i=0; i<20; i++) {
902
+ entities.particles.push({
903
+ x: x,
904
+ y: y,
905
+ vx: (Math.random()-0.5)*400,
906
+ vy: (Math.random()-0.5)*400,
907
+ life: 1.0,
908
+ color: color,
909
+ size: Math.random()*6+3,
910
+ type: 'debris'
911
+ });
912
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
913
  }
 
914
 
915
+ function createFloatingText(x, y, text) {
916
+ entities.particles.push({
917
+ x: x,
918
+ y: y,
919
+ vx: 0,
920
+ vy: -50,
921
+ life: 1.5,
922
+ text: text,
923
+ type: 'text'
924
+ });
925
+ }
926
+
927
+ function updateParticles(dt) {
928
+ entities.particles.forEach(p => {
929
+ p.x += p.vx * dt;
930
+ p.y += p.vy * dt;
931
+ p.life -= dt;
932
+ if (p.type === 'dust' || p.type === 'sparkle') {
933
+ p.vy += 100 * dt; // gravity
 
 
 
 
 
 
 
 
 
 
 
 
934
  }
935
+ });
936
+ entities.particles = entities.particles.filter(p => p.life > 0);
937
+ }
938
+
939
+ function drawParticles() {
940
+ entities.particles.forEach(p => {
941
+ ctx.save();
942
+ ctx.globalAlpha = Math.max(0, p.life);
943
+ if (p.type === 'text') {
944
+ ctx.fillStyle = '#F7931A';
945
+ ctx.font = 'bold 20px Fredoka One';
946
+ ctx.fillText(p.text, p.x, p.y);
947
+ } else {
948
+ ctx.fillStyle = p.color;
949
+ ctx.beginPath();
950
+ ctx.arc(p.x, p.y, p.size, 0, Math.PI*2);
951
+ ctx.fill();
952
  }
953
+ ctx.restore();
954
+ });
955
+ }
 
 
956
 
957
+ // --- Helpers ---
958
+ function getLaneCenter(laneIndex) {
959
+ return (laneIndex * CONFIG.laneWidth) + (CONFIG.laneWidth / 2);
960
  }
 
961
 
962
+ function getLaneY(laneIndex) {
963
+ // In this perspective, all lanes are same Y on screen
964
+ return canvas.height / 2 + 100;
965
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
966
 
967
+ function gameOver() {
968
+ state.screen = 'gameover';
969
+ state.cameraShake = 20;
970
+ document.getElementById('hud').classList.add('hidden');
971
+ document.getElementById('gameOverScreen').classList.remove('hidden');
972
+ document.getElementById('finalScore').innerText = Math.floor(state.score) + "m";
973
+ document.getElementById('finalBTC').innerText = state.btc.toFixed(8);
974
+ }
975
+
976
+ // --- Initialization ---
977
+ document.getElementById('startBtn').addEventListener('click', () => {
978
+ document.getElementById('startScreen').classList.add('hidden');
979
+ document.getElementById('hud').classList.remove('hidden');
980
+ initGame();
981
  });
982
 
983
+ document.getElementById('restartBtn').addEventListener('click', () => {
984
+ document.getElementById('gameOverScreen').classList.add('hidden');
985
+ document.getElementById('hud').classList.remove('hidden');
986
+ initGame();
 
 
 
987
  });
988
+
989
+ // Initial Render
990
+ resize();
991
+ drawBackground();
992
+
993
+ // Draw a static dino for the start screen background
994
+ ctx.fillStyle = '#111827';
995
+ ctx.fillRect(0, 0, canvas.width, canvas.height);
996
+ drawBackground();
997
+ drawGround();
998
+ drawDino(canvas.width/2, canvas.height/2 + 100, false);
999
+
1000
+ </script>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1001
  </body>
1002
  </html>