Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>TaskFlow - Advanced Todo App</title> | |
| <style> | |
| * { | |
| margin: 0; | |
| padding: 0; | |
| box-sizing: border-box; | |
| } | |
| :root { | |
| --primary: #6366f1; | |
| --primary-dark: #4f46e5; | |
| --secondary: #8b5cf6; | |
| --success: #10b981; | |
| --warning: #f59e0b; | |
| --danger: #ef4444; | |
| --dark: #1f2937; | |
| --gray: #6b7280; | |
| --light: #f3f4f6; | |
| --white: #ffffff; | |
| --shadow: 0 10px 30px rgba(0, 0, 0, 0.1); | |
| --shadow-sm: 0 2px 10px rgba(0, 0, 0, 0.05); | |
| --radius: 12px; | |
| --transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); | |
| } | |
| [data-theme="dark"] { | |
| --primary: #818cf8; | |
| --primary-dark: #6366f1; | |
| --secondary: #a78bfa; | |
| --dark: #f9fafb; | |
| --gray: #d1d5db; | |
| --light: #374151; | |
| --white: #1f2937; | |
| --shadow: 0 10px 30px rgba(0, 0, 0, 0.5); | |
| --shadow-sm: 0 2px 10px rgba(0, 0, 0, 0.3); | |
| } | |
| body { | |
| font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif; | |
| background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); | |
| min-height: 100vh; | |
| padding: 20px; | |
| transition: var(--transition); | |
| } | |
| [data-theme="dark"] body { | |
| background: linear-gradient(135deg, #1e3a8a 0%, #312e81 100%); | |
| } | |
| .container { | |
| max-width: 900px; | |
| margin: 0 auto; | |
| animation: fadeInUp 0.5s ease; | |
| } | |
| header { | |
| text-align: center; | |
| margin-bottom: 30px; | |
| color: var(--white); | |
| } | |
| .logo { | |
| display: inline-flex; | |
| align-items: center; | |
| gap: 10px; | |
| font-size: 2rem; | |
| font-weight: bold; | |
| margin-bottom: 10px; | |
| } | |
| .logo-icon { | |
| width: 40px; | |
| height: 40px; | |
| background: var(--white); | |
| border-radius: 10px; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| font-size: 1.5rem; | |
| } | |
| .header-links { | |
| display: flex; | |
| justify-content: center; | |
| gap: 20px; | |
| margin-top: 10px; | |
| } | |
| .header-links a { | |
| color: var(--white); | |
| text-decoration: none; | |
| font-size: 0.9rem; | |
| opacity: 0.9; | |
| transition: opacity 0.3s; | |
| } | |
| .header-links a:hover { | |
| opacity: 1; | |
| } | |
| .main-card { | |
| background: var(--white); | |
| border-radius: var(--radius); | |
| box-shadow: var(--shadow); | |
| overflow: hidden; | |
| transition: var(--transition); | |
| } | |
| .stats-bar { | |
| background: linear-gradient(135deg, var(--primary) 0%, var(--secondary) 100%); | |
| padding: 20px; | |
| display: grid; | |
| grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); | |
| gap: 20px; | |
| color: var(--white); | |
| } | |
| .stat-item { | |
| text-align: center; | |
| } | |
| .stat-value { | |
| font-size: 2rem; | |
| font-weight: bold; | |
| margin-bottom: 5px; | |
| } | |
| .stat-label { | |
| font-size: 0.9rem; | |
| opacity: 0.9; | |
| } | |
| .controls { | |
| padding: 20px; | |
| background: var(--light); | |
| display: flex; | |
| gap: 15px; | |
| flex-wrap: wrap; | |
| align-items: center; | |
| } | |
| .search-box { | |
| flex: 1; | |
| min-width: 200px; | |
| position: relative; | |
| } | |
| .search-box input { | |
| width: 100%; | |
| padding: 12px 40px 12px 15px; | |
| border: 2px solid transparent; | |
| border-radius: 8px; | |
| background: var(--white); | |
| font-size: 1rem; | |
| transition: var(--transition); | |
| } | |
| .search-box input:focus { | |
| outline: none; | |
| border-color: var(--primary); | |
| } | |
| .search-icon { | |
| position: absolute; | |
| right: 15px; | |
| top: 50%; | |
| transform: translateY(-50%); | |
| color: var(--gray); | |
| } | |
| .filter-buttons { | |
| display: flex; | |
| gap: 10px; | |
| } | |
| .filter-btn { | |
| padding: 10px 20px; | |
| border: none; | |
| border-radius: 8px; | |
| background: var(--white); | |
| color: var(--gray); | |
| cursor: pointer; | |
| transition: var(--transition); | |
| font-weight: 500; | |
| } | |
| .filter-btn:hover { | |
| background: var(--primary); | |
| color: var(--white); | |
| } | |
| .filter-btn.active { | |
| background: var(--primary); | |
| color: var(--white); | |
| } | |
| .theme-toggle { | |
| width: 45px; | |
| height: 45px; | |
| border-radius: 50%; | |
| border: none; | |
| background: var(--white); | |
| cursor: pointer; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| font-size: 1.2rem; | |
| transition: var(--transition); | |
| box-shadow: var(--shadow-sm); | |
| } | |
| .theme-toggle:hover { | |
| transform: scale(1.1); | |
| } | |
| .add-todo-section { | |
| padding: 20px; | |
| border-bottom: 1px solid var(--light); | |
| } | |
| .add-todo-form { | |
| display: flex; | |
| gap: 15px; | |
| flex-wrap: wrap; | |
| } | |
| .input-group { | |
| flex: 1; | |
| min-width: 200px; | |
| } | |
| .input-group input, .input-group select { | |
| width: 100%; | |
| padding: 12px 15px; | |
| border: 2px solid var(--light); | |
| border-radius: 8px; | |
| font-size: 1rem; | |
| transition: var(--transition); | |
| background: var(--white); | |
| color: var(--dark); | |
| } | |
| .input-group input:focus, .input-group select:focus { | |
| outline: none; | |
| border-color: var(--primary); | |
| } | |
| .priority-select { | |
| min-width: 150px; | |
| } | |
| .add-btn { | |
| padding: 12px 30px; | |
| border: none; | |
| border-radius: 8px; | |
| background: linear-gradient(135deg, var(--primary) 0%, var(--secondary) 100%); | |
| color: var(--white); | |
| font-weight: bold; | |
| cursor: pointer; | |
| transition: var(--transition); | |
| font-size: 1rem; | |
| } | |
| .add-btn:hover { | |
| transform: translateY(-2px); | |
| box-shadow: var(--shadow); | |
| } | |
| .todo-list { | |
| padding: 20px; | |
| min-height: 300px; | |
| } | |
| .todo-item { | |
| background: var(--light); | |
| border-radius: 10px; | |
| padding: 15px; | |
| margin-bottom: 15px; | |
| display: flex; | |
| align-items: center; | |
| gap: 15px; | |
| transition: var(--transition); | |
| cursor: move; | |
| position: relative; | |
| overflow: hidden; | |
| } | |
| .todo-item:hover { | |
| transform: translateX(5px); | |
| box-shadow: var(--shadow-sm); | |
| } | |
| .todo-item.dragging { | |
| opacity: 0.5; | |
| transform: rotate(5deg); | |
| } | |
| .todo-item.completed { | |
| opacity: 0.7; | |
| } | |
| .todo-item.completed .todo-text { | |
| text-decoration: line-through; | |
| color: var(--gray); | |
| } | |
| .todo-checkbox { | |
| width: 24px; | |
| height: 24px; | |
| border: 2px solid var(--primary); | |
| border-radius: 50%; | |
| cursor: pointer; | |
| position: relative; | |
| transition: var(--transition); | |
| flex-shrink: 0; | |
| } | |
| .todo-checkbox:hover { | |
| background: var(--primary); | |
| } | |
| .todo-checkbox.checked { | |
| background: var(--primary); | |
| } | |
| .todo-checkbox.checked::after { | |
| content: 'β'; | |
| position: absolute; | |
| top: 50%; | |
| left: 50%; | |
| transform: translate(-50%, -50%); | |
| color: var(--white); | |
| font-size: 14px; | |
| } | |
| .todo-content { | |
| flex: 1; | |
| } | |
| .todo-text { | |
| font-size: 1rem; | |
| color: var(--dark); | |
| margin-bottom: 5px; | |
| transition: var(--transition); | |
| } | |
| .todo-meta { | |
| display: flex; | |
| gap: 15px; | |
| font-size: 0.85rem; | |
| color: var(--gray); | |
| } | |
| .todo-category { | |
| background: var(--primary); | |
| color: var(--white); | |
| padding: 2px 8px; | |
| border-radius: 12px; | |
| font-size: 0.75rem; | |
| } | |
| .priority-badge { | |
| padding: 2px 8px; | |
| border-radius: 12px; | |
| font-size: 0.75rem; | |
| font-weight: bold; | |
| } | |
| .priority-high { | |
| background: var(--danger); | |
| color: var(--white); | |
| } | |
| .priority-medium { | |
| background: var(--warning); | |
| color: var(--white); | |
| } | |
| .priority-low { | |
| background: var(--success); | |
| color: var(--white); | |
| } | |
| .todo-actions { | |
| display: flex; | |
| gap: 10px; | |
| } | |
| .action-btn { | |
| width: 35px; | |
| height: 35px; | |
| border: none; | |
| border-radius: 8px; | |
| background: var(--white); | |
| color: var(--gray); | |
| cursor: pointer; | |
| transition: var(--transition); | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| } | |
| .action-btn:hover { | |
| background: var(--primary); | |
| color: var(--white); | |
| transform: scale(1.1); | |
| } | |
| .empty-state { | |
| text-align: center; | |
| padding: 60px 20px; | |
| color: var(--gray); | |
| } | |
| .empty-icon { | |
| font-size: 4rem; | |
| margin-bottom: 20px; | |
| opacity: 0.5; | |
| } | |
| .progress-bar { | |
| height: 6px; | |
| background: var(--light); | |
| border-radius: 3px; | |
| overflow: hidden; | |
| margin-top: 20px; | |
| } | |
| .progress-fill { | |
| height: 100%; | |
| background: linear-gradient(90deg, var(--success) 0%, var(--primary) 100%); | |
| border-radius: 3px; | |
| transition: width 0.5s ease; | |
| } | |
| @keyframes fadeInUp { | |
| from { | |
| opacity: 0; | |
| transform: translateY(20px); | |
| } | |
| to { | |
| opacity: 1; | |
| transform: translateY(0); | |
| } | |
| } | |
| @keyframes slideIn { | |
| from { | |
| transform: translateX(-100%); | |
| } | |
| to { | |
| transform: translateX(0); | |
| } | |
| } | |
| .toast { | |
| position: fixed; | |
| bottom: 30px; | |
| right: 30px; | |
| background: var(--dark); | |
| color: var(--white); | |
| padding: 15px 25px; | |
| border-radius: 8px; | |
| box-shadow: var(--shadow); | |
| transform: translateX(400px); | |
| transition: transform 0.3s ease; | |
| z-index: 1000; | |
| } | |
| .toast.show { | |
| transform: translateX(0); | |
| } | |
| @media (max-width: 640px) { | |
| .controls { | |
| flex-direction: column; | |
| } | |
| .filter-buttons { | |
| width: 100%; | |
| justify-content: center; | |
| } | |
| .add-todo-form { | |
| flex-direction: column; | |
| } | |
| .stats-bar { | |
| grid-template-columns: repeat(2, 1fr); | |
| } | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="container"> | |
| <header> | |
| <div class="logo"> | |
| <div class="logo-icon">π</div> | |
| <span>TaskFlow</span> | |
| </div> | |
| <div class="header-links"> | |
| <a href="https://huggingface.co/spaces/akhaliq/anycoder" target="_blank">Built with anycoder</a> | |
| </div> | |
| </header> | |
| <main class="main-card"> | |
| <div class="stats-bar"> | |
| <div class="stat-item"> | |
| <div class="stat-value" id="totalTasks">0</div> | |
| <div class="stat-label">Total Tasks</div> | |
| </div> | |
| <div class="stat-item"> | |
| <div class="stat-value" id="completedTasks">0</div> | |
| <div class="stat-label">Completed</div> | |
| </div> | |
| <div class="stat-item"> | |
| <div class="stat-value" id="pendingTasks">0</div> | |
| <div class="stat-label">Pending</div> | |
| </div> | |
| <div class="stat-item"> | |
| <div class="stat-value" id="progressPercent">0%</div> | |
| <div class="stat-label">Progress</div> | |
| </div> | |
| </div> | |
| <div class="progress-bar"> | |
| <div class="progress-fill" id="progressBar"></div> | |
| </div> | |
| <div class="controls"> | |
| <div class="search-box"> | |
| <input type="text" id="searchInput" placeholder="Search tasks..."> | |
| <span class="search-icon">π</span> | |
| </div> | |
| <div class="filter-buttons"> | |
| <button class="filter-btn active" data-filter="all">All</button> | |
| <button class="filter-btn" data-filter="active">Active</button> | |
| <button class="filter-btn" data-filter="completed">Completed</button> | |
| </div> | |
| <button class="theme-toggle" id="themeToggle">π</button> | |
| </div> | |
| <div class="add-todo-section"> | |
| <form class="add-todo-form" id="addTodoForm"> | |
| <div class="input-group"> | |
| <input type="text" id="todoInput" placeholder="What needs to be done?" required> | |
| </div> | |
| <div class="input-group priority-select"> | |
| <select id="prioritySelect"> | |
| <option value="low">Low Priority</option> | |
| <option value="medium" selected>Medium Priority</option> | |
| <option value="high">High Priority</option> | |
| </select> | |
| </div> | |
| <div class="input-group"> | |
| <select id="categorySelect"> | |
| <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> | |
| </div> | |
| <div class="input-group"> | |
| <input type="date" id="dueDateInput"> | |
| </div> | |
| <button type="submit" class="add-btn">Add Task</button> | |
| </form> | |
| </div> | |
| <div class="todo-list" id="todoList"> | |
| <div class="empty-state"> | |
| <div class="empty-icon">π</div> | |
| <h3>No tasks yet</h3> | |
| <p>Add your first task to get started!</p> | |
| </div> | |
| </div> | |
| </main> | |
| </div> | |
| <div class="toast" id="toast"></div> | |
| <script> | |
| class TodoApp { | |
| constructor() { | |
| this.todos = JSON.parse(localStorage.getItem('todos')) || []; | |
| this.currentFilter = 'all'; | |
| this.searchTerm = ''; | |
| this.draggedItem = null; | |
| this.init(); | |
| } | |
| init() { | |
| this.setupEventListeners(); | |
| this.loadTheme(); | |
| this.render(); | |
| this.updateStats(); | |
| } | |
| setupEventListeners() { | |
| // Add todo form | |
| document.getElementById('addTodoForm').addEventListener('submit', (e) => { | |
| e.preventDefault(); | |
| this.addTodo(); | |
| }); | |
| // Filter buttons | |
| document.querySelectorAll('.filter-btn').forEach(btn => { | |
| btn.addEventListener('click', (e) => { | |
| document.querySelectorAll('.filter-btn').forEach(b => b.classList.remove('active')); | |
| e.target.classList.add('active'); | |
| this.currentFilter = e.target.dataset.filter; | |
| this.render(); | |
| }); | |
| }); | |
| // Search input | |
| document.getElementById('searchInput').addEventListener('input', (e) => { | |
| this.searchTerm = e.target.value.toLowerCase(); | |
| this.render(); | |
| }); | |
| // Theme toggle | |
| document.getElementById('themeToggle').addEventListener('click', () => { | |
| this.toggleTheme(); | |
| }); | |
| // Set today as default due date | |
| document.getElementById('dueDateInput').valueAsDate = new Date(); | |
| } | |
| addTodo() { | |
| const input = document.getElementById('todoInput'); | |
| const priority = document.getElementById('prioritySelect').value; | |
| const category = document.getElementById('categorySelect').value; | |
| const dueDate = document.getElementById('dueDateInput').value; | |
| if (!input.value.trim()) return; | |
| const todo = { | |
| id: Date.now(), | |
| text: input.value.trim(), | |
| completed: false, | |
| priority, | |
| category, | |
| dueDate, | |
| createdAt: new Date().toISOString() | |
| }; | |
| this.todos.unshift(todo); | |
| this.saveTodos(); | |
| this.render(); | |
| this.updateStats(); | |
| // Reset form | |
| input.value = ''; | |
| document.getElementById('dueDateInput').valueAsDate = new Date(); | |
| this.showToast('Task added successfully! π'); | |
| } | |
| toggleTodo(id) { | |
| const todo = this.todos.find(t => t.id === id); | |
| if (todo) { | |
| todo.completed = !todo.completed; | |
| this.saveTodos(); | |
| this.render(); | |
| this.updateStats(); | |
| this.showToast(todo.completed ? 'Task completed! β ' : 'Task marked as active'); | |
| } | |
| } | |
| deleteTodo(id) { | |
| this.todos = this.todos.filter(t => t.id !== id); | |
| this.saveTodos(); | |
| this.render(); | |
| this.updateStats(); | |
| this.showToast('Task deleted'); | |
| } | |
| editTodo(id) { | |
| const todo = this.todos.find(t => t.id === id); | |
| if (todo) { | |
| const newText = prompt('Edit task:', todo.text); | |
| if (newText && newText.trim()) { | |
| todo.text = newText.trim(); | |
| this.saveTodos(); | |
| this.render(); | |
| this.showToast('Task updated'); | |
| } | |
| } | |
| } | |
| getFilteredTodos() { | |
| let filtered = [...this.todos]; | |
| // Apply filter | |
| if (this.currentFilter === 'active') { | |
| filtered = filtered.filter(t => !t.completed); | |
| } else if (this.currentFilter === 'completed') { | |
| filtered = filtered.filter(t => t.completed); | |
| } | |
| // Apply search | |
| if (this.searchTerm) { | |
| filtered = filtered.filter(t => | |
| t.text.toLowerCase().includes(this.searchTerm) | |
| ); | |
| } | |
| return filtered; | |
| } | |
| render() { | |
| const todoList = document.getElementById('todoList'); | |
| const filteredTodos = this.getFilteredTodos(); | |
| if (filteredTodos.length === 0) { | |
| todoList.innerHTML = ` | |
| <div class="empty-state"> | |
| <div class="empty-icon">π</div> | |
| <h3>No tasks found</h3> | |
| <p>${this.searchTerm ? 'Try a different search term' : 'Add your first task to get started!'}</p> | |
| </div> | |
| `; | |
| return; | |
| } | |
| todoList.innerHTML = filteredTodos.map(todo => ` | |
| <div class="todo-item ${todo.completed ? 'completed' : ''}" | |
| draggable="true" | |
| data-id="${todo.id}"> | |
| <div class="todo-checkbox ${todo.completed ? 'checked' : ''}" | |
| onclick="app.toggleTodo(${todo.id})"></div> | |
| <div class="todo-content"> | |
| <div class="todo-text">${this.escapeHtml(todo.text)}</div> | |
| <div class="todo-meta"> | |
| <span class="todo-category">${todo.category}</span> | |
| <span class="priority-badge priority-${todo.priority}">${todo.priority}</span> | |
| ${todo.dueDate ? `<span>π ${this.formatDate(todo.dueDate)}</span>` : ''} | |
| </div> | |
| </div> | |
| <div class="todo-actions"> | |
| <button class="action-btn" onclick="app.editTodo(${todo.id})">βοΈ</button> | |
| <button class="action-btn" onclick="app.deleteTodo(${todo.id})">ποΈ</button> | |
| </div> | |
| </div> | |
| `).join(''); | |
| this.setupDragAndDrop(); | |
| } | |
| setupDragAndDrop() { | |
| const items = document.querySelectorAll('.todo-item'); | |
| items.forEach(item => { | |
| item.addEventListener('dragstart', (e) => { | |
| this.draggedItem = item; | |
| item.classList.add('dragging'); | |
| }); | |
| item.addEventListener('dragend', (e) => { | |
| item.classList.remove('dragging'); | |
| }); | |
| item.addEventListener('dragover', (e) => { | |
| e.preventDefault(); | |
| const afterElement = this.getDragAfterElement(document.getElementById('todoList'), e.clientY); | |
| if (afterElement == null) { | |
| document.getElementById('todoList').appendChild(this.draggedItem); | |
| } else { | |
| document.getElementById('todoList').insertBefore(this.draggedItem, afterElement); | |
| } | |
| }); | |
| }); | |
| } | |
| getDragAfterElement(container, y) { | |
| const draggableElements = [...container.querySelectorAll('.todo-item:not(.dragging)')]; | |
| return draggableElements.reduce((closest, child) => { | |
| const box = child.getBoundingClientRect(); | |
| const offset = y - box.top - box.height / 2; | |
| if (offset < 0 && offset > closest.offset) { | |
| return { offset: offset, element: child }; | |
| } else { | |
| return closest; | |
| } | |
| }, { offset: Number.NEGATIVE_INFINITY }).element; | |
| } | |
| updateStats() { | |
| const total = this.todos.length; | |
| const completed = this.todos.filter(t => t.completed).length; | |
| const pending = total - completed; | |
| const progress = 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 = progress + '%'; | |
| document.getElementById('progressBar').style.width = progress + '%'; | |
| } | |
| saveTodos() { | |
| localStorage.setItem('todos', JSON.stringify(this.todos)); | |
| } | |
| toggleTheme() { | |
| const currentTheme = document.documentElement.getAttribute('data-theme'); | |
| const newTheme = currentTheme === 'dark' ? 'light' : 'dark'; | |
| document.documentElement.setAttribute('data-theme', newTheme); | |
| localStorage.setItem('theme', newTheme); | |
| document.getElementById('themeToggle').textContent = newTheme === 'dark' ? 'βοΈ' : 'π'; | |
| } | |
| loadTheme() { | |
| const savedTheme = localStorage.getItem('theme') || 'light'; | |
| document.documentElement.setAttribute('data-theme', savedTheme); | |
| document.getElementById('themeToggle').textContent = savedTheme === 'dark' ? 'βοΈ' : 'π'; | |
| } | |
| formatDate(dateString) { | |
| const date = new Date(dateString); | |
| const today = new Date(); | |
| const tomorrow = new Date(today); | |
| tomorrow.setDate(tomorrow.getDate() + 1); | |
| if (date.toDateString() === today.toDateString()) { | |
| return 'Today'; | |
| } else if (date.toDateString() === tomorrow.toDateString()) { | |
| return 'Tomorrow'; | |
| } else { | |
| return date.toLocaleDateString('en-US', { month: 'short', day: 'numeric' }); | |
| } | |
| } | |
| escapeHtml(text) { | |
| const div = document.createElement('div'); | |
| div.textContent = text; | |
| return div.innerHTML; | |
| } | |
| showToast(message) { | |
| const toast = document.getElementById('toast'); | |
| toast.textContent = message; | |
| toast.classList.add('show'); | |
| setTimeout(() => { | |
| toast.classList.remove('show'); | |
| }, 3000); | |
| } | |
| } | |
| // Initialize app | |
| const app = new TodoApp(); | |
| </script> | |
| </body> | |
| </html> |