Multimedix commited on
Commit
fbc992b
·
verified ·
1 Parent(s): 0ab499d

Upload folder using huggingface_hub

Browse files
Files changed (1) hide show
  1. index.html +726 -19
index.html CHANGED
@@ -1,19 +1,726 @@
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>VoiceDo - Speech Recognition Todo List</title>
7
+
8
+ <!-- Google Fonts -->
9
+ <link rel="preconnect" href="https://fonts.googleapis.com">
10
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
11
+ <link href="https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;500;600;700&display=swap" rel="stylesheet">
12
+
13
+ <!-- FontAwesome Icons -->
14
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
15
+
16
+ <style>
17
+ :root {
18
+ --primary: #6366f1;
19
+ --primary-dark: #4f46e5;
20
+ --secondary: #ec4899;
21
+ --bg-gradient: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
22
+ --glass-bg: rgba(255, 255, 255, 0.95);
23
+ --glass-border: rgba(255, 255, 255, 0.5);
24
+ --text-main: #1f2937;
25
+ --text-muted: #6b7280;
26
+ --danger: #ef4444;
27
+ --success: #10b981;
28
+ --shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.1), 0 8px 10px -6px rgba(0, 0, 0, 0.1);
29
+ --radius: 16px;
30
+ }
31
+
32
+ * {
33
+ margin: 0;
34
+ padding: 0;
35
+ box-sizing: border-box;
36
+ font-family: 'Outfit', sans-serif;
37
+ }
38
+
39
+ body {
40
+ background: var(--bg-gradient);
41
+ min-height: 100vh;
42
+ display: flex;
43
+ justify-content: center;
44
+ align-items: center;
45
+ padding: 20px;
46
+ overflow-x: hidden;
47
+ }
48
+
49
+ /* Background decoration */
50
+ .blobs {
51
+ position: fixed;
52
+ top: 0;
53
+ left: 0;
54
+ width: 100%;
55
+ height: 100%;
56
+ pointer-events: none;
57
+ z-index: -1;
58
+ overflow: hidden;
59
+ }
60
+
61
+ .blob {
62
+ position: absolute;
63
+ border-radius: 50%;
64
+ filter: blur(60px);
65
+ opacity: 0.6;
66
+ animation: float 10s infinite ease-in-out;
67
+ }
68
+
69
+ .blob:nth-child(1) {
70
+ top: -10%;
71
+ left: -10%;
72
+ width: 400px;
73
+ height: 400px;
74
+ background: #4facfe;
75
+ }
76
+
77
+ .blob:nth-child(2) {
78
+ bottom: -10%;
79
+ right: -10%;
80
+ width: 350px;
81
+ height: 350px;
82
+ background: #f093fb;
83
+ animation-delay: -5s;
84
+ }
85
+
86
+ @keyframes float {
87
+ 0%, 100% { transform: translate(0, 0); }
88
+ 50% { transform: translate(30px, 50px); }
89
+ }
90
+
91
+ /* Main App Container */
92
+ .app-container {
93
+ width: 100%;
94
+ max-width: 500px;
95
+ background: var(--glass-bg);
96
+ backdrop-filter: blur(12px);
97
+ -webkit-backdrop-filter: blur(12px);
98
+ border-radius: var(--radius);
99
+ box-shadow: var(--shadow);
100
+ border: 1px solid var(--glass-border);
101
+ overflow: hidden;
102
+ display: flex;
103
+ flex-direction: column;
104
+ max-height: 90vh;
105
+ }
106
+
107
+ /* Header */
108
+ header {
109
+ padding: 24px 24px 10px;
110
+ text-align: center;
111
+ }
112
+
113
+ header h1 {
114
+ font-size: 1.8rem;
115
+ font-weight: 700;
116
+ background: linear-gradient(to right, var(--primary), var(--secondary));
117
+ -webkit-background-clip: text;
118
+ -webkit-text-fill-color: transparent;
119
+ margin-bottom: 5px;
120
+ }
121
+
122
+ .anycoder-link {
123
+ display: inline-block;
124
+ font-size: 0.8rem;
125
+ color: var(--text-muted);
126
+ text-decoration: none;
127
+ font-weight: 500;
128
+ transition: color 0.3s;
129
+ padding: 4px 10px;
130
+ background: rgba(0,0,0,0.05);
131
+ border-radius: 20px;
132
+ }
133
+
134
+ .anycoder-link:hover {
135
+ color: var(--primary);
136
+ background: rgba(99, 102, 241, 0.1);
137
+ }
138
+
139
+ /* Input Section */
140
+ .input-section {
141
+ padding: 20px 24px;
142
+ position: relative;
143
+ }
144
+
145
+ .input-wrapper {
146
+ display: flex;
147
+ gap: 10px;
148
+ position: relative;
149
+ }
150
+
151
+ #todo-input {
152
+ flex: 1;
153
+ padding: 14px 16px;
154
+ border-radius: 12px;
155
+ border: 2px solid transparent;
156
+ background: #f3f4f6;
157
+ font-size: 1rem;
158
+ transition: all 0.3s ease;
159
+ outline: none;
160
+ }
161
+
162
+ #todo-input:focus {
163
+ background: #fff;
164
+ border-color: var(--primary);
165
+ box-shadow: 0 0 0 4px rgba(99, 102, 241, 0.1);
166
+ }
167
+
168
+ .btn {
169
+ border: none;
170
+ cursor: pointer;
171
+ border-radius: 12px;
172
+ display: flex;
173
+ align-items: center;
174
+ justify-content: center;
175
+ transition: all 0.2s;
176
+ font-size: 1.1rem;
177
+ }
178
+
179
+ #mic-btn {
180
+ width: 50px;
181
+ background: #f3f4f6;
182
+ color: var(--text-muted);
183
+ position: relative;
184
+ overflow: hidden;
185
+ }
186
+
187
+ #mic-btn:hover {
188
+ background: #e5e7eb;
189
+ color: var(--text-main);
190
+ }
191
+
192
+ #mic-btn.listening {
193
+ background: var(--danger);
194
+ color: white;
195
+ animation: pulse-red 1.5s infinite;
196
+ }
197
+
198
+ #add-btn {
199
+ width: 50px;
200
+ background: var(--primary);
201
+ color: white;
202
+ }
203
+
204
+ #add-btn:hover {
205
+ background: var(--primary-dark);
206
+ transform: translateY(-1px);
207
+ }
208
+
209
+ #add-btn:active {
210
+ transform: translateY(1px);
211
+ }
212
+
213
+ /* Listening Overlay/Indicator */
214
+ .listening-status {
215
+ font-size: 0.85rem;
216
+ color: var(--danger);
217
+ margin-top: 8px;
218
+ height: 20px;
219
+ opacity: 0;
220
+ transition: opacity 0.3s;
221
+ display: flex;
222
+ align-items: center;
223
+ gap: 6px;
224
+ }
225
+
226
+ .listening-status.active {
227
+ opacity: 1;
228
+ }
229
+
230
+ .wave-bars {
231
+ display: flex;
232
+ gap: 2px;
233
+ height: 12px;
234
+ align-items: center;
235
+ }
236
+
237
+ .bar {
238
+ width: 3px;
239
+ background: var(--danger);
240
+ border-radius: 2px;
241
+ animation: wave 0.5s infinite ease-in-out;
242
+ }
243
+ .bar:nth-child(1) { height: 4px; animation-delay: 0.0s; }
244
+ .bar:nth-child(2) { height: 8px; animation-delay: 0.1s; }
245
+ .bar:nth-child(3) { height: 12px; animation-delay: 0.2s; }
246
+ .bar:nth-child(4) { height: 8px; animation-delay: 0.3s; }
247
+ .bar:nth-child(5) { height: 4px; animation-delay: 0.4s; }
248
+
249
+ @keyframes wave {
250
+ 0%, 100% { transform: scaleY(1); }
251
+ 50% { transform: scaleY(1.8); }
252
+ }
253
+
254
+ @keyframes pulse-red {
255
+ 0% { box-shadow: 0 0 0 0 rgba(239, 68, 68, 0.7); }
256
+ 70% { box-shadow: 0 0 0 10px rgba(239, 68, 68, 0); }
257
+ 100% { box-shadow: 0 0 0 0 rgba(239, 68, 68, 0); }
258
+ }
259
+
260
+ /* Filters */
261
+ .filters {
262
+ display: flex;
263
+ padding: 0 24px 15px;
264
+ gap: 15px;
265
+ border-bottom: 1px solid rgba(0,0,0,0.05);
266
+ }
267
+
268
+ .filter-btn {
269
+ background: none;
270
+ border: none;
271
+ color: var(--text-muted);
272
+ font-weight: 600;
273
+ font-size: 0.9rem;
274
+ cursor: pointer;
275
+ padding-bottom: 5px;
276
+ position: relative;
277
+ }
278
+
279
+ .filter-btn.active {
280
+ color: var(--primary);
281
+ }
282
+
283
+ .filter-btn.active::after {
284
+ content: '';
285
+ position: absolute;
286
+ bottom: -1px;
287
+ left: 0;
288
+ width: 100%;
289
+ height: 2px;
290
+ background: var(--primary);
291
+ border-radius: 2px;
292
+ }
293
+
294
+ /* Task List */
295
+ .todo-list-container {
296
+ flex: 1;
297
+ overflow-y: auto;
298
+ padding: 20px 24px;
299
+ }
300
+
301
+ /* Custom Scrollbar */
302
+ .todo-list-container::-webkit-scrollbar {
303
+ width: 6px;
304
+ }
305
+ .todo-list-container::-webkit-scrollbar-track {
306
+ background: transparent;
307
+ }
308
+ .todo-list-container::-webkit-scrollbar-thumb {
309
+ background: #d1d5db;
310
+ border-radius: 10px;
311
+ }
312
+
313
+ .todo-list {
314
+ list-style: none;
315
+ display: flex;
316
+ flex-direction: column;
317
+ gap: 12px;
318
+ }
319
+
320
+ .todo-item {
321
+ background: white;
322
+ border-radius: 12px;
323
+ padding: 12px 16px;
324
+ display: flex;
325
+ align-items: center;
326
+ justify-content: space-between;
327
+ box-shadow: 0 2px 5px rgba(0,0,0,0.03);
328
+ border: 1px solid rgba(0,0,0,0.05);
329
+ transition: all 0.3s;
330
+ animation: slideIn 0.3s ease-out forwards;
331
+ }
332
+
333
+ @keyframes slideIn {
334
+ from { opacity: 0; transform: translateY(10px); }
335
+ to { opacity: 1; transform: translateY(0); }
336
+ }
337
+
338
+ .todo-item:hover {
339
+ transform: translateY(-2px);
340
+ box-shadow: 0 4px 12px rgba(0,0,0,0.06);
341
+ }
342
+
343
+ .todo-content {
344
+ display: flex;
345
+ align-items: center;
346
+ gap: 12px;
347
+ flex: 1;
348
+ overflow: hidden;
349
+ }
350
+
351
+ .custom-checkbox {
352
+ appearance: none;
353
+ -webkit-appearance: none;
354
+ width: 22px;
355
+ height: 22px;
356
+ border: 2px solid #d1d5db;
357
+ border-radius: 6px;
358
+ cursor: pointer;
359
+ position: relative;
360
+ transition: all 0.2s;
361
+ flex-shrink: 0;
362
+ }
363
+
364
+ .custom-checkbox:checked {
365
+ background: var(--success);
366
+ border-color: var(--success);
367
+ }
368
+
369
+ .custom-checkbox:checked::after {
370
+ content: '\f00c';
371
+ font-family: 'Font Awesome 6 Free';
372
+ font-weight: 900;
373
+ color: white;
374
+ font-size: 12px;
375
+ position: absolute;
376
+ top: 50%;
377
+ left: 50%;
378
+ transform: translate(-50%, -50%);
379
+ }
380
+
381
+ .todo-text {
382
+ font-size: 1rem;
383
+ color: var(--text-main);
384
+ transition: all 0.2s;
385
+ word-break: break-word;
386
+ }
387
+
388
+ .todo-item.completed .todo-text {
389
+ text-decoration: line-through;
390
+ color: var(--text-muted);
391
+ }
392
+
393
+ .todo-item.completed {
394
+ background: #f9fafb;
395
+ opacity: 0.8;
396
+ }
397
+
398
+ .delete-btn {
399
+ background: none;
400
+ border: none;
401
+ color: #d1d5db;
402
+ cursor: pointer;
403
+ padding: 8px;
404
+ font-size: 1rem;
405
+ transition: color 0.2s;
406
+ margin-left: 8px;
407
+ }
408
+
409
+ .delete-btn:hover {
410
+ color: var(--danger);
411
+ }
412
+
413
+ /* Empty State */
414
+ .empty-state {
415
+ text-align: center;
416
+ padding: 40px 20px;
417
+ color: var(--text-muted);
418
+ display: none;
419
+ }
420
+
421
+ .empty-state i {
422
+ font-size: 3rem;
423
+ margin-bottom: 15px;
424
+ opacity: 0.3;
425
+ }
426
+
427
+ .empty-state p {
428
+ font-size: 0.95rem;
429
+ }
430
+
431
+ /* Notification Toast */
432
+ .toast {
433
+ position: fixed;
434
+ bottom: 20px;
435
+ left: 50%;
436
+ transform: translateX(-50%) translateY(100px);
437
+ background: #333;
438
+ color: white;
439
+ padding: 12px 24px;
440
+ border-radius: 50px;
441
+ font-size: 0.9rem;
442
+ box-shadow: 0 5px 15px rgba(0,0,0,0.2);
443
+ transition: transform 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275);
444
+ z-index: 100;
445
+ display: flex;
446
+ align-items: center;
447
+ gap: 10px;
448
+ }
449
+
450
+ .toast.show {
451
+ transform: translateX(-50%) translateY(0);
452
+ }
453
+
454
+ /* Responsive */
455
+ @media (max-width: 480px) {
456
+ .app-container {
457
+ height: 100vh;
458
+ max-height: 100vh;
459
+ border-radius: 0;
460
+ }
461
+
462
+ header h1 {
463
+ font-size: 1.5rem;
464
+ }
465
+ }
466
+ </style>
467
+ </head>
468
+ <body>
469
+
470
+ <!-- Background Blobs -->
471
+ <div class="blobs">
472
+ <div class="blob"></div>
473
+ <div class="blob"></div>
474
+ </div>
475
+
476
+ <main class="app-container">
477
+ <header>
478
+ <h1>VoiceDo</h1>
479
+ <a href="https://huggingface.co/spaces/akhaliq/anycoder" target="_blank" class="anycoder-link">Built with anycoder</a>
480
+ </header>
481
+
482
+ <section class="input-section">
483
+ <div class="input-wrapper">
484
+ <input type="text" id="todo-input" placeholder="Type or say something...">
485
+ <button class="btn" id="mic-btn" title="Speak to add">
486
+ <i class="fa-solid fa-microphone"></i>
487
+ </button>
488
+ <button class="btn" id="add-btn" title="Add Task">
489
+ <i class="fa-solid fa-plus"></i>
490
+ </button>
491
+ </div>
492
+ <div class="listening-status" id="listening-status">
493
+ <div class="wave-bars">
494
+ <div class="bar"></div>
495
+ <div class="bar"></div>
496
+ <div class="bar"></div>
497
+ <div class="bar"></div>
498
+ <div class="bar"></div>
499
+ </div>
500
+ <span>Listening...</span>
501
+ </div>
502
+ </section>
503
+
504
+ <section class="filters">
505
+ <button class="filter-btn active" data-filter="all">All</button>
506
+ <button class="filter-btn" data-filter="active">Active</button>
507
+ <button class="filter-btn" data-filter="completed">Completed</button>
508
+ </section>
509
+
510
+ <div class="todo-list-container">
511
+ <ul class="todo-list" id="todo-list">
512
+ <!-- Tasks will be added here -->
513
+ </ul>
514
+
515
+ <div class="empty-state" id="empty-state">
516
+ <i class="fa-solid fa-clipboard-list"></i>
517
+ <p>No tasks found. Start by adding one!</p>
518
+ </div>
519
+ </div>
520
+ </main>
521
+
522
+ <div class="toast" id="toast">
523
+ <i class="fa-solid fa-circle-info"></i>
524
+ <span id="toast-msg">Notification</span>
525
+ </div>
526
+
527
+ <script>
528
+ // --- DOM Elements ---
529
+ const todoInput = document.getElementById('todo-input');
530
+ const addBtn = document.getElementById('add-btn');
531
+ const micBtn = document.getElementById('mic-btn');
532
+ const todoList = document.getElementById('todo-list');
533
+ const filterBtns = document.querySelectorAll('.filter-btn');
534
+ const emptyState = document.getElementById('empty-state');
535
+ const listeningStatus = document.getElementById('listening-status');
536
+ const toast = document.getElementById('toast');
537
+ const toastMsg = document.getElementById('toast-msg');
538
+
539
+ // --- State ---
540
+ let todos = JSON.parse(localStorage.getItem('voicedo_todos')) || [];
541
+ let currentFilter = 'all';
542
+ let isListening = false;
543
+
544
+ // --- Speech Recognition Setup ---
545
+ const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
546
+ let recognition;
547
+
548
+ if (SpeechRecognition) {
549
+ recognition = new SpeechRecognition();
550
+ recognition.continuous = false;
551
+ recognition.lang = 'en-US';
552
+ recognition.interimResults = false;
553
+
554
+ recognition.onstart = () => {
555
+ isListening = true;
556
+ micBtn.classList.add('listening');
557
+ micBtn.innerHTML = '<i class="fa-solid fa-stop"></i>';
558
+ listeningStatus.classList.add('active');
559
+ todoInput.placeholder = "Listening...";
560
+ };
561
+
562
+ recognition.onend = () => {
563
+ isListening = false;
564
+ micBtn.classList.remove('listening');
565
+ micBtn.innerHTML = '<i class="fa-solid fa-microphone"></i>';
566
+ listeningStatus.classList.remove('active');
567
+ todoInput.placeholder = "Type or say something...";
568
+ };
569
+
570
+ recognition.onresult = (event) => {
571
+ const transcript = event.results[0][0].transcript;
572
+ todoInput.value = transcript;
573
+ // Optional: Auto-add if confident? Let's keep it manual for review.
574
+ todoInput.focus();
575
+ showToast(`Heard: "${transcript}"`);
576
+ };
577
+
578
+ recognition.onerror = (event) => {
579
+ console.error(event.error);
580
+ showToast('Microphone error or permission denied.');
581
+ isListening = false;
582
+ micBtn.classList.remove('listening');
583
+ listeningStatus.classList.remove('active');
584
+ };
585
+ } else {
586
+ micBtn.style.display = 'none'; // Hide mic if not supported
587
+ console.log("Speech Recognition not supported in this browser.");
588
+ }
589
+
590
+ // --- Functions ---
591
+
592
+ function saveTodos() {
593
+ localStorage.setItem('voicedo_todos', JSON.stringify(todos));
594
+ }
595
+
596
+ function renderTodos() {
597
+ todoList.innerHTML = '';
598
+
599
+ const filteredTodos = todos.filter(todo => {
600
+ if (currentFilter === 'active') return !todo.completed;
601
+ if (currentFilter === 'completed') return todo.completed;
602
+ return true;
603
+ });
604
+
605
+ if (filteredTodos.length === 0) {
606
+ emptyState.style.display = 'block';
607
+ } else {
608
+ emptyState.style.display = 'none';
609
+
610
+ filteredTodos.forEach(todo => {
611
+ const li = document.createElement('li');
612
+ li.className = `todo-item ${todo.completed ? 'completed' : ''}`;
613
+
614
+ li.innerHTML = `
615
+ <div class="todo-content">
616
+ <input type="checkbox" class="custom-checkbox"
617
+ ${todo.completed ? 'checked' : ''}
618
+ onclick="toggleTodo(${todo.id})">
619
+ <span class="todo-text">${escapeHtml(todo.text)}</span>
620
+ </div>
621
+ <button class="delete-btn" onclick="deleteTodo(${todo.id})">
622
+ <i class="fa-solid fa-trash"></i>
623
+ </button>
624
+ `;
625
+ todoList.appendChild(li);
626
+ });
627
+ }
628
+ }
629
+
630
+ function addTodo() {
631
+ const text = todoInput.value.trim();
632
+ if (text === '') {
633
+ showToast('Please enter a task!');
634
+ return;
635
+ }
636
+
637
+ const newTodo = {
638
+ id: Date.now(),
639
+ text: text,
640
+ completed: false,
641
+ createdAt: new Date()
642
+ };
643
+
644
+ todos.unshift(newTodo); // Add to top
645
+ saveTodos();
646
+ renderTodos();
647
+ todoInput.value = '';
648
+ showToast('Task added successfully');
649
+ }
650
+
651
+ // Expose to global scope for onclick attributes
652
+ window.deleteTodo = function(id) {
653
+ todos = todos.filter(t => t.id !== id);
654
+ saveTodos();
655
+ renderTodos();
656
+ showToast('Task deleted');
657
+ };
658
+
659
+ window.toggleTodo = function(id) {
660
+ const todo = todos.find(t => t.id === id);
661
+ if (todo) {
662
+ todo.completed = !todo.completed;
663
+ saveTodos();
664
+ renderTodos();
665
+ }
666
+ };
667
+
668
+ function toggleSpeech() {
669
+ if (!recognition) {
670
+ showToast('Speech recognition not supported.');
671
+ return;
672
+ }
673
+ if (isListening) {
674
+ recognition.stop();
675
+ } else {
676
+ recognition.start();
677
+ }
678
+ }
679
+
680
+ function showToast(message) {
681
+ toastMsg.innerText = message;
682
+ toast.classList.add('show');
683
+ setTimeout(() => {
684
+ toast.classList.remove('show');
685
+ }, 3000);
686
+ }
687
+
688
+ function escapeHtml(text) {
689
+ const map = {
690
+ '&': '&amp;',
691
+ '<': '&lt;',
692
+ '>': '&gt;',
693
+ '"': '&quot;',
694
+ "'": '&#039;'
695
+ };
696
+ return text.replace(/[&<>"']/g, function(m) { return map[m]; });
697
+ }
698
+
699
+ // --- Event Listeners ---
700
+
701
+ addBtn.addEventListener('click', addTodo);
702
+
703
+ todoInput.addEventListener('keypress', (e) => {
704
+ if (e.key === 'Enter') addTodo();
705
+ });
706
+
707
+ micBtn.addEventListener('click', toggleSpeech);
708
+
709
+ filterBtns.forEach(btn => {
710
+ btn.addEventListener('click', () => {
711
+ // Update UI
712
+ filterBtns.forEach(b => b.classList.remove('active'));
713
+ btn.classList.add('active');
714
+
715
+ // Update Logic
716
+ currentFilter = btn.dataset.filter;
717
+ renderTodos();
718
+ });
719
+ });
720
+
721
+ // --- Initialization ---
722
+ renderTodos();
723
+
724
+ </script>
725
+ </body>
726
+ </html>