| <!DOCTYPE html> |
| <html lang="en"> |
| <head> |
| <meta charset="UTF-8"> |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| <title>ZenTask - Minimal Todo App</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> |
| @import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap'); |
| |
| body { |
| font-family: 'Inter', sans-serif; |
| background-color: #f8fafc; |
| } |
| |
| .task-item:hover .task-actions { |
| opacity: 1; |
| } |
| |
| .task-actions { |
| opacity: 0; |
| transition: opacity 0.2s ease; |
| } |
| |
| .checkbox-container input { |
| position: absolute; |
| opacity: 0; |
| cursor: pointer; |
| height: 0; |
| width: 0; |
| } |
| |
| .checkmark { |
| position: relative; |
| height: 20px; |
| width: 20px; |
| border: 2px solid #94a3b8; |
| border-radius: 4px; |
| transition: all 0.2s ease; |
| } |
| |
| .checkbox-container input:checked ~ .checkmark { |
| background-color: #4f46e5; |
| border-color: #4f46e5; |
| } |
| |
| .checkmark:after { |
| content: ""; |
| position: absolute; |
| display: none; |
| } |
| |
| .checkbox-container input:checked ~ .checkmark:after { |
| display: block; |
| } |
| |
| .checkbox-container .checkmark:after { |
| left: 6px; |
| top: 2px; |
| width: 5px; |
| height: 10px; |
| border: solid white; |
| border-width: 0 2px 2px 0; |
| transform: rotate(45deg); |
| } |
| |
| .fade-in { |
| animation: fadeIn 0.3s ease-in-out; |
| } |
| |
| @keyframes fadeIn { |
| from { opacity: 0; transform: translateY(10px); } |
| to { opacity: 1; transform: translateY(0); } |
| } |
| |
| .priority-high { |
| border-left: 4px solid #ef4444; |
| } |
| |
| .priority-medium { |
| border-left: 4px solid #f59e0b; |
| } |
| |
| .priority-low { |
| border-left: 4px solid #10b981; |
| } |
| </style> |
| </head> |
| <body class="min-h-screen bg-gray-50"> |
| <div class="container mx-auto px-4 py-8 max-w-3xl"> |
| <header class="mb-8 text-center"> |
| <h1 class="text-4xl font-bold text-indigo-600 mb-2">ZenTask</h1> |
| <p class="text-gray-600">Your minimalist productivity companion</p> |
| </header> |
| |
| <div class="bg-white rounded-xl shadow-md overflow-hidden mb-6 transition-all duration-300 hover:shadow-lg"> |
| <div class="p-6"> |
| <div class="flex items-center mb-4"> |
| <div class="relative flex-grow"> |
| <input |
| type="text" |
| id="new-task-input" |
| placeholder="What needs to be done?" |
| class="w-full px-4 py-3 border-0 rounded-lg bg-gray-100 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:bg-white transition" |
| autocomplete="off" |
| > |
| </div> |
| <button |
| id="add-task-btn" |
| class="ml-3 bg-indigo-600 hover:bg-indigo-700 text-white px-4 py-3 rounded-lg transition flex items-center" |
| > |
| <i class="fas fa-plus mr-2"></i> Add |
| </button> |
| </div> |
| |
| <div class="flex items-center justify-between text-sm"> |
| <div class="flex space-x-2"> |
| <button id="priority-high-btn" class="px-3 py-1 rounded-full bg-red-100 text-red-600 flex items-center"> |
| <i class="fas fa-exclamation-circle mr-1"></i> High |
| </button> |
| <button id="priority-medium-btn" class="px-3 py-1 rounded-full bg-amber-100 text-amber-600 flex items-center"> |
| <i class="fas fa-exclamation mr-1"></i> Medium |
| </button> |
| <button id="priority-low-btn" class="px-3 py-1 rounded-full bg-emerald-100 text-emerald-600 flex items-center"> |
| <i class="fas fa-arrow-down mr-1"></i> Low |
| </button> |
| </div> |
| |
| <div class="flex items-center"> |
| <span id="task-count" class="text-gray-500">0 tasks</span> |
| </div> |
| </div> |
| </div> |
| </div> |
| |
| <div class="mb-6 flex justify-between items-center"> |
| <div class="flex space-x-2"> |
| <button id="filter-all" class="px-4 py-2 rounded-lg bg-indigo-100 text-indigo-700 font-medium">All</button> |
| <button id="filter-active" class="px-4 py-2 rounded-lg text-gray-600 hover:bg-gray-100">Active</button> |
| <button id="filter-completed" class="px-4 py-2 rounded-lg text-gray-600 hover:bg-gray-100">Completed</button> |
| </div> |
| |
| <button id="clear-completed" class="text-gray-500 hover:text-gray-700 text-sm"> |
| <i class="fas fa-trash-alt mr-1"></i> Clear completed |
| </button> |
| </div> |
| |
| <div id="tasks-container" class="space-y-3"> |
| |
| <div class="text-center py-10 text-gray-500" id="empty-state"> |
| <i class="fas fa-tasks text-4xl mb-3 opacity-30"></i> |
| <p class="text-lg">No tasks yet</p> |
| <p class="text-sm mt-1">Add your first task to get started</p> |
| </div> |
| </div> |
| </div> |
|
|
| <script> |
| document.addEventListener('DOMContentLoaded', function() { |
| |
| const newTaskInput = document.getElementById('new-task-input'); |
| const addTaskBtn = document.getElementById('add-task-btn'); |
| const tasksContainer = document.getElementById('tasks-container'); |
| const emptyState = document.getElementById('empty-state'); |
| const taskCount = document.getElementById('task-count'); |
| const clearCompletedBtn = document.getElementById('clear-completed'); |
| |
| |
| const priorityHighBtn = document.getElementById('priority-high-btn'); |
| const priorityMediumBtn = document.getElementById('priority-medium-btn'); |
| const priorityLowBtn = document.getElementById('priority-low-btn'); |
| |
| |
| const filterAllBtn = document.getElementById('filter-all'); |
| const filterActiveBtn = document.getElementById('filter-active'); |
| const filterCompletedBtn = document.getElementById('filter-completed'); |
| |
| |
| let tasks = JSON.parse(localStorage.getItem('tasks')) || []; |
| let currentPriority = 'medium'; |
| let currentFilter = 'all'; |
| |
| |
| updateTaskCount(); |
| renderTasks(); |
| |
| |
| addTaskBtn.addEventListener('click', addTask); |
| newTaskInput.addEventListener('keypress', function(e) { |
| if (e.key === 'Enter') addTask(); |
| }); |
| |
| priorityHighBtn.addEventListener('click', () => setPriority('high')); |
| priorityMediumBtn.addEventListener('click', () => setPriority('medium')); |
| priorityLowBtn.addEventListener('click', () => setPriority('low')); |
| |
| filterAllBtn.addEventListener('click', () => setFilter('all')); |
| filterActiveBtn.addEventListener('click', () => setFilter('active')); |
| filterCompletedBtn.addEventListener('click', () => setFilter('completed')); |
| |
| clearCompletedBtn.addEventListener('click', clearCompletedTasks); |
| |
| |
| function addTask() { |
| const taskText = newTaskInput.value.trim(); |
| if (taskText === '') return; |
| |
| const newTask = { |
| id: Date.now(), |
| text: taskText, |
| completed: false, |
| priority: currentPriority, |
| createdAt: new Date().toISOString() |
| }; |
| |
| tasks.unshift(newTask); |
| saveTasks(); |
| renderTasks(); |
| updateTaskCount(); |
| |
| newTaskInput.value = ''; |
| newTaskInput.focus(); |
| } |
| |
| function renderTasks() { |
| if (tasks.length === 0) { |
| emptyState.style.display = 'block'; |
| tasksContainer.innerHTML = ''; |
| tasksContainer.appendChild(emptyState); |
| return; |
| } |
| |
| emptyState.style.display = 'none'; |
| |
| let filteredTasks = [...tasks]; |
| |
| if (currentFilter === 'active') { |
| filteredTasks = tasks.filter(task => !task.completed); |
| } else if (currentFilter === 'completed') { |
| filteredTasks = tasks.filter(task => task.completed); |
| } |
| |
| if (filteredTasks.length === 0) { |
| const noTasksMessage = document.createElement('div'); |
| noTasksMessage.className = 'text-center py-10 text-gray-500'; |
| noTasksMessage.innerHTML = ` |
| <i class="fas fa-info-circle text-3xl mb-3 opacity-30"></i> |
| <p class="text-lg">No ${currentFilter} tasks</p> |
| `; |
| tasksContainer.innerHTML = ''; |
| tasksContainer.appendChild(noTasksMessage); |
| return; |
| } |
| |
| tasksContainer.innerHTML = ''; |
| |
| filteredTasks.forEach(task => { |
| const taskElement = document.createElement('div'); |
| taskElement.className = `task-item bg-white rounded-lg shadow-sm p-4 flex items-center justify-between fade-in priority-${task.priority}`; |
| taskElement.dataset.id = task.id; |
| |
| const leftSection = document.createElement('div'); |
| leftSection.className = 'flex items-center'; |
| |
| const checkboxContainer = document.createElement('label'); |
| checkboxContainer.className = 'checkbox-container flex items-center cursor-pointer mr-3'; |
| |
| const checkbox = document.createElement('input'); |
| checkbox.type = 'checkbox'; |
| checkbox.checked = task.completed; |
| checkbox.className = 'task-checkbox'; |
| |
| const checkmark = document.createElement('span'); |
| checkmark.className = 'checkmark'; |
| |
| checkboxContainer.appendChild(checkbox); |
| checkboxContainer.appendChild(checkmark); |
| |
| const taskText = document.createElement('span'); |
| taskText.className = `task-text ${task.completed ? 'line-through text-gray-400' : 'text-gray-700'}`; |
| taskText.textContent = task.text; |
| |
| leftSection.appendChild(checkboxContainer); |
| leftSection.appendChild(taskText); |
| |
| const taskActions = document.createElement('div'); |
| taskActions.className = 'task-actions flex space-x-2'; |
| |
| const editBtn = document.createElement('button'); |
| editBtn.className = 'text-gray-400 hover:text-indigo-500 transition'; |
| editBtn.innerHTML = '<i class="fas fa-pencil-alt"></i>'; |
| |
| const deleteBtn = document.createElement('button'); |
| deleteBtn.className = 'text-gray-400 hover:text-red-500 transition'; |
| deleteBtn.innerHTML = '<i class="fas fa-trash-alt"></i>'; |
| |
| taskActions.appendChild(editBtn); |
| taskActions.appendChild(deleteBtn); |
| |
| taskElement.appendChild(leftSection); |
| taskElement.appendChild(taskActions); |
| |
| tasksContainer.appendChild(taskElement); |
| |
| |
| checkbox.addEventListener('change', () => toggleTaskComplete(task.id)); |
| editBtn.addEventListener('click', () => editTask(task.id)); |
| deleteBtn.addEventListener('click', () => deleteTask(task.id)); |
| }); |
| } |
| |
| function toggleTaskComplete(taskId) { |
| const taskIndex = tasks.findIndex(task => task.id === taskId); |
| if (taskIndex !== -1) { |
| tasks[taskIndex].completed = !tasks[taskIndex].completed; |
| saveTasks(); |
| updateTaskCount(); |
| |
| if (currentFilter !== 'all') { |
| renderTasks(); |
| } else { |
| const taskElement = document.querySelector(`.task-item[data-id="${taskId}"]`); |
| if (taskElement) { |
| const taskText = taskElement.querySelector('.task-text'); |
| if (tasks[taskIndex].completed) { |
| taskText.classList.add('line-through', 'text-gray-400'); |
| } else { |
| taskText.classList.remove('line-through', 'text-gray-400'); |
| } |
| } |
| } |
| } |
| } |
| |
| function editTask(taskId) { |
| const task = tasks.find(task => task.id === taskId); |
| if (!task) return; |
| |
| const taskElement = document.querySelector(`.task-item[data-id="${taskId}"]`); |
| if (!taskElement) return; |
| |
| const leftSection = taskElement.querySelector('.flex.items-center'); |
| const taskText = taskElement.querySelector('.task-text'); |
| const taskActions = taskElement.querySelector('.task-actions'); |
| |
| const editInput = document.createElement('input'); |
| editInput.type = 'text'; |
| editInput.value = task.text; |
| editInput.className = 'px-2 py-1 border border-gray-300 rounded-md focus:outline-none focus:ring-1 focus:ring-indigo-500'; |
| |
| const saveBtn = document.createElement('button'); |
| saveBtn.className = 'ml-2 px-3 py-1 bg-indigo-600 text-white rounded-md hover:bg-indigo-700 transition'; |
| saveBtn.textContent = 'Save'; |
| |
| const cancelBtn = document.createElement('button'); |
| cancelBtn.className = 'ml-2 px-3 py-1 bg-gray-200 text-gray-700 rounded-md hover:bg-gray-300 transition'; |
| cancelBtn.textContent = 'Cancel'; |
| |
| leftSection.removeChild(taskText); |
| leftSection.appendChild(editInput); |
| taskActions.style.display = 'none'; |
| |
| leftSection.appendChild(saveBtn); |
| leftSection.appendChild(cancelBtn); |
| |
| editInput.focus(); |
| |
| saveBtn.addEventListener('click', () => { |
| const newText = editInput.value.trim(); |
| if (newText !== '') { |
| task.text = newText; |
| saveTasks(); |
| } |
| cancelEdit(); |
| }); |
| |
| cancelBtn.addEventListener('click', cancelEdit); |
| |
| editInput.addEventListener('keypress', function(e) { |
| if (e.key === 'Enter') { |
| const newText = editInput.value.trim(); |
| if (newText !== '') { |
| task.text = newText; |
| saveTasks(); |
| } |
| cancelEdit(); |
| } |
| }); |
| |
| function cancelEdit() { |
| leftSection.removeChild(editInput); |
| leftSection.removeChild(saveBtn); |
| leftSection.removeChild(cancelBtn); |
| leftSection.appendChild(taskText); |
| taskActions.style.display = 'flex'; |
| } |
| } |
| |
| function deleteTask(taskId) { |
| tasks = tasks.filter(task => task.id !== taskId); |
| saveTasks(); |
| renderTasks(); |
| updateTaskCount(); |
| } |
| |
| function clearCompletedTasks() { |
| tasks = tasks.filter(task => !task.completed); |
| saveTasks(); |
| renderTasks(); |
| updateTaskCount(); |
| } |
| |
| function setPriority(priority) { |
| currentPriority = priority; |
| |
| |
| priorityHighBtn.className = 'px-3 py-1 rounded-full flex items-center'; |
| priorityMediumBtn.className = 'px-3 py-1 rounded-full flex items-center'; |
| priorityLowBtn.className = 'px-3 py-1 rounded-full flex items-center'; |
| |
| |
| if (priority === 'high') { |
| priorityHighBtn.className = 'px-3 py-1 rounded-full bg-red-100 text-red-600 flex items-center'; |
| } else if (priority === 'medium') { |
| priorityMediumBtn.className = 'px-3 py-1 rounded-full bg-amber-100 text-amber-600 flex items-center'; |
| } else { |
| priorityLowBtn.className = 'px-3 py-1 rounded-full bg-emerald-100 text-emerald-600 flex items-center'; |
| } |
| } |
| |
| function setFilter(filter) { |
| currentFilter = filter; |
| |
| |
| filterAllBtn.className = 'px-4 py-2 rounded-lg text-gray-600 hover:bg-gray-100'; |
| filterActiveBtn.className = 'px-4 py-2 rounded-lg text-gray-600 hover:bg-gray-100'; |
| filterCompletedBtn.className = 'px-4 py-2 rounded-lg text-gray-600 hover:bg-gray-100'; |
| |
| |
| if (filter === 'all') { |
| filterAllBtn.className = 'px-4 py-2 rounded-lg bg-indigo-100 text-indigo-700 font-medium'; |
| } else if (filter === 'active') { |
| filterActiveBtn.className = 'px-4 py-2 rounded-lg bg-indigo-100 text-indigo-700 font-medium'; |
| } else { |
| filterCompletedBtn.className = 'px-4 py-2 rounded-lg bg-indigo-100 text-indigo-700 font-medium'; |
| } |
| |
| renderTasks(); |
| } |
| |
| function updateTaskCount() { |
| const totalTasks = tasks.length; |
| const completedTasks = tasks.filter(task => task.completed).length; |
| const activeTasks = totalTasks - completedTasks; |
| |
| if (totalTasks === 0) { |
| taskCount.textContent = 'No tasks'; |
| } else if (completedTasks === 0) { |
| taskCount.textContent = `${activeTasks} ${activeTasks === 1 ? 'task' : 'tasks'} remaining`; |
| } else { |
| taskCount.textContent = `${activeTasks} of ${totalTasks} ${totalTasks === 1 ? 'task' : 'tasks'} remaining`; |
| } |
| } |
| |
| function saveTasks() { |
| localStorage.setItem('tasks', JSON.stringify(tasks)); |
| } |
| }); |
| </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=claudelle/todo" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> |
| </html> |