HAL1993 commited on
Commit
372a3f5
·
verified ·
1 Parent(s): 77e973c

Upload folder using huggingface_hub

Browse files
Files changed (1) hide show
  1. index.html +1165 -845
index.html CHANGED
@@ -2,1001 +2,1321 @@
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>₿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>
 
 
 
 
 
 
 
 
 
2
  <html lang="en">
3
  <head>
4
  <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
6
+ <title>Dino Run - Bitcoin Edition</title>
 
 
7
  <style>
8
+ @import url('https://fonts.googleapis.com/css2?family=Orbitron:wght@400;700;900&family=Exo+2:wght@300;400;600;700&display=swap');
9
+
10
+ * {
11
  margin: 0;
12
+ padding: 0;
13
+ box-sizing: border-box;
14
+ }
15
+
16
+ :root {
17
+ --primary: #F7931A;
18
+ --primary-dark: #E88A0C;
19
+ --secondary: #4CAF50;
20
+ --accent: #00E5FF;
21
+ --dark: #1a1a2e;
22
+ --light: #ffffff;
23
+ --glass: rgba(255, 255, 255, 0.1);
24
+ --glass-border: rgba(255, 255, 255, 0.2);
25
+ }
26
+
27
+ body {
28
+ font-family: 'Exo 2', sans-serif;
29
+ background: var(--dark);
30
  overflow: hidden;
31
+ touch-action: none;
32
+ user-select: none;
33
+ }
34
+
35
+ #gameContainer {
36
+ position: relative;
37
+ width: 100vw;
38
+ height: 100vh;
39
+ display: flex;
40
+ justify-content: center;
41
+ align-items: center;
42
+ background: linear-gradient(135deg, #1a1a2e 0%, #16213e 50%, #0f3460 100%);
43
  }
44
+
45
+ #gameCanvas {
46
  display: block;
47
+ background: transparent;
48
  }
49
+
50
+ .ui-overlay {
51
  position: absolute;
52
  top: 0;
53
  left: 0;
54
  width: 100%;
55
  height: 100%;
56
  pointer-events: none;
57
+ z-index: 10;
58
+ }
59
+
60
+ .screen {
61
+ position: absolute;
62
+ top: 0;
63
+ left: 0;
64
+ width: 100%;
65
+ height: 100%;
66
  display: flex;
67
  flex-direction: column;
68
+ justify-content: center;
69
+ align-items: center;
70
+ background: rgba(26, 26, 46, 0.95);
71
+ backdrop-filter: blur(10px);
72
+ transition: opacity 0.5s ease, transform 0.5s ease;
73
+ pointer-events: auto;
74
+ }
75
+
76
+ .screen.hidden {
77
+ opacity: 0;
78
+ transform: scale(1.1);
79
+ pointer-events: none;
80
+ }
81
+
82
+ .title {
83
+ font-family: 'Orbitron', monospace;
84
+ font-size: clamp(2rem, 8vw, 5rem);
85
+ font-weight: 900;
86
+ background: linear-gradient(135deg, var(--primary) 0%, var(--accent) 50%, var(--secondary) 100%);
87
+ -webkit-background-clip: text;
88
+ -webkit-text-fill-color: transparent;
89
+ background-clip: text;
90
+ text-shadow: 0 0 60px rgba(247, 147, 26, 0.5);
91
+ margin-bottom: 1rem;
92
+ animation: titleGlow 2s ease-in-out infinite;
93
+ }
94
+
95
+ @keyframes titleGlow {
96
+ 0%, 100% { filter: brightness(1); }
97
+ 50% { filter: brightness(1.3); }
98
+ }
99
+
100
+ .subtitle {
101
+ font-size: clamp(1rem, 3vw, 1.5rem);
102
+ color: rgba(255, 255, 255, 0.7);
103
+ margin-bottom: 3rem;
104
+ letter-spacing: 0.3em;
105
+ text-transform: uppercase;
106
+ }
107
+
108
+ .btn {
109
+ font-family: 'Orbitron', monospace;
110
+ font-size: clamp(1rem, 2.5vw, 1.3rem);
111
+ font-weight: 700;
112
+ padding: 1rem 3rem;
113
+ border: 2px solid var(--primary);
114
+ background: transparent;
115
+ color: var(--primary);
116
+ cursor: pointer;
117
+ position: relative;
118
+ overflow: hidden;
119
+ transition: all 0.3s ease;
120
+ text-transform: uppercase;
121
+ letter-spacing: 0.1em;
122
+ margin: 0.5rem;
123
+ }
124
+
125
+ .btn::before {
126
+ content: '';
127
+ position: absolute;
128
+ top: 0;
129
+ left: -100%;
130
+ width: 100%;
131
+ height: 100%;
132
+ background: linear-gradient(90deg, transparent, var(--primary), transparent);
133
+ transition: left 0.5s ease;
134
+ }
135
+
136
+ .btn:hover::before {
137
+ left: 100%;
138
+ }
139
+
140
+ .btn:hover {
141
+ background: var(--primary);
142
+ color: var(--dark);
143
+ box-shadow: 0 0 30px rgba(247, 147, 26, 0.5);
144
+ transform: scale(1.05);
145
+ }
146
+
147
+ .btn-secondary {
148
+ border-color: var(--accent);
149
+ color: var(--accent);
150
+ }
151
+
152
+ .btn-secondary:hover {
153
+ background: var(--accent);
154
+ box-shadow: 0 0 30px rgba(0, 229, 255, 0.5);
155
+ }
156
+
157
+ .hud {
158
+ position: absolute;
159
+ top: 0;
160
+ left: 0;
161
+ width: 100%;
162
+ padding: 1rem 2rem;
163
+ display: flex;
164
  justify-content: space-between;
165
+ align-items: flex-start;
166
+ pointer-events: none;
167
+ }
168
+
169
+ .hud-left, .hud-right {
170
+ display: flex;
171
+ flex-direction: column;
172
+ gap: 0.5rem;
173
+ }
174
+
175
+ .hud-item {
176
+ background: var(--glass);
177
+ border: 1px solid var(--glass-border);
178
+ padding: 0.5rem 1rem;
179
+ border-radius: 8px;
180
+ backdrop-filter: blur(5px);
181
+ }
182
+
183
+ .hud-label {
184
+ font-size: 0.7rem;
185
+ color: rgba(255, 255, 255, 0.6);
186
+ text-transform: uppercase;
187
+ letter-spacing: 0.1em;
188
+ }
189
+
190
+ .hud-value {
191
+ font-family: 'Orbitron', monospace;
192
+ font-size: 1.2rem;
193
+ font-weight: 700;
194
+ color: var(--light);
195
+ }
196
+
197
+ .hud-value.btc {
198
+ color: var(--primary);
199
+ }
200
+
201
+ .hud-value.speed {
202
+ color: var(--accent);
203
+ }
204
+
205
+ .hud-center {
206
+ position: absolute;
207
+ top: 1rem;
208
+ left: 50%;
209
+ transform: translateX(-50%);
210
+ display: flex;
211
+ gap: 1rem;
212
+ }
213
+
214
+ .powerup-indicator {
215
+ width: 50px;
216
+ height: 50px;
217
+ border-radius: 50%;
218
+ display: flex;
219
+ justify-content: center;
220
+ align-items: center;
221
+ font-size: 1.5rem;
222
+ opacity: 0.3;
223
+ transition: all 0.3s ease;
224
+ background: var(--glass);
225
+ border: 2px solid var(--glass-border);
226
  }
227
+
228
+ .powerup-indicator.active {
229
+ opacity: 1;
230
+ animation: pulse 1s ease-in-out infinite;
231
+ }
232
+
233
+ .powerup-indicator.magnet { border-color: var(--accent); color: var(--accent); }
234
+ .powerup-indicator.shield { border-color: #E91E63; color: #E91E63; }
235
+ .powerup-indicator.multiplier { border-color: var(--primary); color: var(--primary); }
236
+ .powerup-indicator.speed { border-color: var(--secondary); color: var(--secondary); }
237
+
238
+ @keyframes pulse {
239
+ 0%, 100% { transform: scale(1); }
240
+ 50% { transform: scale(1.1); }
241
+ }
242
+
243
+ .pause-btn {
244
+ position: absolute;
245
+ top: 1rem;
246
+ right: 1rem;
247
+ width: 50px;
248
+ height: 50px;
249
+ border-radius: 50%;
250
+ background: var(--glass);
251
+ border: 2px solid var(--glass-border);
252
+ color: white;
253
+ font-size: 1.5rem;
254
+ cursor: pointer;
255
  pointer-events: auto;
256
+ transition: all 0.3s ease;
257
+ display: flex;
258
+ justify-content: center;
259
+ align-items: center;
260
+ }
261
+
262
+ .pause-btn:hover {
263
+ background: var(--primary);
264
+ border-color: var(--primary);
265
  }
266
+
267
+ .controls-hint {
 
 
 
 
 
 
 
 
 
 
 
268
  position: absolute;
269
+ bottom: 2rem;
270
+ left: 50%;
271
+ transform: translateX(-50%);
272
+ display: flex;
273
+ gap: 2rem;
274
+ color: rgba(255, 255, 255, 0.5);
275
+ font-size: 0.8rem;
276
  }
277
+
278
+ .control-key {
279
+ display: flex;
280
+ align-items: center;
281
+ gap: 0.5rem;
 
 
 
 
282
  }
283
+
284
+ .key {
285
+ background: var(--glass);
286
+ border: 1px solid var(--glass-border);
287
+ padding: 0.3rem 0.6rem;
288
+ border-radius: 4px;
289
+ font-family: 'Orbitron', monospace;
290
+ font-size: 0.7rem;
291
  }
292
 
293
+ .final-score {
294
+ font-family: 'Orbitron', monospace;
295
+ font-size: clamp(1.5rem, 5vw, 3rem);
296
+ color: var(--primary);
297
+ margin: 1rem 0;
298
+ }
299
+
300
+ .final-btc {
301
+ font-family: 'Orbitron', monospace;
302
+ font-size: clamp(1rem, 3vw, 1.5rem);
303
+ color: var(--accent);
304
+ margin-bottom: 2rem;
305
+ }
306
+
307
+ .game-over-stats {
308
+ display: flex;
309
+ gap: 2rem;
310
+ margin-bottom: 2rem;
311
+ }
312
+
313
+ .stat-item {
314
+ text-align: center;
315
+ }
316
+
317
+ .stat-value {
318
+ font-family: 'Orbitron', monospace;
319
+ font-size: 1.5rem;
320
+ color: white;
321
+ }
322
+
323
+ .stat-label {
324
+ font-size: 0.8rem;
325
+ color: rgba(255, 255, 255, 0.6);
326
+ }
327
+
328
+ .new-record {
329
+ color: var(--secondary);
330
+ font-family: 'Orbitron', monospace;
331
+ animation: recordPulse 0.5s ease-in-out infinite;
332
+ }
333
+
334
+ @keyframes recordPulse {
335
+ 0%, 100% { transform: scale(1); }
336
+ 50% { transform: scale(1.05); }
337
+ }
338
+
339
+ .mobile-controls {
340
+ display: none;
341
+ position: absolute;
342
+ bottom: 2rem;
343
+ left: 0;
344
+ width: 100%;
345
+ justify-content: space-between;
346
+ padding: 0 2rem;
347
+ pointer-events: auto;
348
+ }
349
+
350
+ @media (max-width: 768px) {
351
+ .mobile-controls {
352
+ display: flex;
353
+ }
354
+ .controls-hint {
355
+ display: none;
356
+ }
357
+ }
358
+
359
+ .mobile-btn {
360
+ width: 70px;
361
+ height: 70px;
362
+ border-radius: 50%;
363
+ background: var(--glass);
364
+ border: 2px solid var(--glass-border);
365
+ color: white;
366
+ font-size: 1.5rem;
367
+ display: flex;
368
+ justify-content: center;
369
+ align-items: center;
370
+ transition: all 0.2s ease;
371
+ }
372
+
373
+ .mobile-btn:active {
374
+ transform: scale(0.9);
375
+ background: var(--primary);
376
+ border-color: var(--primary);
377
+ }
378
+
379
+ .mobile-btn.vertical {
380
+ flex-direction: column;
381
+ gap: 0.5rem;
382
+ }
383
+
384
+ .powered-by {
385
+ position: absolute;
386
+ bottom: 1rem;
387
+ left: 50%;
388
+ transform: translateX(-50%);
389
+ font-size: 0.8rem;
390
+ color: rgba(255, 255, 255, 0.4);
391
+ text-decoration: none;
392
+ transition: color 0.3s ease;
393
+ }
394
+
395
+ .powered-by:hover {
396
+ color: var(--primary);
397
+ }
398
+
399
+ .combo-display {
400
+ position: absolute;
401
+ top: 50%;
402
+ left: 50%;
403
+ transform: translate(-50%, -50%);
404
+ font-family: 'Orbitron', monospace;
405
+ font-size: 2rem;
406
+ color: var(--primary);
407
+ text-shadow: 0 0 20px var(--primary);
408
+ opacity: 0;
409
+ transition: all 0.3s ease;
410
+ pointer-events: none;
411
+ }
412
+
413
+ .combo-display.show {
414
+ opacity: 1;
415
+ animation: comboAnim 0.5s ease-out;
416
+ }
417
+
418
+ @keyframes comboAnim {
419
+ 0% { transform: translate(-50%, -50%) scale(0.5); opacity: 0; }
420
+ 50% { transform: translate(-50%, -50%) scale(1.2); opacity: 1; }
421
+ 100% { transform: translate(-50%, -50%) scale(1); opacity: 0; }
422
  }
423
  </style>
424
  </head>
425
  <body>
426
+ <div id="gameContainer">
427
+ <canvas id="gameCanvas"></canvas>
 
 
 
 
 
428
 
429
+ <!-- Start Screen -->
430
+ <div id="startScreen" class="screen">
431
+ <h1 class="title">DINO RUN</h1>
432
+ <p class="subtitle">Bitcoin Edition</p>
433
+ <button class="btn" id="startBtn">START GAME</button>
434
+ <div class="controls-hint">
435
+ <div class="control-key"><span class="key">←</span><span class="key">→</span> Move</div>
436
+ <div class="control-key"><span class="key"></span> Jump</div>
437
+ <div class="control-key"><span class="key">↓</span> Slide</div>
 
 
 
 
 
 
 
 
438
  </div>
439
  </div>
440
 
441
+ <!-- HUD -->
442
+ <div id="hud" class="ui-overlay" style="display: none;">
443
+ <div class="hud">
444
+ <div class="hud-left">
445
+ <div class="hud-item">
446
+ <div class="hud-label">Distance</div>
447
+ <div class="hud-value" id="distanceDisplay">0m</div>
448
+ </div>
449
+ </div>
450
+ <div class="hud-center">
451
+ <div class="powerup-indicator magnet" id="magnetIndicator">🧲</div>
452
+ <div class="powerup-indicator shield" id="shieldIndicator">🛡️</div>
453
+ <div class="powerup-indicator multiplier" id="multiplierIndicator">×2</div>
454
+ <div class="powerup-indicator speed" id="speedIndicator">⚡</div>
455
  </div>
456
+ <div class="hud-right">
457
+ <div class="hud-item">
458
+ <div class="hud-label">Balance</div>
459
+ <div class="hud-value btc" id="btcDisplay">₿ 0.00000000</div>
460
+ </div>
461
+ <div class="hud-item">
462
+ <div class="hud-label">Speed</div>
463
+ <div class="hud-value speed" id="speedDisplay">0 km/h</div>
464
+ </div>
465
  </div>
466
+ </div>
467
+ <button class="pause-btn" id="pauseBtn"></button>
468
+
469
+ <div class="mobile-controls">
470
+ <div class="mobile-btn vertical">
471
+ <span id="mobileJump">↑</span>
472
+ <span>Jump</span>
473
  </div>
474
+ <div class="mobile-btn" id="mobileLeft">←</div>
475
+ <div class="mobile-btn" id="mobileRight"></div>
476
+ <div class="mobile-btn vertical">
477
+ <span id="mobileSlide">↓</span>
478
+ <span>Slide</span>
479
  </div>
480
  </div>
481
 
482
+ <div class="combo-display" id="comboDisplay"></div>
483
+ </div>
484
+
485
+ <!-- Pause Screen -->
486
+ <div id="pauseScreen" class="screen hidden">
487
+ <h1 class="title">PAUSED</h1>
488
+ <button class="btn" id="resumeBtn">RESUME</button>
489
+ <button class="btn btn-secondary" id="quitBtn">QUIT</button>
490
  </div>
491
 
492
  <!-- Game Over Screen -->
493
+ <div id="gameOverScreen" class="screen hidden">
494
+ <h1 class="title">GAME OVER</h1>
495
+ <div class="game-over-stats">
496
+ <div class="stat-item">
497
+ <div class="stat-value" id="finalDistance">0m</div>
498
+ <div class="stat-label">Distance</div>
499
+ </div>
500
+ <div class="stat-item">
501
+ <div class="stat-value" id="finalBtc">₿ 0.00000000</div>
502
+ <div class="stat-label">Earned</div>
503
+ </div>
504
  </div>
505
+ <div class="final-score" id="newRecordText" style="display: none;">NEW RECORD!</div>
506
+ <button class="btn" id="restartBtn">PLAY AGAIN</button>
507
+ <button class="btn btn-secondary" id="homeBtn">HOME</button>
508
  </div>
509
 
510
+ <a href="https://huggingface.co/spaces/akhaliq/anycoder" class="powered-by" target="_blank">Built with anycoder</a>
 
 
 
 
 
 
511
  </div>
512
 
513
  <script>
514
+ // ============================================
515
+ // GAME CONFIGURATION
516
+ // ============================================
 
 
 
517
  const CONFIG = {
518
+ CANVAS_WIDTH: 1200,
519
+ CANVAS_HEIGHT: 700,
520
+ LANE_COUNT: 3,
521
+ LANE_WIDTH: 200,
522
+ PLAYER_WIDTH: 80,
523
+ PLAYER_HEIGHT: 100,
524
+ BASE_SPEED: 400,
525
+ MAX_SPEED: 1200,
526
+ SPEED_INCREMENT: 10,
527
+ GRAVITY: 1800,
528
+ JUMP_FORCE: -650,
529
+ SLIDE_DURATION: 600,
530
+ COIN_VALUE: 0.00000001,
531
+ BONUS_COIN_VALUE: 0.00000005,
532
+ SPAWN_INTERVAL_BASE: 1500,
533
+ POWERUP_CHANCE: 0.08,
534
+ MAGNET_DURATION: 8000,
535
+ SHIELD_DURATION: 10000,
536
+ MULTIPLIER_DURATION: 10000,
537
+ SPEED_BOOST_DURATION: 5000,
538
+ COIN_SPIN_SPEED: 3,
539
+ PARTICLE_COUNT: 5
540
  };
541
 
542
+ // ============================================
543
+ // GAME STATE
544
+ // ============================================
545
+ const game = {
546
+ canvas: null,
547
+ ctx: null,
548
+ width: 0,
549
+ height: 0,
550
+ state: 'start', // start, playing, paused, gameover
551
+ deltaTime: 0,
552
  lastTime: 0,
553
+ distance: 0,
554
+ btcBalance: 0,
555
+ speed: CONFIG.BASE_SPEED,
556
+ currentLane: 1,
557
+ targetLane: 1,
558
+ player: {
559
+ x: 0,
560
+ y: 0,
561
+ width: CONFIG.PLAYER_WIDTH,
562
+ height: CONFIG.PLAYER_HEIGHT,
563
+ velocityY: 0,
564
+ isJumping: false,
565
+ isSliding: false,
566
+ slideTimer: 0,
567
+ animFrame: 0,
568
+ animTimer: 0,
569
+ runCycle: 0
570
+ },
 
 
 
 
571
  obstacles: [],
572
  coins: [],
573
+ powerups: [],
574
  particles: [],
575
+ background: {
576
+ layers: [],
577
+ groundOffset: 0
578
+ },
579
+ effects: {
580
+ shake: 0,
581
+ speedLines: []
582
+ },
583
+ powerups: {
584
+ magnet: { active: false, timer: 0 },
585
+ shield: { active: false, timer: 0 },
586
+ multiplier: { active: false, timer: 0 },
587
+ speedBoost: { active: false, timer: 0 }
588
+ },
589
+ combo: {
590
+ count: 0,
591
+ timer: 0,
592
+ multiplier: 1
593
+ },
594
+ spawnTimer: 0,
595
+ highScore: 0,
596
+ keys: {}
597
  };
598
 
599
+ // ============================================
600
+ // INITIALIZATION
601
+ // ============================================
602
+ function init() {
603
+ game.canvas = document.getElementById('gameCanvas');
604
+ game.ctx = game.canvas.getContext('2d');
605
+
606
+ resizeCanvas();
607
+ window.addEventListener('resize', resizeCanvas);
608
+
609
+ setupEventListeners();
610
+ initBackground();
611
+
612
+ game.lastTime = performance.now();
613
+ requestAnimationFrame(gameLoop);
614
+ }
615
+
616
+ function resizeCanvas() {
617
+ const container = document.getElementById('gameContainer');
618
+ const aspectRatio = CONFIG.CANVAS_WIDTH / CONFIG.CANVAS_HEIGHT;
619
+
620
+ let width = container.clientWidth;
621
+ let height = container.clientHeight;
622
+
623
+ if (width / height > aspectRatio) {
624
+ width = height * aspectRatio;
 
 
 
 
 
 
 
 
 
 
625
  } else {
626
+ height = width / aspectRatio;
 
 
 
 
627
  }
628
+
629
+ game.canvas.width = CONFIG.CANVAS_WIDTH;
630
+ game.canvas.height = CONFIG.CANVAS_HEIGHT;
631
+ game.canvas.style.width = width + 'px';
632
+ game.canvas.style.height = height + 'px';
633
+
634
+ game.width = CONFIG.CANVAS_WIDTH;
635
+ game.height = CONFIG.CANVAS_HEIGHT;
636
  }
637
 
638
+ function setupEventListeners() {
639
+ // Keyboard
640
+ window.addEventListener('keydown', (e) => {
641
+ game.keys[e.code] = true;
642
+ if (game.state === 'playing') {
643
+ handleInput(e.code);
644
+ }
645
+ });
646
 
647
+ window.addEventListener('keyup', (e) => {
648
+ game.keys[e.code] = false;
649
+ });
650
+
651
+ // Buttons
652
+ document.getElementById('startBtn').addEventListener('click', startGame);
653
+ document.getElementById('pauseBtn').addEventListener('click', pauseGame);
654
+ document.getElementById('resumeBtn').addEventListener('click', resumeGame);
655
+ document.getElementById('quitBtn').addEventListener('click', quitToMenu);
656
+ document.getElementById('restartBtn').addEventListener('click', restartGame);
657
+ document.getElementById('homeBtn').addEventListener('click', quitToMenu);
658
+
659
+ // Mobile controls
660
+ document.getElementById('mobileLeft').addEventListener('touchstart', () => {
661
+ if (game.state === 'playing') changeLane(-1);
662
+ });
663
+ document.getElementById('mobileRight').addEventListener('touchstart', () => {
664
+ if (game.state === 'playing') changeLane(1);
665
+ });
666
+ document.getElementById('mobileJump').parentElement.addEventListener('touchstart', (e) => {
667
+ e.preventDefault();
668
+ if (game.state === 'playing') jump();
669
+ });
670
+ document.getElementById('mobileSlide').parentElement.addEventListener('touchstart', (e) => {
671
+ e.preventDefault();
672
+ if (game.state === 'playing') slide();
673
+ });
674
+ }
675
+
676
+ // ============================================
677
+ // INPUT HANDLING
678
+ // ============================================
679
+ function handleInput(code) {
680
+ switch(code) {
681
+ case 'ArrowLeft':
682
+ case 'KeyA':
683
+ changeLane(-1);
684
+ break;
685
+ case 'ArrowRight':
686
+ case 'KeyD':
687
+ changeLane(1);
688
+ break;
689
+ case 'ArrowUp':
690
+ case 'KeyW':
691
+ case 'Space':
692
+ jump();
693
+ break;
694
+ case 'ArrowDown':
695
+ case 'KeyS':
696
+ slide();
697
+ break;
698
+ case 'Escape':
699
+ pauseGame();
700
+ break;
701
  }
702
  }
703
 
704
+ function changeLane(direction) {
705
+ const newLane = game.targetLane + direction;
706
+ if (newLane >= 0 && newLane < CONFIG.LANE_COUNT) {
707
+ game.targetLane = newLane;
 
708
  }
709
  }
710
 
711
  function jump() {
712
+ if (!game.player.isJumping && !game.player.isSliding) {
713
+ game.player.isJumping = true;
714
+ game.player.velocityY = CONFIG.JUMP_FORCE;
 
715
  }
716
  }
717
 
718
  function slide() {
719
+ if (!game.player.isJumping && !game.player.isSliding) {
720
+ game.player.isSliding = true;
721
+ game.player.slideTimer = CONFIG.SLIDE_DURATION;
722
  }
723
  }
724
 
725
+ // ============================================
726
+ // GAME STATE MANAGEMENT
727
+ // ============================================
728
+ function startGame() {
729
+ document.getElementById('startScreen').classList.add('hidden');
730
+ document.getElementById('hud').style.display = 'block';
731
+ resetGame();
732
+ game.state = 'playing';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
733
  }
734
 
735
+ function pauseGame() {
736
+ if (game.state === 'playing') {
737
+ game.state = 'paused';
738
+ document.getElementById('pauseScreen').classList.remove('hidden');
 
 
 
 
 
739
  }
 
740
  }
741
 
742
+ function resumeGame() {
743
+ game.state = 'playing';
744
+ document.getElementById('pauseScreen').classList.add('hidden');
745
+ game.lastTime = performance.now();
746
+ }
 
747
 
748
+ function quitToMenu() {
749
+ game.state = 'start';
750
+ document.getElementById('pauseScreen').classList.add('hidden');
751
+ document.getElementById('gameOverScreen').classList.add('hidden');
752
+ document.getElementById('hud').style.display = 'none';
753
+ document.getElementById('startScreen').classList.remove('hidden');
754
+ }
755
 
756
+ function restartGame() {
757
+ document.getElementById('gameOverScreen').classList.add('hidden');
758
+ resetGame();
759
+ game.state = 'playing';
760
  }
761
 
762
+ function gameOver() {
763
+ game.state = 'gameover';
764
+
765
+ const isNewRecord = game.distance > game.highScore;
766
+ if (isNewRecord) {
767
+ game.highScore = Math.floor(game.distance);
768
  }
769
+
770
+ document.getElementById('finalDistance').textContent = Math.floor(game.distance) + 'm';
771
+ document.getElementById('finalBtc').textContent = '₿ ' + game.btcBalance.toFixed(8);
772
+ document.getElementById('newRecordText').style.display = isNewRecord ? 'block' : 'none';
773
+
774
+ document.getElementById('gameOverScreen').classList.remove('hidden');
775
+ }
776
 
777
+ function resetGame() {
778
+ game.distance = 0;
779
+ game.btcBalance = 0;
780
+ game.speed = CONFIG.BASE_SPEED;
781
+ game.currentLane = 1;
782
+ game.targetLane = 1;
783
+ game.spawnTimer = 0;
784
+ game.effects.shake = 0;
785
+ game.combo = { count: 0, timer: 0, multiplier: 1 };
786
+
787
+ game.player = {
788
+ x: getLaneX(1),
789
+ y: game.height - 180,
790
+ width: CONFIG.PLAYER_WIDTH,
791
+ height: CONFIG.PLAYER_HEIGHT,
792
+ velocityY: 0,
793
+ isJumping: false,
794
+ isSliding: false,
795
+ slideTimer: 0,
796
+ animFrame: 0,
797
+ animTimer: 0,
798
+ runCycle: 0
799
+ };
800
+
801
+ game.obstacles = [];
802
+ game.coins = [];
803
+ game.powerups = {
804
+ magnet: { active: false, timer: 0 },
805
+ shield: { active: false, timer: 0 },
806
+ multiplier: { active: false, timer: 0 },
807
+ speedBoost: { active: false, timer: 0 }
808
+ };
809
+ game.particles = [];
810
+
811
+ updateHUD();
812
+ }
813
 
814
+ // ============================================
815
+ // GAME LOOP
816
+ // ============================================
817
+ function gameLoop(timestamp) {
818
+ game.deltaTime = Math.min((timestamp - game.lastTime) / 1000, 0.1);
819
+ game.lastTime = timestamp;
820
+
821
+ if (game.state === 'playing') {
822
+ update();
823
+ render();
824
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
825
 
826
+ requestAnimationFrame(gameLoop);
827
+ }
828
 
829
+ function update() {
830
+ // Update speed
831
+ if (game.powerups.speedBoost.active) {
832
+ game.speed = CONFIG.MAX_SPEED * 1.3;
833
+ } else {
834
+ game.speed = Math.min(CONFIG.MAX_SPEED, CONFIG.BASE_SPEED + game.distance * 0.5);
835
+ }
836
+
837
+ // Update distance
838
+ game.distance += game.speed * game.deltaTime / 60;
839
+
840
+ // Update player
841
+ updatePlayer();
842
+
843
+ // Update lane position
844
+ const targetX = getLaneX(game.targetLane);
845
+ game.player.x += (targetX - game.player.x) * 10 * game.deltaTime;
846
+ game.currentLane = game.player.x;
847
+
848
+ // Spawn objects
849
+ game.spawnTimer -= game.deltaTime * 1000;
850
+ if (game.spawnTimer <= 0) {
851
+ spawnObjects();
852
+ const spawnInterval = Math.max(800, CONFIG.SPAWN_INTERVAL_BASE - game.distance * 0.5);
853
+ game.spawnTimer = spawnInterval;
854
+ }
855
+
856
+ // Update objects
857
+ updateObstacles();
858
+ updateCoins();
859
+ updatePowerups();
860
+ updateParticles();
861
+
862
+ // Update powerup timers
863
+ updatePowerupTimers();
864
+
865
+ // Update combo
866
+ if (game.combo.timer > 0) {
867
+ game.combo.timer -= game.deltaTime * 1000;
868
+ } else {
869
+ game.combo.count = 0;
870
+ game.combo.multiplier = 1;
871
+ }
872
+
873
+ // Update background
874
+ updateBackground();
875
+
876
+ // Update screen shake
877
+ if (game.effects.shake > 0) {
878
+ game.effects.shake *= 0.9;
879
+ }
880
+
881
+ // Speed lines
882
+ if (game.speed > CONFIG.BASE_SPEED * 1.5) {
883
+ if (Math.random() < 0.3) {
884
+ game.effects.speedLines.push({
885
+ x: game.width,
886
+ y: Math.random() * game.height,
887
+ length: 50 + Math.random() * 100,
888
+ speed: 1500 + Math.random() * 500,
889
+ alpha: 0.3 + Math.random() * 0.4
890
+ });
891
+ }
892
+ }
893
+
894
+ game.effects.speedLines = game.effects.speedLines.filter(line => {
895
+ line.x -= line.speed * game.deltaTime;
896
+ return line.x + line.length > 0;
897
+ });
898
+
899
+ // Check collisions
900
+ checkCollisions();
901
 
902
+ // Update HUD
903
+ updateHUD();
 
 
 
 
904
  }
905
 
906
+ function updatePlayer() {
907
+ const groundY = game.height - 180;
908
+
909
+ // Jump physics
910
+ if (game.player.isJumping) {
911
+ game.player.velocityY += CONFIG.GRAVITY * game.deltaTime;
912
+ game.player.y += game.player.velocityY * game.deltaTime;
 
 
 
913
 
914
+ if (game.player.y >= groundY) {
915
+ game.player.y = groundY;
916
+ game.player.isJumping = false;
917
+ game.player.velocityY = 0;
918
+ }
919
+ } else {
920
+ game.player.y = groundY;
921
+ }
922
+
923
+ // Slide
924
+ if (game.player.isSliding) {
925
+ game.player.slideTimer -= game.deltaTime * 1000;
926
+ if (game.player.slideTimer <= 0) {
927
+ game.player.isSliding = false;
928
  }
929
+ }
930
+
931
+ // Animation
932
+ game.player.animTimer += game.deltaTime * 1000;
933
+ if (game.player.animTimer > 100) {
934
+ game.player.animTimer = 0;
935
+ game.player.runCycle = (game.player.runCycle + 0.2) % (Math.PI * 2);
936
+ }
937
+ }
938
 
939
+ function getLaneX(lane) {
940
+ const totalWidth = CONFIG.LANE_COUNT * CONFIG.LANE_WIDTH;
941
+ const startX = (game.width - totalWidth) / 2;
942
+ return startX + lane * CONFIG.LANE_WIDTH + CONFIG.LANE_WIDTH / 2;
943
+ }
944
+
945
+ // ============================================
946
+ // SPAWNING
947
+ // ============================================
948
+ function spawnObjects() {
949
+ const lane = Math.floor(Math.random() * CONFIG.LANE_COUNT);
950
+ const type = Math.random();
951
+
952
+ // Spawn obstacle
953
+ if (type < 0.6) {
954
+ spawnObstacle(lane);
955
+ }
956
+
957
+ // Spawn coins
958
+ if (Math.random() < 0.7) {
959
+ spawnCoinPattern(lane);
960
+ }
961
+
962
+ // Spawn powerup
963
+ if (Math.random() < CONFIG.POWERUP_CHANCE) {
964
+ spawnPowerup(lane);
965
  }
966
  }
967
 
968
  function spawnObstacle(lane) {
969
+ const obstacleTypes = ['barrier', 'barrier_tall', 'gap'];
970
+ const type = obstacleTypes[Math.floor(Math.random() * obstacleTypes.length)];
971
+
972
+ game.obstacles.push({
973
+ x: getLaneX(lane),
974
+ y: game.height - 180,
975
+ width: type === 'barrier_tall' ? 60 : 80,
976
+ height: type === 'barrier_tall' ? 120 : 80,
977
  type: type,
978
+ lane: lane,
979
+ passed: false
 
 
980
  });
981
  }
982
 
983
+ function spawnCoinPattern(lane) {
984
+ const pattern = Math.floor(Math.random() * 3);
985
+ const startX = getLaneX(lane);
986
+
987
+ if (pattern === 0) {
988
+ // Horizontal line
989
+ for (let i = 0; i < 5; i++) {
990
+ game.coins.push({
991
+ x: startX + (i - 2) * 50,
992
+ y: game.height - 200 - Math.random() * 100,
993
+ radius: 25,
994
+ value: CONFIG.COIN_VALUE,
995
+ rotation: Math.random() * Math.PI * 2,
996
+ glow: 0
997
+ });
998
  }
999
+ } else if (pattern === 1) {
1000
+ // Arc
1001
+ for (let i = 0; i < 5; i++) {
1002
+ const angle = (i / 4) * Math.PI - Math.PI / 2;
1003
+ game.coins.push({
1004
+ x: startX + Math.cos(angle) * 80,
1005
+ y: game.height - 200 + Math.sin(angle) * 50,
1006
+ radius: 25,
1007
+ value: CONFIG.COIN_VALUE,
1008
+ rotation: Math.random() * Math.PI * 2,
1009
+ glow: 0
1010
+ });
1011
+ }
1012
+ } else {
1013
+ // Single bonus coin
1014
+ game.coins.push({
1015
+ x: startX,
1016
+ y: game.height - 250,
1017
+ radius: 35,
1018
+ value: CONFIG.BONUS_COIN_VALUE,
1019
+ rotation: Math.random() * Math.PI * 2,
1020
+ glow: 1
1021
  });
1022
  }
1023
  }
1024
 
1025
+ function spawnPowerup(lane) {
1026
+ const types = ['magnet', 'shield', 'multiplier', 'speedBoost'];
1027
+ const type = types[Math.floor(Math.random() * types.length)];
1028
+
1029
+ game.powerups.push({
1030
+ x: getLaneX(lane),
1031
+ y: game.height - 250,
1032
+ width: 50,
1033
+ height: 50,
1034
+ type: type,
1035
+ rotation: 0,
1036
+ bounce: 0
1037
+ });
1038
+ }
1039
 
1040
+ // ============================================
1041
+ // UPDATE OBJECTS
1042
+ // ============================================
1043
+ function updateObstacles() {
1044
+ game.obstacles = game.obstacles.filter(obs => {
1045
+ obs.x -= game.speed * game.deltaTime;
1046
 
1047
+ // Remove if off screen
1048
+ if (obs.x < -100) {
1049
+ return false;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1050
  }
1051
+
1052
+ // Check if passed
1053
+ if (!obs.passed && obs.x < game.player.x - 50) {
1054
+ obs.passed = true;
1055
+ addCombo();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1056
  }
1057
+
1058
+ return true;
1059
+ });
1060
+ }
1061
 
1062
+ function updateCoins() {
1063
+ game.coins = game.coins.filter(coin => {
1064
+ coin.x -= game.speed * game.deltaTime;
1065
+ coin.rotation += CONFIG.COIN_SPIN_SPEED * game.deltaTime;
1066
+ coin.glow = (coin.glow + 0.1) % (Math.PI * 2);
1067
+
1068
+ // Magnet effect
1069
+ if (game.powerups.magnet.active) {
1070
+ const dx = game.player.x - coin.x;
1071
+ const dy = (game.player.y + game.player.height / 2) - coin.y;
1072
+ const dist = Math.sqrt(dx * dx + dy * dy);
1073
+
1074
+ if (dist < 300) {
1075
+ coin.x += dx * 0.1;
1076
+ coin.y += dy * 0.1;
1077
  }
1078
  }
1079
+
1080
+ // Remove if off screen
1081
+ return coin.x > -50;
1082
  });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1083
  }
1084
 
1085
+ function updatePowerups() {
1086
+ game.powerups = game.powerups.filter(pu => {
1087
+ pu.x -= game.speed * game.deltaTime;
1088
+ pu.rotation += 2 * game.deltaTime;
1089
+ pu.bounce += 5 * game.deltaTime;
1090
+
1091
+ return pu.x > -50;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1092
  });
 
 
 
 
 
1093
  }
1094
 
1095
+ function updateParticles() {
1096
+ game.particles = game.particles.filter(p => {
1097
+ p.x += p.vx * game.deltaTime;
1098
+ p.y += p.vy * game.deltaTime;
1099
+ p.life -= game.deltaTime;
1100
+ p.vy += 500 * game.deltaTime;
1101
+ return p.life > 0;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1102
  });
1103
  }
1104
 
1105
+ function updatePowerupTimers() {
1106
+ const updatePowerup = (name, duration) => {
1107
+ if (game.powerups[name].active) {
1108
+ game.powerups[name].timer -= game.deltaTime * 1000;
1109
+ if (game.powerups[name].timer <= 0) {
1110
+ game.powerups[name].active = false;
1111
+ }
1112
+ }
1113
+ };
1114
 
1115
+ updatePowerup('magnet', CONFIG.MAGNET_DURATION);
1116
+ updatePowerup('shield', CONFIG.SHIELD_DURATION);
1117
+ updatePowerup('multiplier', CONFIG.MULTIPLIER_DURATION);
1118
+ updatePowerup('speedBoost', CONFIG.SPEED_BOOST_DURATION);
 
1119
 
1120
+ // Update indicators
1121
+ document.getElementById('magnetIndicator').classList.toggle('active', game.powerups.magnet.active);
1122
+ document.getElementById('shieldIndicator').classList.toggle('active', game.powerups.shield.active);
1123
+ document.getElementById('multiplierIndicator').classList.toggle('active', game.powerups.multiplier.active);
1124
+ document.getElementById('speedIndicator').classList.toggle('active', game.powerups.speedBoost.active);
 
 
 
1125
  }
1126
 
1127
+ function addCombo() {
1128
+ game.combo.count++;
1129
+ game.combo.timer = 2000;
1130
+ game.combo.multiplier = Math.min(5, 1 + Math.floor(game.combo.count / 5));
1131
 
1132
+ if (game.combo.count > 0 && game.combo.count % 5 === 0) {
1133
+ showCombo(game.combo.multiplier + 'x COMBO!');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1134
  }
1135
+ }
1136
 
1137
+ function showCombo(text) {
1138
+ const comboEl = document.getElementById('comboDisplay');
1139
+ comboEl.textContent = text;
1140
+ comboEl.classList.remove('show');
1141
+ void comboEl.offsetWidth;
1142
+ comboEl.classList.add('show');
1143
  }
1144
 
1145
+ // ============================================
1146
+ // COLLISION DETECTION
1147
+ // ============================================
1148
+ function checkCollisions() {
1149
+ const playerBox = {
1150
+ x: game.player.x - game.player.width / 2,
1151
+ y: game.player.y,
1152
+ width: game.player.width,
1153
+ height: game.player.isSliding ? game.player.height / 2 : game.player.height
1154
+ };
1155
+
1156
+ // Check obstacle collisions
1157
+ for (const obs of game.obstacles) {
1158
+ const obsBox = {
1159
+ x: obs.x - obs.width / 2,
1160
+ y: obs.y - obs.height,
1161
+ width: obs.width,
1162
+ height: obs.height
1163
+ };
1164
+
1165
+ if (boxCollision(playerBox, obsBox)) {
1166
+ if (game.powerups.shield.active) {
1167
+ // Use shield
1168
+ game.powerups.shield.active = false;
1169
+ game.effects.shake = 10;
1170
+ createParticles(obs.x, obs.y - obs.height / 2, '#E91E63', 10);
1171
+ } else {
1172
+ gameOver();
1173
+ return;
1174
+ }
1175
+ }
1176
  }
1177
+
1178
+ // Check coin collisions
1179
+ game.coins = game.coins.filter(coin => {
1180
+ const dx = coin.x - game.player.x;
1181
+ const dy = coin.y - (game.player.y + game.player.height / 2);
1182
+ const dist = Math.sqrt(dx * dx + dy * dy);
1183
+
1184
+ if (dist < coin.radius + 40) {
1185
+ // Collect coin
1186
+ let value = coin.value;
1187
+ if (game.powerups.multiplier.active) {
1188
+ value *= 2;
1189
+ }
1190
+ value *= game.combo.multiplier;
1191
+
1192
+ game.btcBalance += value;
1193
+ createParticles(coin.x, coin.y, '#F7931A', CONFIG.PARTICLE_COUNT);
1194
+ return false;
1195
+ }
1196
+
1197
+ return true;
1198
+ });
1199
+
1200
+ // Check powerup collisions
1201
+ game.powerups = game.powerups.filter(pu => {
1202
+ const dx = pu.x - game.player.x;
1203
+ const dy = pu.y - (game.player.y + game.player.height / 2);
1204
+ const dist = Math.sqrt(dx * dx + dy * dy);
1205
+
1206
+ if (dist < 50) {
1207
+ activatePowerup(pu.type);
1208
+ createParticles(pu.x, pu.y, getPowerupColor(pu.type), 15);
1209
+ return false;
1210
+ }
1211
+
1212
+ return true;
1213
+ });
1214
  }
1215
 
1216
+ function boxCollision(a, b) {
1217
+ return a.x < b.x + b.width &&
1218
+ a.x + a.width > b.x &&
1219
+ a.y < b.y + b.height &&
1220
+ a.y + a.height > b.y;
1221
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1222
 
1223
+ function activatePowerup(type) {
1224
+ switch(type) {
1225
+ case 'magnet':
1226
+ game.powerups.magnet.active = true;
1227
+ game.powerups.magnet.timer = CONFIG.MAGNET_DURATION;
1228
+ break;
1229
+ case 'shield':
1230
+ game.powerups.shield.active = true;
1231
+ game.powerups.shield.timer = CONFIG.SHIELD_DURATION;
1232
+ break;
1233
+ case 'multiplier':
1234
+ game.powerups.multiplier.active = true;
1235
+ game.powerups.multiplier.timer = CONFIG.MULTIPLIER_DURATION;
1236
+ break;
1237
+ case 'speedBoost':
1238
+ game.powerups.speedBoost.active = true;
1239
+ game.powerups.speedBoost.timer = CONFIG.SPEED_BOOST_DURATION;
1240
+ break;
1241
  }
1242
  }
1243
 
1244
+ function getPowerupColor(type) {
1245
+ const colors = {
1246
+ magnet: '#00E5FF',
1247
+ shield: '#E91E63',
1248
+ multiplier: '#F7931A',
1249
+ speedBoost: '#4CAF50'
1250
+ };
1251
+ return colors[type] || '#ffffff';
 
 
 
 
 
1252
  }
1253
 
1254
+ function createParticles(x, y, color, count) {
1255
+ for (let i = 0; i < count; i++) {
1256
+ const angle = (Math.PI * 2 * i) / count + Math.random() * 0.5;
1257
+ const speed = 200 + Math.random() * 300;
1258
+ game.particles.push({
1259
  x: x,
1260
  y: y,
1261
+ vx: Math.cos(angle) * speed,
1262
+ vy: Math.sin(angle) * speed - 200,
 
1263
  color: color,
1264
+ size: 3 + Math.random() * 5,
1265
+ life: 1
1266
  });
1267
  }
1268
  }
1269
 
1270
+ // ============================================
1271
+ // BACKGROUND
1272
+ // ============================================
1273
+ function initBackground() {
1274
+ game.background.layers = [
1275
+ { color: '#1a1a3e', speed: 0.2, y: 0, height: game.height },
1276
+ { color: '#252550', speed: 0.4, y: game.height * 0.3, height: game.height * 0.7 },
1277
+ { color: '#2d2d5a', speed: 0.6, y: game.height * 0.5, height: game.height * 0.5 }
1278
+ ];
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1279
  }
1280
 
1281
+ function updateBackground() {
1282
+ game.background.groundOffset += game.speed * game.deltaTime;
1283
+ if (game.background.groundOffset > 100) {
1284
+ game.background.groundOffset = 0;
1285
+ }
 
 
1286
  }
1287
 
1288
+ // ============================================
1289
+ // RENDERING
1290
+ // ============================================
1291
+ function render() {
1292
+ const ctx = game.ctx;
1293
+
1294
+ // Apply screen shake
1295
+ ctx.save();
1296
+ if (game.effects.shake > 0.5) {
1297
+ ctx.translate(
1298
+ (Math.random() - 0.5) * game.effects.shake,
1299
+ (Math.random() - 0.5) * game.effects.shake
1300
+ );
1301
+ }
1302
+
1303
+ // Clear
1304
+ ctx.fillStyle = '#1a1a2e';
1305
+ ctx.fillRect(0, 0, game.width, game.height);
1306
+
1307
+ // Draw background
1308
+ renderBackground(ctx);
1309
+
1310
+ // Draw lanes
1311
+ renderLanes(ctx);
1312
+
1313
+ // Draw ground
1314
+ renderGround(ctx);
1315
+
1316
+ // Draw obstacles
1317
+ renderObstacles(ctx);
1318
+
1319
+ // Draw coins
1320
+ renderCoins(ctx);
1321
+
1322
+ // Draw