akhaliq HF Staff commited on
Commit
753533e
·
verified ·
1 Parent(s): 507d90f

Upload folder using huggingface_hub

Browse files
Files changed (1) hide show
  1. index.html +815 -19
index.html CHANGED
@@ -1,19 +1,815 @@
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>Modern Todo App</title>
7
+ <!-- Import Remix Icon for high-quality icons -->
8
+ <link href="https://cdn.jsdelivr.net/npm/[email protected]/fonts/remixicon.css" rel="stylesheet">
9
+ <style>
10
+ /* --- CSS Variables & Reset --- */
11
+ :root {
12
+ /* Light Theme */
13
+ --primary: #6366f1;
14
+ --primary-hover: #4f46e5;
15
+ --bg-body: #f3f4f6;
16
+ --bg-card: #ffffff;
17
+ --text-main: #1f2937;
18
+ --text-muted: #6b7280;
19
+ --border: #e5e7eb;
20
+ --danger: #ef4444;
21
+ --success: #10b981;
22
+ --shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
23
+ --radius: 16px;
24
+ --transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
25
+ }
26
+
27
+ [data-theme="dark"] {
28
+ /* Dark Theme */
29
+ --primary: #818cf8;
30
+ --primary-hover: #6366f1;
31
+ --bg-body: #111827;
32
+ --bg-card: #1f2937;
33
+ --text-main: #f9fafb;
34
+ --text-muted: #9ca3af;
35
+ --border: #374151;
36
+ --shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.5);
37
+ }
38
+
39
+ * {
40
+ box-sizing: border-box;
41
+ margin: 0;
42
+ padding: 0;
43
+ font-family: 'Inter', system-ui, -apple-system, sans-serif;
44
+ }
45
+
46
+ body {
47
+ background-color: var(--bg-body);
48
+ color: var(--text-main);
49
+ min-height: 100vh;
50
+ display: flex;
51
+ flex-direction: column;
52
+ align-items: center;
53
+ padding: 2rem 1rem;
54
+ transition: background-color 0.3s ease, color 0.3s ease;
55
+ }
56
+
57
+ /* --- Header & Branding --- */
58
+ header {
59
+ width: 100%;
60
+ max-width: 600px;
61
+ display: flex;
62
+ justify-content: space-between;
63
+ align-items: center;
64
+ margin-bottom: 2rem;
65
+ }
66
+
67
+ .logo {
68
+ font-size: 1.5rem;
69
+ font-weight: 800;
70
+ color: var(--primary);
71
+ display: flex;
72
+ align-items: center;
73
+ gap: 0.5rem;
74
+ }
75
+
76
+ .anycoder-link {
77
+ font-size: 0.85rem;
78
+ color: var(--text-muted);
79
+ text-decoration: none;
80
+ background: rgba(99, 102, 241, 0.1);
81
+ padding: 0.4rem 0.8rem;
82
+ border-radius: 20px;
83
+ transition: var(--transition);
84
+ font-weight: 500;
85
+ }
86
+
87
+ .anycoder-link:hover {
88
+ background: rgba(99, 102, 241, 0.2);
89
+ color: var(--primary);
90
+ }
91
+
92
+ .theme-toggle {
93
+ background: none;
94
+ border: none;
95
+ cursor: pointer;
96
+ color: var(--text-main);
97
+ font-size: 1.25rem;
98
+ padding: 0.5rem;
99
+ border-radius: 50%;
100
+ transition: var(--transition);
101
+ }
102
+
103
+ .theme-toggle:hover {
104
+ background-color: rgba(0,0,0,0.05);
105
+ }
106
+
107
+ /* --- Main Container --- */
108
+ .app-container {
109
+ width: 100%;
110
+ max-width: 600px;
111
+ background: var(--bg-card);
112
+ border-radius: var(--radius);
113
+ box-shadow: var(--shadow);
114
+ overflow: hidden;
115
+ display: flex;
116
+ flex-direction: column;
117
+ }
118
+
119
+ /* --- Progress Section --- */
120
+ .progress-section {
121
+ padding: 1.5rem 2rem 1rem;
122
+ }
123
+
124
+ .progress-labels {
125
+ display: flex;
126
+ justify-content: space-between;
127
+ margin-bottom: 0.5rem;
128
+ font-size: 0.9rem;
129
+ font-weight: 600;
130
+ color: var(--text-muted);
131
+ }
132
+
133
+ .progress-bar-bg {
134
+ width: 100%;
135
+ height: 8px;
136
+ background-color: var(--border);
137
+ border-radius: 4px;
138
+ overflow: hidden;
139
+ }
140
+
141
+ .progress-bar-fill {
142
+ height: 100%;
143
+ background-color: var(--primary);
144
+ width: 0%;
145
+ transition: width 0.5s cubic-bezier(0.4, 0, 0.2, 1);
146
+ }
147
+
148
+ /* --- Input Area --- */
149
+ .input-area {
150
+ padding: 1rem 2rem;
151
+ display: flex;
152
+ gap: 0.75rem;
153
+ border-bottom: 1px solid var(--border);
154
+ }
155
+
156
+ .input-wrapper {
157
+ position: relative;
158
+ flex-grow: 1;
159
+ }
160
+
161
+ .input-wrapper i {
162
+ position: absolute;
163
+ left: 1rem;
164
+ top: 50%;
165
+ transform: translateY(-50%);
166
+ color: var(--text-muted);
167
+ }
168
+
169
+ input[type="text"] {
170
+ width: 100%;
171
+ padding: 0.8rem 1rem 0.8rem 2.5rem;
172
+ border: 2px solid var(--border);
173
+ border-radius: 12px;
174
+ background: var(--bg-body);
175
+ color: var(--text-main);
176
+ font-size: 1rem;
177
+ outline: none;
178
+ transition: var(--transition);
179
+ }
180
+
181
+ input[type="text"]:focus {
182
+ border-color: var(--primary);
183
+ box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.2);
184
+ }
185
+
186
+ .add-btn {
187
+ background-color: var(--primary);
188
+ color: white;
189
+ border: none;
190
+ padding: 0 1.5rem;
191
+ border-radius: 12px;
192
+ font-weight: 600;
193
+ cursor: pointer;
194
+ transition: var(--transition);
195
+ display: flex;
196
+ align-items: center;
197
+ gap: 0.5rem;
198
+ }
199
+
200
+ .add-btn:hover {
201
+ background-color: var(--primary-hover);
202
+ transform: translateY(-1px);
203
+ }
204
+
205
+ .add-btn:active {
206
+ transform: translateY(1px);
207
+ }
208
+
209
+ /* --- Filters --- */
210
+ .filters {
211
+ display: flex;
212
+ padding: 1rem 2rem;
213
+ gap: 1rem;
214
+ border-bottom: 1px solid var(--border);
215
+ overflow-x: auto;
216
+ }
217
+
218
+ .filter-btn {
219
+ background: none;
220
+ border: none;
221
+ color: var(--text-muted);
222
+ font-weight: 600;
223
+ cursor: pointer;
224
+ padding-bottom: 0.25rem;
225
+ position: relative;
226
+ transition: color 0.3s;
227
+ }
228
+
229
+ .filter-btn.active {
230
+ color: var(--primary);
231
+ }
232
+
233
+ .filter-btn.active::after {
234
+ content: '';
235
+ position: absolute;
236
+ bottom: -4px;
237
+ left: 0;
238
+ width: 100%;
239
+ height: 2px;
240
+ background-color: var(--primary);
241
+ border-radius: 2px;
242
+ }
243
+
244
+ /* --- Task List --- */
245
+ .todo-list {
246
+ list-style: none;
247
+ padding: 0;
248
+ max-height: 50vh;
249
+ overflow-y: auto;
250
+ }
251
+
252
+ .todo-item {
253
+ display: flex;
254
+ align-items: center;
255
+ padding: 1rem 2rem;
256
+ border-bottom: 1px solid var(--border);
257
+ transition: var(--transition);
258
+ animation: slideIn 0.3s ease forwards;
259
+ }
260
+
261
+ .todo-item:last-child {
262
+ border-bottom: none;
263
+ }
264
+
265
+ .todo-item:hover {
266
+ background-color: rgba(0,0,0,0.02);
267
+ }
268
+
269
+ .todo-item.slide-out {
270
+ animation: slideOut 0.3s ease forwards;
271
+ }
272
+
273
+ /* Custom Checkbox */
274
+ .checkbox-wrapper {
275
+ position: relative;
276
+ width: 22px;
277
+ height: 22px;
278
+ margin-right: 1rem;
279
+ cursor: pointer;
280
+ }
281
+
282
+ .checkbox-wrapper input {
283
+ opacity: 0;
284
+ width: 0;
285
+ height: 0;
286
+ }
287
+
288
+ .checkmark {
289
+ position: absolute;
290
+ top: 0;
291
+ left: 0;
292
+ height: 22px;
293
+ width: 22px;
294
+ background-color: var(--bg-body);
295
+ border: 2px solid var(--border);
296
+ border-radius: 6px;
297
+ transition: var(--transition);
298
+ }
299
+
300
+ .checkbox-wrapper:hover .checkmark {
301
+ border-color: var(--primary);
302
+ }
303
+
304
+ .checkbox-wrapper input:checked ~ .checkmark {
305
+ background-color: var(--primary);
306
+ border-color: var(--primary);
307
+ }
308
+
309
+ .checkmark:after {
310
+ content: "";
311
+ position: absolute;
312
+ display: none;
313
+ left: 7px;
314
+ top: 3px;
315
+ width: 5px;
316
+ height: 10px;
317
+ border: solid white;
318
+ border-width: 0 2px 2px 0;
319
+ transform: rotate(45deg);
320
+ }
321
+
322
+ .checkbox-wrapper input:checked ~ .checkmark:after {
323
+ display: block;
324
+ }
325
+
326
+ /* Task Content */
327
+ .task-content {
328
+ flex-grow: 1;
329
+ font-size: 1rem;
330
+ color: var(--text-main);
331
+ transition: var(--transition);
332
+ word-break: break-all;
333
+ }
334
+
335
+ .todo-item.completed .task-content {
336
+ text-decoration: line-through;
337
+ color: var(--text-muted);
338
+ opacity: 0.7;
339
+ }
340
+
341
+ .edit-input {
342
+ width: 100%;
343
+ padding: 0.5rem;
344
+ font-size: 1rem;
345
+ border: 1px solid var(--primary);
346
+ border-radius: 6px;
347
+ outline: none;
348
+ background: var(--bg-body);
349
+ color: var(--text-main);
350
+ }
351
+
352
+ /* Actions */
353
+ .actions {
354
+ display: flex;
355
+ gap: 0.5rem;
356
+ opacity: 0; /* Hidden by default for cleaner look */
357
+ transition: opacity 0.2s;
358
+ }
359
+
360
+ .todo-item:hover .actions {
361
+ opacity: 1;
362
+ }
363
+
364
+ @media (max-width: 600px) {
365
+ .actions { opacity: 1; } /* Always show actions on touch */
366
+ }
367
+
368
+ .action-btn {
369
+ background: none;
370
+ border: none;
371
+ cursor: pointer;
372
+ color: var(--text-muted);
373
+ padding: 0.4rem;
374
+ border-radius: 6px;
375
+ transition: var(--transition);
376
+ }
377
+
378
+ .action-btn:hover {
379
+ background-color: rgba(0,0,0,0.05);
380
+ color: var(--text-main);
381
+ }
382
+
383
+ .action-btn.delete:hover {
384
+ background-color: rgba(239, 68, 68, 0.1);
385
+ color: var(--danger);
386
+ }
387
+
388
+ /* --- Empty State --- */
389
+ .empty-state {
390
+ padding: 3rem 1rem;
391
+ text-align: center;
392
+ color: var(--text-muted);
393
+ display: none;
394
+ }
395
+
396
+ .empty-state i {
397
+ font-size: 3rem;
398
+ margin-bottom: 1rem;
399
+ display: block;
400
+ opacity: 0.5;
401
+ }
402
+
403
+ /* --- Footer Stats --- */
404
+ .app-footer {
405
+ padding: 1rem 2rem;
406
+ background-color: var(--bg-body);
407
+ border-top: 1px solid var(--border);
408
+ display: flex;
409
+ justify-content: space-between;
410
+ align-items: center;
411
+ font-size: 0.85rem;
412
+ color: var(--text-muted);
413
+ }
414
+
415
+ .clear-btn {
416
+ background: none;
417
+ border: none;
418
+ color: var(--text-muted);
419
+ cursor: pointer;
420
+ font-size: 0.85rem;
421
+ transition: var(--transition);
422
+ }
423
+
424
+ .clear-btn:hover {
425
+ color: var(--danger);
426
+ text-decoration: underline;
427
+ }
428
+
429
+ /* --- Toast Notification --- */
430
+ .toast-container {
431
+ position: fixed;
432
+ bottom: 2rem;
433
+ left: 50%;
434
+ transform: translateX(-50%);
435
+ display: flex;
436
+ flex-direction: column;
437
+ gap: 0.5rem;
438
+ z-index: 1000;
439
+ }
440
+
441
+ .toast {
442
+ background-color: #333;
443
+ color: white;
444
+ padding: 0.75rem 1.5rem;
445
+ border-radius: 50px;
446
+ font-size: 0.9rem;
447
+ box-shadow: 0 4px 12px rgba(0,0,0,0.15);
448
+ animation: fadeIn 0.3s ease, fadeOut 0.3s ease 2.7s forwards;
449
+ display: flex;
450
+ align-items: center;
451
+ gap: 0.5rem;
452
+ }
453
+
454
+ /* --- Animations --- */
455
+ @keyframes slideIn {
456
+ from { opacity: 0; transform: translateY(10px); }
457
+ to { opacity: 1; transform: translateY(0); }
458
+ }
459
+
460
+ @keyframes slideOut {
461
+ from { opacity: 1; transform: translateX(0); }
462
+ to { opacity: 0; transform: translateX(50px); }
463
+ }
464
+
465
+ @keyframes fadeIn {
466
+ from { opacity: 0; transform: translateY(20px); }
467
+ to { opacity: 1; transform: translateY(0); }
468
+ }
469
+
470
+ @keyframes fadeOut {
471
+ from { opacity: 1; }
472
+ to { opacity: 0; }
473
+ }
474
+
475
+ /* Scrollbar Styling */
476
+ ::-webkit-scrollbar {
477
+ width: 6px;
478
+ }
479
+ ::-webkit-scrollbar-track {
480
+ background: transparent;
481
+ }
482
+ ::-webkit-scrollbar-thumb {
483
+ background: var(--border);
484
+ border-radius: 3px;
485
+ }
486
+ ::-webkit-scrollbar-thumb:hover {
487
+ background: var(--text-muted);
488
+ }
489
+ </style>
490
+ </head>
491
+ <body>
492
+
493
+ <header>
494
+ <div class="logo">
495
+ <i class="ri-checkbox-circle-fill"></i> TaskMaster
496
+ </div>
497
+ <div style="display: flex; gap: 1rem; align-items: center;">
498
+ <a href="https://huggingface.co/spaces/akhaliq/anycoder" target="_blank" class="anycoder-link">
499
+ Built with anycoder
500
+ </a>
501
+ <button class="theme-toggle" id="themeToggle" aria-label="Toggle Dark Mode">
502
+ <i class="ri-moon-line"></i>
503
+ </button>
504
+ </div>
505
+ </header>
506
+
507
+ <main class="app-container">
508
+ <!-- Progress Section -->
509
+ <section class="progress-section">
510
+ <div class="progress-labels">
511
+ <span id="progressText">0/0 Completed</span>
512
+ <span id="progressPercent">0%</span>
513
+ </div>
514
+ <div class="progress-bar-bg">
515
+ <div class="progress-bar-fill" id="progressBar"></div>
516
+ </div>
517
+ </section>
518
+
519
+ <!-- Input Area -->
520
+ <section class="input-area">
521
+ <div class="input-wrapper">
522
+ <i class="ri-add-line"></i>
523
+ <input type="text" id="todoInput" placeholder="Add a new task..." autocomplete="off">
524
+ </div>
525
+ <button class="add-btn" id="addBtn">
526
+ <i class="ri-add-line"></i> <span>Add</span>
527
+ </button>
528
+ </section>
529
+
530
+ <!-- Filters -->
531
+ <nav class="filters">
532
+ <button class="filter-btn active" data-filter="all">All</button>
533
+ <button class="filter-btn" data-filter="active">Active</button>
534
+ <button class="filter-btn" data-filter="completed">Completed</button>
535
+ </nav>
536
+
537
+ <!-- List -->
538
+ <ul class="todo-list" id="todoList">
539
+ <!-- Items injected via JS -->
540
+ </ul>
541
+
542
+ <!-- Empty State -->
543
+ <div class="empty-state" id="emptyState">
544
+ <i class="ri-clipboard-line"></i>
545
+ <p>No tasks found. Add one to get started!</p>
546
+ </div>
547
+
548
+ <!-- Footer -->
549
+ <footer class="app-footer">
550
+ <span id="itemsLeft">0 items left</span>
551
+ <button class="clear-btn" id="clearCompletedBtn">Clear Completed</button>
552
+ </footer>
553
+ </main>
554
+
555
+ <!-- Toast Container -->
556
+ <div class="toast-container" id="toastContainer"></div>
557
+
558
+ <script>
559
+ // --- State Management ---
560
+ let todos = JSON.parse(localStorage.getItem('todos')) || [];
561
+ let currentFilter = 'all';
562
+
563
+ // --- DOM Elements ---
564
+ const todoInput = document.getElementById('todoInput');
565
+ const addBtn = document.getElementById('addBtn');
566
+ const todoList = document.getElementById('todoList');
567
+ const itemsLeft = document.getElementById('itemsLeft');
568
+ const clearCompletedBtn = document.getElementById('clearCompletedBtn');
569
+ const filterBtns = document.querySelectorAll('.filter-btn');
570
+ const emptyState = document.getElementById('emptyState');
571
+ const progressBar = document.getElementById('progressBar');
572
+ const progressText = document.getElementById('progressText');
573
+ const progressPercent = document.getElementById('progressPercent');
574
+ const themeToggle = document.getElementById('themeToggle');
575
+ const toastContainer = document.getElementById('toastContainer');
576
+
577
+ // --- Initialization ---
578
+ document.addEventListener('DOMContentLoaded', () => {
579
+ render();
580
+ loadTheme();
581
+ });
582
+
583
+ // --- Event Listeners ---
584
+ addBtn.addEventListener('click', addTodo);
585
+
586
+ todoInput.addEventListener('keypress', (e) => {
587
+ if (e.key === 'Enter') addTodo();
588
+ });
589
+
590
+ clearCompletedBtn.addEventListener('click', () => {
591
+ const completedCount = todos.filter(t => t.completed).length;
592
+ if (completedCount === 0) {
593
+ showToast('No completed tasks to clear', 'info');
594
+ return;
595
+ }
596
+ todos = todos.filter(todo => !todo.completed);
597
+ saveAndRender();
598
+ showToast(`${completedCount} tasks cleared`, 'success');
599
+ });
600
+
601
+ filterBtns.forEach(btn => {
602
+ btn.addEventListener('click', () => {
603
+ // Update UI classes
604
+ filterBtns.forEach(b => b.classList.remove('active'));
605
+ btn.classList.add('active');
606
+
607
+ // Update state
608
+ currentFilter = btn.getAttribute('data-filter');
609
+ render();
610
+ });
611
+ });
612
+
613
+ themeToggle.addEventListener('click', () => {
614
+ const isDark = document.documentElement.getAttribute('data-theme') === 'dark';
615
+ const newTheme = isDark ? 'light' : 'dark';
616
+ document.documentElement.setAttribute('data-theme', newTheme);
617
+ themeToggle.innerHTML = isDark ? '<i class="ri-moon-line"></i>' : '<i class="ri-sun-line"></i>';
618
+ localStorage.setItem('theme', newTheme);
619
+ });
620
+
621
+ // --- Core Functions ---
622
+
623
+ function addTodo() {
624
+ const text = todoInput.value.trim();
625
+ if (text === '') {
626
+ showToast('Please enter a task', 'warning');
627
+ return;
628
+ }
629
+
630
+ const newTodo = {
631
+ id: Date.now(),
632
+ text: text,
633
+ completed: false,
634
+ createdAt: new Date().toISOString()
635
+ };
636
+
637
+ todos.unshift(newTodo); // Add to top
638
+ todoInput.value = '';
639
+ saveAndRender();
640
+ showToast('Task added successfully', 'success');
641
+ }
642
+
643
+ function toggleTodo(id) {
644
+ todos = todos.map(todo => {
645
+ if (todo.id === id) {
646
+ return { ...todo, completed: !todo.completed };
647
+ }
648
+ return todo;
649
+ });
650
+ saveAndRender();
651
+ }
652
+
653
+ function deleteTodo(id) {
654
+ // Find element to animate out
655
+ const itemElement = document.querySelector(`[data-id="${id}"]`);
656
+ if (itemElement) {
657
+ itemElement.classList.add('slide-out');
658
+ itemElement.addEventListener('animationend', () => {
659
+ todos = todos.filter(todo => todo.id !== id);
660
+ saveAndRender();
661
+ showToast('Task deleted', 'info');
662
+ });
663
+ } else {
664
+ todos = todos.filter(todo => todo.id !== id);
665
+ saveAndRender();
666
+ }
667
+ }
668
+
669
+ function editTodo(id) {
670
+ const todo = todos.find(t => t.id === id);
671
+ if (!todo) return;
672
+
673
+ const li = document.querySelector(`[data-id="${id}"]`);
674
+ const span = li.querySelector('.task-content');
675
+
676
+ // Create input
677
+ const input = document.createElement('input');
678
+ input.type = 'text';
679
+ input.value = todo.text;
680
+ input.className = 'edit-input';
681
+
682
+ // Replace span with input
683
+ span.replaceWith(input);
684
+ input.focus();
685
+
686
+ // Save on blur or enter
687
+ const saveEdit = () => {
688
+ const newText = input.value.trim();
689
+ if (newText) {
690
+ todos = todos.map(t => t.id === id ? { ...t, text: newText } : t);
691
+ saveAndRender();
692
+ } else {
693
+ render(); // Revert if empty
694
+ }
695
+ };
696
+
697
+ input.addEventListener('blur', saveEdit);
698
+ input.addEventListener('keypress', (e) => {
699
+ if (e.key === 'Enter') {
700
+ input.blur();
701
+ }
702
+ });
703
+ }
704
+
705
+ function saveAndRender() {
706
+ localStorage.setItem('todos', JSON.stringify(todos));
707
+ render();
708
+ }
709
+
710
+ function render() {
711
+ // 1. Filter List
712
+ let filteredTodos = todos;
713
+ if (currentFilter === 'active') {
714
+ filteredTodos = todos.filter(t => !t.completed);
715
+ } else if (currentFilter === 'completed') {
716
+ filteredTodos = todos.filter(t => t.completed);
717
+ }
718
+
719
+ // 2. Clear DOM
720
+ todoList.innerHTML = '';
721
+
722
+ // 3. Check Empty State
723
+ if (filteredTodos.length === 0) {
724
+ emptyState.style.display = 'block';
725
+ } else {
726
+ emptyState.style.display = 'none';
727
+ }
728
+
729
+ // 4. Build DOM
730
+ filteredTodos.forEach(todo => {
731
+ const li = document.createElement('li');
732
+ li.className = `todo-item ${todo.completed ? 'completed' : ''}`;
733
+ li.setAttribute('data-id', todo.id);
734
+
735
+ li.innerHTML = `
736
+ <label class="checkbox-wrapper">
737
+ <input type="checkbox" ${todo.completed ? 'checked' : ''}>
738
+ <span class="checkmark"></span>
739
+ </label>
740
+ <span class="task-content">${escapeHtml(todo.text)}</span>
741
+ <div class="actions">
742
+ <button class="action-btn edit" aria-label="Edit">
743
+ <i class="ri-pencil-line"></i>
744
+ </button>
745
+ <button class="action-btn delete" aria-label="Delete">
746
+ <i class="ri-delete-bin-line"></i>
747
+ </button>
748
+ </div>
749
+ `;
750
+
751
+ // Attach specific listeners to elements inside
752
+ const checkbox = li.querySelector('input[type="checkbox"]');
753
+ checkbox.addEventListener('change', () => toggleTodo(todo.id));
754
+
755
+ const deleteBtn = li.querySelector('.delete');
756
+ deleteBtn.addEventListener('click', () => deleteTodo(todo.id));
757
+
758
+ const editBtn = li.querySelector('.edit');
759
+ editBtn.addEventListener('click', () => editTodo(todo.id));
760
+
761
+ todoList.appendChild(li);
762
+ });
763
+
764
+ updateStats();
765
+ }
766
+
767
+ function updateStats() {
768
+ const activeCount = todos.filter(t => !t.completed).length;
769
+ const totalCount = todos.length;
770
+ const completedCount = totalCount - activeCount;
771
+ const percentage = totalCount === 0 ? 0 : Math.round((completedCount / totalCount) * 100);
772
+
773
+ // Footer Text
774
+ itemsLeft.textContent = `${activeCount} item${activeCount !== 1 ? 's' : ''} left`;
775
+
776
+ // Progress Bar
777
+ progressBar.style.width = `${percentage}%`;
778
+ progressText.textContent = `${completedCount}/${totalCount} Completed`;
779
+ progressPercent.textContent = `${percentage}%`;
780
+ }
781
+
782
+ // --- Helper Functions ---
783
+
784
+ function loadTheme() {
785
+ const savedTheme = localStorage.getItem('theme') || 'light';
786
+ document.documentElement.setAttribute('data-theme', savedTheme);
787
+ themeToggle.innerHTML = savedTheme === 'dark' ? '<i class="ri-sun-line"></i>' : '<i class="ri-moon-line"></i>';
788
+ }
789
+
790
+ function escapeHtml(text) {
791
+ const div = document.createElement('div');
792
+ div.textContent = text;
793
+ return div.innerHTML;
794
+ }
795
+
796
+ function showToast(message, type = 'info') {
797
+ const toast = document.createElement('div');
798
+ toast.className = 'toast';
799
+
800
+ let icon = 'ri-information-line';
801
+ if (type === 'success') icon = 'ri-checkbox-circle-line';
802
+ if (type === 'warning') icon = 'ri-error-warning-line';
803
+
804
+ toast.innerHTML = `<i class="${icon}"></i> ${message}`;
805
+
806
+ toastContainer.appendChild(toast);
807
+
808
+ // Remove after animation (3s total: 0.3 in + 2.4 wait + 0.3 out)
809
+ setTimeout(() => {
810
+ toast.remove();
811
+ }, 3000);
812
+ }
813
+ </script>
814
+ </body>
815
+ </html>