todo / index.html
claudelle's picture
Add 3 files
36b1736 verified
<!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">
<!-- Tasks will be added here dynamically -->
<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() {
// DOM Elements
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');
// Priority buttons
const priorityHighBtn = document.getElementById('priority-high-btn');
const priorityMediumBtn = document.getElementById('priority-medium-btn');
const priorityLowBtn = document.getElementById('priority-low-btn');
// Filter buttons
const filterAllBtn = document.getElementById('filter-all');
const filterActiveBtn = document.getElementById('filter-active');
const filterCompletedBtn = document.getElementById('filter-completed');
// State
let tasks = JSON.parse(localStorage.getItem('tasks')) || [];
let currentPriority = 'medium';
let currentFilter = 'all';
// Initialize
updateTaskCount();
renderTasks();
// Event Listeners
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);
// Functions
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);
// Add event listeners
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;
// Reset all buttons
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';
// Set active button
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;
// Reset all buttons
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';
// Set active button
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>