todo / index.html
aircrushin's picture
use nextjs to build a simple todo list but cool ui - Initial Deployment
7cb879d verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Cosmic Todo 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>
@keyframes float {
0% { transform: translateY(0px); }
50% { transform: translateY(-10px); }
100% { transform: translateY(0px); }
}
.floating {
animation: float 3s ease-in-out infinite;
}
.task-item:hover .task-actions {
opacity: 1;
}
.gradient-bg {
background: linear-gradient(135deg, #6e8efb, #a777e3);
}
.checkbox:checked + .checkmark {
background-color: #a777e3;
border-color: #a777e3;
}
.checkbox:checked + .checkmark:after {
display: block;
}
.checkmark:after {
content: "";
position: absolute;
display: none;
left: 6px;
top: 2px;
width: 4px;
height: 8px;
border: solid white;
border-width: 0 2px 2px 0;
transform: rotate(45deg);
}
</style>
</head>
<body class="min-h-screen bg-gray-100 font-sans">
<div class="container mx-auto px-4 py-8 max-w-3xl">
<!-- Header with floating animation -->
<div class="text-center mb-10 floating">
<h1 class="text-5xl font-bold text-transparent bg-clip-text bg-gradient-to-r from-purple-500 to-pink-500 mb-2">
Cosmic Todo
</h1>
<p class="text-gray-600">Organize your universe one task at a time</p>
</div>
<!-- Main card -->
<div class="bg-white rounded-2xl shadow-xl overflow-hidden transition-all duration-300 hover:shadow-2xl">
<!-- Gradient header -->
<div class="gradient-bg p-6 text-white">
<div class="flex justify-between items-center">
<h2 class="text-2xl font-semibold">My Tasks</h2>
<span id="task-count" class="bg-white text-purple-600 px-3 py-1 rounded-full text-sm font-bold">0 tasks</span>
</div>
<!-- Add task form -->
<div class="mt-6 flex">
<input
type="text"
id="new-task"
placeholder="Add a new cosmic task..."
class="flex-grow px-4 py-3 rounded-l-lg focus:outline-none text-gray-800"
>
<button
id="add-task"
class="bg-pink-500 hover:bg-pink-600 text-white px-6 py-3 rounded-r-lg transition-colors"
>
<i class="fas fa-plus"></i>
</button>
</div>
</div>
<!-- Task list -->
<div class="p-6">
<div id="task-list" class="space-y-3">
<!-- Tasks will be added here dynamically -->
<div class="text-center py-10 text-gray-400" id="empty-state">
<i class="fas fa-meteor text-4xl mb-3"></i>
<p>No tasks in your universe yet!</p>
</div>
</div>
<!-- Filter buttons -->
<div class="mt-6 flex justify-center space-x-2">
<button class="filter-btn active px-4 py-2 rounded-lg bg-purple-100 text-purple-600" data-filter="all">
All
</button>
<button class="filter-btn px-4 py-2 rounded-lg hover:bg-purple-50" data-filter="active">
Active
</button>
<button class="filter-btn px-4 py-2 rounded-lg hover:bg-purple-50" data-filter="completed">
Completed
</button>
</div>
</div>
</div>
<!-- Floating action button -->
<button id="clear-completed" class="fixed bottom-6 right-6 bg-pink-500 text-white w-14 h-14 rounded-full shadow-lg hover:bg-pink-600 transition-colors flex items-center justify-center">
<i class="fas fa-trash-alt"></i>
</button>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
// DOM elements
const taskInput = document.getElementById('new-task');
const addTaskBtn = document.getElementById('add-task');
const taskList = document.getElementById('task-list');
const emptyState = document.getElementById('empty-state');
const taskCount = document.getElementById('task-count');
const filterButtons = document.querySelectorAll('.filter-btn');
const clearCompletedBtn = document.getElementById('clear-completed');
let tasks = JSON.parse(localStorage.getItem('tasks')) || [];
let currentFilter = 'all';
// Initialize the app
function init() {
renderTasks();
updateTaskCount();
// Event listeners
addTaskBtn.addEventListener('click', addTask);
taskInput.addEventListener('keypress', function(e) {
if (e.key === 'Enter') addTask();
});
filterButtons.forEach(btn => {
btn.addEventListener('click', function() {
filterButtons.forEach(b => b.classList.remove('active', 'bg-purple-100', 'text-purple-600'));
btn.classList.add('active', 'bg-purple-100', 'text-purple-600');
currentFilter = btn.dataset.filter;
renderTasks();
});
});
clearCompletedBtn.addEventListener('click', clearCompletedTasks);
}
// Add a new task
function addTask() {
const taskText = taskInput.value.trim();
if (taskText === '') return;
const newTask = {
id: Date.now(),
text: taskText,
completed: false,
createdAt: new Date().toISOString()
};
tasks.unshift(newTask);
saveTasks();
renderTasks();
updateTaskCount();
taskInput.value = '';
taskInput.focus();
}
// Render tasks based on current filter
function renderTasks() {
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.style.display = 'block';
taskList.innerHTML = '';
taskList.appendChild(emptyState);
} else {
emptyState.style.display = 'none';
taskList.innerHTML = '';
filteredTasks.forEach(task => {
const taskElement = createTaskElement(task);
taskList.appendChild(taskElement);
});
}
}
// Create task HTML element
function createTaskElement(task) {
const taskElement = document.createElement('div');
taskElement.className = `task-item group flex items-center justify-between p-4 bg-white rounded-lg border border-gray-100 hover:bg-purple-50 transition-colors relative overflow-hidden`;
taskElement.dataset.id = task.id;
// Add a subtle shine effect on hover
const shine = document.createElement('div');
shine.className = 'absolute inset-0 bg-gradient-to-r from-transparent via-white/50 to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-300 pointer-events-none';
shine.style.transform = 'translateX(-100%)';
// Checkbox and text
const taskContent = document.createElement('div');
taskContent.className = 'flex items-center flex-grow';
const label = document.createElement('label');
label.className = 'flex items-center cursor-pointer';
const checkbox = document.createElement('input');
checkbox.type = 'checkbox';
checkbox.className = 'checkbox opacity-0 absolute';
checkbox.checked = task.completed;
checkbox.addEventListener('change', () => toggleTaskComplete(task.id));
const checkmark = document.createElement('span');
checkmark.className = `checkmark w-5 h-5 border-2 border-gray-300 rounded-md flex-shrink-0 ${task.completed ? 'bg-purple-500 border-purple-500' : ''}`;
const taskText = document.createElement('span');
taskText.className = `ml-3 ${task.completed ? 'line-through text-gray-400' : 'text-gray-700'}`;
taskText.textContent = task.text;
label.appendChild(checkbox);
label.appendChild(checkmark);
label.appendChild(taskText);
// Task actions (edit and delete)
const taskActions = document.createElement('div');
taskActions.className = 'task-actions flex space-x-2 opacity-0 group-hover:opacity-100 transition-opacity';
const editBtn = document.createElement('button');
editBtn.className = 'text-gray-400 hover:text-purple-500 transition-colors';
editBtn.innerHTML = '<i class="fas fa-edit"></i>';
editBtn.addEventListener('click', () => editTask(task.id));
const deleteBtn = document.createElement('button');
deleteBtn.className = 'text-gray-400 hover:text-pink-500 transition-colors';
deleteBtn.innerHTML = '<i class="fas fa-trash-alt"></i>';
deleteBtn.addEventListener('click', () => deleteTask(task.id));
taskActions.appendChild(editBtn);
taskActions.appendChild(deleteBtn);
taskContent.appendChild(label);
taskElement.appendChild(shine);
taskElement.appendChild(taskContent);
taskElement.appendChild(taskActions);
// Add shine animation on hover
taskElement.addEventListener('mouseenter', () => {
shine.style.transform = 'translateX(100%)';
shine.style.transition = 'transform 0.7s ease-in-out';
});
return taskElement;
}
// Toggle task completion status
function toggleTaskComplete(taskId) {
tasks = tasks.map(task => {
if (task.id === taskId) {
return {...task, completed: !task.completed};
}
return task;
});
saveTasks();
renderTasks();
updateTaskCount();
}
// Edit task
function editTask(taskId) {
const task = tasks.find(t => t.id === taskId);
if (!task) return;
const taskElement = document.querySelector(`.task-item[data-id="${taskId}"]`);
const taskTextElement = taskElement.querySelector('span:not(.checkmark)');
const input = document.createElement('input');
input.type = 'text';
input.value = task.text;
input.className = 'ml-3 px-2 py-1 border border-gray-300 rounded flex-grow';
taskTextElement.replaceWith(input);
input.focus();
const handleBlur = () => {
const newText = input.value.trim();
if (newText !== '' && newText !== task.text) {
task.text = newText;
saveTasks();
renderTasks();
} else {
renderTasks();
}
};
input.addEventListener('blur', handleBlur);
input.addEventListener('keypress', function(e) {
if (e.key === 'Enter') {
handleBlur();
}
});
}
// Delete task
function deleteTask(taskId) {
tasks = tasks.filter(task => task.id !== taskId);
saveTasks();
renderTasks();
updateTaskCount();
}
// Clear completed tasks
function clearCompletedTasks() {
tasks = tasks.filter(task => !task.completed);
saveTasks();
renderTasks();
updateTaskCount();
// Show confirmation animation
clearCompletedBtn.innerHTML = '<i class="fas fa-check"></i>';
setTimeout(() => {
clearCompletedBtn.innerHTML = '<i class="fas fa-trash-alt"></i>';
}, 1000);
}
// Update task counter
function updateTaskCount() {
const totalTasks = tasks.length;
const completedTasks = tasks.filter(task => task.completed).length;
if (totalTasks === 0) {
taskCount.textContent = '0 tasks';
} else if (completedTasks === totalTasks) {
taskCount.textContent = `All done! 🎉`;
} else {
taskCount.textContent = `${completedTasks}/${totalTasks} completed`;
}
}
// Save tasks to localStorage
function saveTasks() {
localStorage.setItem('tasks', JSON.stringify(tasks));
}
// Initialize the app
init();
});
</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=aircrushin/todo" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body>
</html>