Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Modern To-Do List</title> | |
| <script src="https://cdn.tailwindcss.com"></script> | |
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> | |
| <style> | |
| /* Custom scrollbar */ | |
| .task-list::-webkit-scrollbar { | |
| width: 6px; | |
| } | |
| .task-list::-webkit-scrollbar-track { | |
| background: #f1f1f1; | |
| border-radius: 10px; | |
| } | |
| .task-list::-webkit-scrollbar-thumb { | |
| background: #cbd5e1; | |
| border-radius: 10px; | |
| } | |
| .task-list::-webkit-scrollbar-thumb:hover { | |
| background: #94a3b8; | |
| } | |
| /* Animation for new tasks */ | |
| @keyframes fadeIn { | |
| from { opacity: 0; transform: translateY(10px); } | |
| to { opacity: 1; transform: translateY(0); } | |
| } | |
| .task-item { | |
| animation: fadeIn 0.3s ease-out; | |
| } | |
| /* Custom checkbox */ | |
| .custom-checkbox { | |
| appearance: none; | |
| -webkit-appearance: none; | |
| width: 20px; | |
| height: 20px; | |
| border: 2px solid #cbd5e1; | |
| border-radius: 4px; | |
| cursor: pointer; | |
| position: relative; | |
| transition: all 0.2s; | |
| } | |
| .custom-checkbox:checked { | |
| background-color: #4f46e5; | |
| border-color: #4f46e5; | |
| } | |
| .custom-checkbox:checked::after { | |
| content: '\2713'; | |
| position: absolute; | |
| color: white; | |
| font-size: 12px; | |
| left: 50%; | |
| top: 50%; | |
| transform: translate(-50%, -50%); | |
| } | |
| /* Dark mode toggle */ | |
| .dark-mode-toggle { | |
| transition: all 0.3s; | |
| } | |
| .dark-mode-toggle:hover { | |
| transform: rotate(30deg); | |
| } | |
| </style> | |
| </head> | |
| <body class="bg-gray-50 min-h-screen transition-colors duration-300 dark:bg-gray-900"> | |
| <div class="container mx-auto px-4 py-8 max-w-3xl"> | |
| <!-- Header --> | |
| <header class="flex justify-between items-center mb-8"> | |
| <h1 class="text-3xl font-bold text-indigo-600 dark:text-indigo-400">TaskMaster</h1> | |
| <button id="darkModeToggle" class="dark-mode-toggle p-2 rounded-full bg-gray-200 dark:bg-gray-700 text-gray-700 dark:text-yellow-300"> | |
| <i class="fas fa-moon dark:hidden"></i> | |
| <i class="fas fa-sun hidden dark:inline"></i> | |
| </button> | |
| </header> | |
| <!-- Add Task Form --> | |
| <div class="bg-white dark:bg-gray-800 rounded-lg shadow-md p-6 mb-6 transition-all hover:shadow-lg"> | |
| <form id="taskForm" class="flex gap-3"> | |
| <input | |
| type="text" | |
| id="taskInput" | |
| placeholder="Add a new task..." | |
| class="flex-1 px-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-500 dark:bg-gray-700 dark:text-white" | |
| autocomplete="off" | |
| required | |
| > | |
| <button | |
| type="submit" | |
| class="bg-indigo-600 hover:bg-indigo-700 text-white px-4 py-2 rounded-lg transition-colors flex items-center gap-2" | |
| > | |
| <i class="fas fa-plus"></i> Add | |
| </button> | |
| </form> | |
| </div> | |
| <!-- Task Filters --> | |
| <div class="flex justify-between items-center mb-4"> | |
| <div class="text-gray-600 dark:text-gray-300"> | |
| <span id="taskCount">0</span> tasks | |
| </div> | |
| <div class="flex gap-2"> | |
| <button | |
| id="filterAll" | |
| class="filter-btn px-3 py-1 rounded-lg bg-indigo-600 text-white" | |
| > | |
| All | |
| </button> | |
| <button | |
| id="filterActive" | |
| class="filter-btn px-3 py-1 rounded-lg bg-gray-200 dark:bg-gray-700 text-gray-700 dark:text-gray-300 hover:bg-gray-300 dark:hover:bg-gray-600 transition-colors" | |
| > | |
| Active | |
| </button> | |
| <button | |
| id="filterCompleted" | |
| class="filter-btn px-3 py-1 rounded-lg bg-gray-200 dark:bg-gray-700 text-gray-700 dark:text-gray-300 hover:bg-gray-300 dark:hover:bg-gray-600 transition-colors" | |
| > | |
| Completed | |
| </button> | |
| </div> | |
| </div> | |
| <!-- Task List --> | |
| <div class="bg-white dark:bg-gray-800 rounded-lg shadow-md overflow-hidden"> | |
| <ul id="taskList" class="task-list max-h-96 overflow-y-auto"> | |
| <!-- Tasks will be added here dynamically --> | |
| <li class="text-center py-8 text-gray-500 dark:text-gray-400"> | |
| <i class="fas fa-tasks text-4xl mb-2"></i> | |
| <p>No tasks yet. Add one above!</p> | |
| </li> | |
| </ul> | |
| <!-- Task Actions --> | |
| <div class="p-4 border-t border-gray-200 dark:border-gray-700 flex justify-between items-center"> | |
| <button | |
| id="clearCompleted" | |
| class="text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-200 transition-colors" | |
| > | |
| Clear completed | |
| </button> | |
| <div class="text-sm text-gray-500 dark:text-gray-400"> | |
| <span id="completedCount">0</span> completed | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Empty State Template (hidden by default) --> | |
| <template id="emptyStateTemplate"> | |
| <li class="text-center py-8 text-gray-500 dark:text-gray-400"> | |
| <i class="fas fa-tasks text-4xl mb-2"></i> | |
| <p>No tasks yet. Add one above!</p> | |
| </li> | |
| </template> | |
| <!-- Task Item Template (hidden by default) --> | |
| <template id="taskItemTemplate"> | |
| <li class="task-item border-b border-gray-200 dark:border-gray-700 last:border-0"> | |
| <div class="flex items-center px-4 py-3 hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors"> | |
| <input type="checkbox" class="custom-checkbox mr-3"> | |
| <span class="flex-1 text-gray-800 dark:text-gray-200 task-text"></span> | |
| <button class="text-gray-400 hover:text-red-500 transition-colors delete-btn p-2 rounded-full"> | |
| <i class="fas fa-trash"></i> | |
| </button> | |
| </div> | |
| </li> | |
| </template> | |
| </div> | |
| <script> | |
| document.addEventListener('DOMContentLoaded', function() { | |
| // DOM Elements | |
| const taskForm = document.getElementById('taskForm'); | |
| const taskInput = document.getElementById('taskInput'); | |
| const taskList = document.getElementById('taskList'); | |
| const taskCount = document.getElementById('taskCount'); | |
| const completedCount = document.getElementById('completedCount'); | |
| const clearCompletedBtn = document.getElementById('clearCompleted'); | |
| const filterAllBtn = document.getElementById('filterAll'); | |
| const filterActiveBtn = document.getElementById('filterActive'); | |
| const filterCompletedBtn = document.getElementById('filterCompleted'); | |
| const darkModeToggle = document.getElementById('darkModeToggle'); | |
| const emptyStateTemplate = document.getElementById('emptyStateTemplate'); | |
| const taskItemTemplate = document.getElementById('taskItemTemplate'); | |
| // State | |
| let tasks = JSON.parse(localStorage.getItem('tasks')) || []; | |
| let currentFilter = 'all'; | |
| // Initialize | |
| renderTasks(); | |
| updateCounters(); | |
| // Event Listeners | |
| taskForm.addEventListener('submit', addTask); | |
| clearCompletedBtn.addEventListener('click', clearCompletedTasks); | |
| filterAllBtn.addEventListener('click', () => setFilter('all')); | |
| filterActiveBtn.addEventListener('click', () => setFilter('active')); | |
| filterCompletedBtn.addEventListener('click', () => setFilter('completed')); | |
| darkModeToggle.addEventListener('click', toggleDarkMode); | |
| // Check for saved dark mode preference | |
| if (localStorage.getItem('darkMode') === 'enabled') { | |
| document.documentElement.classList.add('dark'); | |
| } | |
| // Functions | |
| function addTask(e) { | |
| e.preventDefault(); | |
| const taskText = taskInput.value.trim(); | |
| if (!taskText) return; | |
| const newTask = { | |
| id: Date.now(), | |
| text: taskText, | |
| completed: false | |
| }; | |
| tasks.unshift(newTask); | |
| saveTasks(); | |
| renderTasks(); | |
| updateCounters(); | |
| taskInput.value = ''; | |
| taskInput.focus(); | |
| } | |
| function renderTasks() { | |
| // Clear the task list | |
| taskList.innerHTML = ''; | |
| // Filter tasks based on current filter | |
| let filteredTasks = []; | |
| switch (currentFilter) { | |
| case 'active': | |
| filteredTasks = tasks.filter(task => !task.completed); | |
| break; | |
| case 'completed': | |
| filteredTasks = tasks.filter(task => task.completed); | |
| break; | |
| default: | |
| filteredTasks = [...tasks]; | |
| } | |
| // Show empty state if no tasks | |
| if (filteredTasks.length === 0) { | |
| const emptyState = emptyStateTemplate.content.cloneNode(true); | |
| taskList.appendChild(emptyState); | |
| return; | |
| } | |
| // Render each task | |
| filteredTasks.forEach(task => { | |
| const taskItem = taskItemTemplate.content.cloneNode(true); | |
| const checkbox = taskItem.querySelector('input[type="checkbox"]'); | |
| const taskText = taskItem.querySelector('.task-text'); | |
| const deleteBtn = taskItem.querySelector('.delete-btn'); | |
| checkbox.checked = task.completed; | |
| taskText.textContent = task.text; | |
| if (task.completed) { | |
| taskText.classList.add('line-through', 'text-gray-400', 'dark:text-gray-500'); | |
| } | |
| // Add event listeners to the new elements | |
| checkbox.addEventListener('change', () => toggleTaskCompletion(task.id)); | |
| deleteBtn.addEventListener('click', () => deleteTask(task.id)); | |
| taskList.appendChild(taskItem); | |
| }); | |
| } | |
| function toggleTaskCompletion(taskId) { | |
| tasks = tasks.map(task => { | |
| if (task.id === taskId) { | |
| return { ...task, completed: !task.completed }; | |
| } | |
| return task; | |
| }); | |
| saveTasks(); | |
| renderTasks(); | |
| updateCounters(); | |
| } | |
| function deleteTask(taskId) { | |
| tasks = tasks.filter(task => task.id !== taskId); | |
| saveTasks(); | |
| renderTasks(); | |
| updateCounters(); | |
| } | |
| function clearCompletedTasks() { | |
| tasks = tasks.filter(task => !task.completed); | |
| saveTasks(); | |
| renderTasks(); | |
| updateCounters(); | |
| } | |
| function setFilter(filter) { | |
| currentFilter = filter; | |
| // Update active filter button styles | |
| [filterAllBtn, filterActiveBtn, filterCompletedBtn].forEach(btn => { | |
| btn.classList.remove('bg-indigo-600', 'text-white'); | |
| btn.classList.add('bg-gray-200', 'dark:bg-gray-700', 'text-gray-700', 'dark:text-gray-300'); | |
| }); | |
| switch (filter) { | |
| case 'all': | |
| filterAllBtn.classList.add('bg-indigo-600', 'text-white'); | |
| filterAllBtn.classList.remove('bg-gray-200', 'dark:bg-gray-700', 'text-gray-700', 'dark:text-gray-300'); | |
| break; | |
| case 'active': | |
| filterActiveBtn.classList.add('bg-indigo-600', 'text-white'); | |
| filterActiveBtn.classList.remove('bg-gray-200', 'dark:bg-gray-700', 'text-gray-700', 'dark:text-gray-300'); | |
| break; | |
| case 'completed': | |
| filterCompletedBtn.classList.add('bg-indigo-600', 'text-white'); | |
| filterCompletedBtn.classList.remove('bg-gray-200', 'dark:bg-gray-700', 'text-gray-700', 'dark:text-gray-300'); | |
| break; | |
| } | |
| renderTasks(); | |
| } | |
| function updateCounters() { | |
| const totalTasks = tasks.length; | |
| const completedTasks = tasks.filter(task => task.completed).length; | |
| const activeTasks = totalTasks - completedTasks; | |
| taskCount.textContent = activeTasks; | |
| completedCount.textContent = completedTasks; | |
| // Show/hide clear completed button | |
| if (completedTasks > 0) { | |
| clearCompletedBtn.classList.remove('invisible'); | |
| } else { | |
| clearCompletedBtn.classList.add('invisible'); | |
| } | |
| } | |
| function saveTasks() { | |
| localStorage.setItem('tasks', JSON.stringify(tasks)); | |
| } | |
| function toggleDarkMode() { | |
| document.documentElement.classList.toggle('dark'); | |
| if (document.documentElement.classList.contains('dark')) { | |
| localStorage.setItem('darkMode', 'enabled'); | |
| } else { | |
| localStorage.setItem('darkMode', 'disabled'); | |
| } | |
| } | |
| }); | |
| </script> | |
| <p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=Bencolliss/testrun2" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> | |
| </html> |