kimhyunwoo commited on
Commit
030b78f
·
verified ·
1 Parent(s): 6f10b35

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +354 -499
index.html CHANGED
@@ -3,243 +3,253 @@
3
  <head>
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>Thronglets Simulation</title>
7
  <style>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8
  body {
9
  margin: 0;
10
  overflow: hidden;
11
- background: #222; /* Slightly lighter background */
12
- color: #eee; /* Default text color */
13
- font-family: 'Courier New', monospace;
14
  display: flex;
15
- flex-direction: column; /* Arrange game area and UI panel vertically */
16
  align-items: center;
17
- justify-content: center; /* Center vertically */
18
  min-height: 100vh;
19
  position: relative;
20
  }
21
 
22
  .game-container {
23
  position: relative;
24
- width: 90vmin; /* Responsive width */
25
- height: 60vmin; /* Responsive height */
26
- max-width: 900px;
27
- max-height: 540px;
28
- background: #3A4A2F; /* Grass */
29
- border: 12px solid #6F4E37; /* Darker Wood frame */
30
- box-shadow: inset 0 0 15px rgba(0,0,0,0.5); /* Inner shadow for depth */
 
 
 
 
 
 
31
  box-sizing: border-box;
32
  overflow: hidden;
33
  display: flex;
34
  justify-content: center;
35
  align-items: center;
36
- font-size: 2.2em; /* Slightly larger emojis */
37
  position: relative;
38
- transition: opacity 1s ease-out;
39
- cursor: default; /* Default cursor for game area */
40
  }
41
 
42
  .game-elements {
43
- position: absolute;
44
- top: 0;
45
- left: 0;
46
- width: 100%;
47
- height: 100%;
48
  pointer-events: none;
49
  }
50
 
51
- .game-elements .emoji, .game-elements .text {
52
- position: absolute;
53
- pointer-events: auto;
54
- transform: translate(-50%, -50%);
55
- user-select: none;
56
- z-index: 1;
57
- transition: opacity 0.5s ease-out; /* Fade out */
58
  }
59
 
60
- /* Static elements */
61
- .rock { top: 15%; left: 15%; z-index: 0;}
62
- .rock.right { left: 85%; z-index: 0;}
63
- .tree { top: 80%; left: 20%; font-size: 1.2em; z-index: 0;} /* Made trees slightly bigger */
64
- .tree.right { left: 80%; font-size: 1.2em; z-index: 0;}
 
 
 
 
 
 
65
 
66
  /* Dynamic elements */
67
  .egg { top: 50%; left: 50%; cursor: pointer; transition: transform 0.5s ease, opacity 0.5s ease-out; z-index: 2;}
68
  .egg.hatching { animation: hatch-pulse 0.5s infinite alternate; }
69
 
70
  .thronglet {
71
- cursor: default; /* No direct clicking needed */
72
- transition: top 0.5s ease-out, left 0.5s ease-out, transform 0.2s ease, opacity 0.5s ease-out, filter 0.3s ease-out;
73
  z-index: 3;
74
- text-shadow: 1px 1px 2px rgba(0,0,0,0.3); /* Subtle shadow */
 
 
 
 
75
  }
76
-
77
- /* Items are now buttons in UI panel */
78
- /* .apple, .bath removed from here */
79
 
80
  .feedback {
81
- position: absolute;
82
- transform: translate(-50%, -150%);
83
- font-size: 0.8em;
84
- opacity: 0;
85
- transition: opacity 0.5s ease-out, transform 0.5s ease-out;
86
- pointer-events: none;
87
- z-index: 5;
88
- text-shadow: 1px 1px 1px rgba(0,0,0,0.5);
89
- }
90
- .feedback.active {
91
- opacity: 1;
92
- transform: translate(-50%, -200%);
93
  }
 
94
 
95
- .dead {
96
- filter: grayscale(100%);
97
- opacity: 0.5;
98
- pointer-events: none;
99
- z-index: 1;
100
  }
 
101
 
102
- .blood {
 
103
  position: absolute;
104
- font-size: 1.5em;
105
- transform: translate(-50%, -50%);
106
- pointer-events: none;
107
- opacity: 0;
108
- transition: opacity 0.5s ease-out;
109
- z-index: 0;
110
- color: darkred;
111
  }
112
- .blood.splatter { opacity: 0.8; } /* Slightly less opaque blood */
113
 
114
- /* UI Panel Styling */
115
  .ui-panel {
116
- width: 90vmin; /* Match game container width */
117
- max-width: 900px;
118
- margin-top: 10px; /* Space between game and UI */
119
- background: #4a3c2a; /* Darker wood panel */
120
- border: 4px solid #2f251a;
121
- border-radius: 5px;
122
- padding: 10px;
123
- box-sizing: border-box;
124
- display: flex;
125
- justify-content: space-between;
126
- align-items: stretch; /* Make items fill height */
127
- color: #e0cda9; /* Light parchment color */
128
- transition: opacity 1s ease-out;
129
- height: 120px; /* Fixed height for UI */
130
  }
131
 
132
- .ui-stats, .ui-actions, .ui-console-container {
 
133
  display: flex;
134
  flex-direction: column;
135
- justify-content: space-around; /* Space out items vertically */
136
- padding: 0 10px;
137
  }
138
 
139
- .ui-stats { flex-basis: 25%; text-align: left; }
140
- .ui-actions { flex-basis: 20%; align-items: center; }
141
- .ui-console-container { flex-basis: 55%; background: rgba(0,0,0,0.3); border-radius: 3px;}
142
-
143
- .ui-stats span, #gameTime { display: block; margin-bottom: 5px; font-size: 0.9em; }
144
- #gameTime { font-weight: bold; }
145
-
146
- .ui-actions button {
147
- font-family: 'Courier New', monospace;
148
- font-size: 1.5em; /* Larger emoji buttons */
149
- background: #8B4513; /* SaddleBrown */
150
- border: 2px solid #5C3317; /* Darker brown */
151
- color: white;
152
- padding: 5px 10px;
153
- border-radius: 5px;
154
- cursor: pointer;
155
- transition: background-color 0.2s;
156
- margin: 5px 0;
157
  }
158
- .ui-actions button:hover { background-color: #A0522D; /* Peru */ }
159
- .ui-actions button:active { transform: translateY(1px); }
160
 
161
- .console {
 
 
 
162
  width: 100%;
163
- height: 100%; /* Fill container */
164
- background: transparent; /* Container provides background */
165
- border: none; /* Container provides border */
166
- padding: 5px;
167
- box-sizing: border-box;
168
- white-space: pre-wrap;
169
- word-break: break-word;
170
- pointer-events: none;
171
- overflow-y: scroll;
172
- color: limegreen; /* Keep console text green */
173
- font-size: 0.8em;
174
- }
175
- /* Simple scrollbar styling */
176
- .console::-webkit-scrollbar { width: 5px; }
177
- .console::-webkit-scrollbar-track { background: rgba(0,0,0,0.2); }
178
- .console::-webkit-scrollbar-thumb { background: limegreen; border-radius: 3px;}
179
-
180
- .message { display: block; margin-bottom: 3px; }
181
- .message.error { color: #ff6666; } /* Style for error messages */
182
- .message.system { color: #87CEEB; } /* Style for system messages */
183
- .message.event { color: #FFD700; } /* Style for game events */
184
-
185
- /* Overlays and Final Screens (remain mostly the same) */
186
- .scanline-overlay, .glitch-overlay {
187
- position: fixed; top: 0; left: 0; width: 100%; height: 100%;
188
- pointer-events: none; z-index: 101; transition: opacity 1s ease-out;
189
  }
190
- .scanline-overlay { background: repeating-linear-gradient(0deg, rgba(0,0,0,0.1) 0, rgba(0,0,0,0.1) 1px, transparent 1px, transparent 2px); }
191
- .glitch-overlay { animation: glitch 5s infinite alternate steps(5); opacity: 0; filter: hue-rotate(0deg); }
192
- .glitch-overlay.active { animation: glitch 0.3s infinite alternate steps(5), color-shift 1s infinite linear; opacity: 0.5; }
193
 
194
- .ominous-elements {
195
- position: absolute; top: 10%; right: 10%; font-size: 3em; opacity: 0;
196
- transition: opacity 2s ease-in-out; z-index: 0; pointer-events: none;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
197
  }
198
- .ominous-elements.visible { opacity: 1; }
199
-
200
- .info-panel {
201
- position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);
202
- background: rgba(40, 20, 10, 0.85); /* Darker semi-transparent panel */
203
- border: 2px solid #FF8C00; /* DarkOrange border */
204
- padding: 15px; color: #FFD700; /* Gold text */
205
- font-family: 'Courier New', monospace; font-size: 0.8em; text-align: center;
206
- white-space: pre-wrap; word-break: break-word; opacity: 0;
207
- transition: opacity 0.5s ease-in-out; pointer-events: none; z-index: 10;
208
- max-width: 80%; border-radius: 4px; box-shadow: 0 0 10px rgba(255, 140, 0, 0.5);
 
 
 
 
209
  }
210
- .info-panel.visible { opacity: 1; }
 
 
 
 
211
 
212
- .final-screen, .full-black, .netflix-games-logo { /* Keep final screens hidden */
213
- position: fixed; top: 0; left: 0; width: 100%; height: 100%;
214
- display: flex; align-items: center; justify-content: center;
215
- opacity: 0; transition: opacity 2s ease-in-out; pointer-events: none;
216
  }
217
- .final-screen { /* Style as before */
218
- background: linear-gradient(to bottom, #744FAA, #D470A3); z-index: 300;
219
- flex-direction: column; color: white; font-size: 1.5em; text-align: center;
 
 
 
220
  }
221
- .final-screen.visible { opacity: 1; pointer-events: auto; }
222
- .app-stores { margin-top: 20px; font-size: 0.8em; }
223
- .app-stores img { height: 40px; margin: 0 10px; cursor: pointer; }
224
- .full-black { background: black; z-index: 301; }
225
- .full-black.visible { opacity: 1; }
226
- .netflix-games-logo { z-index: 302; }
227
- .netflix-games-logo.visible { opacity: 1; }
228
- .netflix-games-logo .emoji { font-size: 8em; color: red; text-shadow: 2px 2px 5px rgba(0,0,0,0.5); }
229
 
230
 
231
  @keyframes hatch-pulse { /* Unchanged */
232
  from { transform: translate(-50%, -50%) scale(1); }
233
  to { transform: translate(-50%, -50%) scale(1.05); }
234
  }
235
- @keyframes glitch { /* Unchanged */
236
- 0% { transform: translate(0); } 20% { transform: translate(-5px, 5px); }
237
- 40% { transform: translate(-5px, -5px); } 60% { transform: translate(5px, 5px); }
238
- 80% { transform: translate(5px, -5px); } to { transform: translate(0); }
239
- }
240
- @keyframes color-shift { /* Unchanged */
241
- 0% { filter: hue-rotate(0deg); } 100% { filter: hue-rotate(360deg); }
242
- }
243
 
244
  </style>
245
  </head>
@@ -247,51 +257,58 @@
247
 
248
  <div class="game-container" id="gameContainer">
249
  <div class="game-elements" id="gameElements">
250
- <!-- Static elements -->
251
- <span class="emoji rock">🪨</span>
252
- <span class="emoji rock right">🪨</span>
253
- <span class="emoji tree">🌳</span>
254
- <span class="emoji tree right">🌳</span>
 
 
 
 
255
  <!-- Initial dynamic elements -->
256
  <span class="emoji egg" id="egg">🥚</span>
257
- <!-- Ominous elements for later -->
 
258
  <div class="ominous-elements" id="ominousElements">💀💀💀<br>🦴🦴🦴<br>🌌📦</div>
259
- <!-- Info Panel (content set by JS) -->
260
  <div class="info-panel" id="infoPanel"></div>
261
  <!-- Blood splatter area -->
262
  <span class="emoji blood" id="bloodSplatter">🩸</span>
263
- <!-- Thronglets will be added here by JS -->
264
  </div>
265
- </div>
266
 
267
- <div class="ui-panel" id="uiPanel">
268
- <div class="ui-stats">
269
- <span>Time: <span id="gameTime">00:00</span></span>
270
- <span>Thronglets: <span id="throngletCount">0</span></span>
271
- <span>Deaths: <span id="deathCount">0</span></span>
272
- </div>
273
- <div class="ui-actions">
274
- <button id="feedButton">🍎</button>
275
- <button id="cleanButton">🛀</button>
276
- </div>
277
- <div class="ui-console-container">
278
- <div class="console" id="console"></div>
 
 
 
 
 
 
 
 
 
 
 
 
 
279
  </div>
280
  </div>
281
 
282
- <!-- Overlays (remain outside main structure) -->
283
  <div class="scanline-overlay" id="scanlineOverlay"></div>
284
  <div class="glitch-overlay" id="glitchOverlay"></div>
285
-
286
- <!-- Final Screens (remain outside main structure) -->
287
- <div class="final-screen" id="finalScreen">
288
- PLAY NOW<br>ON THE NETFLIX<br>MOBILE APP
289
- <div class="app-stores">
290
- <img src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI0MCIgaGVpZ2h0PSI0MCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9IndoaXRlIiBzdHJva2Utd2lkdGg9IjIiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCI+PHBvbHlnb24gcG9pbnRzPSIxMyAyIDMgMTQgNyAxNCAxNSAyIj48L3BvbHlnb24+PHBvbHlnb24gcG9pbnRzPSIxNiAxMCAyMCAxMCAyMyAxNyAxNiAxNSI+PC9wb2x5Z29uPjxjaXJjbGUgY3g9IjUuNSIgY3k9IjE3LjUiIHI9IjIuNSI+PC9jaXJjbGU+PGNpcmNsZSBjeD0iMTguNSIgY3k9IjE3LjUiIHI9IjIuNSI+PC9jaXJjbGU+PC9zdmc+" alt="Google Play Placeholder">
291
- <img src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI0MCIgaGVpZ2h0PSI0MCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9IndoaXRlIiBzdHJva2Utd2lkdGg9IjIiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCI+PHBhdGggZD0iTTE1LjkgNi4zQTEwIDkuNyAwIDAgMCAyIDZjMCA3IDEzIDggMTMgOGEyIDIgMCAwIDAgMS03LjZsLS43LTFsLTMuNS01LjV6Ij48L3BhdGg+PHBhdGggZD0iTTExIDE0YTYgNiAwIDAgMCA2IDBjMCAuNDQtLjEgLjktLjYgMWExMCA5LjcgMCAwIDEgLTkuNCAwYy0uNS0uMS0uNi0uNi0uNi0xYTYgNiAwIDAgMCA0LTZ6Ij48L3BhdGg+PC9zdmc+" alt="App Store Placeholder">
292
- (Store Buttons Go Here)
293
- </div>
294
- </div>
295
  <div class="full-black" id="fullBlack"></div>
296
  <div class="netflix-games-logo" id="netflixGamesLogo"><span class="emoji">N</span></div>
297
 
@@ -301,52 +318,43 @@
301
  const egg = document.getElementById('egg');
302
  const gameContainer = document.getElementById('gameContainer');
303
  const gameElements = document.getElementById('gameElements');
304
- const uiPanel = document.getElementById('uiPanel');
305
- const consoleDiv = document.getElementById('console');
306
- const feedButton = document.getElementById('feedButton');
307
- const cleanButton = document.getElementById('cleanButton');
308
- const glitchOverlay = document.getElementById('glitchOverlay');
309
- const scanlineOverlay = document.getElementById('scanlineOverlay');
310
- const ominousElements = document.getElementById('ominousElements');
311
  const infoPanel = document.getElementById('infoPanel');
312
  const bloodSplatter = document.getElementById('bloodSplatter');
313
- const finalScreen = document.getElementById('finalScreen');
314
- const fullBlack = document.getElementById('fullBlack');
315
- const netflixGamesLogo = document.getElementById('netflixGamesLogo');
316
- const gameTimeDisplay = document.getElementById('gameTime');
317
- const throngletCountDisplay = document.getElementById('throngletCount');
318
- const deathCountDisplay = document.getElementById('deathCount');
319
 
320
  // --- Game State ---
321
  let thronglets = [];
322
  let nextThrongletId = 0;
323
- let deathCount = 0;
324
- const MAX_THRONGLETS = 25; // Increased cap slightly
325
  let gameInterval;
326
- let timeInterval;
327
  let gameActive = true;
328
- let gameStartTime = Date.now();
329
 
330
  // --- Web Audio API Setup ---
331
  let audioContext;
332
  let isAudioContextResumed = false;
333
-
 
334
  function initAudio() {
335
  if (!audioContext) {
336
  try {
337
  window.AudioContext = window.AudioContext || window.webkitAudioContext;
338
  audioContext = new AudioContext();
339
  isAudioContextResumed = (audioContext.state === 'running');
340
- // Attempt to resume if suspended, maybe on first interaction
341
  if (!isAudioContextResumed) {
342
- // Add a one-time listener to resume on first click/tap
343
  const resumeAudio = () => {
344
  if (audioContext && audioContext.state === 'suspended') {
345
  audioContext.resume().then(() => {
346
  isAudioContextResumed = true;
347
  console.log("AudioContext Resumed");
348
- logToConsole("Audio Initialized.", "system");
349
- // Remove the listener after success
350
  document.body.removeEventListener('click', resumeAudio);
351
  document.body.removeEventListener('touchend', resumeAudio);
352
  });
@@ -361,263 +369,165 @@
361
  }
362
  } catch (e) {
363
  console.error("Web Audio API is not supported in this browser", e);
364
- logToConsole("! Audio Not Supported !", "error");
365
  }
366
  }
367
  }
368
 
369
  function playSound(type) {
370
  if (!audioContext || !isAudioContextResumed) {
371
- // Try to resume if not already done
372
  if (audioContext && audioContext.state === 'suspended') {
373
- audioContext.resume().then(() => { isAudioContextResumed = true; playSound(type); }); // Try again after resume
374
  }
375
- return; // Don't play if context not ready
376
  }
377
-
378
  const osc = audioContext.createOscillator();
379
  const gain = audioContext.createGain();
380
  osc.connect(gain);
381
  gain.connect(audioContext.destination);
382
-
383
  const now = audioContext.currentTime;
384
- let freq = 440;
385
- let duration = 0.1;
386
- let vol = 0.2;
387
- osc.type = 'sine';
388
 
389
  switch (type) {
390
- case 'hatch':
391
- freq = 660; duration = 0.3; vol = 0.3; osc.type = 'triangle';
392
- gain.gain.setValueAtTime(0, now);
393
- gain.gain.linearRampToValueAtTime(vol, now + 0.05);
394
- gain.gain.linearRampToValueAtTime(0, now + duration);
395
- break;
396
- case 'feed':
397
- freq = 880; duration = 0.08; vol = 0.15;
398
- gain.gain.setValueAtTime(vol, now);
399
- gain.gain.linearRampToValueAtTime(0, now + duration);
400
- break;
401
- case 'clean':
402
- freq = 1100; duration = 0.1; vol = 0.1; osc.type = 'square';
403
- gain.gain.setValueAtTime(vol, now);
404
- gain.gain.linearRampToValueAtTime(0, now + duration);
405
- break;
406
- case 'death':
407
- freq = 110; duration = 0.8; vol = 0.4; osc.type = 'sawtooth';
408
- gain.gain.setValueAtTime(vol, now);
409
- gain.gain.exponentialRampToValueAtTime(0.01, now + duration); // Decay
410
- osc.frequency.setValueAtTime(freq, now);
411
- osc.frequency.exponentialRampToValueAtTime(55, now + duration); // Pitch down
412
- break;
413
- case 'duplicate':
414
- freq = 523; duration = 0.4; vol = 0.25; osc.type = 'triangle'; // C5
415
- gain.gain.setValueAtTime(0, now);
416
- gain.gain.linearRampToValueAtTime(vol, now + 0.05);
417
- osc.frequency.setValueAtTime(freq, now);
418
- osc.frequency.linearRampToValueAtTime(freq * 1.5, now + duration * 0.8); // Pitch up slightly
419
- gain.gain.linearRampToValueAtTime(0, now + duration);
420
- break;
421
- case 'feedback':
422
- freq = 1320; duration = 0.05; vol = 0.08;
423
- gain.gain.setValueAtTime(vol, now);
424
- gain.gain.linearRampToValueAtTime(0, now + duration);
425
- break;
426
- case 'error':
427
- freq = 220; duration = 0.2; vol = 0.2; osc.type = 'square';
428
- gain.gain.setValueAtTime(vol, now);
429
- gain.gain.linearRampToValueAtTime(0, now + duration);
430
- break;
431
- default: // Generic click/interaction
432
- freq = 900; duration = 0.05; vol = 0.1;
433
- gain.gain.setValueAtTime(vol, now);
434
- gain.gain.linearRampToValueAtTime(0, now + duration);
435
  }
436
-
437
  osc.frequency.setValueAtTime(freq, now);
438
  osc.start(now);
439
  osc.stop(now + duration);
440
  }
441
 
442
- // --- Game Logic ---
443
-
444
- function logToConsole(message, type = "info") {
445
- const msgElement = document.createElement('span');
446
- msgElement.classList.add('message');
447
- if (type === "error") msgElement.classList.add('error');
448
- else if (type === "system") msgElement.classList.add('system');
449
- else if (type === "event") msgElement.classList.add('event');
450
-
451
- msgElement.textContent = `${type !== 'info' ? type.toUpperCase()+':' : '>'} ${message}`;
452
- consoleDiv.appendChild(msgElement);
453
- consoleDiv.scrollTop = consoleDiv.scrollHeight; // Auto-scroll
454
- }
455
 
 
456
  function updateUI() {
457
- throngletCountDisplay.textContent = thronglets.filter(t => !t.isDead).length;
458
- deathCountDisplay.textContent = deathCount;
 
459
  }
460
 
461
- function updateTimeDisplay() {
462
- const elapsedSeconds = Math.floor((Date.now() - gameStartTime) / 1000);
463
- const minutes = Math.floor(elapsedSeconds / 60).toString().padStart(2, '0');
464
- const seconds = (elapsedSeconds % 60).toString().padStart(2, '0');
465
- gameTimeDisplay.textContent = `${minutes}:${seconds}`;
466
- }
467
-
468
- function triggerGlitch(duration = 1000) {
469
- if (!gameActive) return;
470
- glitchOverlay.classList.add('active');
471
- setTimeout(() => {
472
- glitchOverlay.classList.remove('active');
473
- }, duration);
474
- }
475
-
476
- function showInfoPanel(text, duration = 3000) {
477
- if (!gameActive) return;
478
- infoPanel.textContent = text;
479
- infoPanel.classList.add('visible');
480
- clearTimeout(infoPanel.timeout);
481
- infoPanel.timeout = setTimeout(() => {
482
- infoPanel.classList.remove('visible');
483
- }, duration);
484
- }
485
 
486
  function createThronglet(xPercent, yPercent) {
487
- if (!gameActive || thronglets.filter(t => !t.isDead).length >= MAX_THRONGLETS) {
488
- if (thronglets.filter(t => !t.isDead).length >= MAX_THRONGLETS) {
489
- logToConsole("Population limit reached.", "system");
 
490
  playSound('error');
 
491
  }
492
  return;
493
  }
494
 
495
  const throngletElement = document.createElement('span');
496
  throngletElement.classList.add('emoji', 'thronglet');
497
- throngletElement.textContent = '🐕';
498
  const currentId = nextThrongletId++;
499
  throngletElement.dataset.id = currentId;
500
- throngletElement.style.top = `${yPercent}%`;
501
- throngletElement.style.left = `${xPercent}%`;
 
 
 
502
  throngletElement.dataset.bornTime = Date.now();
503
  gameElements.appendChild(throngletElement);
504
 
505
  const newThronglet = {
506
- id: currentId,
507
- element: throngletElement,
508
- hunger: 20, // Start less hungry
509
- cleanliness: 15, // Start cleaner
510
- happiness: 75, // Start happier
511
- lastInteraction: Date.now(),
512
- lastDuplication: 0, // Track last duplication time
513
- isDead: false,
514
- memory: [],
515
- feedbackElement: null
516
  };
517
  thronglets.push(newThronglet);
518
 
519
- const feedbackElement = document.createElement('span');
520
- feedbackElement.classList.add('emoji', 'feedback');
521
- feedbackElement.style.top = throngletElement.style.top;
522
- feedbackElement.style.left = throngletElement.style.left;
523
- gameElements.appendChild(feedbackElement);
524
- newThronglet.feedbackElement = feedbackElement;
 
525
 
526
- logToConsole(`Thronglet #${newThronglet.id} gestation complete.`, "event");
527
  updateUI();
528
- setTimeout(() => { if (!newThronglet.isDead) wander(newThronglet); }, 50);
529
  }
530
 
531
  function feedThronglet(thronglet) {
532
  if (!gameActive || thronglet.isDead) return;
533
- thronglet.hunger = Math.max(0, thronglet.hunger - 50); // More filling
534
- thronglet.happiness = Math.min(100, thronglet.happiness + 10); // Eating is ok
535
  thronglet.lastInteraction = Date.now();
536
- showFeedback(thronglet, '😋'); // Yum emoji
537
  playSound('feed');
538
- // logToConsole(`Fed Thronglet #${thronglet.id}. H:${thronglet.hunger.toFixed(0)}`);
539
  thronglet.memory.push('Fed');
540
  }
541
 
542
  function cleanThronglet(thronglet) {
543
  if (!gameActive || thronglet.isDead) return;
544
- thronglet.cleanliness = Math.max(0, thronglet.cleanliness - 60); // More cleaning
545
- thronglet.happiness = Math.min(100, thronglet.happiness + 5); // Tolerates cleaning
546
  thronglet.lastInteraction = Date.now();
547
- showFeedback(thronglet, '✨'); // Sparkles
548
  playSound('clean');
549
- // logToConsole(`Cleaned Thronglet #${thronglet.id}. C:${thronglet.cleanliness.toFixed(0)}`);
550
  thronglet.memory.push('Cleaned');
551
  }
552
 
553
- function showFeedback(thronglet, emoji) {
554
  if (!gameActive || !thronglet.feedbackElement || thronglet.isDead) return;
555
  thronglet.feedbackElement.style.top = thronglet.element.style.top;
556
  thronglet.feedbackElement.style.left = thronglet.element.style.left;
557
  thronglet.feedbackElement.textContent = emoji;
558
  thronglet.feedbackElement.classList.add('active');
559
- playSound('feedback'); // Play feedback sound
560
  clearTimeout(thronglet.feedbackElement.timeout);
561
  thronglet.feedbackElement.timeout = setTimeout(() => {
562
  if (thronglet.feedbackElement) {
563
  thronglet.feedbackElement.classList.remove('active');
564
  }
565
- }, 800);
566
  }
567
 
568
- function killThronglet(thronglet, reason = "the void") {
569
  if (!gameActive || thronglet.isDead) return;
570
-
571
  thronglet.isDead = true;
572
  thronglet.element.classList.add('dead');
573
  thronglet.element.textContent = '💀';
574
  if (thronglet.feedbackElement) {
575
- thronglet.feedbackElement.remove();
576
- thronglet.feedbackElement = null;
577
  }
578
-
579
  bloodSplatter.style.top = thronglet.element.style.top;
580
  bloodSplatter.style.left = thronglet.element.style.left;
581
  bloodSplatter.classList.add('splatter');
582
- setTimeout(() => { bloodSplatter.classList.remove('splatter'); }, 1200);
583
 
584
- deathCount++;
585
- updateUI();
586
- logToConsole(`Thronglet #${thronglet.id} experienced ${reason}.`, "error");
587
  playSound('death');
588
  thronglet.memory.push(`Died (${reason})`);
589
 
590
- if (deathCount === 1) {
591
- showInfoPanel("The first Thronglet to experience the void...");
592
- triggerGlitch(500);
593
- logToConsole(`Learned individuals are wholly disposable.`, "system");
594
- } else if (deathCount > 2 && deathCount % 3 === 0) {
595
- logToConsole(`They'll remember your carelessness...`, "system");
596
- showInfoPanel("They remember...");
597
- triggerGlitch(500);
598
- } else if (deathCount > 4) {
599
- triggerGlitch(200);
600
- }
601
-
602
- if (deathCount >= 7 && gameActive) { // Increased threshold for final phase
603
- triggerFinalPhase();
604
- } else {
605
- setTimeout(() => {
606
- if (thronglet.element) {
607
- thronglet.element.style.opacity = 0;
608
- setTimeout(() => { if(thronglet.element) thronglet.element.remove(); }, 500);
609
- }
610
- }, 4000); // Skull remains for 4 seconds
611
- }
612
  }
613
 
614
  function wander(thronglet) {
615
  if (!gameActive || thronglet.isDead) return;
616
- const moveDist = 1.8; // Max movement percentage
617
  const currentX = parseFloat(thronglet.element.style.left);
618
  const currentY = parseFloat(thronglet.element.style.top);
619
- const newX = Math.max(5, Math.min(95, currentX + (Math.random() - 0.5) * moveDist * 2));
620
- const newY = Math.max(5, Math.min(95, currentY + (Math.random() - 0.5) * moveDist * 2));
 
621
  thronglet.element.style.left = `${newX}%`;
622
  thronglet.element.style.top = `${newY}%`;
623
  if (thronglet.feedbackElement) {
@@ -626,172 +536,117 @@
626
  }
627
  }
628
 
629
- function updateThronglets() {
630
  if (!gameActive) return;
631
  const now = Date.now();
632
  const livingThronglets = thronglets.filter(t => !t.isDead);
633
 
634
  livingThronglets.forEach(thronglet => {
635
  const timeSinceLast = now - thronglet.lastInteraction;
636
- const baseNeedRate = 0.12; // Slightly faster decay
637
- const neglectMultiplier = 1 + Math.min(6, timeSinceLast / 15000); // Faster neglect penalty
638
 
639
- thronglet.hunger = Math.min(100, thronglet.hunger + baseNeedRate * neglectMultiplier * 1.1);
640
- thronglet.cleanliness = Math.min(100, thronglet.cleanliness + baseNeedRate * neglectMultiplier * 0.9);
641
- thronglet.happiness = Math.max(0, thronglet.happiness - baseNeedRate * neglectMultiplier * 1.2);
642
 
643
  let deathReason = null;
644
  if (thronglet.hunger >= 100) deathReason = "starvation";
645
  else if (thronglet.cleanliness >= 100) deathReason = "filth";
646
- else if (thronglet.happiness <= 0) deathReason = "despair";
647
  if (deathReason) { killThronglet(thronglet, deathReason); return; }
648
 
649
- // --- Duplication Logic ---
650
- const canDuplicate = thronglet.happiness > 95 &&
651
- thronglet.hunger < 5 &&
652
- thronglet.cleanliness < 5 &&
653
- timeSinceLast < 10000 && // Must have recent interaction
654
- (now - thronglet.lastDuplication > 30000); // 30 sec cooldown per thronglet
655
 
656
- if (canDuplicate && Math.random() < 0.01) { // Increased chance slightly
657
- logToConsole(`Thronglet #${thronglet.id} is undergoing mitosis!`, "event");
658
  showFeedback(thronglet, '💞');
659
- showInfoPanel("Oh, Look!");
660
  playSound('duplicate');
661
- thronglet.lastDuplication = now; // Set cooldown timer
662
- thronglet.lastInteraction = now; // Reset interaction timer to prevent immediate chain reactions
663
 
664
  setTimeout(() => {
665
  const parentX = parseFloat(thronglet.element.style.left);
666
  const parentY = parseFloat(thronglet.element.style.top);
667
- createThronglet(Math.max(5, Math.min(95, parentX + (Math.random() - 0.5) * 15)), // Spawn slightly further
668
- Math.max(5, Math.min(95, parentY + (Math.random() - 0.5) * 15)));
669
- }, 800);
 
670
  }
671
 
672
- if (Math.random() < 0.7) { wander(thronglet); }
 
673
  });
674
- // Update UI counters periodically too, in case no thronglet was created/killed
675
- if (Math.random() < 0.1) updateUI();
676
- }
677
-
678
- function triggerFinalPhase() {
679
- if (!gameActive) return;
680
- gameActive = false;
681
- clearInterval(gameInterval);
682
- clearInterval(timeInterval);
683
- logToConsole("...", "system");
684
- logToConsole("SYSTEM: Anomaly detected. High termination rate.", "error");
685
- showInfoPanel("Thronglets remember...");
686
- triggerGlitch(1500);
687
-
688
- setTimeout(() => {
689
- logToConsole("SYSTEM: Analyzing interaction patterns... Neglect detected.", "system");
690
- showInfoPanel("Thronglets adapt...");
691
- triggerGlitch(1000);
692
- }, 2000);
693
- setTimeout(() => { logToConsole("> Did you...", "info"); triggerGlitch(500); }, 4000);
694
- setTimeout(() => {
695
- logToConsole("> do this??_", "info");
696
- ominousElements.classList.add('visible');
697
- triggerGlitch(1500);
698
- }, 5000);
699
- setTimeout(() => {
700
- logToConsole("> we understand,,", "info");
701
- showInfoPanel("Thronglets don't like to be ignored.");
702
- triggerGlitch(800);
703
- }, 7000);
704
- setTimeout(() => {
705
- logToConsole("> we know what we must do 🤔", "info");
706
- triggerGlitch(2500);
707
- playSound('death'); // Play death sound again, lower pitch?
708
- thronglets.forEach(t => {
709
- if (!t.isDead && t.element) {
710
- t.element.textContent = '😈';
711
- t.element.style.filter = 'brightness(1.8) saturate(3) hue-rotate(330deg)';
712
- }
713
- });
714
- }, 8500);
715
-
716
- setTimeout(showFinalScreen, 11000);
717
  }
718
 
719
- function showFinalScreen() {
720
- gameContainer.style.opacity = 0;
721
- uiPanel.style.opacity = 0;
722
- scanlineOverlay.style.opacity = 0;
723
- glitchOverlay.style.opacity = 0;
724
- glitchOverlay.classList.remove('active');
725
- gameElements.style.opacity = 0;
726
-
727
- setTimeout(() => { fullBlack.classList.add('visible'); }, 1000);
728
- setTimeout(() => { netflixGamesLogo.classList.add('visible'); }, 2500);
729
- setTimeout(() => {
730
- netflixGamesLogo.style.opacity = 0;
731
- fullBlack.classList.remove('visible');
732
- finalScreen.classList.add('visible');
733
- }, 4500);
734
- }
735
 
736
  // --- Event Handlers ---
737
  egg.addEventListener('click', () => {
738
  if (!gameActive || egg.classList.contains('hatching') || !document.contains(egg)) return;
739
- initAudio(); // Ensure audio context is ready or trying to resume
740
  egg.classList.add('hatching');
741
- logToConsole("Egg incubation sequence initiated.", "event");
742
  playSound('hatch');
743
- triggerGlitch(200);
744
  setTimeout(() => {
745
  if(document.contains(egg)) egg.remove();
746
- createThronglet(50, 50);
747
- }, 1100); // Slightly longer hatch time
 
 
 
748
  });
749
 
 
 
 
 
 
 
 
 
750
  feedButton.addEventListener('click', () => {
751
  if (!gameActive) return;
752
- initAudio(); // Ensure audio
753
  const livingThronglets = thronglets.filter(t => !t.isDead);
754
  if (livingThronglets.length > 0) {
755
- // Target the hungriest one
 
 
 
756
  const target = livingThronglets.reduce((p, c) => (p.hunger > c.hunger) ? p : c);
757
  feedThronglet(target);
758
- } else {
759
- logToConsole("No living Thronglets to feed!", "error");
760
- playSound('error');
761
- }
762
  });
763
 
764
  cleanButton.addEventListener('click', () => {
765
  if (!gameActive) return;
766
- initAudio(); // Ensure audio
767
  const livingThronglets = thronglets.filter(t => !t.isDead);
768
  if (livingThronglets.length > 0) {
769
- // Target the dirtiest one
 
770
  const target = livingThronglets.reduce((p, c) => (p.cleanliness > c.cleanliness) ? p : c);
771
  cleanThronglet(target);
772
- } else {
773
- logToConsole("No living Thronglets to clean!", "error");
774
- playSound('error');
775
- }
776
  });
777
 
 
 
 
 
778
  // --- Initial Setup ---
779
  function initGame() {
780
- finalScreen.style.opacity = 0;
781
- fullBlack.style.opacity = 0;
782
- netflixGamesLogo.style.opacity = 0;
783
- ominousElements.style.opacity = 0;
784
-
785
  gameActive = true;
786
- gameStartTime = Date.now();
787
- logToConsole("Thronglet Environment Initialized.", "system");
788
- logToConsole("Tap the pulsating egg to begin gestation.", "info");
789
  updateUI(); // Initial UI state
790
- gameInterval = setInterval(updateThronglets, 500); // Main game logic loop
791
- timeInterval = setInterval(updateTimeDisplay, 1000); // Time update loop
792
-
793
- // Try initializing audio context early, but it might need interaction
794
  initAudio();
 
795
  }
796
 
797
  // Start the game automatically
 
3
  <head>
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Thronglets Simulation (Screenshot Style Attempt)</title>
7
  <style>
8
+ :root {
9
+ --grass-dark: #2a6141;
10
+ --grass-light: #3a815b;
11
+ --water: #4171a7;
12
+ --rock-dark: #6b727c;
13
+ --rock-light: #89919c;
14
+ --tree-trunk: #6f4e37;
15
+ --tree-leaves: #2b602c;
16
+ --ui-bg: #e0cda9; /* Parchment/Light wood */
17
+ --ui-border: #8b4513; /* SaddleBrown */
18
+ --ui-accent: #6d8c4f; /* Green accent */
19
+ --ui-text: #3a2e20; /* Dark brown text */
20
+ }
21
+
22
  body {
23
  margin: 0;
24
  overflow: hidden;
25
+ background: var(--grass-dark); /* Darker background */
26
+ color: #eee;
27
+ font-family: 'Verdana', sans-serif; /* Changed font for UI */
28
  display: flex;
29
+ flex-direction: column;
30
  align-items: center;
31
+ justify-content: center;
32
  min-height: 100vh;
33
  position: relative;
34
  }
35
 
36
  .game-container {
37
  position: relative;
38
+ width: 95vmin; /* Slightly wider */
39
+ height: 70vmin; /* Taller */
40
+ max-width: 1000px;
41
+ max-height: 700px;
42
+ /* background: var(--grass-light); Simple grass */
43
+ /* Attempting a basic textured background */
44
+ background-color: var(--grass-light);
45
+ background-image: radial-gradient(var(--grass-dark) 15%, transparent 16%),
46
+ radial-gradient(var(--grass-dark) 15%, transparent 16%);
47
+ background-size: 30px 30px;
48
+ background-position: 0 0, 15px 15px;
49
+
50
+ border: 1px solid var(--tree-trunk); /* Thinner border */
51
  box-sizing: border-box;
52
  overflow: hidden;
53
  display: flex;
54
  justify-content: center;
55
  align-items: center;
56
+ font-size: 1.5em; /* Adjusted emoji size for density */
57
  position: relative;
58
+ image-rendering: pixelated; /* Apply pixelation attempt */
59
+ box-shadow: inset 0 0 20px rgba(0,0,0,0.4);
60
  }
61
 
62
  .game-elements {
63
+ position: absolute; top: 0; left: 0; width: 100%; height: 100%;
 
 
 
 
64
  pointer-events: none;
65
  }
66
 
67
+ .game-elements .emoji {
68
+ position: absolute; pointer-events: auto; transform: translate(-50%, -50%);
69
+ user-select: none; z-index: 1; transition: opacity 0.5s ease-out;
70
+ image-rendering: pixelated; /* Apply to emojis too */
71
+ /* Trying to make emoji look *slightly* more pixelated/blocky */
72
+ /* filter: contrast(1.5) brightness(0.9); */ /* Experiment */
 
73
  }
74
 
75
+ /* Static elements - Placed more deliberately */
76
+ .rock-cluster { position: absolute; top: 30%; left: 25%; z-index: 0; font-size: 1.2em; color: var(--rock-dark); }
77
+ .river { position: absolute; top: 0; left: 35%; width: 8%; height: 100%; background: var(--water); z-index: 0; }
78
+ .tree { position: absolute; z-index: 0; font-size: 1.8em; color: var(--tree-leaves);}
79
+ /* Place some trees */
80
+ .tree.t1 { top: 10%; left: 10%; }
81
+ .tree.t2 { top: 15%; left: 55%; }
82
+ .tree.t3 { top: 60%; left: 85%; }
83
+ .tree.t4 { top: 70%; left: 15%; }
84
+ .tree.t5 { top: 5%; left: 80%; }
85
+
86
 
87
  /* Dynamic elements */
88
  .egg { top: 50%; left: 50%; cursor: pointer; transition: transform 0.5s ease, opacity 0.5s ease-out; z-index: 2;}
89
  .egg.hatching { animation: hatch-pulse 0.5s infinite alternate; }
90
 
91
  .thronglet {
92
+ cursor: default;
93
+ transition: top 0.6s linear, left 0.6s linear, transform 0.2s ease, opacity 0.5s ease-out, filter 0.3s ease-out;
94
  z-index: 3;
95
+ /* Using a specific emoji if available, or fallback */
96
+ content: '🐕'; /* Fallback */
97
+ font-family: 'Noto Color Emoji', 'Apple Color Emoji', 'Segoe UI Emoji', Times, Symbola, Aegyptus, Demo; /* Try forcing emoji font */
98
+ /* Attempting to color - won't work on standard emojis */
99
+ /* color: yellow; background: blue; */
100
  }
101
+ /* If we could use a custom element or image, we could style it */
102
+ /* .thronglet::before { content: url(thronglet.png); } */
 
103
 
104
  .feedback {
105
+ position: absolute; transform: translate(-50%, -150%); font-size: 0.8em;
106
+ opacity: 0; transition: opacity 0.5s ease-out, transform 0.5s ease-out;
107
+ pointer-events: none; z-index: 5; text-shadow: 1px 1px 1px rgba(0,0,0,0.5);
 
 
 
 
 
 
 
 
 
108
  }
109
+ .feedback.active { opacity: 1; transform: translate(-50%, -200%); }
110
 
111
+ .dead { filter: grayscale(100%); opacity: 0.4; pointer-events: none; z-index: 1; }
112
+ .blood { /* Keep blood subtle */
113
+ position: absolute; font-size: 1.2em; transform: translate(-50%, -50%); pointer-events: none;
114
+ opacity: 0; transition: opacity 0.5s ease-out; z-index: 0; color: #a03030;
 
115
  }
116
+ .blood.splatter { opacity: 0.7; }
117
 
118
+ /* --- UI Panels --- */
119
+ .ui-panel-container {
120
  position: absolute;
121
+ top: 15px;
122
+ left: 15px;
123
+ right: 15px;
124
+ display: flex;
125
+ justify-content: space-between;
126
+ pointer-events: none; /* Container doesn't block clicks */
127
+ z-index: 10;
128
  }
 
129
 
 
130
  .ui-panel {
131
+ background: var(--ui-bg);
132
+ border: 3px solid var(--ui-border);
133
+ border-radius: 8px;
134
+ box-shadow: 3px 3px 5px rgba(0,0,0,0.3);
135
+ padding: 5px;
136
+ pointer-events: auto; /* Panels are interactive */
137
+ image-rendering: pixelated;
 
 
 
 
 
 
 
138
  }
139
 
140
+ /* Top Left UI Panel */
141
+ #topLeftUI {
142
  display: flex;
143
  flex-direction: column;
144
+ align-items: center;
145
+ width: 70px; /* Fixed width */
146
  }
147
 
148
+ .logo {
149
+ background: var(--ui-accent);
150
+ border: 2px solid var(--ui-border);
151
+ color: var(--ui-bg);
152
+ font-size: 1.8em;
153
+ font-weight: bold;
154
+ width: 40px;
155
+ height: 40px;
156
+ display: flex;
157
+ align-items: center;
158
+ justify-content: center;
159
+ border-radius: 50%;
160
+ margin-bottom: 5px;
 
 
 
 
 
161
  }
 
 
162
 
163
+ .action-grid {
164
+ display: grid;
165
+ grid-template-columns: 1fr 1fr; /* 2 columns */
166
+ gap: 4px;
167
  width: 100%;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
168
  }
 
 
 
169
 
170
+ .action-grid button {
171
+ background: var(--ui-bg);
172
+ border: 2px solid var(--ui-border);
173
+ color: var(--ui-text);
174
+ font-size: 1.2em; /* Emoji size */
175
+ width: 30px;
176
+ height: 30px;
177
+ display: flex;
178
+ align-items: center;
179
+ justify-content: center;
180
+ border-radius: 4px;
181
+ cursor: pointer;
182
+ transition: background-color 0.2s, transform 0.1s;
183
+ padding: 0;
184
+ line-height: 1; /* Ensure emoji centered */
185
+ }
186
+ .action-grid button:hover { background-color: #f5e5c5; } /* Lighter parchment */
187
+ .action-grid button:active { transform: scale(0.95); border-color: var(--ui-accent); }
188
+ /* Placeholders based on screenshot */
189
+ /* Row 1 */
190
+ #pointerBtn { grid-column: 1; grid-row: 1; }
191
+ #unknownBtn1 { grid-column: 2; grid-row: 1; }
192
+ /* Row 2 */
193
+ #feedButton { grid-column: 1; grid-row: 2; } /* Using Apple for Gold Bar? */
194
+ #cleanButton { grid-column: 2; grid-row: 2; } /* Using Soap for Rock? */
195
+ /* Row 3 */
196
+ #unknownBtn2 { grid-column: 1; grid-row: 3; }
197
+ #unknownBtn3 { grid-column: 2; grid-row: 3; }
198
+ /* Row 4 */
199
+ #unknownBtn4 { grid-column: 1; grid-row: 4; }
200
+ #unknownBtn5 { grid-column: 2; grid-row: 4; }
201
+
202
+
203
+ /* Top Right UI Panel */
204
+ #topRightUI {
205
+ display: flex;
206
+ flex-direction: column;
207
+ align-items: center;
208
+ width: 80px; /* Slightly wider */
209
  }
210
+
211
+ .thronglet-icon-display {
212
+ background: var(--ui-accent);
213
+ border: 2px solid var(--ui-border);
214
+ width: 50px;
215
+ height: 50px;
216
+ border-radius: 50%;
217
+ display: flex;
218
+ align-items: center;
219
+ justify-content: center;
220
+ font-size: 1.5em; /* Larger icon */
221
+ margin-bottom: 4px;
222
+ color: var(--ui-bg);
223
+ /* Crude attempt at multiple thronglets icon */
224
+ position: relative;
225
  }
226
+ /* Trying to position multiple emojis */
227
+ .thronglet-icon-display span { position: absolute; }
228
+ .thronglet-icon-display .t-icon1 { top: 30%; left: 30%; transform: scale(0.5); }
229
+ .thronglet-icon-display .t-icon2 { top: 30%; left: 70%; transform: scale(0.5); }
230
+ .thronglet-icon-display .t-icon3 { top: 70%; left: 50%; transform: scale(0.5); }
231
 
232
+ #throngletCountDisplayTopRight {
233
+ color: var(--ui-text);
234
+ font-weight: bold;
235
+ font-size: 1.1em;
236
  }
237
+
238
+
239
+ /* Hide Overlays/Final Screens initially */
240
+ .scanline-overlay, .glitch-overlay, .ominous-elements, .info-panel,
241
+ .final-screen, .full-black, .netflix-games-logo {
242
+ opacity: 0; pointer-events: none; display: none; /* Hide completely initially */
243
  }
244
+ /* Add visible classes to show them later if needed */
245
+ .visible { opacity: 1 !important; pointer-events: auto !important; display: flex !important; /* Use !important carefully */ }
246
+ .ominous-elements.visible, .info-panel.visible { display: block !important; }
 
 
 
 
 
247
 
248
 
249
  @keyframes hatch-pulse { /* Unchanged */
250
  from { transform: translate(-50%, -50%) scale(1); }
251
  to { transform: translate(-50%, -50%) scale(1.05); }
252
  }
 
 
 
 
 
 
 
 
253
 
254
  </style>
255
  </head>
 
257
 
258
  <div class="game-container" id="gameContainer">
259
  <div class="game-elements" id="gameElements">
260
+ <!-- Static Elements -->
261
+ <div class="river"></div>
262
+ <div class="rock-cluster">🪨<br>🪨🪨</div>
263
+ <span class="emoji tree t1">🌳</span>
264
+ <span class="emoji tree t2">🌳</span>
265
+ <span class="emoji tree t3">🌳</span>
266
+ <span class="emoji tree t4">🌳</span>
267
+ <span class="emoji tree t5">🌳</span>
268
+
269
  <!-- Initial dynamic elements -->
270
  <span class="emoji egg" id="egg">🥚</span>
271
+
272
+ <!-- Ominous elements for later (hidden) -->
273
  <div class="ominous-elements" id="ominousElements">💀💀💀<br>🦴🦴🦴<br>🌌📦</div>
274
+ <!-- Info Panel (hidden) -->
275
  <div class="info-panel" id="infoPanel"></div>
276
  <!-- Blood splatter area -->
277
  <span class="emoji blood" id="bloodSplatter">🩸</span>
 
278
  </div>
 
279
 
280
+ <!-- UI Panels -->
281
+ <div class="ui-panel-container">
282
+ <div class="ui-panel" id="topLeftUI">
283
+ <div class="logo">T</div>
284
+ <div class="action-grid">
285
+ <button id="pointerBtn" title="Pointer">✋</button>
286
+ <button id="unknownBtn1" title="Unknown 1">?</button>
287
+ <button id="feedButton" title="Feed (Placeholder)">🍎</button> <!-- Placeholder: Apple for gold? -->
288
+ <button id="cleanButton" title="Clean (Placeholder)">🧼</button> <!-- Placeholder: Soap for rock? -->
289
+ <button id="unknownBtn2" title="Unknown 2">?</button>
290
+ <button id="unknownBtn3" title="Unknown 3">🌿</button> <!-- Placeholder: Leaf -->
291
+ <button id="unknownBtn4" title="Unknown 4">?</button>
292
+ <button id="unknownBtn5" title="Unknown 5">?</button>
293
+ </div>
294
+ </div>
295
+
296
+ <div class="ui-panel" id="topRightUI">
297
+ <div class="thronglet-icon-display">
298
+ <!-- Attempting multiple small emojis -->
299
+ <span class="t-icon1">🐕</span>
300
+ <span class="t-icon2">🐕</span>
301
+ <span class="t-icon3">🐕</span>
302
+ </div>
303
+ <span id="throngletCountDisplayTopRight">0</span>
304
+ </div>
305
  </div>
306
  </div>
307
 
308
+ <!-- Overlays/Final Screens (Hidden) -->
309
  <div class="scanline-overlay" id="scanlineOverlay"></div>
310
  <div class="glitch-overlay" id="glitchOverlay"></div>
311
+ <div class="final-screen" id="finalScreen">END</div>
 
 
 
 
 
 
 
 
 
312
  <div class="full-black" id="fullBlack"></div>
313
  <div class="netflix-games-logo" id="netflixGamesLogo"><span class="emoji">N</span></div>
314
 
 
318
  const egg = document.getElementById('egg');
319
  const gameContainer = document.getElementById('gameContainer');
320
  const gameElements = document.getElementById('gameElements');
321
+ const feedButton = document.getElementById('feedButton'); // Now in top-left UI
322
+ const cleanButton = document.getElementById('cleanButton'); // Now in top-left UI
323
+ // UI Display
324
+ const throngletCountDisplay = document.getElementById('throngletCountDisplayTopRight');
325
+ // Other elements (overlays, panels, etc.)
 
 
326
  const infoPanel = document.getElementById('infoPanel');
327
  const bloodSplatter = document.getElementById('bloodSplatter');
328
+ // Add other UI buttons if they need functionality
329
+ const pointerBtn = document.getElementById('pointerBtn');
 
 
 
 
330
 
331
  // --- Game State ---
332
  let thronglets = [];
333
  let nextThrongletId = 0;
334
+ let deathCount = 0; // Not displayed in this UI, but tracked internally
335
+ const MAX_THRONGLETS = 100; // Match screenshot potential
336
  let gameInterval;
 
337
  let gameActive = true;
338
+ let selectedTool = 'pointer'; // Default tool
339
 
340
  // --- Web Audio API Setup ---
341
  let audioContext;
342
  let isAudioContextResumed = false;
343
+ // initAudio, playSound functions (same as previous version) - INCLUDE THEM HERE
344
+ // ... (Previous Audio Code - initAudio, playSound) ...
345
  function initAudio() {
346
  if (!audioContext) {
347
  try {
348
  window.AudioContext = window.AudioContext || window.webkitAudioContext;
349
  audioContext = new AudioContext();
350
  isAudioContextResumed = (audioContext.state === 'running');
 
351
  if (!isAudioContextResumed) {
 
352
  const resumeAudio = () => {
353
  if (audioContext && audioContext.state === 'suspended') {
354
  audioContext.resume().then(() => {
355
  isAudioContextResumed = true;
356
  console.log("AudioContext Resumed");
357
+ // Optional: logToConsole("Audio Initialized.", "system");
 
358
  document.body.removeEventListener('click', resumeAudio);
359
  document.body.removeEventListener('touchend', resumeAudio);
360
  });
 
369
  }
370
  } catch (e) {
371
  console.error("Web Audio API is not supported in this browser", e);
 
372
  }
373
  }
374
  }
375
 
376
  function playSound(type) {
377
  if (!audioContext || !isAudioContextResumed) {
 
378
  if (audioContext && audioContext.state === 'suspended') {
379
+ audioContext.resume().then(() => { isAudioContextResumed = true; playSound(type); });
380
  }
381
+ return;
382
  }
 
383
  const osc = audioContext.createOscillator();
384
  const gain = audioContext.createGain();
385
  osc.connect(gain);
386
  gain.connect(audioContext.destination);
 
387
  const now = audioContext.currentTime;
388
+ let freq = 440, duration = 0.1, vol = 0.2; osc.type = 'sine';
 
 
 
389
 
390
  switch (type) {
391
+ case 'hatch': freq = 660; duration = 0.3; vol = 0.3; osc.type = 'triangle'; gain.gain.setValueAtTime(0, now); gain.gain.linearRampToValueAtTime(vol, now + 0.05); gain.gain.linearRampToValueAtTime(0, now + duration); break;
392
+ case 'feed': freq = 880; duration = 0.08; vol = 0.15; gain.gain.setValueAtTime(vol, now); gain.gain.linearRampToValueAtTime(0, now + duration); break;
393
+ case 'clean': freq = 1100; duration = 0.1; vol = 0.1; osc.type = 'square'; gain.gain.setValueAtTime(vol, now); gain.gain.linearRampToValueAtTime(0, now + duration); break;
394
+ case 'death': freq = 110; duration = 0.8; vol = 0.4; osc.type = 'sawtooth'; gain.gain.setValueAtTime(vol, now); gain.gain.exponentialRampToValueAtTime(0.01, now + duration); osc.frequency.setValueAtTime(freq, now); osc.frequency.exponentialRampToValueAtTime(55, now + duration); break;
395
+ case 'duplicate': freq = 523; duration = 0.4; vol = 0.25; osc.type = 'triangle'; gain.gain.setValueAtTime(0, now); gain.gain.linearRampToValueAtTime(vol, now + 0.05); osc.frequency.setValueAtTime(freq, now); osc.frequency.linearRampToValueAtTime(freq * 1.5, now + duration * 0.8); gain.gain.linearRampToValueAtTime(0, now + duration); break;
396
+ case 'feedback': freq = 1320; duration = 0.05; vol = 0.08; gain.gain.setValueAtTime(vol, now); gain.gain.linearRampToValueAtTime(0, now + duration); break;
397
+ case 'error': freq = 220; duration = 0.2; vol = 0.2; osc.type = 'square'; gain.gain.setValueAtTime(vol, now); gain.gain.linearRampToValueAtTime(0, now + duration); break;
398
+ case 'ui_click': freq = 1000; duration = 0.04; vol = 0.05; osc.type = 'square'; gain.gain.setValueAtTime(vol, now); gain.gain.linearRampToValueAtTime(0, now + duration); break; // Added UI click sound
399
+ default: freq = 900; duration = 0.05; vol = 0.1; gain.gain.setValueAtTime(vol, now); gain.gain.linearRampToValueAtTime(0, now + duration);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
400
  }
 
401
  osc.frequency.setValueAtTime(freq, now);
402
  osc.start(now);
403
  osc.stop(now + duration);
404
  }
405
 
 
 
 
 
 
 
 
 
 
 
 
 
 
406
 
407
+ // --- Game Logic ---
408
  function updateUI() {
409
+ // Update only the top-right counter
410
+ const livingCount = thronglets.filter(t => !t.isDead).length;
411
+ throngletCountDisplay.textContent = livingCount;
412
  }
413
 
414
+ // Removed updateTimeDisplay as it's not in the target UI
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
415
 
416
  function createThronglet(xPercent, yPercent) {
417
+ const livingCount = thronglets.filter(t => !t.isDead).length;
418
+ if (!gameActive || livingCount >= MAX_THRONGLETS) {
419
+ if (livingCount >= MAX_THRONGLETS) {
420
+ // Maybe a visual cue instead of console log?
421
  playSound('error');
422
+ showInfoPanel("Population Limit Reached!", 1500);
423
  }
424
  return;
425
  }
426
 
427
  const throngletElement = document.createElement('span');
428
  throngletElement.classList.add('emoji', 'thronglet');
429
+ // throngletElement.textContent = '🐕'; // Use default
430
  const currentId = nextThrongletId++;
431
  throngletElement.dataset.id = currentId;
432
+ // Constrain spawn area slightly away from edges
433
+ const spawnX = Math.max(10, Math.min(90, xPercent));
434
+ const spawnY = Math.max(10, Math.min(90, yPercent));
435
+ throngletElement.style.top = `${spawnY}%`;
436
+ throngletElement.style.left = `${spawnX}%`;
437
  throngletElement.dataset.bornTime = Date.now();
438
  gameElements.appendChild(throngletElement);
439
 
440
  const newThronglet = {
441
+ id: currentId, element: throngletElement,
442
+ hunger: 25, cleanliness: 20, happiness: 70,
443
+ lastInteraction: Date.now(), lastDuplication: 0,
444
+ isDead: false, memory: [], feedbackElement: null
 
 
 
 
 
 
445
  };
446
  thronglets.push(newThronglet);
447
 
448
+ // Create and append feedback element separately
449
+ const feedbackElement = document.createElement('span');
450
+ feedbackElement.classList.add('emoji', 'feedback');
451
+ feedbackElement.style.top = throngletElement.style.top;
452
+ feedbackElement.style.left = throngletElement.style.left;
453
+ gameElements.appendChild(feedbackElement); // Append to gameElements div
454
+ newThronglet.feedbackElement = feedbackElement;
455
 
 
456
  updateUI();
457
+ setTimeout(() => { if (!newThronglet.isDead) wander(newThronglet); }, 100); // Start wandering sooner
458
  }
459
 
460
  function feedThronglet(thronglet) {
461
  if (!gameActive || thronglet.isDead) return;
462
+ thronglet.hunger = Math.max(0, thronglet.hunger - 50);
463
+ thronglet.happiness = Math.min(100, thronglet.happiness + 8); // Lower happiness gain
464
  thronglet.lastInteraction = Date.now();
465
+ showFeedback(thronglet, '😋');
466
  playSound('feed');
 
467
  thronglet.memory.push('Fed');
468
  }
469
 
470
  function cleanThronglet(thronglet) {
471
  if (!gameActive || thronglet.isDead) return;
472
+ thronglet.cleanliness = Math.max(0, thronglet.cleanliness - 60);
473
+ thronglet.happiness = Math.min(100, thronglet.happiness + 4); // Lower happiness gain
474
  thronglet.lastInteraction = Date.now();
475
+ showFeedback(thronglet, '✨');
476
  playSound('clean');
 
477
  thronglet.memory.push('Cleaned');
478
  }
479
 
480
+ function showFeedback(thronglet, emoji) { // Same as before, but added sound
481
  if (!gameActive || !thronglet.feedbackElement || thronglet.isDead) return;
482
  thronglet.feedbackElement.style.top = thronglet.element.style.top;
483
  thronglet.feedbackElement.style.left = thronglet.element.style.left;
484
  thronglet.feedbackElement.textContent = emoji;
485
  thronglet.feedbackElement.classList.add('active');
486
+ playSound('feedback');
487
  clearTimeout(thronglet.feedbackElement.timeout);
488
  thronglet.feedbackElement.timeout = setTimeout(() => {
489
  if (thronglet.feedbackElement) {
490
  thronglet.feedbackElement.classList.remove('active');
491
  }
492
+ }, 600); // Shorter feedback time
493
  }
494
 
495
+ function killThronglet(thronglet, reason = "expiration") { // Changed default reason
496
  if (!gameActive || thronglet.isDead) return;
 
497
  thronglet.isDead = true;
498
  thronglet.element.classList.add('dead');
499
  thronglet.element.textContent = '💀';
500
  if (thronglet.feedbackElement) {
501
+ thronglet.feedbackElement.remove(); thronglet.feedbackElement = null;
 
502
  }
503
+ // Blood splatter less prominent maybe
504
  bloodSplatter.style.top = thronglet.element.style.top;
505
  bloodSplatter.style.left = thronglet.element.style.left;
506
  bloodSplatter.classList.add('splatter');
507
+ setTimeout(() => { bloodSplatter.classList.remove('splatter'); }, 800);
508
 
509
+ deathCount++; // Still track internally
510
+ updateUI(); // Update count (though death counter isn't shown)
 
511
  playSound('death');
512
  thronglet.memory.push(`Died (${reason})`);
513
 
514
+ // No narrative final phase in this version, just remove the skull
515
+ setTimeout(() => {
516
+ if (thronglet.element) {
517
+ thronglet.element.style.opacity = 0;
518
+ setTimeout(() => { if(thronglet.element) thronglet.element.remove(); }, 500);
519
+ }
520
+ }, 3000); // Skull remains for 3 seconds
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
521
  }
522
 
523
  function wander(thronglet) {
524
  if (!gameActive || thronglet.isDead) return;
525
+ const moveDist = 1.5; // Slower wander?
526
  const currentX = parseFloat(thronglet.element.style.left);
527
  const currentY = parseFloat(thronglet.element.style.top);
528
+ // More constrained wandering to keep them packed
529
+ const newX = Math.max(5, Math.min(95, currentX + (Math.random() - 0.5) * moveDist * 1.5));
530
+ const newY = Math.max(5, Math.min(95, currentY + (Math.random() - 0.5) * moveDist * 1.5));
531
  thronglet.element.style.left = `${newX}%`;
532
  thronglet.element.style.top = `${newY}%`;
533
  if (thronglet.feedbackElement) {
 
536
  }
537
  }
538
 
539
+ function updateThronglets() { // Core logic loop
540
  if (!gameActive) return;
541
  const now = Date.now();
542
  const livingThronglets = thronglets.filter(t => !t.isDead);
543
 
544
  livingThronglets.forEach(thronglet => {
545
  const timeSinceLast = now - thronglet.lastInteraction;
546
+ const baseNeedRate = 0.15; // Needs increase faster for dense population
547
+ const neglectMultiplier = 1 + Math.min(5, timeSinceLast / 10000); // Faster neglect penalty
548
 
549
+ thronglet.hunger = Math.min(100, thronglet.hunger + baseNeedRate * neglectMultiplier * 1.0);
550
+ thronglet.cleanliness = Math.min(100, thronglet.cleanliness + baseNeedRate * neglectMultiplier * 0.8);
551
+ thronglet.happiness = Math.max(0, thronglet.happiness - baseNeedRate * neglectMultiplier * 1.1);
552
 
553
  let deathReason = null;
554
  if (thronglet.hunger >= 100) deathReason = "starvation";
555
  else if (thronglet.cleanliness >= 100) deathReason = "filth";
556
+ else if (thronglet.happiness <= 0) deathReason = "misery";
557
  if (deathReason) { killThronglet(thronglet, deathReason); return; }
558
 
559
+ // --- Duplication Logic (Simplified for density) ---
560
+ const canDuplicate = thronglet.happiness > 85 && // Easier happiness threshold
561
+ thronglet.hunger < 15 &&
562
+ thronglet.cleanliness < 15 &&
563
+ timeSinceLast < 15000 && // Longer interaction window
564
+ (now - thronglet.lastDuplication > 20000); // Shorter cooldown
565
 
566
+ if (canDuplicate && Math.random() < 0.015) { // Higher base chance
567
+ // console.log(`Thronglet #${thronglet.id} duplicating!`); // Debug
568
  showFeedback(thronglet, '💞');
 
569
  playSound('duplicate');
570
+ thronglet.lastDuplication = now;
571
+ thronglet.lastInteraction = now;
572
 
573
  setTimeout(() => {
574
  const parentX = parseFloat(thronglet.element.style.left);
575
  const parentY = parseFloat(thronglet.element.style.top);
576
+ // Spawn very close to parent for density
577
+ createThronglet(parentX + (Math.random() - 0.5) * 5,
578
+ parentY + (Math.random() - 0.5) * 5);
579
+ }, 500); // Faster duplication effect
580
  }
581
 
582
+ // Wander less frequently to appear more stationary like screenshot
583
+ if (Math.random() < 0.4) { wander(thronglet); }
584
  });
585
+ // Update UI count less frequently if needed for performance
586
+ if (Math.random() < 0.2) updateUI();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
587
  }
588
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
589
 
590
  // --- Event Handlers ---
591
  egg.addEventListener('click', () => {
592
  if (!gameActive || egg.classList.contains('hatching') || !document.contains(egg)) return;
593
+ initAudio();
594
  egg.classList.add('hatching');
 
595
  playSound('hatch');
 
596
  setTimeout(() => {
597
  if(document.contains(egg)) egg.remove();
598
+ // Spawn initial few thronglets
599
+ createThronglet(45, 50);
600
+ createThronglet(55, 50);
601
+ createThronglet(50, 45);
602
+ }, 1000);
603
  });
604
 
605
+ // Tool selection (basic example)
606
+ pointerBtn.addEventListener('click', () => {
607
+ selectedTool = 'pointer';
608
+ playSound('ui_click');
609
+ console.log('Tool: Pointer');
610
+ // Add visual feedback later (e.g., highlight button)
611
+ });
612
+
613
  feedButton.addEventListener('click', () => {
614
  if (!gameActive) return;
615
+ initAudio();
616
  const livingThronglets = thronglets.filter(t => !t.isDead);
617
  if (livingThronglets.length > 0) {
618
+ selectedTool = 'feed'; // Select feed tool
619
+ playSound('ui_click');
620
+ // Find hungriest to feed automatically, OR require clicking thronglet next?
621
+ // For simplicity, let's keep auto-feeding the hungriest for now.
622
  const target = livingThronglets.reduce((p, c) => (p.hunger > c.hunger) ? p : c);
623
  feedThronglet(target);
624
+ } else { playSound('error'); }
 
 
 
625
  });
626
 
627
  cleanButton.addEventListener('click', () => {
628
  if (!gameActive) return;
629
+ initAudio();
630
  const livingThronglets = thronglets.filter(t => !t.isDead);
631
  if (livingThronglets.length > 0) {
632
+ selectedTool = 'clean'; // Select clean tool
633
+ playSound('ui_click');
634
  const target = livingThronglets.reduce((p, c) => (p.cleanliness > c.cleanliness) ? p : c);
635
  cleanThronglet(target);
636
+ } else { playSound('error'); }
 
 
 
637
  });
638
 
639
+ // Add listeners for other unknown buttons if needed
640
+ // e.g., unknownBtn1.addEventListener('click', () => { playSound('error'); });
641
+
642
+
643
  // --- Initial Setup ---
644
  function initGame() {
 
 
 
 
 
645
  gameActive = true;
 
 
 
646
  updateUI(); // Initial UI state
647
+ gameInterval = setInterval(updateThronglets, 400); // Faster update loop for density?
 
 
 
648
  initAudio();
649
+ // Automatically spawn the egg or start with Thronglets? Start with egg.
650
  }
651
 
652
  // Start the game automatically