oldjeff123 commited on
Commit
cb98aa9
·
verified ·
1 Parent(s): d710f95

Upload app.py with huggingface_hub

Browse files
Files changed (1) hide show
  1. app.py +918 -0
app.py ADDED
@@ -0,0 +1,918 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ === index.html ===
2
+ <!DOCTYPE html>
3
+ <html lang="en">
4
+ <head>
5
+ <meta charset="UTF-8">
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
+ <title>Dino RPG Runner</title>
8
+ <link rel="stylesheet" href="style.css">
9
+ <!-- Mandatory transformers.js Import -->
10
+ <script type="module">
11
+ import { pipeline } from 'https://cdn.jsdelivr.net/npm/@huggingface/transformers@3.7.3';
12
+ window.transformersPipeline = pipeline;
13
+ </script>
14
+ </head>
15
+ <body>
16
+ <header>
17
+ <div class="header-content">
18
+ <h1>Dino RPG Runner</h1>
19
+ <a href="https://huggingface.co/spaces/akhaliq/anycoder" target="_blank" class="anycoder-link">
20
+ Built with anycoder
21
+ </a>
22
+ </div>
23
+ </header>
24
+
25
+ <main id="app-container">
26
+ <!-- Loading State Overlay -->
27
+ <div id="loading-overlay" class="overlay active">
28
+ <div class="loader"></div>
29
+ <p>Initializing AI components and game assets...</p>
30
+ </div>
31
+
32
+ <!-- Error State Overlay -->
33
+ <div id="error-overlay" class="overlay">
34
+ <p>An error occurred. Check console for details.</p>
35
+ </div>
36
+
37
+ <!-- Page 1: The Game -->
38
+ <section id="page-game" class="page active" data-page-index="0">
39
+ <div class="game-area">
40
+ <div class="ground"></div>
41
+ <div id="dino" class="dino">🦖</div>
42
+ <!-- Obstacles generated here -->
43
+ <div id="game-info">
44
+ Score: <span id="game-score">0</span> | HP: <span id="dino-hp">3</span>
45
+ </div>
46
+ <div id="game-message" class="hidden">PRESS SPACE TO JUMP!</div>
47
+ </div>
48
+ <p class="navigation-hint">Use ⬅️ and ➡️ to switch pages.</p>
49
+ </section>
50
+
51
+ <!-- Page 2: Stats and AI Ability Tree -->
52
+ <section id="page-stats" class="page" data-page-index="1">
53
+ <h2>The Fossil Codex (Stats & Abilities)</h2>
54
+ <div class="stats-grid">
55
+ <div class="stat-box">
56
+ <h3>Performance</h3>
57
+ <p>Total Distance Run: <span id="stat-distance">0</span> m</p>
58
+ <p>Obstacles Dodged: <span id="stat-dodged">0</span></p>
59
+ </div>
60
+ <div class="stat-box">
61
+ <h3>Abilities</h3>
62
+ <p>Current HP: <span id="ability-hp">3</span></p>
63
+ <p>Available Level Up Points: <span id="ability-points">0</span></p>
64
+ <button id="btn-upgrade-hp" disabled>Level Up HP (Cost: 1 Pt)</button>
65
+ </div>
66
+ </div>
67
+
68
+ <div class="ability-tree-generator">
69
+ <h3>Ancient Lore Generator (Powered by AI)</h3>
70
+ <p>Use our machine learning artifact to describe your next power-up!</p>
71
+ <input type="text" id="ability-prompt" placeholder="e.g., A better jump, stronger skin, faster speed...">
72
+ <button id="btn-generate-lore">Generate Lore</button>
73
+
74
+ <div id="lore-output" class="output-box">
75
+ <p id="lore-text">The whispers of the ancient ones await your command...</p>
76
+ <div id="lore-loading" class="loader small hidden"></div>
77
+ </div>
78
+ </div>
79
+ </section>
80
+
81
+ <!-- Page 3: Death Animation / Memorial -->
82
+ <section id="page-memorial" class="page" data-page-index="2">
83
+ <h2>The Graveyard of Giants</h2>
84
+ <p>A solemn place dedicated to the fallen heroes who dared to run...</p>
85
+ <div id="memorial-container">
86
+ <!-- Died dinosaurs fall here -->
87
+ </div>
88
+ </section>
89
+
90
+ </main>
91
+ <script src="index.js"></script>
92
+ </body>
93
+ </html>
94
+
95
+ === index.js ===
96
+ // index.js content here
97
+
98
+ // --- CONFIGURATION ---
99
+ const DINO_JUMP_VELOCITY = -18;
100
+ const GRAVITY = 1;
101
+ const GAME_SPEED_START = 6;
102
+ const OBSTACLE_INTERVAL_BASE = 1200; // ms
103
+ const MODEL_NAME = 'Xenova/distilgpt2'; // Fast text generation model
104
+ const LORE_PROMPT_PREFIX = "In the mythical world of Terra, describe the ancient dinosaur power-up known as ";
105
+
106
+ // --- GLOBAL STATE ---
107
+ let state = {
108
+ // Game State
109
+ currentPage: 0,
110
+ gameLoopId: null,
111
+ isGameRunning: false,
112
+ isJumping: false,
113
+ dinoY: 0,
114
+ dinoVY: 0,
115
+ score: 0,
116
+ hp: 3,
117
+ maxHp: 3,
118
+ gameSpeed: GAME_SPEED_START,
119
+ obstacleTimer: 0,
120
+ lastTime: 0,
121
+
122
+ // Stats State
123
+ totalDistance: 0,
124
+ obstaclesDodged: 0,
125
+ abilityPoints: 0,
126
+
127
+ // AI State
128
+ aiPipeline: null,
129
+ aiLoading: false,
130
+ hasWebGPU: false,
131
+ };
132
+
133
+ // --- DOM ELEMENTS ---
134
+ const elements = {
135
+ appContainer: document.getElementById('app-container'),
136
+ dino: document.getElementById('dino'),
137
+ gameArea: document.querySelector('#page-game .game-area'),
138
+ gameScore: document.getElementById('game-score'),
139
+ dinoHp: document.getElementById('dino-hp'),
140
+ gameMessage: document.getElementById('game-message'),
141
+ pages: document.querySelectorAll('.page'),
142
+ loadingOverlay: document.getElementById('loading-overlay'),
143
+ errorOverlay: document.getElementById('error-overlay'),
144
+
145
+ // Stats Page
146
+ statDistance: document.getElementById('stat-distance'),
147
+ statDodged: document.getElementById('stat-dodged'),
148
+ abilityHp: document.getElementById('ability-hp'),
149
+ abilityPoints: document.getElementById('ability-points'),
150
+ btnUpgradeHp: document.getElementById('btn-upgrade-hp'),
151
+
152
+ // AI Elements
153
+ abilityPrompt: document.getElementById('ability-prompt'),
154
+ btnGenerateLore: document.getElementById('btn-generate-lore'),
155
+ loreText: document.getElementById('lore-text'),
156
+ loreLoading: document.getElementById('lore-loading'),
157
+
158
+ // Memorial Page
159
+ memorialContainer: document.getElementById('memorial-container'),
160
+ };
161
+
162
+ // --- INITIALIZATION ---
163
+
164
+ /**
165
+ * Initializes the transformers.js pipeline.
166
+ */
167
+ async function initializeAI() {
168
+ state.aiLoading = true;
169
+ try {
170
+ if (!window.transformersPipeline) {
171
+ throw new Error("Transformers.js pipeline is not available.");
172
+ }
173
+
174
+ // Check for WebGPU support (optional device setting)
175
+ const device = navigator.gpu ? 'webgpu' : 'cpu';
176
+ state.hasWebGPU = (device === 'webgpu');
177
+ console.log(`Using device: ${device}`);
178
+
179
+ elements.loadingOverlay.querySelector('p').textContent = `Loading AI model (${MODEL_NAME}) on ${device}...`;
180
+
181
+ const pipeline = window.transformersPipeline;
182
+ state.aiPipeline = await pipeline('text-generation', MODEL_NAME, { device: device });
183
+
184
+ console.log("AI Pipeline Loaded.");
185
+ state.aiLoading = false;
186
+ elements.loadingOverlay.classList.remove('active');
187
+
188
+ // Start Game
189
+ resetGame();
190
+ elements.gameMessage.classList.remove('hidden');
191
+
192
+ } catch (e) {
193
+ console.error("Failed to load AI pipeline:", e);
194
+ elements.loadingOverlay.classList.remove('active');
195
+ elements.errorOverlay.classList.add('active');
196
+ elements.errorOverlay.querySelector('p').textContent =
197
+ `Error loading AI. Try refreshing or check console. Details: ${e.message}`;
198
+ state.aiLoading = false;
199
+ }
200
+ }
201
+
202
+ // --- GAME LOGIC (PAGE 1) ---
203
+
204
+ /**
205
+ * Resets the game state.
206
+ */
207
+ function resetGame() {
208
+ state.score = 0;
209
+ state.hp = state.maxHp;
210
+ state.gameSpeed = GAME_SPEED_START;
211
+ state.dinoY = 0;
212
+ state.dinoVY = 0;
213
+ state.isJumping = false;
214
+ elements.dino.style.bottom = '0px';
215
+ elements.gameArea.querySelectorAll('.obstacle').forEach(o => o.remove());
216
+ updateGameUI();
217
+
218
+ if (state.currentPage === 0) {
219
+ state.isGameRunning = true;
220
+ gameLoop(0);
221
+ }
222
+ }
223
+
224
+ /**
225
+ * Updates the game score and UI elements.
226
+ */
227
+ function updateGameUI() {
228
+ elements.gameScore.textContent = Math.floor(state.score);
229
+ elements.dinoHp.textContent = state.hp;
230
+ elements.abilityHp.textContent = state.maxHp;
231
+ elements.abilityPoints.textContent = state.abilityPoints;
232
+ elements.statDistance.textContent = Math.floor(state.totalDistance);
233
+ elements.statDodged.textContent = state.obstaclesDodged;
234
+
235
+ elements.btnUpgradeHp.disabled = state.abilityPoints < 1;
236
+ }
237
+
238
+ /**
239
+ * Handles the dinosaur jump action.
240
+ */
241
+ function jump() {
242
+ if (!state.isJumping && state.isGameRunning) {
243
+ state.isJumping = true;
244
+ state.dinoVY = DINO_JUMP_VELOCITY;
245
+ }
246
+ }
247
+
248
+ /**
249
+ * Generates and adds a new obstacle to the game area.
250
+ */
251
+ function spawnObstacle() {
252
+ const obstacle = document.createElement('div');
253
+ const type = Math.random() < 0.7 ? 'cactus' : 'wall';
254
+ const height = type === 'cactus' ? (20 + Math.random() * 30) : (40 + Math.random() * 50);
255
+
256
+ obstacle.className = `obstacle ${type}`;
257
+ obstacle.style.height = `${height}px`;
258
+ obstacle.style.right = '0px'; // Start off-screen right
259
+
260
+ elements.gameArea.appendChild(obstacle);
261
+ }
262
+
263
+ /**
264
+ * Checks for collision between the dinosaur and obstacles.
265
+ * @param {HTMLElement} obstacle
266
+ * @returns {boolean}
267
+ */
268
+ function checkCollision(obstacle) {
269
+ const dinoRect = elements.dino.getBoundingClientRect();
270
+ const obsRect = obstacle.getBoundingClientRect();
271
+
272
+ // Define generous hitbox for simplicity
273
+ return (
274
+ dinoRect.right - 10 > obsRect.left &&
275
+ dinoRect.left + 10 < obsRect.right &&
276
+ dinoRect.bottom > obsRect.top
277
+ );
278
+ }
279
+
280
+ /**
281
+ * Main game loop using requestAnimationFrame.
282
+ * @param {number} timestamp
283
+ */
284
+ function gameLoop(timestamp) {
285
+ if (!state.isGameRunning || state.currentPage !== 0) {
286
+ state.gameLoopId = null;
287
+ return;
288
+ }
289
+
290
+ const deltaTime = (timestamp - state.lastTime) / 1000;
291
+ state.lastTime = timestamp;
292
+
293
+ // 1. Dinosaur Physics Update
294
+ if (state.isJumping) {
295
+ state.dinoVY += GRAVITY;
296
+ state.dinoY += state.dinoVY;
297
+
298
+ if (state.dinoY >= 0) {
299
+ state.dinoY = 0;
300
+ state.dinoVY = 0;
301
+ state.isJumping = false;
302
+ }
303
+ elements.dino.style.bottom = `${state.dinoY}px`;
304
+ }
305
+
306
+ // 2. Obstacle Movement & Collision
307
+ const obstacles = elements.gameArea.querySelectorAll('.obstacle');
308
+ let hit = false;
309
+ let dodgedCount = 0;
310
+
311
+ obstacles.forEach(obstacle => {
312
+ let currentRight = parseFloat(obstacle.style.right);
313
+
314
+ // Move obstacle
315
+ currentRight += state.gameSpeed * 60 * deltaTime; // Speed scaling
316
+ obstacle.style.right = `${currentRight}px`;
317
+
318
+ const obstacleWidth = obstacle.offsetWidth;
319
+ const dinoLeftOffset = elements.dino.offsetLeft; // Dino is centered
320
+
321
+ // Check for collision when obstacle is near the dino
322
+ if (currentRight + obstacleWidth > dinoLeftOffset && currentRight < dinoLeftOffset + 40) {
323
+ if (checkCollision(obstacle)) {
324
+ if (!obstacle.dataset.hit) {
325
+ state.hp -= 1;
326
+ obstacle.dataset.hit = 'true'; // Prevent multiple hits
327
+ hit = true;
328
+ }
329
+ }
330
+ }
331
+
332
+ // Check for obstacle passing
333
+ if (currentRight > elements.gameArea.offsetWidth + 50) {
334
+ obstacle.remove();
335
+ if (!obstacle.dataset.hit) {
336
+ dodgedCount++;
337
+ }
338
+ }
339
+ });
340
+
341
+ if (hit) {
342
+ elements.dino.classList.add('hit');
343
+ setTimeout(() => elements.dino.classList.remove('hit'), 150);
344
+ }
345
+
346
+ state.obstaclesDodged += dodgedCount;
347
+ state.abilityPoints += dodgedCount;
348
+
349
+ // 3. Spawning Logic
350
+ state.obstacleTimer += deltaTime * 1000;
351
+ const interval = Math.max(800, OBSTACLE_INTERVAL_BASE - state.gameSpeed * 20);
352
+
353
+ if (state.obstacleTimer >= interval) {
354
+ spawnObstacle();
355
+ state.obstacleTimer = 0;
356
+ }
357
+
358
+ // 4. Score and Speed Update
359
+ state.score += deltaTime * state.gameSpeed;
360
+ state.totalDistance += deltaTime * state.gameSpeed;
361
+ state.gameSpeed = Math.min(20, GAME_SPEED_START + state.score / 500); // Speed increases gradually
362
+
363
+ updateGameUI();
364
+
365
+ // 5. Game Over Check
366
+ if (state.hp <= 0) {
367
+ gameOver();
368
+ return;
369
+ }
370
+
371
+ state.gameLoopId = requestAnimationFrame(gameLoop);
372
+ }
373
+
374
+ /**
375
+ * Handles the game over state.
376
+ */
377
+ function gameOver() {
378
+ state.isGameRunning = false;
379
+ cancelAnimationFrame(state.gameLoopId);
380
+ state.gameLoopId = null;
381
+
382
+ elements.gameMessage.textContent = `GAME OVER! Score: ${Math.floor(state.score)}. Press SPACE to restart.`;
383
+ elements.gameMessage.classList.remove('hidden');
384
+
385
+ // Add a dead dino to the memorial
386
+ addDeadDinoToMemorial();
387
+
388
+ // Switch to memorial page for immediate visual feedback
389
+ // switchPage(2);
390
+ }
391
+
392
+ // --- AI LORE GENERATOR (PAGE 2) ---
393
+
394
+ /**
395
+ * Generates lore using the transformers.js model.
396
+ */
397
+ async function generateLore() {
398
+ if (state.aiLoading || !state.aiPipeline) {
399
+ alert("AI model is still loading or failed.");
400
+ return;
401
+ }
402
+
403
+ const promptText = elements.abilityPrompt.value.trim();
404
+ if (!promptText) {
405
+ elements.loreText.textContent = "Please enter a concept for the power-up (e.g., 'faster run').";
406
+ return;
407
+ }
408
+
409
+ elements.loreLoading.classList.remove('hidden');
410
+ elements.btnGenerateLore.disabled = true;
411
+
412
+ const fullPrompt = LORE_PROMPT_PREFIX + promptText + ". The description must be detailed and majestic.";
413
+
414
+ try {
415
+ const output = await state.aiPipeline(fullPrompt, {
416
+ max_new_tokens: 60,
417
+ temperature: 0.8,
418
+ do_sample: true,
419
+ repetition_penalty: 1.2
420
+ });
421
+
422
+ if (output && output.length > 0) {
423
+ // Clean up the generated text (often repeats the prompt or generates incomplete sentence)
424
+ let lore = output[0].generated_text.replace(fullPrompt, '').trim();
425
+ // Try to find the first complete sentence after the prompt
426
+ lore = lore.split('.').filter(s => s.length > 5).join('. ').trim();
427
+ if (lore.length < 10) {
428
+ lore = output[0].generated_text.substring(fullPrompt.length, fullPrompt.length + 100).trim() + '...';
429
+ }
430
+ elements.loreText.textContent = lore;
431
+ } else {
432
+ elements.loreText.textContent = "AI failed to generate lore. Try again.";
433
+ }
434
+
435
+ } catch (e) {
436
+ console.error("AI Generation Error:", e);
437
+ elements.loreText.textContent = `AI Generation Failed: ${e.message}`;
438
+ } finally {
439
+ elements.loreLoading.classList.add('hidden');
440
+ elements.btnGenerateLore.disabled = false;
441
+ }
442
+ }
443
+
444
+ // --- PAGE 3 ANIMATION ---
445
+
446
+ /**
447
+ * Adds a visual representation of a fallen dino to the memorial page.
448
+ */
449
+ function addDeadDinoToMemorial() {
450
+ const dino = document.createElement('div');
451
+ dino.className = 'dead-dino';
452
+ dino.textContent = '💀';
453
+ // Randomize initial horizontal position
454
+ dino.style.left = `${Math.random() * 90 + 5}vw`;
455
+ // Randomize duration for a staggered fall effect
456
+ dino.style.animationDuration = `${3 + Math.random() * 2}s`;
457
+ elements.memorialContainer.appendChild(dino);
458
+
459
+ // Limit memorial to 20 dinos for performance
460
+ if (elements.memorialContainer.children.length > 20) {
461
+ elements.memorialContainer.firstChild.remove();
462
+ }
463
+ }
464
+
465
+ // --- NAVIGATION & EVENT HANDLERS ---
466
+
467
+ /**
468
+ * Switches the active page view.
469
+ * @param {number} index
470
+ */
471
+ function switchPage(index) {
472
+ if (index < 0 || index >= elements.pages.length) return;
473
+
474
+ // Pause/Resume animations based on the page
475
+ if (index === 0) {
476
+ if (state.hp > 0 && !state.isGameRunning) {
477
+ resetGame(); // Restart game loop if switching back to the game
478
+ }
479
+ } else {
480
+ if (state.gameLoopId) {
481
+ cancelAnimationFrame(state.gameLoopId);
482
+ state.gameLoopId = null;
483
+ state.isGameRunning = false;
484
+ }
485
+ }
486
+
487
+ // Update active class
488
+ elements.pages[state.currentPage].classList.remove('active');
489
+ state.currentPage = index;
490
+ elements.pages[state.currentPage].classList.add('active');
491
+ }
492
+
493
+ /**
494
+ * Global keydown handler for navigation and game controls.
495
+ * @param {KeyboardEvent} e
496
+ */
497
+ function handleKeyDown(e) {
498
+ if (e.key === 'ArrowRight') {
499
+ switchPage((state.currentPage + 1) % elements.pages.length);
500
+ } else if (e.key === 'ArrowLeft') {
501
+ switchPage((state.currentPage - 1 + elements.pages.length) % elements.pages.length);
502
+ } else if (e.key === ' ' && state.currentPage === 0) { // Space key for jump/restart
503
+ e.preventDefault();
504
+ if (state.isGameRunning) {
505
+ jump();
506
+ } else {
507
+ resetGame();
508
+ elements.gameMessage.classList.add('hidden');
509
+ }
510
+ }
511
+ }
512
+
513
+ /**
514
+ * Handles the HP upgrade button click.
515
+ */
516
+ function handleHpUpgrade() {
517
+ if (state.abilityPoints >= 1) {
518
+ state.abilityPoints -= 1;
519
+ state.maxHp += 1;
520
+ state.hp = state.maxHp; // Refill HP on level up
521
+ updateGameUI();
522
+ alert(`Maximum HP increased to ${state.maxHp}! Health fully restored.`);
523
+ }
524
+ }
525
+
526
+
527
+ // --- MAIN EXECUTION ---
528
+ document.addEventListener('DOMContentLoaded', () => {
529
+ // Set up event listeners
530
+ document.addEventListener('keydown', handleKeyDown);
531
+
532
+ elements.btnGenerateLore.addEventListener('click', generateLore);
533
+ elements.btnUpgradeHp.addEventListener('click', handleHpUpgrade);
534
+
535
+ // Initial AI and Game Setup
536
+ initializeAI();
537
+ });
538
+
539
+ === style.css ===
540
+ /* style.css content here */
541
+
542
+ :root {
543
+ --color-primary: #5c8d4e;
544
+ --color-secondary: #f0f0c0;
545
+ --color-background: #fff8e1;
546
+ --color-dark: #333;
547
+ --color-danger: #c0392b;
548
+ --font-family: 'Arial', sans-serif;
549
+ }
550
+
551
+ * {
552
+ box-sizing: border-box;
553
+ margin: 0;
554
+ padding: 0;
555
+ }
556
+
557
+ body {
558
+ font-family: var(--font-family);
559
+ background-color: var(--color-background);
560
+ color: var(--color-dark);
561
+ line-height: 1.6;
562
+ overflow-x: hidden;
563
+ }
564
+
565
+ header {
566
+ background-color: var(--color-primary);
567
+ color: white;
568
+ padding: 10px 20px;
569
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
570
+ }
571
+
572
+ .header-content {
573
+ display: flex;
574
+ justify-content: space-between;
575
+ align-items: center;
576
+ max-width: 1200px;
577
+ margin: 0 auto;
578
+ }
579
+
580
+ h1 {
581
+ font-size: 1.8rem;
582
+ }
583
+
584
+ .anycoder-link {
585
+ color: var(--color-secondary);
586
+ text-decoration: none;
587
+ font-size: 0.9rem;
588
+ transition: color 0.2s;
589
+ }
590
+
591
+ .anycoder-link:hover {
592
+ color: white;
593
+ text-decoration: underline;
594
+ }
595
+
596
+ /* --- General Page Layout --- */
597
+
598
+ main {
599
+ max-width: 1200px;
600
+ margin: 0 auto;
601
+ padding: 20px;
602
+ position: relative;
603
+ min-height: calc(100vh - 70px);
604
+ }
605
+
606
+ .page {
607
+ position: absolute;
608
+ top: 20px;
609
+ left: 20px;
610
+ right: 20px;
611
+ min-height: 80vh;
612
+ padding: 20px;
613
+ background: white;
614
+ border-radius: 12px;
615
+ box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1);
616
+ opacity: 0;
617
+ visibility: hidden;
618
+ transition: opacity 0.5s ease, visibility 0.5s;
619
+ }
620
+
621
+ .page.active {
622
+ opacity: 1;
623
+ visibility: visible;
624
+ position: relative; /* Take up space when active */
625
+ }
626
+
627
+ h2 {
628
+ color: var(--color-primary);
629
+ margin-bottom: 20px;
630
+ border-bottom: 2px solid var(--color-secondary);
631
+ padding-bottom: 5px;
632
+ }
633
+
634
+ .navigation-hint {
635
+ text-align: center;
636
+ margin-top: 20px;
637
+ font-size: 0.9rem;
638
+ color: #666;
639
+ }
640
+
641
+ /* --- Loading/Error Overlays --- */
642
+
643
+ .overlay {
644
+ position: fixed;
645
+ top: 0;
646
+ left: 0;
647
+ width: 100%;
648
+ height: 100%;
649
+ background: rgba(255, 255, 255, 0.95);
650
+ display: flex;
651
+ flex-direction: column;
652
+ justify-content: center;
653
+ align-items: center;
654
+ z-index: 1000;
655
+ visibility: hidden;
656
+ opacity: 0;
657
+ transition: opacity 0.3s;
658
+ }
659
+
660
+ .overlay.active {
661
+ visibility: visible;
662
+ opacity: 1;
663
+ }
664
+
665
+ .loader {
666
+ border: 8px solid #f3f3f3;
667
+ border-top: 8px solid var(--color-primary);
668
+ border-radius: 50%;
669
+ width: 60px;
670
+ height: 60px;
671
+ animation: spin 1s linear infinite;
672
+ margin-bottom: 15px;
673
+ }
674
+
675
+ .loader.small {
676
+ width: 20px;
677
+ height: 20px;
678
+ border-width: 3px;
679
+ margin-right: 10px;
680
+ display: inline-block;
681
+ }
682
+
683
+ @keyframes spin {
684
+ 0% { transform: rotate(0deg); }
685
+ 100% { transform: rotate(360deg); }
686
+ }
687
+
688
+ /* --- Page 1: Game Styles --- */
689
+
690
+ #page-game {
691
+ display: flex;
692
+ flex-direction: column;
693
+ align-items: center;
694
+ }
695
+
696
+ .game-area {
697
+ width: 100%;
698
+ max-width: 800px;
699
+ height: 300px;
700
+ border: 3px solid var(--color-dark);
701
+ margin: 20px 0;
702
+ position: relative;
703
+ overflow: hidden;
704
+ background-color: #e0f7fa;
705
+ }
706
+
707
+ .ground {
708
+ position: absolute;
709
+ bottom: 0;
710
+ width: 100%;
711
+ height: 15px;
712
+ background-color: var(--color-primary);
713
+ box-shadow: 0 4px 5px rgba(0, 0, 0, 0.2) inset;
714
+ z-index: 5;
715
+ }
716
+
717
+ #dino {
718
+ position: absolute;
719
+ left: 20%; /* Dinosaur is centered relative to the game viewport */
720
+ bottom: 0;
721
+ font-size: 40px;
722
+ transition: filter 0.1s ease;
723
+ z-index: 10;
724
+ }
725
+
726
+ #dino.hit {
727
+ filter: drop-shadow(0 0 10px var(--color-danger));
728
+ animation: shake 0.2s 2;
729
+ }
730
+
731
+ @keyframes shake {
732
+ 0%, 100% { transform: translate(0, 0); }
733
+ 25% { transform: translate(-2px, 0); }
734
+ 75% { transform: translate(2px, 0); }
735
+ }
736
+
737
+ .obstacle {
738
+ position: absolute;
739
+ bottom: 0;
740
+ width: 30px;
741
+ background-color: var(--color-primary);
742
+ z-index: 8;
743
+ border-radius: 5px 5px 0 0;
744
+ }
745
+
746
+ .obstacle.cactus {
747
+ width: 25px;
748
+ background: #008000;
749
+ }
750
+ .obstacle.wall {
751
+ width: 40px;
752
+ background: #a0522d;
753
+ }
754
+
755
+ #game-info {
756
+ position: absolute;
757
+ top: 10px;
758
+ right: 10px;
759
+ font-weight: bold;
760
+ padding: 5px 10px;
761
+ background: rgba(255, 255, 255, 0.8);
762
+ border-radius: 5px;
763
+ z-index: 15;
764
+ }
765
+
766
+ #game-message {
767
+ position: absolute;
768
+ top: 50%;
769
+ left: 50%;
770
+ transform: translate(-50%, -50%);
771
+ font-size: 1.5rem;
772
+ font-weight: bold;
773
+ color: var(--color-danger);
774
+ background: rgba(255, 255, 255, 0.9);
775
+ padding: 10px 20px;
776
+ border-radius: 5px;
777
+ z-index: 20;
778
+ }
779
+
780
+ .hidden {
781
+ display: none !important;
782
+ }
783
+
784
+ /* --- Page 2: Stats/Ability Tree Styles --- */
785
+
786
+ .stats-grid {
787
+ display: grid;
788
+ grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
789
+ gap: 20px;
790
+ margin-bottom: 40px;
791
+ }
792
+
793
+ .stat-box {
794
+ background: var(--color-secondary);
795
+ padding: 20px;
796
+ border-radius: 8px;
797
+ box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
798
+ }
799
+
800
+ .stat-box h3 {
801
+ color: var(--color-dark);
802
+ margin-top: 0;
803
+ }
804
+
805
+ button {
806
+ background-color: var(--color-primary);
807
+ color: white;
808
+ padding: 10px 15px;
809
+ border: none;
810
+ border-radius: 5px;
811
+ cursor: pointer;
812
+ font-weight: bold;
813
+ transition: background-color 0.2s;
814
+ margin-top: 10px;
815
+ }
816
+
817
+ button:hover:not(:disabled) {
818
+ background-color: #4a7541;
819
+ }
820
+
821
+ button:disabled {
822
+ background-color: #ccc;
823
+ cursor: not-allowed;
824
+ }
825
+
826
+ .ability-tree-generator {
827
+ padding: 20px;
828
+ background: #f8f8f8;
829
+ border: 1px dashed #ddd;
830
+ border-radius: 8px;
831
+ }
832
+
833
+ #ability-prompt {
834
+ width: 100%;
835
+ padding: 10px;
836
+ margin-bottom: 10px;
837
+ border: 1px solid #ccc;
838
+ border-radius: 5px;
839
+ font-size: 1rem;
840
+ }
841
+
842
+ .output-box {
843
+ margin-top: 20px;
844
+ padding: 15px;
845
+ border: 1px solid var(--color-primary);
846
+ background: #e6f3e6;
847
+ border-left: 5px solid var(--color-primary);
848
+ min-height: 80px;
849
+ display: flex;
850
+ align-items: center;
851
+ }
852
+
853
+ #lore-text {
854
+ font-style: italic;
855
+ color: #444;
856
+ }
857
+
858
+ /* --- Page 3: Memorial Styles --- */
859
+
860
+ #page-memorial {
861
+ text-align: center;
862
+ overflow: hidden; /* Hide dinos when they fall past the bottom */
863
+ }
864
+
865
+ #memorial-container {
866
+ position: relative;
867
+ width: 100%;
868
+ height: 60vh;
869
+ border: 1px solid #eee;
870
+ background: #333 url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="10" height="10"><rect width="10" height="10" fill="gray"/><circle cx="5" cy="5" r="1" fill="#444"/></svg>'); /* Simple texture */
871
+ overflow: hidden;
872
+ margin: 20px auto;
873
+ border-radius: 8px;
874
+ }
875
+
876
+ .dead-dino {
877
+ position: absolute;
878
+ top: -50px;
879
+ font-size: 3rem;
880
+ color: #777;
881
+ animation: fall linear forwards;
882
+ pointer-events: none;
883
+ }
884
+
885
+ /* The 'fall' animation is only active when Page 3 is loaded and running */
886
+ @keyframes fall {
887
+ 0% { transform: translateY(0) rotate(0deg); opacity: 1; }
888
+ 90% { opacity: 1; }
889
+ 100% { transform: translateY(calc(60vh + 100px)) rotate(360deg); opacity: 0; }
890
+ }
891
+
892
+ /* --- Responsiveness --- */
893
+ @media (max-width: 768px) {
894
+ h1 {
895
+ font-size: 1.5rem;
896
+ }
897
+
898
+ .page {
899
+ padding: 10px;
900
+ top: 10px;
901
+ left: 10px;
902
+ right: 10px;
903
+ }
904
+
905
+ .game-area {
906
+ height: 200px;
907
+ margin: 10px 0;
908
+ }
909
+
910
+ #dino {
911
+ font-size: 30px;
912
+ left: 10%;
913
+ }
914
+
915
+ .stats-grid {
916
+ grid-template-columns: 1fr;
917
+ }
918
+ }