Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Modern Todo App</title> | |
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> | |
| <style> | |
| :root { | |
| --primary-color: #6366f1; | |
| --primary-dark: #4f46e5; | |
| --secondary-color: #8b5cf6; | |
| --success-color: #10b981; | |
| --danger-color: #ef4444; | |
| --warning-color: #f59e0b; | |
| --dark-color: #1f2937; | |
| --light-color: #f3f4f6; | |
| --white: #ffffff; | |
| --gray-100: #f9fafb; | |
| --gray-200: #f3f4f6; | |
| --gray-300: #e5e7eb; | |
| --gray-400: #d1d5db; | |
| --gray-500: #9ca3af; | |
| --gray-600: #6b7280; | |
| --gray-700: #4b5563; | |
| --gray-800: #374151; | |
| --gray-900: #1f2937; | |
| --shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.05); | |
| --shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06); | |
| --shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05); | |
| --shadow-xl: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04); | |
| } | |
| * { | |
| margin: 0; | |
| padding: 0; | |
| box-sizing: border-box; | |
| } | |
| body { | |
| font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; | |
| background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); | |
| min-height: 100vh; | |
| display: flex; | |
| justify-content: center; | |
| align-items: center; | |
| padding: 20px; | |
| color: var(--dark-color); | |
| } | |
| .container { | |
| width: 100%; | |
| max-width: 600px; | |
| background: var(--white); | |
| border-radius: 20px; | |
| box-shadow: var(--shadow-xl); | |
| overflow: hidden; | |
| animation: slideUp 0.5s ease-out; | |
| } | |
| @keyframes slideUp { | |
| from { | |
| opacity: 0; | |
| transform: translateY(30px); | |
| } | |
| to { | |
| opacity: 1; | |
| transform: translateY(0); | |
| } | |
| } | |
| .header { | |
| background: linear-gradient(135deg, var(--primary-color), var(--secondary-color)); | |
| padding: 25px 30px; | |
| text-align: center; | |
| position: relative; | |
| } | |
| .header h1 { | |
| color: var(--white); | |
| font-size: 28px; | |
| font-weight: 700; | |
| margin-bottom: 10px; | |
| } | |
| .anycoder-link { | |
| color: var(--white); | |
| text-decoration: none; | |
| font-size: 14px; | |
| opacity: 0.9; | |
| transition: opacity 0.3s ease; | |
| display: inline-flex; | |
| align-items: center; | |
| gap: 5px; | |
| } | |
| .anycoder-link:hover { | |
| opacity: 1; | |
| } | |
| .main-content { | |
| padding: 30px; | |
| } | |
| .add-todo { | |
| display: flex; | |
| gap: 12px; | |
| margin-bottom: 25px; | |
| } | |
| .add-todo input { | |
| flex: 1; | |
| padding: 14px 20px; | |
| border: 2px solid var(--gray-200); | |
| border-radius: 12px; | |
| font-size: 16px; | |
| transition: all 0.3s ease; | |
| } | |
| .add-todo input:focus { | |
| outline: none; | |
| border-color: var(--primary-color); | |
| box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.1); | |
| } | |
| .add-todo button { | |
| padding: 14px 24px; | |
| background: linear-gradient(135deg, var(--primary-color), var(--primary-dark)); | |
| color: var(--white); | |
| border: none; | |
| border-radius: 12px; | |
| font-size: 16px; | |
| font-weight: 600; | |
| cursor: pointer; | |
| transition: all 0.3s ease; | |
| display: flex; | |
| align-items: center; | |
| gap: 8px; | |
| } | |
| .add-todo button:hover { | |
| transform: translateY(-2px); | |
| box-shadow: var(--shadow-lg); | |
| } | |
| .filters { | |
| display: flex; | |
| gap: 10px; | |
| margin-bottom: 25px; | |
| background: var(--gray-100); | |
| padding: 5px; | |
| border-radius: 12px; | |
| } | |
| .filter-btn { | |
| flex: 1; | |
| padding: 10px; | |
| border: none; | |
| background: transparent; | |
| border-radius: 8px; | |
| cursor: pointer; | |
| font-size: 14px; | |
| font-weight: 500; | |
| color: var(--gray-600); | |
| transition: all 0.3s ease; | |
| } | |
| .filter-btn.active { | |
| background: var(--white); | |
| color: var(--primary-color); | |
| box-shadow: var(--shadow-sm); | |
| } | |
| .todo-list { | |
| list-style: none; | |
| margin-bottom: 25px; | |
| min-height: 300px; | |
| } | |
| .todo-item { | |
| display: flex; | |
| align-items: center; | |
| padding: 16px; | |
| background: var(--white); | |
| border: 2px solid var(--gray-200); | |
| border-radius: 12px; | |
| margin-bottom: 12px; | |
| transition: all 0.3s ease; | |
| animation: fadeIn 0.3s ease-out; | |
| } | |
| @keyframes fadeIn { | |
| from { | |
| opacity: 0; | |
| transform: translateX(-20px); | |
| } | |
| to { | |
| opacity: 1; | |
| transform: translateX(0); | |
| } | |
| } | |
| .todo-item:hover { | |
| border-color: var(--primary-color); | |
| box-shadow: var(--shadow-md); | |
| } | |
| .todo-item.completed { | |
| opacity: 0.7; | |
| background: var(--gray-50); | |
| } | |
| .todo-item.completed .todo-text { | |
| text-decoration: line-through; | |
| color: var(--gray-500); | |
| } | |
| .todo-checkbox { | |
| width: 22px; | |
| height: 22px; | |
| border: 2px solid var(--gray-400); | |
| border-radius: 6px; | |
| margin-right: 15px; | |
| cursor: pointer; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| transition: all 0.3s ease; | |
| } | |
| .todo-checkbox.checked { | |
| background: var(--success-color); | |
| border-color: var(--success-color); | |
| } | |
| .todo-checkbox.checked::after { | |
| content: '✓'; | |
| color: var(--white); | |
| font-size: 14px; | |
| font-weight: bold; | |
| } | |
| .todo-text { | |
| flex: 1; | |
| font-size: 16px; | |
| color: var(--dark-color); | |
| } | |
| .todo-actions { | |
| display: flex; | |
| gap: 8px; | |
| } | |
| .todo-actions button { | |
| width: 32px; | |
| height: 32px; | |
| border: none; | |
| border-radius: 8px; | |
| background: var(--gray-100); | |
| color: var(--gray-600); | |
| cursor: pointer; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| transition: all 0.3s ease; | |
| } | |
| .todo-actions button:hover { | |
| background: var(--gray-200); | |
| } | |
| .todo-actions button.delete:hover { | |
| background: var(--danger-color); | |
| color: var(--white); | |
| } | |
| .todo-actions button.edit:hover { | |
| background: var(--primary-color); | |
| color: var(--white); | |
| } | |
| .stats { | |
| display: flex; | |
| justify-content: space-between; | |
| padding: 15px 20px; | |
| background: var(--gray-100); | |
| border-radius: 12px; | |
| font-size: 14px; | |
| color: var(--gray-600); | |
| } | |
| .stat-item { | |
| display: flex; | |
| align-items: center; | |
| gap: 5px; | |
| } | |
| .empty-state { | |
| text-align: center; | |
| padding: 40px 20px; | |
| color: var(--gray-500); | |
| } | |
| .empty-state i { | |
| font-size: 48px; | |
| margin-bottom: 15px; | |
| opacity: 0.5; | |
| } | |
| .empty-state p { | |
| font-size: 16px; | |
| margin-bottom: 20px; | |
| } | |
| .clear-completed { | |
| background: transparent; | |
| border: none; | |
| color: var(--danger-color); | |
| cursor: pointer; | |
| font-size: 14px; | |
| padding: 5px 10px; | |
| border-radius: 6px; | |
| transition: all 0.3s ease; | |
| } | |
| .clear-completed:hover { | |
| background: var(--danger-color); | |
| color: var(--white); | |
| } | |
| .edit-modal { | |
| display: none; | |
| position: fixed; | |
| top: 0; | |
| left: 0; | |
| width: 100%; | |
| height: 100%; | |
| background: rgba(0, 0, 0, 0.5); | |
| align-items: center; | |
| justify-content: center; | |
| z-index: 1000; | |
| } | |
| .edit-modal.active { | |
| display: flex; | |
| } | |
| .edit-modal-content { | |
| background: var(--white); | |
| padding: 25px; | |
| border-radius: 16px; | |
| width: 90%; | |
| max-width: 400px; | |
| animation: modalSlideIn 0.3s ease-out; | |
| } | |
| @keyframes modalSlideIn { | |
| from { | |
| opacity: 0; | |
| transform: scale(0.9); | |
| } | |
| to { | |
| opacity: 1; | |
| transform: scale(1); | |
| } | |
| } | |
| .edit-modal h3 { | |
| margin-bottom: 15px; | |
| color: var(--dark-color); | |
| } | |
| .edit-modal input { | |
| width: 100%; | |
| padding: 12px; | |
| border: 2px solid var(--gray-200); | |
| border-radius: 10px; | |
| font-size: 16px; | |
| margin-bottom: 15px; | |
| } | |
| .edit-modal-buttons { | |
| display: flex; | |
| gap: 10px; | |
| } | |
| .edit-modal-buttons button { | |
| flex: 1; | |
| padding: 12px; | |
| border: none; | |
| border-radius: 10px; | |
| font-size: 14px; | |
| font-weight: 600; | |
| cursor: pointer; | |
| transition: all 0.3s ease; | |
| } | |
| .edit-modal-buttons button.save { | |
| background: var(--success-color); | |
| color: var(--white); | |
| } | |
| .edit-modal-buttons button.cancel { | |
| background: var(--gray-200); | |
| color: var(--dark-color); | |
| } | |
| .edit-modal-buttons button:hover { | |
| transform: translateY(-2px); | |
| box-shadow: var(--shadow-md); | |
| } | |
| @media (max-width: 480px) { | |
| .container { | |
| border-radius: 15px; | |
| } | |
| .header h1 { | |
| font-size: 24px; | |
| } | |
| .main-content { | |
| padding: 20px; | |
| } | |
| .add-todo { | |
| flex-direction: column; | |
| } | |
| .filters { | |
| flex-wrap: wrap; | |
| } | |
| .todo-item { | |
| padding: 12px; | |
| } | |
| .todo-text { | |
| font-size: 14px; | |
| } | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="container"> | |
| <div class="header"> | |
| <h1>📝 Modern Todo App</h1> | |
| <a href="https://huggingface.co/spaces/akhaliq/anycoder" class="anycoder-link" target="_blank"> | |
| <i class="fas fa-code"></i> Built with anycoder | |
| </a> | |
| </div> | |
| <div class="main-content"> | |
| <div class="add-todo"> | |
| <input type="text" id="todoInput" placeholder="What needs to be done?" /> | |
| <button id="addBtn"> | |
| <i class="fas fa-plus"></i> Add Todo | |
| </button> | |
| </div> | |
| <div class="filters"> | |
| <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> | |
| <ul class="todo-list" id="todoList"></ul> | |
| <div class="stats"> | |
| <div class="stat-item"> | |
| <i class="fas fa-list"></i> | |
| <span id="totalTodos">0 total</span> | |
| </div> | |
| <div class="stat-item"> | |
| <i class="fas fa-check-circle"></i> | |
| <span id="completedTodos">0 completed</span> | |
| </div> | |
| <div class="stat-item"> | |
| <i class="fas fa-circle"></i> | |
| <span id="activeTodos">0 active</span> | |
| </div> | |
| <button class="clear-completed" id="clearCompleted"> | |
| <i class="fas fa-trash"></i> Clear Completed | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="edit-modal" id="editModal"> | |
| <div class="edit-modal-content"> | |
| <h3>Edit Todo</h3> | |
| <input type="text" id="editInput" /> | |
| <div class="edit-modal-buttons"> | |
| <button class="save" id="saveEdit">Save</button> | |
| <button class="cancel" id="cancelEdit">Cancel</button> | |
| </div> | |
| </div> | |
| </div> | |
| <script> | |
| class TodoApp { | |
| constructor() { | |
| this.todos = JSON.parse(localStorage.getItem('todos')) || []; | |
| this.currentFilter = 'all'; | |
| this.editingIndex = -1; | |
| this.init(); | |
| } | |
| init() { | |
| this.bindEvents(); | |
| this.render(); | |
| this.updateStats(); | |
| } | |
| bindEvents() { | |
| document.getElementById('addBtn').addEventListener('click', () => this.addTodo()); | |
| document.getElementById('todoInput').addEventListener('keypress', (e) => { | |
| if (e.key === 'Enter') this.addTodo(); | |
| }); | |
| 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(); | |
| }); | |
| }); | |
| document.getElementById('clearCompleted').addEventListener('click', () => this.clearCompleted()); | |
| document.getElementById('saveEdit').addEventListener('click', () => this.saveEdit()); | |
| document.getElementById('cancelEdit').addEventListener('click', () => this.closeEditModal()); | |
| } | |
| addTodo() { | |
| const input = document.getElementById('todoInput'); | |
| const text = input.value.trim(); | |
| if (text) { | |
| this.todos.push({ | |
| text, | |
| completed: false, | |
| createdAt: new Date().toISOString() | |
| }); | |
| input.value = ''; | |
| this.saveToLocalStorage(); | |
| this.render(); | |
| this.updateStats(); | |
| } | |
| } | |
| toggleTodo(index) { | |
| this.todos[index].completed = !this.todos[index].completed; | |
| this.saveToLocalStorage(); | |
| this.render(); | |
| this.updateStats(); | |
| } | |
| deleteTodo(index) { | |
| this.todos.splice(index, 1); | |
| this.saveToLocalStorage(); | |
| this.render(); | |
| this.updateStats(); | |
| } | |
| editTodo(index) { | |
| this.editingIndex = index; | |
| const todo = this.todos[index]; | |
| document.getElementById('editInput').value = todo.text; | |
| document.getElementById('editModal').classList.add('active'); | |
| } | |
| saveEdit() { | |
| const newText = document.getElementById('editInput').value.trim(); | |
| if (newText && this.editingIndex !== -1) { | |
| this.todos[this.editingIndex].text = newText; | |
| this.saveToLocalStorage(); | |
| this.render(); | |
| this.closeEditModal(); | |
| } | |
| } | |
| closeEditModal() { | |
| document.getElementById('editModal').classList.remove('active'); | |
| this.editingIndex = -1; | |
| } | |
| clearCompleted() { | |
| this.todos = this.todos.filter(todo => !todo.completed); | |
| this.saveToLocalStorage(); | |
| this.render(); | |
| this.updateStats(); | |
| } | |
| getFilteredTodos() { | |
| switch (this.currentFilter) { | |
| case 'active': | |
| return this.todos.filter(todo => !todo.completed); | |
| case 'completed': | |
| return this.todos.filter(todo => todo.completed); | |
| default: | |
| return this.todos; | |
| } | |
| } | |
| render() { | |
| const todoList = document.getElementById('todoList'); | |
| const filteredTodos = this.getFilteredTodos(); | |
| if (filteredTodos.length === 0) { | |
| todoList.innerHTML = ` | |
| <div class="empty-state"> | |
| <i class="fas fa-clipboard-list"></i> | |
| <p>No todos found</p> | |
| <p>Add a new todo to get started!</p> | |
| </div> | |
| `; | |
| return; | |
| } | |
| todoList.innerHTML = filteredTodos.map((todo, index) => { | |
| const actualIndex = this.todos.findIndex(t => t === todo); | |
| return ` | |
| <li class="todo-item ${todo.completed ? 'completed' : ''}"> | |
| <div class="todo-checkbox ${todo.completed ? 'checked' : ''}" | |
| onclick="todoApp.toggleTodo(${actualIndex})"></div> | |
| <span class="todo-text">${this.escapeHtml(todo.text)}</span> | |
| <div class="todo-actions"> | |
| <button class="edit" onclick="todoApp.editTodo(${actualIndex})"> | |
| <i class="fas fa-edit"></i> | |
| </button> | |
| <button class="delete" onclick="todoApp.deleteTodo(${actualIndex})"> | |
| <i class="fas fa-trash"></i> | |
| </button> | |
| </div> | |
| </li> | |
| `; | |
| }).join(''); | |
| } | |
| updateStats() { | |
| const total = this.todos.length; | |
| const completed = this.todos.filter(todo => todo.completed).length; | |
| const active = total - completed; | |
| document.getElementById('totalTodos').textContent = `${total} total`; | |
| document.getElementById('completedTodos').textContent = `${completed} completed`; | |
| document.getElementById('activeTodos').textContent = `${active} active`; | |
| } | |
| saveToLocalStorage() { | |
| localStorage.setItem('todos', JSON.stringify(this.todos)); | |
| } | |
| escapeHtml(text) { | |
| const map = { | |
| '&': '&', | |
| '<': '<', | |
| '>': '>', | |
| '"': '"', | |
| "'": ''' | |
| }; | |
| return text.replace(/[&<>"']/g, m => map[m]); | |
| } | |
| } | |
| const todoApp = new TodoApp(); | |
| </script> | |
| </body> | |
| </html> |