rmz92002 commited on
Commit
20e3ce5
·
verified ·
1 Parent(s): b2ffdae

Upload folder using huggingface_hub

Browse files
Files changed (1) hide show
  1. index.html +671 -19
index.html CHANGED
@@ -1,19 +1,671 @@
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>Hyphen Hunter: Interactive Exercise</title>
7
+ <!-- Importing FontAwesome for Icons -->
8
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
9
+
10
+ <style>
11
+ :root {
12
+ --primary: #6366f1;
13
+ --primary-dark: #4f46e5;
14
+ --secondary: #ec4899;
15
+ --bg-dark: #0f172a;
16
+ --bg-card: #1e293b;
17
+ --text-main: #f8fafc;
18
+ --text-muted: #94a3b8;
19
+ --success: #22c55e;
20
+ --error: #ef4444;
21
+ --hyphen-color: #fbbf24;
22
+ }
23
+
24
+ * {
25
+ box-sizing: border-box;
26
+ margin: 0;
27
+ padding: 0;
28
+ font-family: 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
29
+ }
30
+
31
+ body {
32
+ background-color: var(--bg-dark);
33
+ color: var(--text-main);
34
+ min-height: 100vh;
35
+ display: flex;
36
+ flex-direction: column;
37
+ overflow-x: hidden;
38
+ }
39
+
40
+ /* --- Header --- */
41
+ header {
42
+ background: rgba(30, 41, 59, 0.8);
43
+ backdrop-filter: blur(10px);
44
+ padding: 1rem 2rem;
45
+ display: flex;
46
+ justify-content: space-between;
47
+ align-items: center;
48
+ border-bottom: 1px solid rgba(255, 255, 255, 0.1);
49
+ position: sticky;
50
+ top: 0;
51
+ z-index: 100;
52
+ }
53
+
54
+ .logo {
55
+ font-size: 1.5rem;
56
+ font-weight: 700;
57
+ background: linear-gradient(to right, var(--primary), var(--secondary));
58
+ -webkit-background-clip: text;
59
+ -webkit-text-fill-color: transparent;
60
+ display: flex;
61
+ align-items: center;
62
+ gap: 10px;
63
+ }
64
+
65
+ .built-with {
66
+ font-size: 0.9rem;
67
+ color: var(--text-muted);
68
+ text-decoration: none;
69
+ transition: color 0.3s;
70
+ }
71
+
72
+ .built-with:hover {
73
+ color: var(--primary);
74
+ }
75
+
76
+ /* --- Main Content --- */
77
+ main {
78
+ flex: 1;
79
+ display: flex;
80
+ flex-direction: column;
81
+ align-items: center;
82
+ justify-content: center;
83
+ padding: 2rem;
84
+ position: relative;
85
+ }
86
+
87
+ /* --- Game Container --- */
88
+ .game-container {
89
+ width: 100%;
90
+ max-width: 900px;
91
+ background: var(--bg-card);
92
+ border-radius: 20px;
93
+ padding: 3rem;
94
+ box-shadow: 0 20px 50px rgba(0, 0, 0, 0.5);
95
+ border: 1px solid rgba(255, 255, 255, 0.05);
96
+ text-align: center;
97
+ position: relative;
98
+ overflow: hidden;
99
+ }
100
+
101
+ /* --- Start Screen --- */
102
+ .screen {
103
+ display: none;
104
+ flex-direction: column;
105
+ align-items: center;
106
+ gap: 2rem;
107
+ animation: fadeIn 0.5s ease-out;
108
+ }
109
+
110
+ .screen.active {
111
+ display: flex;
112
+ }
113
+
114
+ h1 {
115
+ font-size: 2.5rem;
116
+ margin-bottom: 1rem;
117
+ }
118
+
119
+ .instruction {
120
+ color: var(--text-muted);
121
+ font-size: 1.1rem;
122
+ max-width: 600px;
123
+ line-height: 1.6;
124
+ }
125
+
126
+ .highlight {
127
+ color: var(--hyphen-color);
128
+ font-weight: bold;
129
+ font-size: 1.2em;
130
+ }
131
+
132
+ /* --- Word Display --- */
133
+ .word-display {
134
+ font-size: 4rem;
135
+ font-weight: 800;
136
+ letter-spacing: 2px;
137
+ margin: 2rem 0;
138
+ display: flex;
139
+ justify-content: center;
140
+ align-items: center;
141
+ min-height: 120px;
142
+ position: relative;
143
+ cursor: pointer;
144
+ transition: transform 0.2s;
145
+ user-select: none;
146
+ }
147
+
148
+ .word-display:active {
149
+ transform: scale(0.98);
150
+ }
151
+
152
+ .letter {
153
+ display: inline-block;
154
+ transition: all 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275);
155
+ }
156
+
157
+ .letter.correct {
158
+ color: var(--success);
159
+ text-shadow: 0 0 15px rgba(34, 197, 94, 0.5);
160
+ }
161
+
162
+ .letter.wrong {
163
+ color: var(--error);
164
+ text-decoration: line-through;
165
+ opacity: 0.5;
166
+ }
167
+
168
+ /* --- Controls --- */
169
+ .controls {
170
+ display: flex;
171
+ gap: 1rem;
172
+ margin-top: 1rem;
173
+ }
174
+
175
+ .btn {
176
+ padding: 1rem 2rem;
177
+ border: none;
178
+ border-radius: 50px;
179
+ font-size: 1.1rem;
180
+ font-weight: 600;
181
+ cursor: pointer;
182
+ transition: all 0.3s ease;
183
+ display: flex;
184
+ align-items: center;
185
+ gap: 0.5rem;
186
+ }
187
+
188
+ .btn-primary {
189
+ background: linear-gradient(135deg, var(--primary), var(--secondary));
190
+ color: white;
191
+ box-shadow: 0 4px 15px rgba(99, 102, 241, 0.4);
192
+ }
193
+
194
+ .btn-primary:hover {
195
+ transform: translateY(-2px);
196
+ box-shadow: 0 8px 25px rgba(99, 102, 241, 0.6);
197
+ }
198
+
199
+ .btn-primary:active {
200
+ transform: translateY(1px);
201
+ }
202
+
203
+ .btn-outline {
204
+ background: transparent;
205
+ border: 2px solid var(--text-muted);
206
+ color: var(--text-muted);
207
+ }
208
+
209
+ .btn-outline:hover {
210
+ border-color: var(--text-main);
211
+ color: var(--text-main);
212
+ }
213
+
214
+ /* --- Feedback Area --- */
215
+ .feedback-area {
216
+ height: 40px;
217
+ margin-top: 1rem;
218
+ font-weight: 600;
219
+ font-size: 1.2rem;
220
+ opacity: 0;
221
+ transition: opacity 0.3s;
222
+ }
223
+
224
+ .feedback-area.visible {
225
+ opacity: 1;
226
+ }
227
+
228
+ .feedback-area.success { color: var(--success); }
229
+ .feedback-area.error { color: var(--error); }
230
+
231
+ /* --- Stats --- */
232
+ .stats-bar {
233
+ display: flex;
234
+ justify-content: space-between;
235
+ width: 100%;
236
+ padding: 1rem;
237
+ background: rgba(0,0,0,0.2);
238
+ border-radius: 12px;
239
+ margin-bottom: 2rem;
240
+ font-size: 1.1rem;
241
+ }
242
+
243
+ .stat-item {
244
+ display: flex;
245
+ align-items: center;
246
+ gap: 0.5rem;
247
+ }
248
+
249
+ .stat-value {
250
+ font-weight: bold;
251
+ color: var(--primary);
252
+ }
253
+
254
+ /* --- Confetti Canvas --- */
255
+ #confetti-canvas {
256
+ position: absolute;
257
+ top: 0;
258
+ left: 0;
259
+ width: 100%;
260
+ height: 100%;
261
+ pointer-events: none;
262
+ z-index: 10;
263
+ }
264
+
265
+ /* --- Animations --- */
266
+ @keyframes fadeIn {
267
+ from { opacity: 0; transform: translateY(10px); }
268
+ to { opacity: 1; transform: translateY(0); }
269
+ }
270
+
271
+ @keyframes shake {
272
+ 0%, 100% { transform: translateX(0); }
273
+ 25% { transform: translateX(-10px); }
274
+ 75% { transform: translateX(10px); }
275
+ }
276
+
277
+ .shake {
278
+ animation: shake 0.4s ease-in-out;
279
+ }
280
+
281
+ /* --- Responsive --- */
282
+ @media (max-width: 600px) {
283
+ .game-container {
284
+ padding: 1.5rem;
285
+ }
286
+ .word-display {
287
+ font-size: 2.5rem;
288
+ }
289
+ h1 {
290
+ font-size: 1.8rem;
291
+ }
292
+ .controls {
293
+ flex-direction: column;
294
+ width: 100%;
295
+ }
296
+ .btn {
297
+ width: 100%;
298
+ justify-content: center;
299
+ }
300
+ }
301
+ </style>
302
+ </head>
303
+ <body>
304
+
305
+ <header>
306
+ <div class="logo">
307
+ <i class="fa-solid fa-asterisk"></i> Hyphen Hunter
308
+ </div>
309
+ <a href="https://huggingface.co/spaces/akhaliq/anycoder" target="_blank" class="built-with">
310
+ Built with anycoder <i class="fa-solid fa-arrow-up-right-from-square"></i>
311
+ </a>
312
+ </header>
313
+
314
+ <main>
315
+ <div class="game-container">
316
+ <canvas id="confetti-canvas"></canvas>
317
+
318
+ <!-- Stats Bar -->
319
+ <div class="stats-bar">
320
+ <div class="stat-item">
321
+ <i class="fa-solid fa-heart" style="color: var(--error)"></i>
322
+ <span>Score: <span id="score" class="stat-value">0</span></span>
323
+ </div>
324
+ <div class="stat-item">
325
+ <i class="fa-solid fa-trophy" style="color: var(--hyphen-color)"></i>
326
+ <span>Streak: <span id="streak" class="stat-value">0</span></span>
327
+ </div>
328
+ </div>
329
+
330
+ <!-- Start Screen -->
331
+ <div id="start-screen" class="screen active">
332
+ <div style="font-size: 4rem; color: var(--hyphen-color); margin-bottom: 1rem;">
333
+ <i class="fa-solid fa-scissors"></i>
334
+ </div>
335
+ <h1>Identify the Hyphen</h1>
336
+ <p class="instruction">
337
+ Your goal is to spot the <span class="highlight">hyphen (-)</span> inside the word.<br>
338
+ Tap the letter that contains the hyphen to score points.
339
+ </p>
340
+ <button class="btn btn-primary" onclick="startGame()">
341
+ <i class="fa-solid fa-play"></i> Start Game
342
+ </button>
343
+ </div>
344
+
345
+ <!-- Game Screen -->
346
+ <div id="game-screen" class="screen">
347
+ <div class="word-display" id="word-display">
348
+ <!-- Letters injected here -->
349
+ </div>
350
+
351
+ <div id="feedback" class="feedback-area"></div>
352
+
353
+ <div class="controls">
354
+ <button class="btn btn-outline" onclick="skipWord()" id="skip-btn">
355
+ <i class="fa-solid fa-forward"></i> Skip
356
+ </button>
357
+ <button class="btn btn-primary" onclick="checkAnswer()" id="check-btn">
358
+ <i class="fa-solid fa-check"></i> Confirm
359
+ </button>
360
+ </div>
361
+ </div>
362
+
363
+ <!-- Game Over Screen -->
364
+ <div id="game-over-screen" class="screen">
365
+ <div style="font-size: 4rem; color: var(--primary); margin-bottom: 1rem;">
366
+ <i class="fa-solid fa-flag-checkered"></i>
367
+ </div>
368
+ <h1>Game Over</h1>
369
+ <p class="instruction">
370
+ Great effort! You finished the set.
371
+ </p>
372
+ <div style="font-size: 2rem; margin: 1rem 0;">
373
+ Final Score: <span id="final-score" class="stat-value">0</span>
374
+ </div>
375
+ <div class="controls">
376
+ <button class="btn btn-primary" onclick="startGame()">
377
+ <i class="fa-solid fa-rotate-right"></i> Play Again
378
+ </button>
379
+ </div>
380
+ </div>
381
+ </div>
382
+ </main>
383
+
384
+ <script>
385
+ // --- Game Data ---
386
+ const words = [
387
+ { text: "mother-in-law", index: 3 },
388
+ { text: "well-being", index: 4 },
389
+ { text: "state-of-the-art", index: 7 },
390
+ { text: "decision-making", index: 5 },
391
+ { text: "full-time", index: 3 },
392
+ { text: "twenty-one", index: 5 },
393
+ { text: "high-level", index: 4 },
394
+ { text: "copy-editing", index: 3 },
395
+ { text: "fourth-of-july", index: 6 },
396
+ { text: "decision-making", index: 5 },
397
+ { text: "co-operate", index: 2 },
398
+ { text: "eco-friendly", index: 2 },
399
+ { text: "self-control", index: 3 },
400
+ { text: "high-tech", index: 3 },
401
+ { text: "long-term", index: 3 },
402
+ { text: "decision-making", index: 5 }
403
+ ];
404
+
405
+ // --- State ---
406
+ let currentWord = "";
407
+ let currentHyphenIndex = -1;
408
+ let score = 0;
409
+ let streak = 0;
410
+ let isProcessing = false;
411
+ let audioCtx = null;
412
+
413
+ // --- Elements ---
414
+ const screens = {
415
+ start: document.getElementById('start-screen'),
416
+ game: document.getElementById('game-screen'),
417
+ over: document.getElementById('game-over-screen')
418
+ };
419
+ const wordDisplay = document.getElementById('word-display');
420
+ const scoreEl = document.getElementById('score');
421
+ const streakEl = document.getElementById('streak');
422
+ const feedbackEl = document.getElementById('feedback');
423
+ const finalScoreEl = document.getElementById('final-score');
424
+
425
+ // --- Audio System (Web Audio API) ---
426
+ function initAudio() {
427
+ if (!audioCtx) {
428
+ audioCtx = new (window.AudioContext || window.webkitAudioContext)();
429
+ }
430
+ }
431
+
432
+ function playSound(type) {
433
+ if (!audioCtx) return;
434
+ const osc = audioCtx.createOscillator();
435
+ const gain = audioCtx.createGain();
436
+ osc.connect(gain);
437
+ gain.connect(audioCtx.destination);
438
+
439
+ const now = audioCtx.currentTime;
440
+
441
+ if (type === 'success') {
442
+ osc.type = 'sine';
443
+ osc.frequency.setValueAtTime(500, now);
444
+ osc.frequency.exponentialRampToValueAtTime(1000, now + 0.1);
445
+ gain.gain.setValueAtTime(0.3, now);
446
+ gain.gain.exponentialRampToValueAtTime(0.01, now + 0.3);
447
+ osc.start(now);
448
+ osc.stop(now + 0.3);
449
+ } else if (type === 'error') {
450
+ osc.type = 'sawtooth';
451
+ osc.frequency.setValueAtTime(200, now);
452
+ osc.frequency.linearRampToValueAtTime(100, now + 0.2);
453
+ gain.gain.setValueAtTime(0.3, now);
454
+ gain.gain.exponentialRampToValueAtTime(0.01, now + 0.3);
455
+ osc.start(now);
456
+ osc.stop(now + 0.3);
457
+ } else if (type === 'hover') {
458
+ osc.type = 'triangle';
459
+ osc.frequency.setValueAtTime(800, now);
460
+ gain.gain.setValueAtTime(0.05, now);
461
+ gain.gain.exponentialRampToValueAtTime(0.01, now + 0.05);
462
+ osc.start(now);
463
+ osc.stop(now + 0.05);
464
+ }
465
+ }
466
+
467
+ // --- Game Logic ---
468
+
469
+ function switchScreen(screenName) {
470
+ Object.values(screens).forEach(s => s.classList.remove('active'));
471
+ screens[screenName].classList.add('active');
472
+ }
473
+
474
+ function startGame() {
475
+ score = 0;
476
+ streak = 0;
477
+ updateStats();
478
+ switchScreen('game');
479
+ loadNewWord();
480
+ }
481
+
482
+ function loadNewWord() {
483
+ isProcessing = false;
484
+ feedbackEl.className = 'feedback-area';
485
+ feedbackEl.innerText = '';
486
+
487
+ // Pick random word
488
+ const randomIndex = Math.floor(Math.random() * words.length);
489
+ currentWord = words[randomIndex].text;
490
+ currentHyphenIndex = words[randomIndex].index;
491
+
492
+ renderWord();
493
+ }
494
+
495
+ function renderWord() {
496
+ wordDisplay.innerHTML = '';
497
+ currentWord.split('').forEach((char, index) => {
498
+ const span = document.createElement('span');
499
+ span.className = 'letter';
500
+ span.innerText = char;
501
+ span.dataset.index = index;
502
+
503
+ // Interaction
504
+ span.addEventListener('click', () => handleLetterClick(span));
505
+ span.addEventListener('mouseenter', () => playSound('hover'));
506
+
507
+ wordDisplay.appendChild(span);
508
+ });
509
+ }
510
+
511
+ function handleLetterClick(element) {
512
+ if (isProcessing) return;
513
+
514
+ const index = parseInt(element.dataset.index);
515
+ const letter = currentWord[index];
516
+
517
+ // Visual feedback immediately on click
518
+ if (letter === '-') {
519
+ element.classList.add('correct');
520
+ // Don't change color to green yet, wait for confirm
521
+ } else {
522
+ element.classList.add('wrong');
523
+ playSound('error');
524
+ streak = 0;
525
+ updateStats();
526
+ triggerShake();
527
+ }
528
+ }
529
+
530
+ function checkAnswer() {
531
+ if (isProcessing) return;
532
+
533
+ // Find the selected hyphen
534
+ const selected = wordDisplay.querySelector('.letter.correct');
535
+
536
+ if (selected) {
537
+ const clickedIndex = parseInt(selected.dataset.index);
538
+ if (clickedIndex === currentHyphenIndex) {
539
+ handleSuccess();
540
+ } else {
541
+ handleFailure(selected);
542
+ }
543
+ } else {
544
+ // No selection made
545
+ feedbackEl.innerText = "Please select a letter first!";
546
+ feedbackEl.classList.add('visible', 'error');
547
+ }
548
+ }
549
+
550
+ function handleSuccess() {
551
+ isProcessing = true;
552
+ playSound('success');
553
+
554
+ // Highlight the correct letter green permanently
555
+ const correctSpan = wordDisplay.querySelectorAll('.letter')[currentHyphenIndex];
556
+ correctSpan.classList.remove('correct'); // remove temp color
557
+ correctSpan.style.color = 'var(--success)';
558
+
559
+ score += 10 + (streak * 2);
560
+ streak++;
561
+ updateStats();
562
+
563
+ feedbackEl.innerText = "Correct! +Points";
564
+ feedbackEl.className = 'feedback-area visible success';
565
+
566
+ // Confetti effect
567
+ fireConfetti();
568
+
569
+ setTimeout(() => {
570
+ loadNewWord();
571
+ }, 1500);
572
+ }
573
+
574
+ function handleFailure(wrongSpan) {
575
+ isProcessing = true;
576
+ playSound('error');
577
+
578
+ streak = 0;
579
+ updateStats();
580
+
581
+ wrongSpan.classList.remove('wrong');
582
+ wrongSpan.style.color = 'var(--error)';
583
+
584
+ feedbackEl.innerText = "Wrong! That wasn't the hyphen.";
585
+ feedbackEl.className = 'feedback-area visible error';
586
+
587
+ triggerShake();
588
+
589
+ setTimeout(() => {
590
+ // Reset visual state
591
+ wrongSpan.style.color = '';
592
+ loadNewWord();
593
+ }, 1000);
594
+ }
595
+
596
+ function skipWord() {
597
+ if (isProcessing) return;
598
+ streak = 0;
599
+ updateStats();
600
+ loadNewWord();
601
+ }
602
+
603
+ function triggerShake() {
604
+ wordDisplay.classList.add('shake');
605
+ setTimeout(() => wordDisplay.classList.remove('shake'), 400);
606
+ }
607
+
608
+ function updateStats() {
609
+ scoreEl.innerText = score;
610
+ streakEl.innerText = streak;
611
+ }
612
+
613
+ // --- Confetti System (Canvas) ---
614
+ const canvas = document.getElementById('confetti-canvas');
615
+ const ctx = canvas.getContext('2d');
616
+ let particles = [];
617
+
618
+ function resizeCanvas() {
619
+ canvas.width = window.innerWidth;
620
+ canvas.height = window.innerHeight;
621
+ }
622
+ window.addEventListener('resize', resizeCanvas);
623
+ resizeCanvas();
624
+
625
+ function fireConfetti() {
626
+ const colors = ['#6366f1', '#ec4899', '#22c55e', '#fbbf24'];
627
+ for (let i = 0; i < 100; i++) {
628
+ particles.push({
629
+ x: canvas.width / 2,
630
+ y: canvas.height / 2,
631
+ vx: (Math.random() - 0.5) * 15,
632
+ vy: (Math.random() - 0.5) * 15 - 5,
633
+ life: 1,
634
+ color: colors[Math.floor(Math.random() * colors.length)],
635
+ size: Math.random() * 5 + 2
636
+ });
637
+ }
638
+ animateConfetti();
639
+ }
640
+
641
+ function animateConfetti() {
642
+ if (particles.length === 0) return;
643
+
644
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
645
+
646
+ for (let i = 0; i < particles.length; i++) {
647
+ const p = particles[i];
648
+ p.x += p.vx;
649
+ p.y += p.vy;
650
+ p.vy += 0.2; // Gravity
651
+ p.life -= 0.02;
652
+
653
+ ctx.fillStyle = p.color;
654
+ ctx.globalAlpha = p.life;
655
+ ctx.beginPath();
656
+ ctx.arc(p.x, p.y, p.size, 0, Math.PI * 2);
657
+ ctx.fill();
658
+ }
659
+
660
+ particles = particles.filter(p => p.life > 0);
661
+
662
+ if (particles.length > 0) {
663
+ requestAnimationFrame(animateConfetti);
664
+ } else {
665
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
666
+ }
667
+ }
668
+
669
+ </script>
670
+ </body>
671
+ </html>