Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>TaskFlow - Modern Todo Application</title> | |
| <script src="https://cdn.tailwindcss.com"></script> | |
| <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet"> | |
| <style> | |
| body { | |
| font-family: 'Inter', sans-serif; | |
| background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); | |
| min-height: 100vh; | |
| } | |
| .glass-effect { | |
| background: rgba(255, 255, 255, 0.95); | |
| backdrop-filter: blur(10px); | |
| border: 1px solid rgba(255, 255, 255, 0.2); | |
| } | |
| .task-item { | |
| transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); | |
| } | |
| .task-item:hover { | |
| transform: translateX(4px); | |
| } | |
| .task-item.completed .task-text { | |
| text-decoration: line-through; | |
| color: #9ca3af; | |
| } | |
| .checkbox-custom { | |
| appearance: none; | |
| width: 20px; | |
| height: 20px; | |
| border: 2px solid #d1d5db; | |
| border-radius: 6px; | |
| cursor: pointer; | |
| position: relative; | |
| transition: all 0.2s; | |
| } | |
| .checkbox-custom:checked { | |
| background: #10b981; | |
| border-color: #10b981; | |
| } | |
| .checkbox-custom:checked::after { | |
| content: 'β'; | |
| position: absolute; | |
| top: 50%; | |
| left: 50%; | |
| transform: translate(-50%, -50%); | |
| color: white; | |
| font-size: 12px; | |
| font-weight: bold; | |
| } | |
| .priority-high { border-left: 4px solid #ef4444; } | |
| .priority-medium { border-left: 4px solid #f59e0b; } | |
| .priority-low { border-left: 4px solid #10b981; } | |
| .fade-in { | |
| animation: fadeIn 0.3s ease-in; | |
| } | |
| @keyframes fadeIn { | |
| from { opacity: 0; transform: translateY(-10px); } | |
| to { opacity: 1; transform: translateY(0); } | |
| } | |
| .slide-out { | |
| animation: slideOut 0.3s ease-out forwards; | |
| } | |
| @keyframes slideOut { | |
| to { opacity: 0; transform: translateX(100%); } | |
| } | |
| .progress-ring { | |
| transform: rotate(-90deg); | |
| } | |
| .progress-ring-circle { | |
| transition: stroke-dashoffset 0.35s; | |
| transform-origin: 50% 50%; | |
| } | |
| /* Custom scrollbar */ | |
| ::-webkit-scrollbar { | |
| width: 6px; | |
| } | |
| ::-webkit-scrollbar-track { | |
| background: #f1f1f1; | |
| border-radius: 3px; | |
| } | |
| ::-webkit-scrollbar-thumb { | |
| background: #c1c1c1; | |
| border-radius: 3px; | |
| } | |
| ::-webkit-scrollbar-thumb:hover { | |
| background: #a1a1a1; | |
| } | |
| .category-btn.active { | |
| background: #667eea; | |
| color: white; | |
| } | |
| .filter-btn.active { | |
| color: #667eea; | |
| border-bottom: 2px solid #667eea; | |
| } | |
| </style> | |
| </head> | |
| <body class="p-4 md:p-8"> | |
| <div class="max-w-4xl mx-auto"> | |
| <!-- Header with Built with anycoder --> | |
| <div class="text-center mb-6"> | |
| <a href="https://huggingface.co/spaces/akhaliq/anycoder" target="_blank" | |
| class="inline-flex items-center gap-2 px-4 py-2 bg-white/20 backdrop-blur-sm rounded-full text-white text-sm font-medium hover:bg-white/30 transition-all"> | |
| <span>Built with anycoder</span> | |
| <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"> | |
| <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14"></path> | |
| </svg> | |
| </a> | |
| </div> | |
| <!-- Main Container --> | |
| <div class="glass-effect rounded-3xl shadow-2xl overflow-hidden"> | |
| <!-- Header Section --> | |
| <div class="bg-gradient-to-r from-indigo-600 to-purple-600 p-6 md:p-8 text-white"> | |
| <div class="flex items-center justify-between mb-4"> | |
| <div> | |
| <h1 class="text-3xl md:text-4xl font-bold mb-1">TaskFlow</h1> | |
| <p class="text-indigo-100 text-sm md:text-base" id="dateDisplay">Loading date...</p> | |
| </div> | |
| <div class="relative"> | |
| <svg class="w-16 h-16 md:w-20 md:h-20 progress-ring" viewBox="0 0 120 120"> | |
| <circle cx="60" cy="60" r="54" fill="none" stroke="rgba(255,255,255,0.2)" stroke-width="8"/> | |
| <circle id="progressCircle" cx="60" cy="60" r="54" fill="none" stroke="white" stroke-width="8" | |
| stroke-dasharray="339.292" stroke-dashoffset="339.292" stroke-linecap="round" class="progress-ring-circle"/> | |
| </svg> | |
| <div class="absolute inset-0 flex items-center justify-center"> | |
| <span id="progressPercent" class="text-lg md:text-xl font-bold">0%</span> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Stats --> | |
| <div class="flex gap-6 text-sm"> | |
| <div> | |
| <span class="text-indigo-200">Total</span> | |
| <p class="text-2xl font-bold" id="totalTasks">0</p> | |
| </div> | |
| <div> | |
| <span class="text-indigo-200">Completed</span> | |
| <p class="text-2xl font-bold" id="completedTasks">0</p> | |
| </div> | |
| <div> | |
| <span class="text-indigo-200">Pending</span> | |
| <p class="text-2xl font-bold" id="pendingTasks">0</p> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Input Section --> | |
| <div class="p-6 border-b border-gray-100"> | |
| <div class="flex flex-col md:flex-row gap-3"> | |
| <input type="text" id="taskInput" placeholder="What needs to be done?" | |
| class="flex-1 px-4 py-3 rounded-xl border border-gray-200 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-transparent transition-all"> | |
| <select id="prioritySelect" class="px-4 py-3 rounded-xl border border-gray-200 focus:outline-none focus:ring-2 focus:ring-indigo-500 bg-white"> | |
| <option value="low">Low Priority</option> | |
| <option value="medium" selected>Medium Priority</option> | |
| <option value="high">High Priority</option> | |
| </select> | |
| <select id="categorySelect" class="px-4 py-3 rounded-xl border border-gray-200 focus:outline-none focus:ring-2 focus:ring-indigo-500 bg-white"> | |
| <option value="personal">Personal</option> | |
| <option value="work">Work</option> | |
| <option value="shopping">Shopping</option> | |
| <option value="health">Health</option> | |
| <option value="other">Other</option> | |
| </select> | |
| <button onclick="addTask()" class="px-6 py-3 bg-indigo-600 text-white rounded-xl font-medium hover:bg-indigo-700 transition-all flex items-center justify-center gap-2 shadow-lg hover:shadow-xl"> | |
| <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"> | |
| <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4"></path> | |
| </svg> | |
| Add | |
| </button> | |
| </div> | |
| </div> | |
| <!-- Filters --> | |
| <div class="px-6 py-4 border-b border-gray-100 flex flex-wrap gap-2 items-center justify-between"> | |
| <div class="flex gap-1 bg-gray-100 p-1 rounded-lg"> | |
| <button onclick="setFilter('all')" class="filter-btn active px-4 py-2 rounded-md text-sm font-medium transition-all" data-filter="all">All</button> | |
| <button onclick="setFilter('active')" class="filter-btn px-4 py-2 rounded-md text-sm font-medium text-gray-600 transition-all" data-filter="active">Active</button> | |
| <button onclick="setFilter('completed')" class="filter-btn px-4 py-2 rounded-md text-sm font-medium text-gray-600 transition-all" data-filter="completed">Completed</button> | |
| </div> | |
| <div class="flex gap-2"> | |
| <button onclick="clearCompleted()" class="px-4 py-2 text-sm text-red-600 hover:bg-red-50 rounded-lg transition-all"> | |
| Clear Completed | |
| </button> | |
| </div> | |
| </div> | |
| <!-- Category Filter --> | |
| <div class="px-6 py-3 border-b border-gray-100 flex flex-wrap gap-2"> | |
| <button onclick="setCategory('all')" class="category-btn active px-3 py-1.5 rounded-full text-xs font-medium bg-gray-100 text-gray-600 transition-all" data-category="all">All</button> | |
| <button onclick="setCategory('personal')" class="category-btn px-3 py-1.5 rounded-full text-xs font-medium bg-gray-100 text-gray-600 transition-all" data-category="personal">π€ Personal</button> | |
| <button onclick="setCategory('work')" class="category-btn px-3 py-1.5 rounded-full text-xs font-medium bg-gray-100 text-gray-600 transition-all" data-category="work">πΌ Work</button> | |
| <button onclick="setCategory('shopping')" class="category-btn px-3 py-1.5 rounded-full text-xs font-medium bg-gray-100 text-gray-600 transition-all" data-category="shopping">π Shopping</button> | |
| <button onclick="setCategory('health')" class="category-btn px-3 py-1.5 rounded-full text-xs font-medium bg-gray-100 text-gray-600 transition-all" data-category="health">β€οΈ Health</button> | |
| <button onclick="setCategory('other')" class="category-btn px-3 py-1.5 rounded-full text-xs font-medium bg-gray-100 text-gray-600 transition-all" data-category="other">π Other</button> | |
| </div> | |
| <!-- Task List --> | |
| <div class="p-6 min-h-[300px] max-h-[500px] overflow-y-auto"> | |
| <ul id="taskList" class="space-y-3"> | |
| <!-- Tasks will be rendered here --> | |
| </ul> | |
| <!-- Empty State --> | |
| <div id="emptyState" class="hidden flex-col items-center justify-center py-12 text-center"> | |
| <div class="w-24 h-24 bg-indigo-100 rounded-full flex items-center justify-center mb-4"> | |
| <svg class="w-12 h-12 text-indigo-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"> | |
| <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path> | |
| </svg> | |
| </div> | |
| <h3 class="text-lg font-semibold text-gray-700 mb-1">No tasks found</h3> | |
| <p class="text-gray-500 text-sm">Add a new task to get started!</p> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Footer --> | |
| <p class="text-center text-white/80 text-sm mt-6"> | |
| Press <kbd class="px-2 py-1 bg-white/20 rounded text-xs">Enter</kbd> to add a task | |
| </p> | |
| </div> | |
| <script> | |
| // State | |
| let tasks = JSON.parse(localStorage.getItem('tasks')) || []; | |
| let currentFilter = 'all'; | |
| let currentCategory = 'all'; | |
| // Initialize | |
| document.addEventListener('DOMContentLoaded', () => { | |
| updateDate(); | |
| renderTasks(); | |
| updateStats(); | |
| // Enter key to add task | |
| document.getElementById('taskInput').addEventListener('keypress', (e) => { | |
| if (e.key === 'Enter') addTask(); | |
| }); | |
| }); | |
| // Update date display | |
| function updateDate() { | |
| const options = { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' }; | |
| document.getElementById('dateDisplay').textContent = new Date().toLocaleDateString('en-US', options); | |
| } | |
| // Add task | |
| function addTask() { | |
| const input = document.getElementById('taskInput'); | |
| const priority = document.getElementById('prioritySelect').value; | |
| const category = document.getElementById('categorySelect').value; | |
| const text = input.value.trim(); | |
| if (!text) { | |
| input.focus(); | |
| return; | |
| } | |
| const task = { | |
| id: Date.now(), | |
| text: text, | |
| completed: false, | |
| priority: priority, | |
| category: category, | |
| createdAt: new Date().toISOString() | |
| }; | |
| tasks.unshift(task); | |
| saveTasks(); | |
| renderTasks(); | |
| updateStats(); | |
| input.value = ''; | |
| input.focus(); | |
| } | |
| // Toggle task completion | |
| function toggleTask(id) { | |
| const task = tasks.find(t => t.id === id); | |
| if (task) { | |
| task.completed = !task.completed; | |
| saveTasks(); | |
| renderTasks(); | |
| updateStats(); | |
| } | |
| } | |
| // Delete task | |
| function deleteTask(id) { | |
| const taskElement = document.querySelector(`[data-task-id="${id}"]`); | |
| if (taskElement) { | |
| taskElement.classList.add('slide-out'); | |
| setTimeout(() => { | |
| tasks = tasks.filter(t => t.id !== id); | |
| saveTasks(); | |
| renderTasks(); | |
| updateStats(); | |
| }, 300); | |
| } | |
| } | |
| // Edit task | |
| function editTask(id) { | |
| const task = tasks.find(t => t.id === id); | |
| if (!task) return; | |
| const newText = prompt('Edit task:', task.text); | |
| if (newText !== null && newText.trim() !== '') { | |
| task.text = newText.trim(); | |
| saveTasks(); | |
| renderTasks(); | |
| } | |
| } | |
| // Clear completed tasks | |
| function clearCompleted() { | |
| tasks = tasks.filter(t => !t.completed); | |
| saveTasks(); | |
| renderTasks(); | |
| updateStats(); | |
| } | |
| // Set filter | |
| function setFilter(filter) { | |
| currentFilter = filter; | |
| document.querySelectorAll('.filter-btn').forEach(btn => { | |
| btn.classList.remove('active'); | |
| if (btn.dataset.filter === filter) { | |
| btn.classList.add('active'); | |
| } | |
| }); | |
| renderTasks(); | |
| } | |
| // Set category | |
| function setCategory(category) { | |
| currentCategory = category; | |
| document.querySelectorAll('.category-btn').forEach(btn => { | |
| btn.classList.remove('active'); | |
| if (btn.dataset.category === category) { | |
| btn.classList.add('active'); | |
| } | |
| }); | |
| renderTasks(); | |
| } | |
| // Get priority color | |
| function getPriorityColor(priority) { | |
| const colors = { | |
| high: 'text-red-600 bg-red-50', | |
| medium: 'text-amber-600 bg-amber-50', | |
| low: 'text-emerald-600 bg-emerald-50' | |
| }; | |
| return colors[priority] || colors.low; | |
| } | |
| // Get category icon | |
| function getCategoryIcon(category) { | |
| const icons = { | |
| personal: 'π€', | |
| work: 'πΌ', | |
| shopping: 'π', | |
| health: 'β€οΈ', | |
| other: 'π' | |
| }; | |
| return icons[category] || 'π'; | |
| } | |
| // Render tasks | |
| function renderTasks() { | |
| const list = document.getElementById('taskList'); | |
| const emptyState = document.getElementById('emptyState'); | |
| let filteredTasks = tasks; | |
| // Apply status filter | |
| if (currentFilter === 'active') { | |
| filteredTasks = filteredTasks.filter(t => !t.completed); | |
| } else if (currentFilter === 'completed') { | |
| filteredTasks = filteredTasks.filter(t => t.completed); | |
| } | |
| // Apply category filter | |
| if (currentCategory !== 'all') { | |
| filteredTasks = filteredTasks.filter(t => t.category === currentCategory); | |
| } | |
| if (filteredTasks.length === 0) { | |
| list.innerHTML = ''; | |
| emptyState.classList.remove('hidden'); | |
| emptyState.classList.add('flex'); | |
| return; | |
| } | |
| emptyState.classList.add('hidden'); | |
| emptyState.classList.remove('flex'); | |
| list.innerHTML = filteredTasks.map(task => ` | |
| <li data-task-id="${task.id}" class="task-item ${task.completed ? 'completed' : ''} priority-${task.priority} bg-white rounded-xl p-4 shadow-sm border border-gray-100 fade-in group"> | |
| <div class="flex items-center gap-3"> | |
| <input type="checkbox" class="checkbox-custom" ${task.completed ? 'checked' : ''} | |
| onchange="toggleTask(${task.id})"> | |
| <div class="flex-1 min-w-0"> | |
| <p class="task-text text-gray-800 font-medium truncate">${escapeHtml(task.text)}</p> | |
| <div class="flex items-center gap-2 mt-1"> | |
| <span class="text-xs px-2 py-0.5 rounded-full ${getPriorityColor(task.priority)} capitalize"> | |
| ${task.priority} | |
| </span> | |
| <span class="text-xs text-gray-500"> | |
| ${getCategoryIcon(task.category)} ${task.category} | |
| </span> | |
| </div> | |
| </div> | |
| <div class="flex items-center gap-1 opacity-0 group-hover:opacity-100 transition-opacity"> | |
| <button onclick="editTask(${task.id})" class="p-2 text-gray-400 hover:text-indigo-600 hover:bg-indigo-50 rounded-lg transition-all"> | |
| <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"> | |
| <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"></path> | |
| </svg> | |
| </button> | |
| <button onclick="deleteTask(${task.id})" class="p-2 text-gray-400 hover:text-red-600 hover:bg-red-50 rounded-lg transition-all"> | |
| <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"> | |
| <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"></path> | |
| </svg> | |
| </button> | |
| </div> | |
| </div> | |
| </li> | |
| `).join(''); | |
| } | |
| // Update statistics | |
| function updateStats() { | |
| const total = tasks.length; | |
| const completed = tasks.filter(t => t.completed).length; | |
| const pending = total - completed; | |
| const percentage = total > 0 ? Math.round((completed / total) * 100) : 0; | |
| document.getElementById('totalTasks').textContent = total; | |
| document.getElementById('completedTasks').textContent = completed; | |
| document.getElementById('pendingTasks').textContent = pending; | |
| document.getElementById('progressPercent').textContent = `${percentage}%`; | |
| // Update progress circle | |
| const circle = document.getElementById('progressCircle'); | |
| const circumference = 2 * Math.PI * 54; | |
| const offset = circumference - (percentage / 100) * circumference; | |
| circle.style.strokeDashoffset = offset; | |
| } | |
| // Save to localStorage | |
| function saveTasks() { | |
| localStorage.setItem('tasks', JSON.stringify(tasks)); | |
| } | |
| // Escape HTML | |
| function escapeHtml(text) { | |
| const div = document.createElement('div'); | |
| div.textContent = text; | |
| return div.innerHTML; | |
| } | |
| </script> | |
| </body> | |
| </html> |