jeevav62 commited on
Commit
8271925
·
verified ·
1 Parent(s): cc72da1

Upload folder using huggingface_hub

Browse files
Files changed (1) hide show
  1. index.html +654 -19
index.html CHANGED
@@ -1,19 +1,654 @@
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="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Neon Tic Tac Toe</title>
7
+ <!-- Google Fonts for Modern Typography -->
8
+ <link rel="preconnect" href="https://fonts.googleapis.com">
9
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
10
+ <link href="https://fonts.googleapis.com/css2?family=Outfit:wght@300;500;700&display=swap" rel="stylesheet">
11
+
12
+ <style>
13
+ :root {
14
+ --bg-color: #0f172a;
15
+ --surface-color: #1e293b;
16
+ --primary-color: #8b5cf6; /* Violet */
17
+ --secondary-color: #06b6d4; /* Cyan */
18
+ --text-color: #f1f5f9;
19
+ --text-muted: #94a3b8;
20
+ --win-color: #10b981;
21
+ --font-main: 'Outfit', sans-serif;
22
+ --cell-size: 100px;
23
+ --gap: 15px;
24
+ }
25
+
26
+ * {
27
+ box-sizing: border-box;
28
+ margin: 0;
29
+ padding: 0;
30
+ user-select: none;
31
+ }
32
+
33
+ body {
34
+ font-family: var(--font-main);
35
+ background-color: var(--bg-color);
36
+ color: var(--text-color);
37
+ min-height: 100vh;
38
+ display: flex;
39
+ flex-direction: column;
40
+ align-items: center;
41
+ justify-content: center;
42
+ overflow-x: hidden;
43
+ }
44
+
45
+ /* --- Header --- */
46
+ header {
47
+ position: absolute;
48
+ top: 20px;
49
+ width: 100%;
50
+ text-align: center;
51
+ z-index: 10;
52
+ }
53
+
54
+ h1 {
55
+ font-weight: 700;
56
+ font-size: 2.5rem;
57
+ letter-spacing: -1px;
58
+ background: linear-gradient(to right, var(--primary-color), var(--secondary-color));
59
+ -webkit-background-clip: text;
60
+ -webkit-text-fill-color: transparent;
61
+ margin-bottom: 5px;
62
+ }
63
+
64
+ .built-with {
65
+ font-size: 0.85rem;
66
+ color: var(--text-muted);
67
+ text-decoration: none;
68
+ opacity: 0.7;
69
+ transition: opacity 0.3s;
70
+ }
71
+
72
+ .built-with:hover {
73
+ opacity: 1;
74
+ text-decoration: underline;
75
+ }
76
+
77
+ /* --- Container --- */
78
+ .app-container {
79
+ width: 100%;
80
+ max-width: 500px;
81
+ padding: 20px;
82
+ position: relative;
83
+ perspective: 1000px;
84
+ }
85
+
86
+ /* --- Screens --- */
87
+ .screen {
88
+ background: var(--surface-color);
89
+ padding: 2rem;
90
+ border-radius: 24px;
91
+ box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.5);
92
+ border: 1px solid rgba(255, 255, 255, 0.05);
93
+ transition: all 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275);
94
+ display: flex;
95
+ flex-direction: column;
96
+ align-items: center;
97
+ gap: 1.5rem;
98
+ }
99
+
100
+ .hidden {
101
+ display: none;
102
+ opacity: 0;
103
+ transform: scale(0.9);
104
+ pointer-events: none;
105
+ }
106
+
107
+ .active {
108
+ display: flex;
109
+ opacity: 1;
110
+ transform: scale(1);
111
+ }
112
+
113
+ /* --- Input Forms --- */
114
+ .input-group {
115
+ width: 100%;
116
+ text-align: center;
117
+ }
118
+
119
+ label {
120
+ display: block;
121
+ font-size: 0.9rem;
122
+ color: var(--text-muted);
123
+ margin-bottom: 8px;
124
+ text-transform: uppercase;
125
+ letter-spacing: 1px;
126
+ }
127
+
128
+ input {
129
+ width: 100%;
130
+ padding: 15px;
131
+ border-radius: 12px;
132
+ border: 2px solid transparent;
133
+ background: rgba(0,0,0,0.2);
134
+ color: white;
135
+ font-size: 1.1rem;
136
+ font-family: inherit;
137
+ text-align: center;
138
+ transition: 0.3s;
139
+ outline: none;
140
+ }
141
+
142
+ input:focus {
143
+ border-color: var(--primary-color);
144
+ background: rgba(0,0,0,0.4);
145
+ }
146
+
147
+ /* --- Buttons --- */
148
+ .btn {
149
+ padding: 15px 40px;
150
+ border-radius: 50px;
151
+ border: none;
152
+ font-size: 1.1rem;
153
+ font-weight: 700;
154
+ cursor: pointer;
155
+ transition: transform 0.2s, box-shadow 0.2s;
156
+ font-family: inherit;
157
+ }
158
+
159
+ .btn-primary {
160
+ background: linear-gradient(135deg, var(--primary-color), #6d28d9);
161
+ color: white;
162
+ box-shadow: 0 10px 20px -5px rgba(139, 92, 246, 0.4);
163
+ }
164
+
165
+ .btn-primary:hover {
166
+ transform: translateY(-2px);
167
+ box-shadow: 0 15px 25px -5px rgba(139, 92, 246, 0.5);
168
+ }
169
+
170
+ .btn-secondary {
171
+ background: transparent;
172
+ color: var(--text-muted);
173
+ font-size: 0.9rem;
174
+ padding: 10px;
175
+ }
176
+
177
+ .btn-secondary:hover {
178
+ color: white;
179
+ }
180
+
181
+ /* --- Scoreboard --- */
182
+ .scoreboard {
183
+ display: flex;
184
+ justify-content: space-between;
185
+ width: 100%;
186
+ background: rgba(0,0,0,0.2);
187
+ padding: 15px;
188
+ border-radius: 16px;
189
+ margin-bottom: 10px;
190
+ }
191
+
192
+ .player-score {
193
+ display: flex;
194
+ flex-direction: column;
195
+ align-items: center;
196
+ flex: 1;
197
+ }
198
+
199
+ .score-val {
200
+ font-size: 1.5rem;
201
+ font-weight: 700;
202
+ }
203
+
204
+ .score-label {
205
+ font-size: 0.75rem;
206
+ color: var(--text-muted);
207
+ text-transform: uppercase;
208
+ }
209
+
210
+ .vs-divider {
211
+ display: flex;
212
+ align-items: center;
213
+ color: var(--text-muted);
214
+ font-weight: 700;
215
+ padding: 0 10px;
216
+ }
217
+
218
+ .active-turn {
219
+ color: var(--win-color);
220
+ text-shadow: 0 0 10px rgba(16, 185, 129, 0.4);
221
+ }
222
+
223
+ /* --- Game Grid --- */
224
+ .game-board {
225
+ display: grid;
226
+ grid-template-columns: repeat(3, 1fr);
227
+ gap: var(--gap);
228
+ margin: 20px 0;
229
+ }
230
+
231
+ .cell {
232
+ width: var(--cell-size);
233
+ height: var(--cell-size);
234
+ background: rgba(255,255,255,0.03);
235
+ border-radius: 16px;
236
+ display: flex;
237
+ align-items: center;
238
+ justify-content: center;
239
+ font-size: 3.5rem;
240
+ font-weight: 700;
241
+ cursor: pointer;
242
+ transition: all 0.2s ease;
243
+ position: relative;
244
+ box-shadow: inset 0 0 0 2px transparent;
245
+ }
246
+
247
+ .cell:hover:not(.taken) {
248
+ background: rgba(255,255,255,0.07);
249
+ transform: scale(1.02);
250
+ }
251
+
252
+ .cell.x {
253
+ color: var(--primary-color);
254
+ text-shadow: 0 0 15px rgba(139, 92, 246, 0.3);
255
+ }
256
+
257
+ .cell.o {
258
+ color: var(--secondary-color);
259
+ text-shadow: 0 0 15px rgba(6, 182, 212, 0.3);
260
+ }
261
+
262
+ .cell.win {
263
+ background: rgba(16, 185, 129, 0.2);
264
+ box-shadow: inset 0 0 0 2px var(--win-color);
265
+ animation: pulse 1s infinite;
266
+ }
267
+
268
+ /* --- Turn Indicator --- */
269
+ .turn-display {
270
+ font-size: 1.2rem;
271
+ font-weight: 500;
272
+ margin-bottom: 10px;
273
+ height: 30px;
274
+ }
275
+
276
+ /* --- Animations --- */
277
+ @keyframes popIn {
278
+ 0% { transform: scale(0); opacity: 0; }
279
+ 80% { transform: scale(1.1); opacity: 1; }
280
+ 100% { transform: scale(1); }
281
+ }
282
+
283
+ .pop {
284
+ animation: popIn 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275) forwards;
285
+ }
286
+
287
+ @keyframes pulse {
288
+ 0% { transform: scale(1); }
289
+ 50% { transform: scale(1.03); }
290
+ 100% { transform: scale(1); }
291
+ }
292
+
293
+ /* --- Canvas for Confetti --- */
294
+ #confetti-canvas {
295
+ position: fixed;
296
+ top: 0;
297
+ left: 0;
298
+ width: 100%;
299
+ height: 100%;
300
+ pointer-events: none;
301
+ z-index: 100;
302
+ }
303
+
304
+ /* --- Responsive --- */
305
+ @media (max-width: 400px) {
306
+ :root { --cell-size: 80px; --gap: 10px; }
307
+ h1 { font-size: 2rem; }
308
+ }
309
+ </style>
310
+ </head>
311
+ <body>
312
+
313
+ <canvas id="confetti-canvas"></canvas>
314
+
315
+ <header>
316
+ <h1>Tic Tac Toe</h1>
317
+ <a href="https://huggingface.co/spaces/akhaliq/anycoder" target="_blank" class="built-with">Built with anycoder</a>
318
+ </header>
319
+
320
+ <div class="app-container">
321
+
322
+ <!-- Screen 1: Setup -->
323
+ <div id="setup-screen" class="screen active">
324
+ <h2 style="font-weight: 300;">Enter Player Names</h2>
325
+
326
+ <div class="input-group">
327
+ <label>Player X</label>
328
+ <input type="text" id="p1-name" placeholder="Name" maxlength="10">
329
+ </div>
330
+
331
+ <div class="input-group">
332
+ <label>Player O</label>
333
+ <input type="text" id="p2-name" placeholder="Name" maxlength="10">
334
+ </div>
335
+
336
+ <button class="btn btn-primary" onclick="startGame()">Start Game</button>
337
+
338
+ <div id="reset-history-container" style="display:none; text-align: center; margin-top: 10px;">
339
+ <button class="btn btn-secondary" onclick="clearHistory()">Reset History</button>
340
+ </div>
341
+ </div>
342
+
343
+ <!-- Screen 2: Gameplay -->
344
+ <div id="game-screen" class="screen hidden">
345
+ <div class="turn-display" id="turn-indicator">Player X's Turn</div>
346
+
347
+ <div class="scoreboard">
348
+ <div class="player-score" id="p1-score-box">
349
+ <span class="score-val" id="score-p1">0</span>
350
+ <span class="score-label" id="name-p1-display">P1</span>
351
+ <span style="font-size: 0.7rem; color: var(--primary-color)">Wins</span>
352
+ </div>
353
+ <div class="vs-divider">VS</div>
354
+ <div class="player-score" id="p2-score-box">
355
+ <span class="score-val" id="score-p2">0</span>
356
+ <span class="score-label" id="name-p2-display">P2</span>
357
+ <span style="font-size: 0.7rem; color: var(--secondary-color)">Wins</span>
358
+ </div>
359
+ </div>
360
+
361
+ <div class="game-board" id="board">
362
+ <!-- Cells generated by JS -->
363
+ </div>
364
+
365
+ <button class="btn btn-secondary" onclick="backToMenu()">Back to Menu</button>
366
+ <button class="btn btn-secondary" onclick="resetRound()">New Round</button>
367
+ </div>
368
+
369
+ </div>
370
+
371
+ <script>
372
+ /* --- State Management --- */
373
+ const gameState = {
374
+ board: Array(9).fill(null),
375
+ currentPlayer: 'X',
376
+ isGameActive: true,
377
+ players: {
378
+ p1: { name: 'Player X', score: 0, symbol: 'X' },
379
+ p2: { name: 'Player O', score: 0, symbol: 'O' }
380
+ }
381
+ };
382
+
383
+ /* --- DOM Elements --- */
384
+ const setupScreen = document.getElementById('setup-screen');
385
+ const gameScreen = document.getElementById('game-screen');
386
+ const boardEl = document.getElementById('board');
387
+ const p1Input = document.getElementById('p1-name');
388
+ const p2Input = document.getElementById('p2-name');
389
+ const turnIndicator = document.getElementById('turn-indicator');
390
+ const resetHistoryBtn = document.getElementById('reset-history-container');
391
+
392
+ /* --- Initialization --- */
393
+ window.onload = () => {
394
+ loadHistory();
395
+ createBoard();
396
+ };
397
+
398
+ function createBoard() {
399
+ boardEl.innerHTML = '';
400
+ for (let i = 0; i < 9; i++) {
401
+ const cell = document.createElement('div');
402
+ cell.classList.add('cell');
403
+ cell.setAttribute('data-index', i);
404
+ cell.addEventListener('click', handleCellClick);
405
+ boardEl.appendChild(cell);
406
+ }
407
+ }
408
+
409
+ /* --- Game Logic --- */
410
+ function startGame() {
411
+ const p1Val = p1Input.value.trim() || 'Player X';
412
+ const p2Val = p2Input.value.trim() || 'Player O';
413
+
414
+ gameState.players.p1.name = p1Val;
415
+ gameState.players.p2.name = p2Val;
416
+
417
+ // Save names to local storage for next time
418
+ localStorage.setItem('ttt_p1_name', p1Val);
419
+ localStorage.setItem('ttt_p2_name', p2Val);
420
+
421
+ updateUI();
422
+ switchScreen('game');
423
+ resetHistoryBtn.style.display = 'block';
424
+ }
425
+
426
+ function backToMenu() {
427
+ switchScreen('setup');
428
+ }
429
+
430
+ function resetRound() {
431
+ gameState.board = Array(9).fill(null);
432
+ gameState.currentPlayer = 'X';
433
+ gameState.isGameActive = true;
434
+
435
+ const cells = document.querySelectorAll('.cell');
436
+ cells.forEach(cell => {
437
+ cell.textContent = '';
438
+ cell.classList.remove('x', 'o', 'taken', 'win', 'pop');
439
+ });
440
+
441
+ updateTurnIndicator();
442
+ }
443
+
444
+ function handleCellClick(e) {
445
+ const cell = e.target;
446
+ const index = cell.getAttribute('data-index');
447
+
448
+ if (gameState.board[index] !== null || !gameState.isGameActive) return;
449
+
450
+ // Update Logic
451
+ gameState.board[index] = gameState.currentPlayer;
452
+
453
+ // Update Visuals
454
+ cell.textContent = gameState.currentPlayer;
455
+ cell.classList.add(gameState.currentPlayer.toLowerCase(), 'taken', 'pop');
456
+
457
+ // Check Win
458
+ if (checkWin()) {
459
+ endGame(false);
460
+ } else if (checkDraw()) {
461
+ endGame(true);
462
+ } else {
463
+ // Swap Turn
464
+ gameState.currentPlayer = gameState.currentPlayer === 'X' ? 'O' : 'X';
465
+ updateTurnIndicator();
466
+ }
467
+ }
468
+
469
+ function checkWin() {
470
+ const winConditions = [
471
+ [0, 1, 2], [3, 4, 5], [6, 7, 8], // Rows
472
+ [0, 3, 6], [1, 4, 7], [2, 5, 8], // Cols
473
+ [0, 4, 8], [2, 4, 6] // Diagonals
474
+ ];
475
+
476
+ return winConditions.some(combination => {
477
+ const [a, b, c] = combination;
478
+ if (gameState.board[a] &&
479
+ gameState.board[a] === gameState.board[b] &&
480
+ gameState.board[a] === gameState.board[c]) {
481
+
482
+ // Highlight winning cells
483
+ highlightWin(combination);
484
+ return true;
485
+ }
486
+ return false;
487
+ });
488
+ }
489
+
490
+ function checkDraw() {
491
+ return gameState.board.every(cell => cell !== null);
492
+ }
493
+
494
+ function endGame(isDraw) {
495
+ gameState.isGameActive = false;
496
+
497
+ if (isDraw) {
498
+ turnIndicator.textContent = "It's a Draw!";
499
+ turnIndicator.style.color = "var(--text-muted)";
500
+ } else {
501
+ const winnerName = gameState.currentPlayer === 'X'
502
+ ? gameState.players.p1.name
503
+ : gameState.players.p2.name;
504
+
505
+ turnIndicator.textContent = `${winnerName} Wins!`;
506
+ turnIndicator.style.color = "var(--win-color)";
507
+
508
+ // Update Score
509
+ if (gameState.currentPlayer === 'X') {
510
+ gameState.players.p1.score++;
511
+ document.getElementById('score-p1').textContent = gameState.players.p1.score;
512
+ } else {
513
+ gameState.players.p2.score++;
514
+ document.getElementById('score-p2').textContent = gameState.players.p2.score;
515
+ }
516
+
517
+ saveScore();
518
+ triggerConfetti();
519
+ }
520
+ }
521
+
522
+ function highlightWin(combo) {
523
+ const cells = document.querySelectorAll('.cell');
524
+ combo.forEach(index => {
525
+ cells[index].classList.add('win');
526
+ });
527
+ }
528
+
529
+ function updateTurnIndicator() {
530
+ const name = gameState.currentPlayer === 'X'
531
+ ? gameState.players.p1.name
532
+ : gameState.players.p2.name;
533
+
534
+ turnIndicator.textContent = `${name}'s Turn (${gameState.currentPlayer})`;
535
+ turnIndicator.style.color = gameState.currentPlayer === 'X' ? 'var(--primary-color)' : 'var(--secondary-color)';
536
+ }
537
+
538
+ function updateUI() {
539
+ document.getElementById('name-p1-display').textContent = gameState.players.p1.name;
540
+ document.getElementById('name-p2-display').textContent = gameState.players.p2.name;
541
+ document.getElementById('score-p1').textContent = gameState.players.p1.score;
542
+ document.getElementById('score-p2').textContent = gameState.players.p2.score;
543
+ updateTurnIndicator();
544
+ }
545
+
546
+ /* --- Storage & History --- */
547
+ function saveScore() {
548
+ const history = {
549
+ p1Score: gameState.players.p1.score,
550
+ p2Score: gameState.players.p2.score
551
+ };
552
+ localStorage.setItem('ttt_scores', JSON.stringify(history));
553
+ }
554
+
555
+ function loadHistory() {
556
+ // Load Names
557
+ const savedP1 = localStorage.getItem('ttt_p1_name');
558
+ const savedP2 = localStorage.getItem('ttt_p2_name');
559
+ if (savedP1) p1Input.value = savedP1;
560
+ if (savedP2) p2Input.value = savedP2;
561
+
562
+ // Load Scores
563
+ const savedScores = localStorage.getItem('ttt_scores');
564
+ if (savedScores) {
565
+ const scores = JSON.parse(savedScores);
566
+ gameState.players.p1.score = scores.p1Score;
567
+ gameState.players.p2.score = scores.p2Score;
568
+ resetHistoryBtn.style.display = 'block';
569
+ } else {
570
+ resetHistoryBtn.style.display = 'none';
571
+ }
572
+ }
573
+
574
+ function clearHistory() {
575
+ if(confirm("Reset all scores and history?")) {
576
+ localStorage.removeItem('ttt_scores');
577
+ localStorage.removeItem('ttt_p1_name');
578
+ localStorage.removeItem('ttt_p2_name');
579
+ gameState.players.p1.score = 0;
580
+ gameState.players.p2.score = 0;
581
+ p1Input.value = '';
582
+ p2Input.value = '';
583
+ resetHistoryBtn.style.display = 'none';
584
+ location.reload();
585
+ }
586
+ }
587
+
588
+ /* --- Helper --- */
589
+ function switchScreen(screenName) {
590
+ if (screenName === 'setup') {
591
+ setupScreen.classList.remove('hidden');
592
+ setupScreen.classList.add('active');
593
+ gameScreen.classList.remove('active');
594
+ gameScreen.classList.add('hidden');
595
+ } else {
596
+ setupScreen.classList.remove('active');
597
+ setupScreen.classList.add('hidden');
598
+ gameScreen.classList.remove('hidden');
599
+ gameScreen.classList.add('active');
600
+ resetRound();
601
+ }
602
+ }
603
+
604
+ /* --- Gamification: Simple Confetti Engine --- */
605
+ const canvas = document.getElementById('confetti-canvas');
606
+ const ctx = canvas.getContext('2d');
607
+ let particles = [];
608
+
609
+ function resizeCanvas() {
610
+ canvas.width = window.innerWidth;
611
+ canvas.height = window.innerHeight;
612
+ }
613
+ window.addEventListener('resize', resizeCanvas);
614
+ resizeCanvas();
615
+
616
+ function triggerConfetti() {
617
+ particles = [];
618
+ for(let i=0; i<150; i++) {
619
+ particles.push({
620
+ x: canvas.width / 2,
621
+ y: canvas.height / 2,
622
+ vx: (Math.random() - 0.5) * 20,
623
+ vy: (Math.random() - 1) * 20,
624
+ size: Math.random() * 8 + 4,
625
+ color: `hsl(${Math.random() * 360}, 100%, 50%)`,
626
+ life: 100
627
+ });
628
+ }
629
+ animateConfetti();
630
+ }
631
+
632
+ function animateConfetti() {
633
+ if(particles.length === 0) return;
634
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
635
+
636
+ particles.forEach((p, index) => {
637
+ p.x += p.vx;
638
+ p.y += p.vy;
639
+ p.vy += 0.5; // Gravity
640
+ p.life--;
641
+ p.size *= 0.96;
642
+
643
+ ctx.fillStyle = p.color;
644
+ ctx.fillRect(p.x, p.y, p.size, p.size);
645
+
646
+ if(p.life <= 0) particles.splice(index, 1);
647
+ });
648
+
649
+ requestAnimationFrame(animateConfetti);
650
+ }
651
+
652
+ </script>
653
+ </body>
654
+ </html>