Erythrocyte89 commited on
Commit
49164be
·
verified ·
1 Parent(s): 1a12daa

Upload 16 files

Browse files
.gitattributes CHANGED
@@ -33,3 +33,6 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
 
 
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
+ assets/bg1.jpg filter=lfs diff=lfs merge=lfs -text
37
+ assets/bg2.jpg filter=lfs diff=lfs merge=lfs -text
38
+ assets/bg3.jpg filter=lfs diff=lfs merge=lfs -text
app.js ADDED
@@ -0,0 +1,1035 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Globální proměnné a Stavy
2
+ let state = {
3
+ screen: 'main-menu',
4
+ phase: 'build', // build, defend, qte, shop, gameover
5
+ stats: {
6
+ score: 0,
7
+ coins: 0,
8
+ wave: 1,
9
+ streak: 0,
10
+ maxStreak: 0,
11
+ kills: 0,
12
+ totalCoins: 0,
13
+ gamesPlayed: 0,
14
+ maxWave: 0
15
+ },
16
+ build: {
17
+ progress: 0, // aktuální krok
18
+ target: 7, // 7 kroků k postavení
19
+ style: 'castle1' // výchozí vzhled
20
+ },
21
+ castle: {
22
+ hp: 100,
23
+ maxHp: 100
24
+ },
25
+ upgrades: {
26
+ slowMultiplier: 1, // 1 = normální rychlost, < 1 zpomaleno
27
+ fairyActive: false
28
+ },
29
+ currentProblem: null, // objekt { text: "2 + 2", answer: 4, type: "normal" }
30
+ enemies: [], // pole aktivních nepřátel
31
+ qte: {
32
+ active: false,
33
+ timer: null,
34
+ timeLeft: 0,
35
+ maxTime: 5000 // 5 sekund
36
+ },
37
+ intervals: {
38
+ gameLoop: null,
39
+ spawner: null,
40
+ fairy: null
41
+ }
42
+ };
43
+
44
+ // --- DOM Elementy ---
45
+ const els = {
46
+ // Screens
47
+ mainMenu: document.getElementById('main-menu'),
48
+ buildScreen: document.getElementById('build-screen'),
49
+ defendScreen: document.getElementById('defend-screen'),
50
+ shopScreen: document.getElementById('shop-screen'),
51
+ gameOverScreen: document.getElementById('game-over-screen'),
52
+ statsMenuScreen: document.getElementById('stats-menu-screen'),
53
+
54
+ // Tlačítka Menu
55
+ btnStart: document.getElementById('btn-start'),
56
+ btnStatsMenu: document.getElementById('btn-stats-menu'),
57
+ btnStatsBack: document.getElementById('btn-stats-back'),
58
+
59
+ // Build
60
+ buildStyleSelection: document.getElementById('build-style-selection'),
61
+ btnStartBuild: document.getElementById('btn-start-build'),
62
+ buildActionArea: document.getElementById('build-action-area'),
63
+ buildVisualStep: document.getElementById('build-visual-step'),
64
+ buildProgress: document.getElementById('build-progress'),
65
+ buildProgressText: document.getElementById('build-progress-text'),
66
+ buildMathProblem: document.getElementById('build-math-problem'),
67
+ buildInput: document.getElementById('build-input'),
68
+ buildCompareBtns: document.getElementById('build-compare-buttons'),
69
+
70
+ // Defend HUD
71
+ waveDisplay: document.getElementById('wave-display'),
72
+ coinsDisplay: document.getElementById('coins-display'),
73
+ streakDisplay: document.getElementById('streak-display'),
74
+ castleHpBar: document.getElementById('castle-hp-bar'),
75
+ castleHpText: document.getElementById('castle-hp-text'),
76
+ mainCastle: document.getElementById('main-castle'),
77
+ enemiesArea: document.getElementById('enemies-area'),
78
+ gameContainer: document.getElementById('game-container'),
79
+ defendInput: document.getElementById('defend-input'),
80
+ defendCompareBtns: document.getElementById('defend-compare-buttons'),
81
+
82
+ // QTE
83
+ qteContainer: document.getElementById('qte-container'),
84
+ qteMathProblem: document.getElementById('qte-math-problem'),
85
+ qteTimerBar: document.getElementById('qte-timer-bar'),
86
+
87
+ // Shop
88
+ shopWaveNum: document.getElementById('shop-wave-num'),
89
+ shopCoins: document.getElementById('shop-coins'),
90
+ btnNextWave: document.getElementById('btn-next-wave'),
91
+ buyHpBtn: document.getElementById('buy-hp'),
92
+ buySlowBtn: document.getElementById('buy-slow'),
93
+ buyFairyBtn: document.getElementById('buy-fairy'),
94
+
95
+ // Game Over
96
+ statWave: document.getElementById('stat-wave'),
97
+ statScore: document.getElementById('stat-score'),
98
+ statStreak: document.getElementById('stat-streak'),
99
+ statKills: document.getElementById('stat-kills'),
100
+ btnRestart: document.getElementById('btn-restart'),
101
+ btnToMain: document.getElementById('btn-to-main'),
102
+
103
+ // Global Stats
104
+ globalBestWave: document.getElementById('global-best-wave'),
105
+ globalGamesPlayed: document.getElementById('global-games-played'),
106
+ globalTotalCoins: document.getElementById('global-total-coins'),
107
+
108
+ // Particles
109
+ particles: document.getElementById('particles')
110
+ };
111
+
112
+ // --- Inicializace ---
113
+ function init() {
114
+ loadGlobalStats();
115
+ attachEventListeners();
116
+ showScreen('main-menu');
117
+ }
118
+
119
+ // --- Systém Obrazovek ---
120
+ function showScreen(screenId) {
121
+ // Skrytí všech
122
+ els.mainMenu.classList.add('hidden');
123
+ els.buildScreen.classList.add('hidden');
124
+ els.defendScreen.classList.add('hidden');
125
+ els.shopScreen.classList.add('hidden');
126
+ els.gameOverScreen.classList.add('hidden');
127
+ els.statsMenuScreen.classList.add('hidden');
128
+
129
+ // Zobrazení vybrané
130
+ if (screenId === 'main-menu') els.mainMenu.classList.remove('hidden');
131
+ else if (screenId === 'build-screen') els.buildScreen.classList.remove('hidden');
132
+ else if (screenId === 'defend-screen') els.defendScreen.classList.remove('hidden');
133
+ else if (screenId === 'shop-screen') els.shopScreen.classList.remove('hidden');
134
+ else if (screenId === 'game-over-screen') els.gameOverScreen.classList.remove('hidden');
135
+ else if (screenId === 'stats-menu-screen') els.statsMenuScreen.classList.remove('hidden');
136
+
137
+ state.screen = screenId;
138
+ }
139
+
140
+ // --- Správa událostí (Event Listeners) ---
141
+ function attachEventListeners() {
142
+ // Main Menu
143
+ els.btnStart.addEventListener('click', startGame);
144
+ els.btnStatsMenu.addEventListener('click', () => {
145
+ updateGlobalStatsUI();
146
+ showScreen('stats-menu-screen');
147
+ });
148
+ els.btnStatsBack.addEventListener('click', () => showScreen('main-menu'));
149
+
150
+ // Game Over
151
+ els.btnRestart.addEventListener('click', startGame);
152
+ els.btnToMain.addEventListener('click', () => showScreen('main-menu'));
153
+
154
+ // Build Style Selection
155
+ document.querySelectorAll('.style-option').forEach(option => {
156
+ option.addEventListener('click', (e) => {
157
+ document.querySelectorAll('.style-option').forEach(opt => opt.classList.remove('selected'));
158
+ e.target.classList.add('selected');
159
+ state.build.style = e.target.dataset.style;
160
+ });
161
+ });
162
+
163
+ els.btnStartBuild.addEventListener('click', () => {
164
+ els.buildStyleSelection.classList.add('hidden');
165
+ els.buildActionArea.classList.remove('hidden');
166
+ els.buildActionArea.style.display = 'flex';
167
+
168
+ // Nastavíme vizuál budování
169
+ els.buildVisualStep.style.backgroundImage = `url('assets/${state.build.style}.svg')`;
170
+ els.buildVisualStep.style.height = '0%';
171
+
172
+ generateBuildProblem();
173
+ });
174
+ }
175
+
176
+ // --- Generování matematických příkladů ---
177
+ function generateMathProblem(difficulty) {
178
+ // difficulty: 1 (lehké, stavba), 2 (střední, vlny 1-3), 3 (těžší, vlny 4+), 4 (QTE - nejtěžší)
179
+ const types = ['add', 'sub', 'mul', 'div', 'comp', 'round'];
180
+ const type = types[Math.floor(Math.random() * types.length)];
181
+
182
+ let text = "";
183
+ let answer = null;
184
+ let probType = "num"; // num (číselný vstup), comp (porovnávání)
185
+
186
+ const getRandomInt = (min, max) => Math.floor(Math.random() * (max - min + 1)) + min;
187
+
188
+ switch (type) {
189
+ case 'add':
190
+ let maxAdd = difficulty === 1 ? 20 : (difficulty === 2 ? 50 : 100);
191
+ let aAdd = getRandomInt(1, maxAdd);
192
+ let bAdd = getRandomInt(1, maxAdd - aAdd > 0 ? maxAdd - aAdd : 10);
193
+ if (difficulty >= 3) {
194
+ aAdd = getRandomInt(10, 50);
195
+ bAdd = getRandomInt(10, 50);
196
+ }
197
+ text = `${aAdd} + ${bAdd}`;
198
+ answer = aAdd + bAdd;
199
+ break;
200
+
201
+ case 'sub':
202
+ let maxSub = difficulty <= 2 ? 30 : 100;
203
+ let aSub = getRandomInt(5, maxSub);
204
+ let bSub = getRandomInt(1, aSub);
205
+ text = `${aSub} - ${bSub}`;
206
+ answer = aSub - bSub;
207
+ break;
208
+
209
+ case 'mul':
210
+ let maxMul = difficulty === 1 ? 5 : (difficulty === 2 ? 8 : 10);
211
+ let aMul = getRandomInt(1, maxMul);
212
+ let bMul = getRandomInt(1, 10);
213
+ text = `${aMul} × ${bMul}`;
214
+ answer = aMul * bMul;
215
+ break;
216
+
217
+ case 'div':
218
+ let divisor = getRandomInt(2, difficulty <= 2 ? 5 : 10);
219
+ let quotient = getRandomInt(1, 10);
220
+ let dividend = divisor * quotient;
221
+ text = `${dividend} : ${divisor}`;
222
+ answer = quotient;
223
+ break;
224
+
225
+ case 'comp':
226
+ probType = "comp";
227
+ let aComp = getRandomInt(1, 100);
228
+ let bComp = getRandomInt(1, 100);
229
+ // snaha o blízka čísla
230
+ if(Math.random() > 0.5) bComp = aComp + getRandomInt(-5, 5);
231
+
232
+ text = `${aComp} ? ${bComp}`;
233
+ if (aComp < bComp) answer = '<';
234
+ else if (aComp > bComp) answer = '>';
235
+ else answer = '=';
236
+ break;
237
+
238
+ case 'round':
239
+ let numRound = getRandomInt(11, 99);
240
+ // Nechceme končící na 0
241
+ if (numRound % 10 === 0) numRound += getRandomInt(1, 4);
242
+ text = `Zaokrouhli ${numRound}`;
243
+ answer = Math.round(numRound / 10) * 10;
244
+ break;
245
+ }
246
+
247
+ return { text, answer, type: probType };
248
+ }
249
+
250
+ // --- Herní Loop a Základní stavy ---
251
+ function startGame() {
252
+ console.log("Start hry...");
253
+
254
+ // Reset statistik pro novou hru
255
+ state.stats.score = 0;
256
+ state.stats.coins = 0;
257
+ state.stats.wave = 1;
258
+ state.stats.streak = 0;
259
+ state.stats.maxStreak = 0;
260
+ state.stats.kills = 0;
261
+
262
+ // Reset hradu
263
+ state.castle.maxHp = 100;
264
+ state.castle.hp = state.castle.maxHp;
265
+
266
+ // Reset upgrady
267
+ state.upgrades.slowMultiplier = 1;
268
+ state.upgrades.fairyActive = false;
269
+
270
+ // Zvýšit počítadlo her
271
+ state.stats.gamesPlayed++;
272
+ saveGlobalStats();
273
+
274
+ startBuildPhase();
275
+ }
276
+
277
+ function startBuildPhase() {
278
+ state.phase = 'build';
279
+ state.build.progress = 0;
280
+
281
+ // Obnovit UI pro výběr stylu
282
+ els.buildStyleSelection.classList.remove('hidden');
283
+ els.buildActionArea.classList.add('hidden');
284
+ els.buildActionArea.style.display = 'none';
285
+
286
+ updateBuildUI();
287
+ showScreen('build-screen');
288
+ }
289
+
290
+ function updateBuildUI() {
291
+ let percent = (state.build.progress / state.build.target) * 100;
292
+ els.buildProgress.style.width = `${percent}%`;
293
+ els.buildProgressText.textContent = `Krok ${state.build.progress} / ${state.build.target}`;
294
+
295
+ // Vizuál rostoucího hradu
296
+ els.buildVisualStep.style.height = `${percent}%`;
297
+ }
298
+
299
+ function generateBuildProblem() {
300
+ state.currentProblem = generateMathProblem(1); // obtížnost 1 pro stavbu
301
+ els.buildMathProblem.textContent = state.currentProblem.text;
302
+
303
+ if (state.currentProblem.type === 'comp') {
304
+ els.buildInput.classList.add('hidden');
305
+ els.buildCompareBtns.classList.remove('hidden');
306
+ } else {
307
+ els.buildInput.classList.remove('hidden');
308
+ els.buildCompareBtns.classList.add('hidden');
309
+ els.buildInput.value = '';
310
+ els.buildInput.focus();
311
+ }
312
+ }
313
+
314
+ // Zpracování vstupu ve fázi stavby
315
+ function handleBuildInput(val) {
316
+ if (state.phase !== 'build') return;
317
+
318
+ // Převod na string pro snadné porovnání (čísla i znaky)
319
+ if (val.toString() === state.currentProblem.answer.toString()) {
320
+ // Správně
321
+ state.build.progress += 1;
322
+ createParticles(els.buildMathProblem, '#43bccd'); // modré jiskry
323
+
324
+ // Mince za stavbu (1 za správnou odpověď)
325
+ state.stats.coins += 1;
326
+ state.stats.totalCoins += 1;
327
+ saveGlobalStats();
328
+
329
+ if (state.build.progress >= state.build.target) {
330
+ state.build.progress = state.build.target;
331
+ updateBuildUI();
332
+ setTimeout(startDefendPhase, 1000); // pauza před obranou
333
+ } else {
334
+ updateBuildUI();
335
+ generateBuildProblem();
336
+ }
337
+ } else {
338
+ // Špatně
339
+ createParticles(els.buildMathProblem, '#e94560'); // červené jiskry, shake
340
+ els.buildMathProblem.style.animation = 'shake 0.5s';
341
+ setTimeout(() => els.buildMathProblem.style.animation = '', 500);
342
+
343
+ if (state.currentProblem.type !== 'comp') {
344
+ els.buildInput.value = '';
345
+ els.buildInput.focus();
346
+ }
347
+ }
348
+ }
349
+
350
+ // Bindování inputů pro Build
351
+ els.buildInput.addEventListener('keyup', (e) => {
352
+ if (e.key === 'Enter' && els.buildInput.value !== '') {
353
+ handleBuildInput(els.buildInput.value);
354
+ }
355
+ });
356
+
357
+ Array.from(els.buildCompareBtns.children).forEach(btn => {
358
+ btn.addEventListener('click', (e) => {
359
+ handleBuildInput(e.target.dataset.val);
360
+ });
361
+ });
362
+
363
+ function startDefendPhase() {
364
+ state.phase = 'defend';
365
+ console.log("Zahájení obrany vlna:", state.stats.wave);
366
+
367
+ // Aktualizace HUD
368
+ els.waveDisplay.textContent = state.stats.wave;
369
+ els.coinsDisplay.textContent = state.stats.coins;
370
+ els.streakDisplay.textContent = state.stats.streak;
371
+ updateCastleHpUI();
372
+
373
+ els.enemiesArea.innerHTML = '';
374
+ state.enemies = [];
375
+
376
+ showScreen('defend-screen');
377
+ els.defendInput.value = '';
378
+ els.defendInput.focus();
379
+
380
+ // Změna pozadí a vzhledu hradu
381
+ els.mainCastle.style.backgroundImage = `url('assets/${state.build.style}.svg')`;
382
+ updateBackground();
383
+
384
+ // Nastavení obtížnosti podle vlny
385
+ let difficulty = 2;
386
+ if (state.stats.wave > 3) difficulty = 3;
387
+
388
+ // Parametry vlny
389
+ let totalEnemies = 5 + (state.stats.wave * 2);
390
+ let spawnedEnemies = 0;
391
+
392
+ // Hlavní herní loop pro obranu (pohyb nepřátel)
393
+ state.intervals.gameLoop = setInterval(() => {
394
+ if (state.phase !== 'defend') return;
395
+ updateEnemies();
396
+ }, 50); // 20 FPS
397
+
398
+ // Spawner nepřátel
399
+ let spawnRate = Math.max(1000, 3000 - (state.stats.wave * 200)); // Rychlejší spawn v dalších vlnách
400
+
401
+ // Jestli je to boss level
402
+ const isBossWave = state.stats.wave % 5 === 0;
403
+
404
+ state.intervals.spawner = setInterval(() => {
405
+ if (state.phase !== 'defend' || state.qte.active) return;
406
+
407
+ if (spawnedEnemies < totalEnemies) {
408
+ // Poslední nepřítel ve vlny dělitelné 5 je Boss
409
+ let spawnBoss = false;
410
+ if (isBossWave && spawnedEnemies === totalEnemies - 1) {
411
+ spawnBoss = true;
412
+ }
413
+ spawnEnemy(difficulty, spawnBoss);
414
+ spawnedEnemies++;
415
+ } else if (state.enemies.length === 0 && !state.qte.active) {
416
+ // Vlna dokončena
417
+ endWave();
418
+ }
419
+
420
+ // Šance na QTE (Záchrana jednorožce) - cca 15% každou vteřinu spawnu, pokud už není a vlna ještě neskončila
421
+ if (!state.qte.active && Math.random() < 0.15 && spawnedEnemies < totalEnemies) {
422
+ triggerQTE();
423
+ }
424
+
425
+ }, spawnRate);
426
+ }
427
+
428
+ function updateBackground() {
429
+ els.gameContainer.classList.remove('bg-1', 'bg-2', 'bg-3');
430
+ if (state.stats.wave <= 3) els.gameContainer.classList.add('bg-1');
431
+ else if (state.stats.wave <= 6) els.gameContainer.classList.add('bg-2');
432
+ else els.gameContainer.classList.add('bg-3');
433
+ }
434
+
435
+ function spawnEnemy(difficulty, isBoss = false) {
436
+ const enemyEl = document.createElement('div');
437
+ enemyEl.classList.add('enemy');
438
+
439
+ // Grafika
440
+ const spriteEl = document.createElement('div');
441
+ spriteEl.classList.add('enemy-sprite');
442
+
443
+ if (isBoss) {
444
+ spriteEl.classList.add('enemy-boss');
445
+ difficulty = Math.min(difficulty + 1, 4); // boss je o něco těžší
446
+ } else {
447
+ const sprites = ['enemy1.svg', 'enemy2.svg', 'enemy3.svg'];
448
+ const chosenSprite = sprites[Math.floor(Math.random() * sprites.length)];
449
+ spriteEl.style.backgroundImage = `url('assets/${chosenSprite}')`;
450
+ }
451
+
452
+ // Příklad
453
+ const mathEl = document.createElement('div');
454
+ mathEl.classList.add('enemy-math');
455
+ const problem = generateMathProblem(difficulty);
456
+ mathEl.textContent = problem.text;
457
+
458
+ enemyEl.appendChild(spriteEl);
459
+ enemyEl.appendChild(mathEl);
460
+
461
+ // Pozice (zprava, náhodná výška)
462
+ const startX = els.enemiesArea.clientWidth;
463
+ const heightOffset = isBoss ? 150 : 80;
464
+ const startY = Math.random() * (els.enemiesArea.clientHeight - heightOffset);
465
+
466
+ enemyEl.style.left = `${startX}px`;
467
+ enemyEl.style.top = `${startY}px`;
468
+
469
+ els.enemiesArea.appendChild(enemyEl);
470
+
471
+ const speed = (Math.random() * 0.5 + 0.5 + (state.stats.wave * 0.1)) * state.upgrades.slowMultiplier; // Rychlost podle vlny a upgrady
472
+
473
+ const id = Date.now() + Math.random();
474
+
475
+ state.enemies.push({
476
+ id: id,
477
+ el: enemyEl,
478
+ x: startX,
479
+ y: startY,
480
+ speed: speed,
481
+ problem: problem
482
+ });
483
+
484
+ // Aktualizuj input mód podle toho, co je nejblíž (nejblížící se nepřítel)
485
+ updateDefendInputMode();
486
+ }
487
+
488
+ function updateEnemies() {
489
+ for (let i = state.enemies.length - 1; i >= 0; i--) {
490
+ let enemy = state.enemies[i];
491
+
492
+ // Pohyb doleva
493
+ enemy.x -= enemy.speed;
494
+ enemy.el.style.left = `${enemy.x}px`;
495
+
496
+ // Kolize s hradem (x <= 0)
497
+ if (enemy.x <= 0) {
498
+ damageCastle(10);
499
+ enemy.el.remove();
500
+ state.enemies.splice(i, 1);
501
+
502
+ // Reset streak
503
+ resetStreak();
504
+ updateDefendInputMode();
505
+ }
506
+ }
507
+ }
508
+
509
+ function damageCastle(amount) {
510
+ state.castle.hp -= amount;
511
+ if (state.castle.hp < 0) state.castle.hp = 0;
512
+
513
+ updateCastleHpUI();
514
+
515
+ // Efekt na hrad
516
+ const castleEl = document.querySelector('.castle');
517
+ castleEl.style.animation = 'shake 0.5s';
518
+ setTimeout(() => castleEl.style.animation = '', 500);
519
+
520
+ if (state.castle.hp === 0) {
521
+ gameOver();
522
+ }
523
+ }
524
+
525
+ function updateDefendInputMode() {
526
+ let aliveEnemies = state.enemies.filter(e => !e.dying);
527
+ if (aliveEnemies.length === 0) return;
528
+ if (state.qte.active) return; // Nepřepínat vstupy, pokud běží QTE
529
+
530
+ // Najdi nejbližšího nepřítele
531
+ let closestEnemy = aliveEnemies.reduce((prev, curr) => (prev.x < curr.x) ? prev : curr);
532
+
533
+ // Vizuální označení cíle
534
+ state.enemies.forEach(e => e.el.classList.remove('targeted'));
535
+ closestEnemy.el.classList.add('targeted');
536
+
537
+ if (closestEnemy.problem.type === 'comp') {
538
+ els.defendInput.classList.add('hidden');
539
+ els.defendCompareBtns.classList.remove('hidden');
540
+ } else {
541
+ els.defendInput.classList.remove('hidden');
542
+ els.defendCompareBtns.classList.add('hidden');
543
+ els.defendInput.focus();
544
+ }
545
+ }
546
+
547
+ function handleDefendInput(val) {
548
+ if (state.phase !== 'defend' || state.qte.active) return;
549
+ if (state.enemies.length === 0) {
550
+ els.defendInput.value = '';
551
+ return;
552
+ }
553
+
554
+ let hit = false;
555
+ let hitEnemyIndex = -1;
556
+
557
+ // Najít nepřítele se správnou odpovědí (priorita nejbližší)
558
+ // Seřadit nepřátele od nejbližšího (nejmenší X)
559
+ let sortedEnemies = [...state.enemies].sort((a, b) => a.x - b.x);
560
+
561
+ for (let enemy of sortedEnemies) {
562
+ if (!enemy.dying && enemy.problem.answer.toString() === val.toString()) {
563
+ hit = true;
564
+ hitEnemyIndex = state.enemies.findIndex(e => e.id === enemy.id);
565
+ break;
566
+ }
567
+ }
568
+
569
+ if (hit && hitEnemyIndex !== -1) {
570
+ // Zásah
571
+ let enemy = state.enemies[hitEnemyIndex];
572
+
573
+ // Označíme nepřítele jako mrtvého, aby už na něj nešlo dál útočit
574
+ enemy.dying = true;
575
+
576
+ // Princezna vystřelí projektil
577
+ shootProjectile(enemy, () => {
578
+ createParticles(enemy.el, '#fca311'); // zlaté částice
579
+ enemy.el.remove();
580
+
581
+ let realIdx = state.enemies.findIndex(e => e.id === enemy.id);
582
+ if (realIdx !== -1) {
583
+ state.enemies.splice(realIdx, 1);
584
+
585
+ state.stats.kills++;
586
+ state.stats.score += 10 * (1 + Math.floor(state.stats.streak / 5)); // Bonus za streak
587
+
588
+ // Mince + Streak
589
+ state.stats.streak++;
590
+ if (state.stats.streak > state.stats.maxStreak) state.stats.maxStreak = state.stats.streak;
591
+
592
+ // Každých 5 ve streaku = bonusová mince
593
+ let coinsEarned = 1 + Math.floor(state.stats.streak / 5);
594
+ state.stats.coins += coinsEarned;
595
+ state.stats.totalCoins += coinsEarned;
596
+
597
+ els.streakDisplay.textContent = state.stats.streak;
598
+ els.coinsDisplay.textContent = state.stats.coins;
599
+
600
+ updateDefendInputMode();
601
+ }
602
+ });
603
+
604
+ els.defendInput.value = '';
605
+
606
+ } else {
607
+ // Minutí
608
+ resetStreak();
609
+
610
+ // Efekt chyby
611
+ els.defendInput.style.animation = 'shake 0.3s';
612
+ els.defendCompareBtns.style.animation = 'shake 0.3s';
613
+ setTimeout(() => {
614
+ els.defendInput.style.animation = '';
615
+ els.defendCompareBtns.style.animation = '';
616
+ }, 300);
617
+
618
+ if (els.defendInput.classList.contains('hidden') === false) {
619
+ els.defendInput.value = '';
620
+ }
621
+ }
622
+ }
623
+
624
+ function shootProjectile(enemy, onHitCallback) {
625
+ const proj = document.createElement('div');
626
+ proj.classList.add('projectile');
627
+
628
+ // Zjistit pozici princezny
629
+ const princessEl = document.querySelector('.princess');
630
+ const pRect = princessEl.getBoundingClientRect();
631
+ const cRect = els.gameContainer.getBoundingClientRect();
632
+
633
+ let startX = pRect.left - cRect.left + pRect.width / 2;
634
+ let startY = pRect.top - cRect.top + pRect.height / 2;
635
+
636
+ proj.style.left = `${startX}px`;
637
+ proj.style.top = `${startY}px`;
638
+
639
+ els.gameContainer.appendChild(proj);
640
+
641
+ // Animace k nepříteli
642
+ let duration = 300; // ms
643
+ let startTime = performance.now();
644
+
645
+ function animateProjectile(currentTime) {
646
+ let elapsed = currentTime - startTime;
647
+ let progress = Math.min(elapsed / duration, 1);
648
+
649
+ let targetX = enemy.x + els.enemiesArea.offsetLeft;
650
+ let targetY = enemy.y + els.enemiesArea.offsetTop + 40; // zhruba střed nepřítele
651
+
652
+ let currentX = startX + (targetX - startX) * progress;
653
+ let currentY = startY + (targetY - startY) * progress;
654
+
655
+ proj.style.left = `${currentX}px`;
656
+ proj.style.top = `${currentY}px`;
657
+
658
+ if (progress < 1) {
659
+ requestAnimationFrame(animateProjectile);
660
+ } else {
661
+ proj.remove();
662
+ if (onHitCallback) onHitCallback();
663
+ }
664
+ }
665
+
666
+ requestAnimationFrame(animateProjectile);
667
+ }
668
+
669
+ function resetStreak() {
670
+ state.stats.streak = 0;
671
+ els.streakDisplay.textContent = state.stats.streak;
672
+ }
673
+
674
+ // Bindování inputů pro Defend
675
+ els.defendInput.addEventListener('keyup', (e) => {
676
+ if (state.phase !== 'defend') return;
677
+ if (e.key === 'Enter' && els.defendInput.value !== '') {
678
+ handleDefendInput(els.defendInput.value);
679
+ }
680
+ });
681
+
682
+ Array.from(els.defendCompareBtns.children).forEach(btn => {
683
+ btn.addEventListener('click', (e) => {
684
+ if (state.phase !== 'defend') return;
685
+ handleDefendInput(e.target.dataset.val);
686
+ });
687
+ });
688
+
689
+ // --- QTE (Záchrana jednorožce) ---
690
+ function triggerQTE() {
691
+ console.log("QTE triggered!");
692
+ state.qte.active = true;
693
+ state.qte.timeLeft = state.qte.maxTime;
694
+
695
+ // Zobrazení QTE UI
696
+ els.qteContainer.classList.remove('hidden');
697
+ els.qteTimerBar.style.width = '100%';
698
+
699
+ // Generování těžšího příkladu (obtížnost 4)
700
+ state.currentProblem = generateMathProblem(4);
701
+ els.qteMathProblem.textContent = state.currentProblem.text;
702
+
703
+ // Nastavení vstupu pro QTE
704
+ if (state.currentProblem.type === 'comp') {
705
+ els.defendInput.classList.add('hidden');
706
+ els.defendCompareBtns.classList.remove('hidden');
707
+ } else {
708
+ els.defendInput.classList.remove('hidden');
709
+ els.defendCompareBtns.classList.add('hidden');
710
+ els.defendInput.value = '';
711
+ els.defendInput.focus();
712
+ }
713
+
714
+ // Spuštění odpočtu (interval 50ms)
715
+ state.qte.timer = setInterval(() => {
716
+ state.qte.timeLeft -= 50;
717
+ let percent = (state.qte.timeLeft / state.qte.maxTime) * 100;
718
+ els.qteTimerBar.style.width = `${percent}%`;
719
+
720
+ if (state.qte.timeLeft <= 0) {
721
+ failQTE();
722
+ }
723
+ }, 50);
724
+ }
725
+
726
+ function resolveQTEInput(val) {
727
+ if (val.toString() === state.currentProblem.answer.toString()) {
728
+ successQTE();
729
+ } else {
730
+ failQTE();
731
+ }
732
+ }
733
+
734
+ function successQTE() {
735
+ clearInterval(state.qte.timer);
736
+
737
+ // Efekt záchrany (magická vlna)
738
+ createParticles(els.qteContainer, '#8e44ad'); // fialové
739
+ createParticles(els.qteContainer, '#f1c40f'); // žluté
740
+
741
+ // Odměna: Zničit všechny aktuální nepřátele na obrazovce
742
+ state.enemies.forEach(enemy => {
743
+ createParticles(enemy.el, '#8e44ad');
744
+ enemy.el.remove();
745
+ state.stats.kills++;
746
+ state.stats.score += 20;
747
+ });
748
+ state.enemies = []; // vyprázdnit pole
749
+
750
+ // Bonusové mince
751
+ state.stats.coins += 10;
752
+ state.stats.totalCoins += 10;
753
+ els.coinsDisplay.textContent = state.stats.coins;
754
+
755
+ closeQTE();
756
+ }
757
+
758
+ function failQTE() {
759
+ clearInterval(state.qte.timer);
760
+
761
+ // Jednorožec uteče - efekt zklamání
762
+ els.qteContainer.style.animation = 'shake 0.5s';
763
+ setTimeout(() => els.qteContainer.style.animation = '', 500);
764
+
765
+ closeQTE();
766
+ }
767
+
768
+ function closeQTE() {
769
+ els.qteContainer.classList.add('hidden');
770
+ state.qte.active = false;
771
+
772
+ // Návrat vstupu zpět na obranu (nebo skrytí, pokud nejsou nepřátelé)
773
+ updateDefendInputMode();
774
+ if (state.enemies.length === 0) {
775
+ els.defendInput.classList.add('hidden');
776
+ els.defendCompareBtns.classList.add('hidden');
777
+ }
778
+ }
779
+
780
+ // Aktualizace vstupů v obraně (sloučení s QTE logic)
781
+ // Nahradíme původní handlery (ty z předchozího kroku) za nové, které zohledňují QTE
782
+ els.defendInput.removeEventListener('keyup', () => {}); // Nelze jednoduše odstranit anonymní funkce, takže je přepíšeme
783
+
784
+ // Ošetříme to přidáním nové podmínky přímo do `handleDefendInput` a zachováme původní event listenery
785
+ // ale přesuneme QTE logiku přímo tam, abychom zamezili duplikacím a problémům.
786
+ // Protože JS neumožňuje snadno odstranit anonymní event listenery vytvořené dříve, upravíme logiku uvnitř listenerů
787
+ // tím, že `handleDefendInput` bude vědět o QTE (což už dělá přes return).
788
+
789
+ function checkGlobalInput(e) {
790
+ if (state.phase !== 'defend') return;
791
+
792
+ // Pokud je aktivní QTE
793
+ if (state.qte.active && e.key === 'Enter' && els.defendInput.value !== '') {
794
+ resolveQTEInput(els.defendInput.value);
795
+ }
796
+ }
797
+
798
+ // Přiřadíme do globálního listeneru, abychom nepoužívali duplicitní event listenery na inputu
799
+ window.addEventListener('keyup', (e) => {
800
+ if (state.phase === 'defend' && state.qte.active) {
801
+ if (e.key === 'Enter' && els.defendInput.value !== '') {
802
+ resolveQTEInput(els.defendInput.value);
803
+ els.defendInput.value = '';
804
+ }
805
+ }
806
+ });
807
+
808
+ // A pro tlačítka na porovnávání (pokud je QTE typu comp):
809
+ Array.from(els.defendCompareBtns.children).forEach(btn => {
810
+ btn.addEventListener('click', (e) => {
811
+ if (state.phase === 'defend' && state.qte.active) {
812
+ resolveQTEInput(e.target.dataset.val);
813
+ }
814
+ });
815
+ });
816
+
817
+
818
+ function endWave() {
819
+ console.log("Konec vlny", state.stats.wave);
820
+
821
+ // Clear intervals
822
+ clearInterval(state.intervals.gameLoop);
823
+ clearInterval(state.intervals.spawner);
824
+
825
+ state.stats.wave++;
826
+ if (state.stats.wave > state.stats.maxWave) state.stats.maxWave = state.stats.wave;
827
+ saveGlobalStats();
828
+
829
+ showShopPhase();
830
+ }
831
+
832
+ function showShopPhase() {
833
+ state.phase = 'shop';
834
+ els.shopWaveNum.textContent = state.stats.wave - 1;
835
+ els.shopCoins.textContent = state.stats.coins;
836
+
837
+ // Obnovení tlačítek a jejich textů
838
+ els.buyHpBtn.disabled = state.stats.coins < 50;
839
+
840
+ if (state.upgrades.slowMultiplier <= 0.5) {
841
+ els.buySlowBtn.textContent = 'MAX (Vyprodáno)';
842
+ els.buySlowBtn.disabled = true;
843
+ } else {
844
+ els.buySlowBtn.disabled = state.stats.coins < 100;
845
+ els.buySlowBtn.textContent = 'Koupit (100 🪙)';
846
+ }
847
+
848
+ if (state.upgrades.fairyActive) {
849
+ els.buyFairyBtn.textContent = 'Aktivní (Vyprodáno)';
850
+ els.buyFairyBtn.disabled = true;
851
+ } else {
852
+ els.buyFairyBtn.disabled = state.stats.coins < 150;
853
+ els.buyFairyBtn.textContent = 'Koupit (150 🪙)';
854
+ }
855
+
856
+ showScreen('shop-screen');
857
+ }
858
+
859
+ function updateShopUI() {
860
+ els.shopCoins.textContent = state.stats.coins;
861
+ showShopPhase(); // znovuzavolání pro přehodnocení disabled stavů
862
+ }
863
+
864
+ // Event Listenery pro Obchod
865
+ els.buyHpBtn.addEventListener('click', () => {
866
+ if (state.stats.coins >= 50) {
867
+ state.stats.coins -= 50;
868
+ state.castle.maxHp += 20;
869
+ state.castle.hp += 20; // vyléčí i aktuální
870
+ updateShopUI();
871
+ }
872
+ });
873
+
874
+ els.buySlowBtn.addEventListener('click', () => {
875
+ if (state.stats.coins >= 100 && state.upgrades.slowMultiplier > 0.5) {
876
+ state.stats.coins -= 100;
877
+ state.upgrades.slowMultiplier -= 0.1; // zpomalení o 10%
878
+ updateShopUI();
879
+ }
880
+ });
881
+
882
+ els.buyFairyBtn.addEventListener('click', () => {
883
+ if (state.stats.coins >= 150 && !state.upgrades.fairyActive) {
884
+ state.stats.coins -= 150;
885
+ state.upgrades.fairyActive = true;
886
+ startFairy();
887
+ updateShopUI();
888
+ }
889
+ });
890
+
891
+ els.btnNextWave.addEventListener('click', () => {
892
+ if (state.phase === 'shop') {
893
+ startDefendPhase();
894
+ }
895
+ });
896
+
897
+ function startFairy() {
898
+ if (state.intervals.fairy) clearInterval(state.intervals.fairy);
899
+
900
+ state.intervals.fairy = setInterval(() => {
901
+ if (state.phase === 'defend' && state.enemies.length > 0 && !state.qte.active) {
902
+ // Víla zničí náhodného nepřítele (nebo prvního)
903
+ let enemyToKill = state.enemies[0];
904
+ createParticles(enemyToKill.el, '#ff9ff3'); // růžová vílí barva
905
+ enemyToKill.el.remove();
906
+ state.enemies.shift(); // odstraní prvního z pole
907
+
908
+ state.stats.kills++;
909
+ state.stats.score += 10;
910
+
911
+ updateDefendInputMode();
912
+
913
+ // Ukázat vílu graficky (krátká animace)
914
+ const fairySprite = document.createElement('div');
915
+ fairySprite.textContent = '🧚‍♀️';
916
+ fairySprite.style.position = 'absolute';
917
+ fairySprite.style.left = enemyToKill.x + 'px';
918
+ fairySprite.style.top = enemyToKill.y - 30 + 'px';
919
+ fairySprite.style.fontSize = '3rem';
920
+ fairySprite.style.animation = 'flyout 1s forwards';
921
+ fairySprite.style.setProperty('--tx', `0px`);
922
+ fairySprite.style.setProperty('--ty', `-100px`);
923
+ els.enemiesArea.appendChild(fairySprite);
924
+ setTimeout(() => fairySprite.remove(), 1000);
925
+ }
926
+ }, 5000); // Každých 5 sekund víla zasáhne
927
+ }
928
+
929
+ // Game Over logiky
930
+ function gameOver() {
931
+ console.log("Hra skončila");
932
+ state.phase = 'gameover';
933
+
934
+ // Vyčištění všech intervalů
935
+ if (state.intervals.gameLoop) clearInterval(state.intervals.gameLoop);
936
+ if (state.intervals.spawner) clearInterval(state.intervals.spawner);
937
+ if (state.intervals.fairy) clearInterval(state.intervals.fairy);
938
+ if (state.qte.timer) clearInterval(state.qte.timer);
939
+
940
+ // Skrytí QTE, pokud běželo
941
+ els.qteContainer.classList.add('hidden');
942
+
943
+ // Uložení aktuálních max statistik
944
+ if (state.stats.wave > state.stats.maxWave) state.stats.maxWave = state.stats.wave;
945
+ saveGlobalStats();
946
+
947
+ // Zobrazení UI
948
+ els.statWave.textContent = state.stats.wave;
949
+ els.statScore.textContent = state.stats.score;
950
+ els.statStreak.textContent = state.stats.maxStreak;
951
+ els.statKills.textContent = state.stats.kills;
952
+
953
+ showScreen('game-over-screen');
954
+ }
955
+
956
+ function updateCastleHpUI() {
957
+ els.castleHpText.textContent = `${state.castle.hp} / ${state.castle.maxHp}`;
958
+ let hpPercent = (state.castle.hp / state.castle.maxHp) * 100;
959
+ els.castleHpBar.style.width = `${hpPercent}%`;
960
+ if (hpPercent < 30) els.castleHpBar.style.backgroundColor = 'red';
961
+ else els.castleHpBar.style.backgroundColor = '#e94560';
962
+ }
963
+
964
+ // Particle system helper
965
+ function createParticles(element, color) {
966
+ const rect = element.getBoundingClientRect();
967
+ const containerRect = document.getElementById('game-container').getBoundingClientRect();
968
+
969
+ const centerX = (rect.left - containerRect.left) + rect.width / 2;
970
+ const centerY = (rect.top - containerRect.top) + rect.height / 2;
971
+
972
+ for (let i = 0; i < 10; i++) {
973
+ const p = document.createElement('div');
974
+ p.classList.add('particle');
975
+ p.style.backgroundColor = color;
976
+ p.style.width = Math.random() * 10 + 5 + 'px';
977
+ p.style.height = p.style.width;
978
+ p.style.left = centerX + 'px';
979
+ p.style.top = centerY + 'px';
980
+
981
+ // Náhodný směr
982
+ const angle = Math.random() * Math.PI * 2;
983
+ const velocity = Math.random() * 50 + 20;
984
+ const tx = Math.cos(angle) * velocity;
985
+ const ty = Math.sin(angle) * velocity;
986
+
987
+ p.style.setProperty('--tx', `${tx}px`);
988
+ p.style.setProperty('--ty', `${ty}px`);
989
+
990
+ // Upravit animaci aby použila translate
991
+ p.style.animation = 'flyout 0.8s forwards';
992
+
993
+ document.getElementById('particles').appendChild(p);
994
+
995
+ setTimeout(() => p.remove(), 800);
996
+ }
997
+ }
998
+
999
+ // Add keyframes for flyout dynamically
1000
+ const styleSheet = document.createElement("style");
1001
+ styleSheet.innerText = `
1002
+ @keyframes flyout {
1003
+ 0% { transform: translate(0, 0) scale(1); opacity: 1; }
1004
+ 100% { transform: translate(var(--tx), var(--ty)) scale(0); opacity: 0; }
1005
+ }`;
1006
+ document.head.appendChild(styleSheet);
1007
+
1008
+
1009
+ // --- Ukládání / Načítání ---
1010
+ function loadGlobalStats() {
1011
+ const saved = localStorage.getItem('mathFortressStats');
1012
+ if (saved) {
1013
+ const parsed = JSON.parse(saved);
1014
+ state.stats.maxWave = parsed.maxWave || 0;
1015
+ state.stats.gamesPlayed = parsed.gamesPlayed || 0;
1016
+ state.stats.totalCoins = parsed.totalCoins || 0;
1017
+ }
1018
+ }
1019
+
1020
+ function saveGlobalStats() {
1021
+ localStorage.setItem('mathFortressStats', JSON.stringify({
1022
+ maxWave: state.stats.maxWave,
1023
+ gamesPlayed: state.stats.gamesPlayed,
1024
+ totalCoins: state.stats.totalCoins
1025
+ }));
1026
+ }
1027
+
1028
+ function updateGlobalStatsUI() {
1029
+ els.globalBestWave.textContent = state.stats.maxWave;
1030
+ els.globalGamesPlayed.textContent = state.stats.gamesPlayed;
1031
+ els.globalTotalCoins.textContent = state.stats.totalCoins;
1032
+ }
1033
+
1034
+ // Spuštění po načtení
1035
+ window.onload = init;
assets/bg1.jpg ADDED

Git LFS Details

  • SHA256: 8a8ab1b22437e06b1d20d718d1ba0f1a8f3558c94573526f11c8371e7fafee6f
  • Pointer size: 131 Bytes
  • Size of remote file: 142 kB
assets/bg2.jpg ADDED

Git LFS Details

  • SHA256: d7e3f973ad3d609b88dd08367c7a700070790edada1135c0c56eb08bc717c330
  • Pointer size: 131 Bytes
  • Size of remote file: 186 kB
assets/bg3.jpg ADDED

Git LFS Details

  • SHA256: 3eeaa96ca10ba05402984d14a0492580deafaa8846cfa15337ba8e2b5c07c26e
  • Pointer size: 131 Bytes
  • Size of remote file: 146 kB
assets/boss.svg ADDED
assets/castle1.svg ADDED
assets/castle2.svg ADDED
assets/castle3.svg ADDED
assets/enemy1.svg ADDED
assets/enemy2.svg ADDED
assets/enemy3.svg ADDED
assets/princess.svg ADDED
assets/projectile.svg ADDED
assets/unicorn.svg ADDED
index.html CHANGED
@@ -1,19 +1,150 @@
1
- <!doctype html>
2
- <html>
3
- <head>
4
- <meta charset="utf-8" />
5
- <meta name="viewport" content="width=device-width" />
6
- <title>My static Space</title>
7
- <link rel="stylesheet" href="style.css" />
8
- </head>
9
- <body>
10
- <div class="card">
11
- <h1>Welcome to your static Space!</h1>
12
- <p>You can modify this app directly by editing <i>index.html</i> in the Files and versions tab.</p>
13
- <p>
14
- Also don't forget to check the
15
- <a href="https://huggingface.co/docs/hub/spaces" target="_blank">Spaces documentation</a>.
16
- </p>
17
- </div>
18
- </body>
19
- </html>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="cs">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Matematická Pevnost: Záchrana Jednorožce</title>
7
+ <link rel="stylesheet" href="style.css">
8
+ </head>
9
+ <body>
10
+ <div id="game-container">
11
+ <!-- Hlavní Menu -->
12
+ <div id="main-menu" class="screen">
13
+ <h1>Matematická Pevnost</h1>
14
+ <p>Postav hrad, ubraň ho před padouši a zachraň jednorožce!</p>
15
+ <button id="btn-start" class="btn primary">Hrát</button>
16
+ <button id="btn-stats-menu" class="btn secondary">Statistiky</button>
17
+ </div>
18
+
19
+ <!-- Fáze 1: Stavění -->
20
+ <div id="build-screen" class="screen hidden">
21
+ <h2>Fáze 1: Stavba Hradu</h2>
22
+
23
+ <div id="build-style-selection">
24
+ <p>Vyber si vzhled svého hradu:</p>
25
+ <div class="style-chooser">
26
+ <div class="style-option selected" data-style="castle1" style="background-image: url('assets/castle1.svg');"></div>
27
+ <div class="style-option" data-style="castle2" style="background-image: url('assets/castle2.svg');"></div>
28
+ <div class="style-option" data-style="castle3" style="background-image: url('assets/castle3.svg');"></div>
29
+ </div>
30
+ <button id="btn-start-build" class="btn primary">Začít stavět!</button>
31
+ </div>
32
+
33
+ <div id="build-action-area" class="hidden" style="display: flex; flex-direction: column; align-items: center; width: 100%;">
34
+ <div class="progress-bar-container">
35
+ <div id="build-progress" class="progress-bar"></div>
36
+ </div>
37
+ <p id="build-progress-text">Krok 0 / 7</p>
38
+
39
+ <div class="build-visual-container">
40
+ <div id="build-visual-step" class="build-visual-step" style="height: 0%;"></div>
41
+ </div>
42
+
43
+ <div id="build-math-problem" class="math-problem"></div>
44
+
45
+ <div class="input-area">
46
+ <input type="number" id="build-input" class="num-input" autocomplete="off" placeholder="Zadej výsledek">
47
+ <div id="build-compare-buttons" class="compare-buttons hidden">
48
+ <button class="btn compare-btn" data-val="<"><</button>
49
+ <button class="btn compare-btn" data-val="=">=</button>
50
+ <button class="btn compare-btn" data-val=">">></button>
51
+ </div>
52
+ </div>
53
+ </div>
54
+ </div>
55
+
56
+ <!-- Fáze 2: Obrana -->
57
+ <div id="defend-screen" class="screen hidden">
58
+ <div class="top-hud">
59
+ <div class="hud-item">Vlna: <span id="wave-display">1</span></div>
60
+ <div class="hud-item">Mince: <span id="coins-display">0</span> 🪙</div>
61
+ <div class="hud-item streak-display">Streak: <span id="streak-display">0</span> 🔥</div>
62
+ </div>
63
+
64
+ <div class="castle-area">
65
+ <div class="castle-container">
66
+ <div class="castle" id="main-castle"></div>
67
+ <div class="princess"></div>
68
+ </div>
69
+ <div class="hp-bar-container">
70
+ <div id="castle-hp-bar" class="hp-bar"></div>
71
+ </div>
72
+ <p id="castle-hp-text">100 / 100</p>
73
+ </div>
74
+
75
+ <!-- Prostor pro nepřátele -->
76
+ <div id="enemies-area"></div>
77
+
78
+ <!-- QTE Jednorožec -->
79
+ <div id="qte-container" class="hidden">
80
+ <div class="qte-character">🦄 Pomoc!</div>
81
+ <div class="progress-bar-container qte-timer-container">
82
+ <div id="qte-timer-bar" class="progress-bar qte-timer"></div>
83
+ </div>
84
+ <div id="qte-math-problem" class="math-problem"></div>
85
+ </div>
86
+
87
+ <div class="input-area">
88
+ <input type="number" id="defend-input" class="num-input" autocomplete="off" placeholder="Zadej výsledek">
89
+ <div id="defend-compare-buttons" class="compare-buttons hidden">
90
+ <button class="btn compare-btn" data-val="<"><</button>
91
+ <button class="btn compare-btn" data-val="=">=</button>
92
+ <button class="btn compare-btn" data-val=">">></button>
93
+ </div>
94
+ </div>
95
+ </div>
96
+
97
+ <!-- Obchod -->
98
+ <div id="shop-screen" class="screen hidden">
99
+ <h2>Obchod - Konec vlny <span id="shop-wave-num"></span></h2>
100
+ <p>Máš: <span id="shop-coins"></span> 🪙</p>
101
+ <div class="shop-items">
102
+ <div class="shop-item">
103
+ <h3>Pevnější Hradby</h3>
104
+ <p>Zvýší maximální životy hradu (+20 HP)</p>
105
+ <button class="btn shop-buy-btn" data-upgrade="hp" id="buy-hp">Koupit (50 🪙)</button>
106
+ </div>
107
+ <div class="shop-item">
108
+ <h3>Zpomalovací Aura</h3>
109
+ <p>Zpomalí pohyb nepřátel</p>
110
+ <button class="btn shop-buy-btn" data-upgrade="slow" id="buy-slow">Koupit (100 🪙)</button>
111
+ </div>
112
+ <div class="shop-item">
113
+ <h3>Vílí Pomocník</h3>
114
+ <p>Občas sám zaútočí na nepřítele</p>
115
+ <button class="btn shop-buy-btn" data-upgrade="fairy" id="buy-fairy">Koupit (150 🪙)</button>
116
+ </div>
117
+ </div>
118
+ <button id="btn-next-wave" class="btn primary mt-2">Další Vlna!</button>
119
+ </div>
120
+
121
+ <!-- Game Over & Statistiky -->
122
+ <div id="game-over-screen" class="screen hidden">
123
+ <h2>Hrad padl! 💥</h2>
124
+ <div class="stats-container">
125
+ <p>Dosažená vlna: <span id="stat-wave"></span></p>
126
+ <p>Celkem skóre: <span id="stat-score"></span></p>
127
+ <p>Nejdelší streak: <span id="stat-streak"></span> 🔥</p>
128
+ <p>Zabito nepřátel: <span id="stat-kills"></span></p>
129
+ </div>
130
+ <button id="btn-restart" class="btn primary mt-2">Hrát znovu</button>
131
+ <button id="btn-to-main" class="btn secondary mt-2">Hlavní Menu</button>
132
+ </div>
133
+
134
+ <!-- Trvalé statistiky (z Menu) -->
135
+ <div id="stats-menu-screen" class="screen hidden">
136
+ <h2>Síň Slávy</h2>
137
+ <div class="stats-container">
138
+ <p>Nejvyšší vlna: <span id="global-best-wave">0</span></p>
139
+ <p>Celkem odehráno her: <span id="global-games-played">0</span></p>
140
+ <p>Nasbíráno mincí (historicky): <span id="global-total-coins">0</span></p>
141
+ </div>
142
+ <button id="btn-stats-back" class="btn secondary mt-2">Zpět</button>
143
+ </div>
144
+
145
+ <!-- Particle container pro efekty -->
146
+ <div id="particles"></div>
147
+ </div>
148
+ <script src="app.js"></script>
149
+ </body>
150
+ </html>
style.css CHANGED
@@ -1,28 +1,434 @@
 
 
 
 
 
 
 
 
1
  body {
2
- padding: 2rem;
3
- font-family: -apple-system, BlinkMacSystemFont, "Arial", sans-serif;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4
  }
5
 
 
6
  h1 {
7
- font-size: 16px;
8
- margin-top: 0;
 
 
 
 
 
 
 
 
 
9
  }
10
 
11
  p {
12
- color: rgb(107, 114, 128);
13
- font-size: 15px;
14
- margin-bottom: 10px;
15
- margin-top: 5px;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
16
  }
 
 
17
 
18
- .card {
19
- max-width: 620px;
20
- margin: 0 auto;
21
- padding: 16px;
22
- border: 1px solid lightgray;
23
- border-radius: 16px;
24
  }
25
 
26
- .card p:last-child {
27
- margin-bottom: 0;
 
 
 
 
 
28
  }
 
 
 
 
 
1
+ /* Reset a základní nastavení */
2
+ * {
3
+ box-sizing: border-box;
4
+ margin: 0;
5
+ padding: 0;
6
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
7
+ }
8
+
9
  body {
10
+ background-color: #1a1a2e;
11
+ color: #fff;
12
+ display: flex;
13
+ justify-content: center;
14
+ align-items: center;
15
+ height: 100vh;
16
+ overflow: hidden;
17
+ }
18
+
19
+ #game-container {
20
+ width: 800px;
21
+ height: 600px;
22
+ background: linear-gradient(135deg, #16213e, #0f3460);
23
+ background-size: cover;
24
+ background-position: center;
25
+ border-radius: 15px;
26
+ box-shadow: 0 0 20px rgba(0, 0, 0, 0.5);
27
+ position: relative;
28
+ overflow: hidden;
29
+ display: flex;
30
+ flex-direction: column;
31
+ transition: background-image 1s ease;
32
+ }
33
+
34
+ .bg-1 { background-image: url('assets/bg1.jpg') !important; }
35
+ .bg-2 { background-image: url('assets/bg2.jpg') !important; }
36
+ .bg-3 { background-image: url('assets/bg3.jpg') !important; }
37
+
38
+ .screen {
39
+ width: 100%;
40
+ height: 100%;
41
+ position: absolute;
42
+ top: 0;
43
+ left: 0;
44
+ display: flex;
45
+ flex-direction: column;
46
+ align-items: center;
47
+ justify-content: center;
48
+ padding: 20px;
49
+ }
50
+
51
+ .hidden {
52
+ display: none !important;
53
  }
54
 
55
+ /* Typografie a Texty */
56
  h1 {
57
+ font-size: 3rem;
58
+ color: #e94560;
59
+ text-shadow: 2px 2px 4px #000;
60
+ margin-bottom: 10px;
61
+ text-align: center;
62
+ }
63
+
64
+ h2 {
65
+ font-size: 2.5rem;
66
+ color: #43bccd;
67
+ margin-bottom: 20px;
68
  }
69
 
70
  p {
71
+ font-size: 1.2rem;
72
+ margin-bottom: 20px;
73
+ text-align: center;
74
+ }
75
+
76
+ /* Tlačítka */
77
+ .btn {
78
+ padding: 10px 20px;
79
+ font-size: 1.2rem;
80
+ border: none;
81
+ border-radius: 8px;
82
+ cursor: pointer;
83
+ margin: 10px;
84
+ transition: transform 0.1s, background-color 0.2s;
85
+ font-weight: bold;
86
+ }
87
+
88
+ .btn:active {
89
+ transform: scale(0.95);
90
+ }
91
+
92
+ .btn:disabled {
93
+ opacity: 0.5;
94
+ cursor: not-allowed;
95
+ }
96
+
97
+ .primary {
98
+ background-color: #e94560;
99
+ color: white;
100
+ }
101
+ .primary:hover:not(:disabled) { background-color: #ff5c77; }
102
+
103
+ .secondary {
104
+ background-color: #43bccd;
105
+ color: white;
106
+ }
107
+ .secondary:hover:not(:disabled) { background-color: #5ed6e6; }
108
+
109
+ .mt-2 { margin-top: 20px; }
110
+
111
+ /* Progress bary (Stavba, HP, QTE) */
112
+ .progress-bar-container {
113
+ width: 80%;
114
+ height: 30px;
115
+ background-color: rgba(0,0,0,0.5);
116
+ border-radius: 15px;
117
+ overflow: hidden;
118
+ margin-bottom: 10px;
119
+ border: 2px solid #333;
120
+ }
121
+
122
+ .progress-bar {
123
+ height: 100%;
124
+ width: 0%;
125
+ transition: width 0.3s ease;
126
+ }
127
+
128
+ #build-progress { background-color: #fca311; }
129
+
130
+ .build-visual-container {
131
+ width: 200px;
132
+ height: 200px;
133
+ margin: 20px 0;
134
+ position: relative;
135
+ border: 2px dashed rgba(255,255,255,0.3);
136
+ border-radius: 10px;
137
+ overflow: hidden;
138
+ }
139
+
140
+ .build-visual-step {
141
+ position: absolute;
142
+ bottom: 0;
143
+ left: 0;
144
+ width: 100%;
145
+ background-size: cover;
146
+ background-position: bottom;
147
+ background-repeat: no-repeat;
148
+ transition: height 0.5s ease;
149
+ }
150
+
151
+ .style-chooser {
152
+ display: flex;
153
+ gap: 20px;
154
+ margin-bottom: 20px;
155
+ }
156
+
157
+ .style-option {
158
+ width: 80px;
159
+ height: 80px;
160
+ border: 3px solid transparent;
161
+ border-radius: 10px;
162
+ cursor: pointer;
163
+ background-size: cover;
164
+ background-position: center;
165
+ transition: transform 0.2s, border-color 0.2s;
166
+ background-color: rgba(255,255,255,0.1);
167
+ }
168
+
169
+ .style-option:hover {
170
+ transform: scale(1.1);
171
+ }
172
+
173
+ .style-option.selected {
174
+ border-color: #fca311;
175
+ transform: scale(1.1);
176
+ box-shadow: 0 0 15px #fca311;
177
+ }
178
+
179
+
180
+ /* HP Bar specificky */
181
+ .hp-bar-container {
182
+ width: 150px;
183
+ height: 20px;
184
+ background-color: #333;
185
+ border-radius: 10px;
186
+ overflow: hidden;
187
+ margin: 5px auto;
188
+ }
189
+ .hp-bar {
190
+ height: 100%;
191
+ width: 100%;
192
+ background-color: #e94560;
193
+ transition: width 0.3s;
194
+ }
195
+
196
+ /* Matematické zadání a Input */
197
+ .math-problem {
198
+ font-size: 4rem;
199
+ font-weight: bold;
200
+ margin: 20px 0;
201
+ background: rgba(255,255,255,0.1);
202
+ padding: 20px 40px;
203
+ border-radius: 15px;
204
+ text-shadow: 2px 2px #000;
205
+ }
206
+
207
+ .input-area {
208
+ display: flex;
209
+ flex-direction: column;
210
+ align-items: center;
211
+ margin-top: 20px;
212
+ z-index: 10;
213
+ }
214
+
215
+ .num-input {
216
+ font-size: 2rem;
217
+ padding: 10px;
218
+ width: 200px;
219
+ text-align: center;
220
+ border-radius: 10px;
221
+ border: none;
222
+ outline: none;
223
+ background-color: #fff;
224
+ color: #000;
225
+ }
226
+
227
+ .compare-buttons {
228
+ display: flex;
229
+ gap: 20px;
230
+ }
231
+ .compare-btn {
232
+ font-size: 2rem;
233
+ width: 60px;
234
+ height: 60px;
235
+ background-color: #43bccd;
236
+ }
237
+
238
+ /* HUD Obrana */
239
+ #defend-screen {
240
+ justify-content: flex-start;
241
+ }
242
+
243
+ .top-hud {
244
+ width: 100%;
245
+ display: flex;
246
+ justify-content: space-between;
247
+ padding: 10px 20px;
248
+ background: rgba(0,0,0,0.4);
249
+ font-size: 1.2rem;
250
+ font-weight: bold;
251
+ }
252
+
253
+ .streak-display { color: #fca311; }
254
+
255
+ .castle-area {
256
+ position: absolute;
257
+ left: 20px;
258
+ top: 50%;
259
+ transform: translateY(-50%);
260
+ text-align: center;
261
+ z-index: 5;
262
+ display: flex;
263
+ flex-direction: column;
264
+ align-items: center;
265
+ }
266
+
267
+ .castle-container {
268
+ display: flex;
269
+ align-items: flex-end;
270
+ margin-bottom: 10px;
271
+ gap: 10px;
272
+ }
273
+
274
+ .castle {
275
+ width: 100px;
276
+ height: 100px;
277
+ background-size: contain;
278
+ background-repeat: no-repeat;
279
+ background-position: bottom center;
280
+ }
281
+
282
+ .princess {
283
+ width: 40px;
284
+ height: 40px;
285
+ background-image: url('assets/princess.svg');
286
+ background-size: contain;
287
+ background-repeat: no-repeat;
288
+ background-position: bottom center;
289
+ }
290
+
291
+ #enemies-area {
292
+ position: absolute;
293
+ top: 60px;
294
+ left: 150px;
295
+ right: 0;
296
+ bottom: 120px; /* misto pro input dole */
297
+ overflow: hidden;
298
+ }
299
+
300
+ /* Nepřítel */
301
+ .enemy {
302
+ position: absolute;
303
+ display: flex;
304
+ flex-direction: column;
305
+ align-items: center;
306
+ transition: left 0.1s linear;
307
+ }
308
+
309
+ .enemy-sprite {
310
+ width: 60px;
311
+ height: 60px;
312
+ background-size: contain;
313
+ background-repeat: no-repeat;
314
+ background-position: center;
315
+ animation: bounce 1s infinite alternate;
316
+ }
317
+
318
+ .enemy-boss {
319
+ width: 120px;
320
+ height: 120px;
321
+ background-image: url('assets/boss.svg');
322
+ }
323
+
324
+ .projectile {
325
+ position: absolute;
326
+ width: 20px;
327
+ height: 20px;
328
+ background-image: url('assets/projectile.svg');
329
+ background-size: contain;
330
+ background-repeat: no-repeat;
331
+ background-position: center;
332
+ z-index: 10;
333
+ }
334
+
335
+ .enemy-math {
336
+ background: rgba(0,0,0,0.7);
337
+ padding: 2px 8px;
338
+ border-radius: 5px;
339
+ font-weight: bold;
340
+ font-size: 1rem;
341
+ white-space: nowrap;
342
+ border: 1px solid #fff;
343
+ }
344
+ .enemy.targeted .enemy-math {
345
+ border-color: #fca311;
346
+ color: #fca311;
347
+ box-shadow: 0 0 10px #fca311;
348
+ }
349
+
350
+
351
+ /* QTE */
352
+ #qte-container {
353
+ position: absolute;
354
+ top: 50%;
355
+ left: 50%;
356
+ transform: translate(-50%, -50%);
357
+ background: rgba(142, 68, 173, 0.9);
358
+ padding: 20px;
359
+ border-radius: 15px;
360
+ border: 3px solid #f1c40f;
361
+ box-shadow: 0 0 30px #f1c40f;
362
+ text-align: center;
363
+ z-index: 20;
364
+ width: 400px;
365
+ }
366
+
367
+ .qte-character {
368
+ width: 60px;
369
+ height: 60px;
370
+ background-image: url('assets/unicorn.svg');
371
+ background-size: contain;
372
+ background-repeat: no-repeat;
373
+ background-position: center;
374
+ margin: 0 auto 10px;
375
+ animation: shake 0.5s infinite;
376
+ }
377
+ .qte-timer-container { width: 100%; height: 15px; margin: 0 auto; }
378
+ .qte-timer { background-color: #f1c40f; width: 100%; }
379
+
380
+ /* Animace */
381
+ @keyframes bounce {
382
+ 0% { transform: translateY(0); }
383
+ 100% { transform: translateY(-10px); }
384
+ }
385
+ @keyframes shake {
386
+ 0% { transform: translate(1px, 1px) rotate(0deg); }
387
+ 10% { transform: translate(-1px, -2px) rotate(-1deg); }
388
+ 20% { transform: translate(-3px, 0px) rotate(1deg); }
389
+ 30% { transform: translate(3px, 2px) rotate(0deg); }
390
+ 40% { transform: translate(1px, -1px) rotate(1deg); }
391
+ 50% { transform: translate(-1px, 2px) rotate(-1deg); }
392
+ 60% { transform: translate(-3px, 1px) rotate(0deg); }
393
+ 70% { transform: translate(3px, 1px) rotate(-1deg); }
394
+ 80% { transform: translate(-1px, -1px) rotate(1deg); }
395
+ 90% { transform: translate(1px, 2px) rotate(0deg); }
396
+ 100% { transform: translate(1px, -2px) rotate(-1deg); }
397
+ }
398
+
399
+ /* Obchod */
400
+ .shop-items {
401
+ display: flex;
402
+ gap: 15px;
403
+ margin-bottom: 20px;
404
+ }
405
+ .shop-item {
406
+ background: rgba(255,255,255,0.1);
407
+ padding: 15px;
408
+ border-radius: 10px;
409
+ width: 200px;
410
+ text-align: center;
411
  }
412
+ .shop-item h3 { font-size: 1.2rem; margin-bottom: 10px; color: #fca311;}
413
+ .shop-item p { font-size: 0.9rem; margin-bottom: 15px; }
414
 
415
+ /* Obrazovka dole pro input v obrane */
416
+ #defend-screen > .input-area {
417
+ position: absolute;
418
+ bottom: 20px;
419
+ left: 50%;
420
+ transform: translateX(-50%);
421
  }
422
 
423
+ /* Particles */
424
+ .particle {
425
+ position: absolute;
426
+ pointer-events: none;
427
+ border-radius: 50%;
428
+ animation: fadeout 0.8s forwards;
429
+ z-index: 100;
430
  }
431
+ @keyframes fadeout {
432
+ 0% { opacity: 1; transform: scale(1); }
433
+ 100% { opacity: 0; transform: scale(2); }
434
+ }