todo / index.html
jphermans's picture
add also an option for due date/time - Initial Deployment
2f5db26 verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Modern 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>
/* Custom scrollbar */
.custom-scrollbar::-webkit-scrollbar {
width: 6px;
}
.custom-scrollbar::-webkit-scrollbar-track {
background: #f1f1f1;
border-radius: 10px;
}
.custom-scrollbar::-webkit-scrollbar-thumb {
background: #888;
border-radius: 10px;
}
.custom-scrollbar::-webkit-scrollbar-thumb:hover {
background: #555;
}
/* Checkbox animation */
.task-checkbox:checked ~ .task-text {
text-decoration: line-through;
color: #9ca3af;
}
/* Floating action button animation */
@keyframes pulse {
0% { transform: scale(1); }
50% { transform: scale(1.1); }
100% { transform: scale(1); }
}
.pulse-animation {
animation: pulse 2s infinite;
}
/* Date input styling */
input[type="datetime-local"]::-webkit-calendar-picker-indicator {
filter: invert(0.5);
cursor: pointer;
}
input[type="datetime-local"]::-webkit-calendar-picker-indicator:hover {
filter: invert(0.3);
}
/* Task item animation */
@keyframes fadeIn {
from { opacity: 0; transform: translateY(10px); }
to { opacity: 1; transform: translateY(0); }
}
.task-item {
animation: fadeIn 0.3s ease-out forwards;
}
</style>
</head>
<body class="bg-gradient-to-br from-indigo-50 to-purple-50 min-h-screen">
<div class="container mx-auto px-4 py-8 max-w-3xl">
<!-- Header -->
<header class="flex flex-col items-center mb-8">
<h1 class="text-4xl font-bold text-transparent bg-clip-text bg-gradient-to-r from-indigo-600 to-purple-600 mb-2">
TaskFlow
</h1>
<p class="text-gray-500">Organize your day, one task at a time</p>
<!-- Stats -->
<div class="flex gap-4 mt-4">
<div class="bg-white rounded-lg shadow p-3 text-center min-w-[100px]">
<div class="text-sm text-gray-500">Total</div>
<div id="total-count" class="text-xl font-bold text-indigo-600">0</div>
</div>
<div class="bg-white rounded-lg shadow p-3 text-center min-w-[100px]">
<div class="text-sm text-gray-500">Completed</div>
<div id="completed-count" class="text-xl font-bold text-green-500">0</div>
</div>
<div class="bg-white rounded-lg shadow p-3 text-center min-w-[100px]">
<div class="text-sm text-gray-500">Pending</div>
<div id="pending-count" class="text-xl font-bold text-yellow-500">0</div>
</div>
</div>
</header>
<!-- Main Content -->
<main class="bg-white rounded-xl shadow-lg overflow-hidden">
<!-- Input Section -->
<div class="p-4 border-b border-gray-100">
<div class="flex gap-2">
<input
type="text"
id="task-input"
placeholder="What needs to be done?"
class="flex-1 px-4 py-3 rounded-lg border border-gray-200 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-transparent"
>
<input
type="datetime-local"
id="task-due"
class="px-4 py-3 rounded-lg border border-gray-200 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-transparent"
>
<button
id="add-task-btn"
class="bg-indigo-600 hover:bg-indigo-700 text-white px-6 py-3 rounded-lg transition-all duration-200 flex items-center gap-2"
>
<i class="fas fa-plus"></i>
<span class="hidden sm:inline">Add Task</span>
</button>
</div>
<!-- Filter Buttons -->
<div class="flex justify-center mt-4 gap-2">
<button data-filter="all" class="filter-btn active px-3 py-1 rounded-full text-sm bg-indigo-100 text-indigo-700">All</button>
<button data-filter="active" class="filter-btn px-3 py-1 rounded-full text-sm hover:bg-gray-100">Active</button>
<button data-filter="completed" class="filter-btn px-3 py-1 rounded-full text-sm hover:bg-gray-100">Completed</button>
</div>
</div>
<!-- Task List -->
<div id="task-list" class="max-h-[400px] overflow-y-auto custom-scrollbar">
<!-- Tasks will be added here dynamically -->
<div class="p-4 text-center text-gray-500" id="empty-state">
<i class="fas fa-tasks text-4xl mb-2 text-gray-300"></i>
<p>No tasks yet. Add your first task!</p>
</div>
</div>
<!-- Clear Completed Button -->
<div class="p-3 border-t border-gray-100 flex justify-end">
<button id="clear-completed" class="text-sm text-gray-500 hover:text-red-500 flex items-center gap-1">
<i class="fas fa-trash-alt"></i>
<span>Clear Completed</span>
</button>
</div>
</main>
<!-- Floating Action Button -->
<button id="scroll-to-top" class="fixed bottom-6 right-6 bg-indigo-600 text-white w-12 h-12 rounded-full shadow-lg flex items-center justify-center hover:bg-indigo-700 transition-all duration-200 hidden">
<i class="fas fa-arrow-up"></i>
</button>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
// DOM Elements
const taskInput = document.getElementById('task-input');
const addTaskBtn = document.getElementById('add-task-btn');
const taskList = document.getElementById('task-list');
const emptyState = document.getElementById('empty-state');
const filterButtons = document.querySelectorAll('.filter-btn');
const clearCompletedBtn = document.getElementById('clear-completed');
const scrollToTopBtn = document.getElementById('scroll-to-top');
const totalCount = document.getElementById('total-count');
const completedCount = document.getElementById('completed-count');
const pendingCount = document.getElementById('pending-count');
// State
let tasks = JSON.parse(localStorage.getItem('tasks')) || [];
let currentFilter = 'all';
// Initialize
renderTasks();
updateStats();
// Event Listeners
addTaskBtn.addEventListener('click', addTask);
taskInput.addEventListener('keypress', function(e) {
if (e.key === 'Enter') addTask();
});
clearCompletedBtn.addEventListener('click', clearCompletedTasks);
scrollToTopBtn.addEventListener('click', scrollToTop);
// Filter buttons
filterButtons.forEach(btn => {
btn.addEventListener('click', function() {
filterButtons.forEach(b => b.classList.remove('active', 'bg-indigo-100', 'text-indigo-700'));
this.classList.add('active', 'bg-indigo-100', 'text-indigo-700');
currentFilter = this.dataset.filter;
renderTasks();
});
});
// Scroll event for floating button
window.addEventListener('scroll', function() {
if (window.pageYOffset > 300) {
scrollToTopBtn.classList.remove('hidden');
} else {
scrollToTopBtn.classList.add('hidden');
}
});
// Functions
function addTask() {
const text = taskInput.value.trim();
if (text === '') return;
const dueDate = document.getElementById('task-due').value;
const newTask = {
id: Date.now(),
text: text,
completed: false,
createdAt: new Date().toISOString(),
dueDate: dueDate || null
};
tasks.unshift(newTask);
saveTasks();
renderTasks();
updateStats();
taskInput.value = '';
taskInput.focus();
// Add pulse animation to the add button
addTaskBtn.classList.add('pulse-animation');
setTimeout(() => {
addTaskBtn.classList.remove('pulse-animation');
}, 2000);
}
function renderTasks() {
// 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];
}
if (filteredTasks.length === 0) {
emptyState.classList.remove('hidden');
taskList.innerHTML = '';
taskList.appendChild(emptyState);
} else {
emptyState.classList.add('hidden');
taskList.innerHTML = '';
filteredTasks.forEach(task => {
const taskElement = createTaskElement(task);
taskList.appendChild(taskElement);
});
}
}
function createTaskElement(task) {
const taskElement = document.createElement('div');
taskElement.className = `task-item p-4 border-b border-gray-100 flex items-center gap-3 hover:bg-gray-50 transition-colors duration-150`;
taskElement.dataset.id = task.id;
const checkbox = document.createElement('input');
checkbox.type = 'checkbox';
checkbox.className = 'task-checkbox w-5 h-5 rounded border-gray-300 text-indigo-600 focus:ring-indigo-500 cursor-pointer';
checkbox.checked = task.completed;
checkbox.addEventListener('change', function() {
toggleTaskCompletion(task.id);
});
const taskText = document.createElement('span');
taskText.className = `task-text flex-1 ${task.completed ? 'line-through text-gray-400' : 'text-gray-700'}`;
taskText.textContent = task.text;
const dateSpan = document.createElement('span');
dateSpan.className = 'text-xs text-gray-400 ml-2';
const date = new Date(task.createdAt);
dateSpan.textContent = date.toLocaleDateString();
if (task.dueDate) {
const dueDate = new Date(task.dueDate);
const dueSpan = document.createElement('span');
dueSpan.className = 'text-xs ml-2 ' +
(new Date() > dueDate && !task.completed ? 'text-red-500' : 'text-gray-500');
dueSpan.textContent = 'Due: ' + dueDate.toLocaleString();
taskText.appendChild(dueSpan);
}
const deleteBtn = document.createElement('button');
deleteBtn.className = 'text-gray-400 hover:text-red-500 transition-colors duration-150';
deleteBtn.innerHTML = '<i class="fas fa-times"></i>';
deleteBtn.addEventListener('click', function(e) {
e.stopPropagation();
deleteTask(task.id);
});
taskText.appendChild(dateSpan);
taskElement.appendChild(checkbox);
taskElement.appendChild(taskText);
taskElement.appendChild(deleteBtn);
return taskElement;
}
function toggleTaskCompletion(taskId) {
const taskIndex = tasks.findIndex(task => task.id === taskId);
if (taskIndex !== -1) {
tasks[taskIndex].completed = !tasks[taskIndex].completed;
saveTasks();
renderTasks();
updateStats();
}
}
function deleteTask(taskId) {
tasks = tasks.filter(task => task.id !== taskId);
saveTasks();
renderTasks();
updateStats();
}
function clearCompletedTasks() {
tasks = tasks.filter(task => !task.completed);
saveTasks();
renderTasks();
updateStats();
// Show confirmation animation
const clearBtn = clearCompletedBtn;
clearBtn.innerHTML = '<i class="fas fa-check"></i> <span>Cleared!</span>';
clearBtn.classList.add('text-green-500');
setTimeout(() => {
clearBtn.innerHTML = '<i class="fas fa-trash-alt"></i> <span>Clear Completed</span>';
clearBtn.classList.remove('text-green-500');
}, 2000);
}
function saveTasks() {
localStorage.setItem('tasks', JSON.stringify(tasks));
}
function updateStats() {
totalCount.textContent = tasks.length;
const completedTasks = tasks.filter(task => task.completed).length;
completedCount.textContent = completedTasks;
pendingCount.textContent = tasks.length - completedTasks;
}
function scrollToTop() {
window.scrollTo({
top: 0,
behavior: 'smooth'
});
}
});
</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=jphermans/todo" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body>
</html>