akhaliq HF Staff commited on
Commit
8088cfa
·
verified ·
1 Parent(s): 688e620

Upload folder using huggingface_hub

Browse files
Files changed (1) hide show
  1. index.html +680 -19
index.html CHANGED
@@ -1,19 +1,680 @@
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>FocusFlow | Modern To-Do</title>
7
+
8
+ <!-- Importing 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=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
12
+
13
+ <!-- Importing Phosphor Icons (Lightweight icon library) -->
14
+ <script src="https://unpkg.com/@phosphor-icons/web"></script>
15
+
16
+ <style>
17
+ :root {
18
+ /* Color Palette */
19
+ --primary: #6366f1;
20
+ --primary-hover: #4f46e5;
21
+ --secondary: #ec4899;
22
+ --bg-gradient-start: #e0c3fc;
23
+ --bg-gradient-end: #8ec5fc;
24
+ --glass-bg: rgba(255, 255, 255, 0.75);
25
+ --glass-border: rgba(255, 255, 255, 0.5);
26
+ --text-main: #1f2937;
27
+ --text-muted: #6b7280;
28
+ --danger: #ef4444;
29
+ --success: #10b981;
30
+ --shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
31
+ --shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
32
+ --radius: 16px;
33
+ }
34
+
35
+ * {
36
+ margin: 0;
37
+ padding: 0;
38
+ box-sizing: border-box;
39
+ font-family: 'Inter', sans-serif;
40
+ }
41
+
42
+ body {
43
+ min-height: 100vh;
44
+ display: flex;
45
+ justify-content: center;
46
+ align-items: center;
47
+ background: linear-gradient(135deg, var(--bg-gradient-start) 0%, var(--bg-gradient-end) 100%);
48
+ background-size: 200% 200%;
49
+ animation: gradientMove 15s ease infinite;
50
+ padding: 20px;
51
+ color: var(--text-main);
52
+ }
53
+
54
+ @keyframes gradientMove {
55
+ 0% { background-position: 0% 50%; }
56
+ 50% { background-position: 100% 50%; }
57
+ 100% { background-position: 0% 50%; }
58
+ }
59
+
60
+ /* Main Container */
61
+ .app-container {
62
+ width: 100%;
63
+ max-width: 500px;
64
+ background: var(--glass-bg);
65
+ backdrop-filter: blur(16px);
66
+ -webkit-backdrop-filter: blur(16px);
67
+ border: 1px solid var(--glass-border);
68
+ border-radius: var(--radius);
69
+ box-shadow: var(--shadow-lg);
70
+ overflow: hidden;
71
+ display: flex;
72
+ flex-direction: column;
73
+ transition: transform 0.3s ease;
74
+ }
75
+
76
+ /* Header */
77
+ header {
78
+ padding: 24px 24px 16px;
79
+ border-bottom: 1px solid rgba(0,0,0,0.05);
80
+ display: flex;
81
+ justify-content: space-between;
82
+ align-items: center;
83
+ }
84
+
85
+ h1 {
86
+ font-size: 1.5rem;
87
+ font-weight: 700;
88
+ background: linear-gradient(to right, var(--primary), var(--secondary));
89
+ -webkit-background-clip: text;
90
+ background-clip: text;
91
+ color: transparent;
92
+ }
93
+
94
+ .brand-link {
95
+ font-size: 0.75rem;
96
+ color: var(--text-muted);
97
+ text-decoration: none;
98
+ font-weight: 500;
99
+ padding: 4px 8px;
100
+ background: rgba(255,255,255,0.5);
101
+ border-radius: 6px;
102
+ transition: all 0.2s;
103
+ }
104
+
105
+ .brand-link:hover {
106
+ background: #fff;
107
+ color: var(--primary);
108
+ }
109
+
110
+ /* Input Area */
111
+ .input-group {
112
+ padding: 20px 24px;
113
+ display: flex;
114
+ gap: 12px;
115
+ position: relative;
116
+ }
117
+
118
+ .input-wrapper {
119
+ position: relative;
120
+ flex: 1;
121
+ }
122
+
123
+ .task-input {
124
+ width: 100%;
125
+ padding: 12px 16px;
126
+ padding-right: 40px;
127
+ border: 2px solid transparent;
128
+ background: rgba(255, 255, 255, 0.6);
129
+ border-radius: 12px;
130
+ font-size: 1rem;
131
+ outline: none;
132
+ transition: all 0.2s;
133
+ box-shadow: inset 0 2px 4px rgba(0,0,0,0.02);
134
+ }
135
+
136
+ .task-input:focus {
137
+ background: #fff;
138
+ border-color: var(--primary);
139
+ box-shadow: 0 0 0 4px rgba(99, 102, 241, 0.1);
140
+ }
141
+
142
+ .add-btn {
143
+ background: var(--primary);
144
+ color: white;
145
+ border: none;
146
+ width: 48px;
147
+ height: 48px;
148
+ border-radius: 12px;
149
+ cursor: pointer;
150
+ display: flex;
151
+ align-items: center;
152
+ justify-content: center;
153
+ font-size: 1.25rem;
154
+ transition: all 0.2s;
155
+ box-shadow: 0 4px 6px rgba(99, 102, 241, 0.3);
156
+ }
157
+
158
+ .add-btn:hover {
159
+ background: var(--primary-hover);
160
+ transform: translateY(-2px);
161
+ box-shadow: 0 6px 10px rgba(99, 102, 241, 0.4);
162
+ }
163
+
164
+ .add-btn:active {
165
+ transform: translateY(0);
166
+ }
167
+
168
+ /* Filters */
169
+ .filters {
170
+ display: flex;
171
+ padding: 0 24px;
172
+ gap: 12px;
173
+ margin-bottom: 12px;
174
+ }
175
+
176
+ .filter-btn {
177
+ background: none;
178
+ border: none;
179
+ padding: 6px 12px;
180
+ border-radius: 8px;
181
+ color: var(--text-muted);
182
+ font-size: 0.875rem;
183
+ font-weight: 500;
184
+ cursor: pointer;
185
+ transition: all 0.2s;
186
+ }
187
+
188
+ .filter-btn.active {
189
+ background: rgba(99, 102, 241, 0.1);
190
+ color: var(--primary);
191
+ font-weight: 600;
192
+ }
193
+
194
+ .filter-btn:hover:not(.active) {
195
+ background: rgba(0,0,0,0.03);
196
+ color: var(--text-main);
197
+ }
198
+
199
+ /* Task List */
200
+ .task-list {
201
+ list-style: none;
202
+ padding: 0 24px 24px;
203
+ max-height: 50vh;
204
+ overflow-y: auto;
205
+ /* Scrollbar styling */
206
+ scrollbar-width: thin;
207
+ scrollbar-color: rgba(0,0,0,0.1) transparent;
208
+ }
209
+
210
+ .task-list::-webkit-scrollbar {
211
+ width: 6px;
212
+ }
213
+ .task-list::-webkit-scrollbar-thumb {
214
+ background-color: rgba(0,0,0,0.1);
215
+ border-radius: 20px;
216
+ }
217
+
218
+ .task-item {
219
+ background: #fff;
220
+ border-radius: 12px;
221
+ padding: 12px 16px;
222
+ margin-bottom: 10px;
223
+ display: flex;
224
+ align-items: center;
225
+ justify-content: space-between;
226
+ gap: 12px;
227
+ box-shadow: var(--shadow-sm);
228
+ animation: slideIn 0.3s ease-out forwards;
229
+ transition: all 0.2s;
230
+ border: 1px solid transparent;
231
+ }
232
+
233
+ .task-item:hover {
234
+ transform: translateY(-1px);
235
+ box-shadow: 0 4px 6px -2px rgba(0, 0, 0, 0.05);
236
+ border-color: rgba(0,0,0,0.05);
237
+ }
238
+
239
+ @keyframes slideIn {
240
+ from { opacity: 0; transform: translateY(10px); }
241
+ to { opacity: 1; transform: translateY(0); }
242
+ }
243
+
244
+ @keyframes slideOut {
245
+ to { opacity: 0; transform: translateX(20px); height: 0; margin-bottom: 0; padding: 0; }
246
+ }
247
+
248
+ .task-content {
249
+ display: flex;
250
+ align-items: center;
251
+ gap: 12px;
252
+ flex: 1;
253
+ overflow: hidden;
254
+ }
255
+
256
+ /* Custom Checkbox */
257
+ .checkbox-wrapper {
258
+ position: relative;
259
+ width: 22px;
260
+ height: 22px;
261
+ flex-shrink: 0;
262
+ }
263
+
264
+ .checkbox-wrapper input {
265
+ opacity: 0;
266
+ width: 100%;
267
+ height: 100%;
268
+ cursor: pointer;
269
+ position: absolute;
270
+ z-index: 2;
271
+ }
272
+
273
+ .custom-checkbox {
274
+ position: absolute;
275
+ top: 0;
276
+ left: 0;
277
+ width: 100%;
278
+ height: 100%;
279
+ border: 2px solid #d1d5db;
280
+ border-radius: 6px;
281
+ transition: all 0.2s;
282
+ display: flex;
283
+ align-items: center;
284
+ justify-content: center;
285
+ color: white;
286
+ font-size: 14px;
287
+ }
288
+
289
+ .checkbox-wrapper input:checked + .custom-checkbox {
290
+ background: var(--success);
291
+ border-color: var(--success);
292
+ }
293
+
294
+ .task-text {
295
+ font-size: 0.95rem;
296
+ color: var(--text-main);
297
+ transition: color 0.2s;
298
+ word-break: break-word;
299
+ cursor: text;
300
+ }
301
+
302
+ .task-item.completed .task-text {
303
+ text-decoration: line-through;
304
+ color: var(--text-muted);
305
+ }
306
+
307
+ /* Edit Mode */
308
+ .edit-input {
309
+ width: 100%;
310
+ font-size: 0.95rem;
311
+ padding: 4px 8px;
312
+ border: 1px solid var(--primary);
313
+ border-radius: 4px;
314
+ outline: none;
315
+ }
316
+
317
+ /* Actions */
318
+ .task-actions {
319
+ display: flex;
320
+ gap: 4px;
321
+ opacity: 0;
322
+ transition: opacity 0.2s;
323
+ }
324
+
325
+ .task-item:hover .task-actions {
326
+ opacity: 1;
327
+ }
328
+
329
+ /* Mobile support: always show actions or make them bigger */
330
+ @media (max-width: 600px) {
331
+ .task-actions { opacity: 1; }
332
+ }
333
+
334
+ .action-btn {
335
+ background: none;
336
+ border: none;
337
+ width: 32px;
338
+ height: 32px;
339
+ border-radius: 6px;
340
+ display: flex;
341
+ align-items: center;
342
+ justify-content: center;
343
+ color: var(--text-muted);
344
+ cursor: pointer;
345
+ transition: all 0.2s;
346
+ }
347
+
348
+ .action-btn:hover {
349
+ background: #f3f4f6;
350
+ color: var(--text-main);
351
+ }
352
+
353
+ .delete-btn:hover {
354
+ background: #fee2e2;
355
+ color: var(--danger);
356
+ }
357
+
358
+ /* Footer Stats */
359
+ .app-footer {
360
+ background: rgba(255,255,255,0.4);
361
+ padding: 16px 24px;
362
+ border-top: 1px solid rgba(0,0,0,0.05);
363
+ display: flex;
364
+ justify-content: space-between;
365
+ align-items: center;
366
+ font-size: 0.8rem;
367
+ color: var(--text-muted);
368
+ }
369
+
370
+ .clear-btn {
371
+ background: none;
372
+ border: none;
373
+ color: var(--text-muted);
374
+ cursor: pointer;
375
+ font-size: 0.8rem;
376
+ transition: color 0.2s;
377
+ }
378
+
379
+ .clear-btn:hover {
380
+ color: var(--text-main);
381
+ text-decoration: underline;
382
+ }
383
+
384
+ /* Empty State */
385
+ .empty-state {
386
+ text-align: center;
387
+ padding: 40px 20px;
388
+ color: var(--text-muted);
389
+ display: none;
390
+ }
391
+
392
+ .empty-state i {
393
+ font-size: 3rem;
394
+ margin-bottom: 10px;
395
+ opacity: 0.5;
396
+ color: var(--primary);
397
+ }
398
+
399
+ /* Toast Notification */
400
+ .toast {
401
+ position: fixed;
402
+ bottom: 20px;
403
+ left: 50%;
404
+ transform: translateX(-50%) translateY(100px);
405
+ background: #333;
406
+ color: #fff;
407
+ padding: 10px 20px;
408
+ border-radius: 30px;
409
+ font-size: 0.9rem;
410
+ box-shadow: 0 4px 12px rgba(0,0,0,0.2);
411
+ opacity: 0;
412
+ transition: all 0.3s cubic-bezier(0.68, -0.55, 0.27, 1.55);
413
+ z-index: 1000;
414
+ }
415
+
416
+ .toast.show {
417
+ transform: translateX(-50%) translateY(0);
418
+ opacity: 1;
419
+ }
420
+
421
+ </style>
422
+ </head>
423
+ <body>
424
+
425
+ <div class="app-container">
426
+ <header>
427
+ <h1>FocusFlow</h1>
428
+ <a href="https://huggingface.co/spaces/akhaliq/anycoder" target="_blank" class="brand-link">
429
+ Built with anycoder
430
+ </a>
431
+ </header>
432
+
433
+ <section class="input-group">
434
+ <div class="input-wrapper">
435
+ <input type="text" id="taskInput" class="task-input" placeholder="What needs to be done?" autocomplete="off">
436
+ </div>
437
+ <button id="addBtn" class="add-btn" aria-label="Add Task">
438
+ <i class="ph ph-plus"></i>
439
+ </button>
440
+ </section>
441
+
442
+ <nav class="filters">
443
+ <button class="filter-btn active" data-filter="all">All</button>
444
+ <button class="filter-btn" data-filter="active">Active</button>
445
+ <button class="filter-btn" data-filter="completed">Completed</button>
446
+ </nav>
447
+
448
+ <div class="empty-state" id="emptyState">
449
+ <i class="ph ph-check-circle"></i>
450
+ <p>No tasks found. Time to relax or get productive!</p>
451
+ </div>
452
+
453
+ <ul class="task-list" id="taskList">
454
+ <!-- Tasks will be injected here via JS -->
455
+ </ul>
456
+
457
+ <footer class="app-footer">
458
+ <span id="itemsLeft">0 items left</span>
459
+ <button class="clear-btn" id="clearCompleted">Clear Completed</button>
460
+ </footer>
461
+ </div>
462
+
463
+ <!-- Toast Notification Element -->
464
+ <div id="toast" class="toast">Action Successful</div>
465
+
466
+ <script>
467
+ // --- State Management ---
468
+ let tasks = JSON.parse(localStorage.getItem('focusflow-tasks')) || [];
469
+ let currentFilter = 'all';
470
+
471
+ // --- DOM Elements ---
472
+ const taskInput = document.getElementById('taskInput');
473
+ const addBtn = document.getElementById('addBtn');
474
+ const taskList = document.getElementById('taskList');
475
+ const itemsLeft = document.getElementById('itemsLeft');
476
+ const clearCompletedBtn = document.getElementById('clearCompleted');
477
+ const filterBtns = document.querySelectorAll('.filter-btn');
478
+ const emptyState = document.getElementById('emptyState');
479
+ const toast = document.getElementById('toast');
480
+
481
+ // --- Functions ---
482
+
483
+ function saveTasks() {
484
+ localStorage.setItem('focusflow-tasks', JSON.stringify(tasks));
485
+ render();
486
+ }
487
+
488
+ function showToast(message) {
489
+ toast.textContent = message;
490
+ toast.classList.add('show');
491
+ setTimeout(() => {
492
+ toast.classList.remove('show');
493
+ }, 2500);
494
+ }
495
+
496
+ function addTask() {
497
+ const text = taskInput.value.trim();
498
+ if (text === '') {
499
+ // Visual feedback for empty input
500
+ taskInput.style.borderColor = 'var(--danger)';
501
+ setTimeout(() => taskInput.style.borderColor = 'transparent', 1000);
502
+ return;
503
+ }
504
+
505
+ const newTask = {
506
+ id: Date.now(),
507
+ text: text,
508
+ completed: false
509
+ };
510
+
511
+ tasks.unshift(newTask); // Add to top
512
+ taskInput.value = '';
513
+ taskInput.focus();
514
+ saveTasks();
515
+ showToast('Task added');
516
+ }
517
+
518
+ function toggleTask(id) {
519
+ tasks = tasks.map(task =>
520
+ task.id === id ? { ...task, completed: !task.completed } : task
521
+ );
522
+ saveTasks();
523
+ }
524
+
525
+ function deleteTask(id) {
526
+ // Find element to animate out
527
+ const element = document.querySelector(`[data-id="${id}"]`);
528
+ if (element) {
529
+ element.style.animation = 'slideOut 0.3s ease-out forwards';
530
+ setTimeout(() => {
531
+ tasks = tasks.filter(task => task.id !== id);
532
+ saveTasks();
533
+ showToast('Task deleted');
534
+ }, 300); // Wait for animation
535
+ }
536
+ }
537
+
538
+ function enableEdit(id, textElement) {
539
+ const currentText = textElement.textContent;
540
+ const input = document.createElement('input');
541
+ input.type = 'text';
542
+ input.className = 'edit-input';
543
+ input.value = currentText;
544
+
545
+ // Replace text with input
546
+ textElement.replaceWith(input);
547
+ input.focus();
548
+
549
+ const saveEdit = () => {
550
+ const newText = input.value.trim();
551
+ if (newText) {
552
+ tasks = tasks.map(t => t.id === id ? { ...t, text: newText } : t);
553
+ saveTasks();
554
+ } else {
555
+ render(); // Revert if empty
556
+ }
557
+ };
558
+
559
+ // Save on blur or Enter key
560
+ input.addEventListener('blur', saveEdit);
561
+ input.addEventListener('keydown', (e) => {
562
+ if (e.key === 'Enter') {
563
+ input.blur();
564
+ }
565
+ });
566
+ }
567
+
568
+ function clearCompleted() {
569
+ const completedCount = tasks.filter(t => t.completed).length;
570
+ if (completedCount === 0) return;
571
+
572
+ tasks = tasks.filter(task => !task.completed);
573
+ saveTasks();
574
+ showToast(`${completedCount} tasks cleared`);
575
+ }
576
+
577
+ function render() {
578
+ taskList.innerHTML = '';
579
+
580
+ // Filter logic
581
+ const filteredTasks = tasks.filter(task => {
582
+ if (currentFilter === 'active') return !task.completed;
583
+ if (currentFilter === 'completed') return task.completed;
584
+ return true;
585
+ });
586
+
587
+ // Empty state handling
588
+ if (filteredTasks.length === 0) {
589
+ emptyState.style.display = 'block';
590
+ if (currentFilter === 'completed') emptyState.querySelector('p').textContent = "No completed tasks yet.";
591
+ else if (currentFilter === 'active') emptyState.querySelector('p').textContent = "No active tasks. Good job!";
592
+ else emptyState.querySelector('p').textContent = "Your list is empty. Add a task!";
593
+ } else {
594
+ emptyState.style.display = 'none';
595
+ }
596
+
597
+ // Create HTML elements
598
+ filteredTasks.forEach(task => {
599
+ const li = document.createElement('li');
600
+ li.className = `task-item ${task.completed ? 'completed' : ''}`;
601
+ li.setAttribute('data-id', task.id);
602
+
603
+ li.innerHTML = `
604
+ <div class="task-content">
605
+ <div class="checkbox-wrapper">
606
+ <input type="checkbox" ${task.completed ? 'checked' : ''}>
607
+ <div class="custom-checkbox">
608
+ <i class="ph ph-check" style="display: ${task.completed ? 'block' : 'none'}"></i>
609
+ </div>
610
+ </div>
611
+ <span class="task-text">${escapeHtml(task.text)}</span>
612
+ </div>
613
+ <div class="task-actions">
614
+ <button class="action-btn edit-btn" title="Edit">
615
+ <i class="ph ph-pencil-simple"></i>
616
+ </button>
617
+ <button class="action-btn delete-btn" title="Delete">
618
+ <i class="ph ph-trash"></i>
619
+ </button>
620
+ </div>
621
+ `;
622
+
623
+ // Event Listeners for this item
624
+ const checkbox = li.querySelector('input[type="checkbox"]');
625
+ checkbox.addEventListener('change', () => toggleTask(task.id));
626
+
627
+ const deleteBtn = li.querySelector('.delete-btn');
628
+ deleteBtn.addEventListener('click', () => deleteTask(task.id));
629
+
630
+ const editBtn = li.querySelector('.edit-btn');
631
+ const textSpan = li.querySelector('.task-text');
632
+
633
+ editBtn.addEventListener('click', () => enableEdit(task.id, textSpan));
634
+
635
+ // Allow double click on text to edit
636
+ textSpan.addEventListener('dblclick', () => enableEdit(task.id, textSpan));
637
+
638
+ taskList.appendChild(li);
639
+ });
640
+
641
+ // Update stats
642
+ const activeCount = tasks.filter(t => !t.completed).length;
643
+ itemsLeft.textContent = `${activeCount} item${activeCount !== 1 ? 's' : ''} left`;
644
+ }
645
+
646
+ // Helper to prevent XSS
647
+ function escapeHtml(text) {
648
+ const div = document.createElement('div');
649
+ div.textContent = text;
650
+ return div.innerHTML;
651
+ }
652
+
653
+ // --- Event Listeners ---
654
+
655
+ addBtn.addEventListener('click', addTask);
656
+
657
+ taskInput.addEventListener('keydown', (e) => {
658
+ if (e.key === 'Enter') addTask();
659
+ });
660
+
661
+ clearCompletedBtn.addEventListener('click', clearCompleted);
662
+
663
+ filterBtns.forEach(btn => {
664
+ btn.addEventListener('click', () => {
665
+ // Update UI classes
666
+ filterBtns.forEach(b => b.classList.remove('active'));
667
+ btn.classList.add('active');
668
+
669
+ // Update state
670
+ currentFilter = btn.dataset.filter;
671
+ render();
672
+ });
673
+ });
674
+
675
+ // --- Initial Render ---
676
+ render();
677
+
678
+ </script>
679
+ </body>
680
+ </html>