| | <!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> |