Todo App

Stay organized and productive

0 items left
=== assets/css/styles.css === /* Reset and base styles */ * { margin: 0; padding: 0; box-sizing: border-box; } body { font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; line-height: 1.6; color: #2d3748; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); min-height: 100vh; padding: 20px; } .container { max-width: 600px; margin: 0 auto; background: white; border-radius: 16px; box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1); overflow: hidden; min-height: 80vh; display: flex; flex-direction: column; } /* Header */ .header { background: linear-gradient(135deg, #4f46e5 0%, #7c3aed 100%); color: white; text-align: center; padding: 40px 30px 30px; } .header h1 { font-size: 2.5rem; font-weight: 600; margin-bottom: 8px; letter-spacing: -0.025em; } .tagline { font-size: 1.1rem; opacity: 0.9; font-weight: 300; } /* Main content */ .main { flex: 1; padding: 30px; } /* Todo input section */ .todo-input-section { margin-bottom: 30px; } .input-container { display: flex; gap: 10px; background: #f7fafc; border: 2px solid #e2e8f0; border-radius: 12px; padding: 4px; transition: all 0.3s ease; } .input-container:focus-within { border-color: #4f46e5; background: white; box-shadow: 0 0 0 3px rgba(79, 70, 229, 0.1); } .todo-input { flex: 1; border: none; outline: none; padding: 16px 20px; font-size: 1rem; background: transparent; color: #2d3748; } .todo-input::placeholder { color: #a0aec0; } .add-btn { background: #4f46e5; color: white; border: none; border-radius: 10px; padding: 12px; cursor: pointer; transition: all 0.3s ease; display: flex; align-items: center; justify-content: center; min-width: 48px; height: 48px; } .add-btn:hover { background: #4338ca; transform: translateY(-1px); box-shadow: 0 4px 12px rgba(79, 70, 229, 0.3); } .add-btn:active { transform: translateY(0); } /* Filters */ .todo-filters { display: flex; gap: 8px; margin-bottom: 20px; justify-content: center; flex-wrap: wrap; } .filter-btn { background: transparent; border: 1px solid #e2e8f0; color: #4a5568; padding: 8px 16px; border-radius: 8px; cursor: pointer; transition: all 0.2s ease; font-size: 0.9rem; font-weight: 500; } .filter-btn:hover { background: #f7fafc; border-color: #cbd5e0; } .filter-btn.active { background: #4f46e5; color: white; border-color: #4f46e5; } /* Stats */ .todo-stats { display: flex; justify-content: space-between; align-items: center; margin-bottom: 25px; font-size: 0.9rem; color: #4a5568; } #itemsLeft { font-weight: 500; } .clear-btn { background: transparent; border: none; color: #e53e3e; cursor: pointer; font-size: 0.9rem; padding: 4px 8px; border-radius: 4px; transition: all 0.2s ease; } .clear-btn:hover { background: #fed7d7; color: #c53030; } /* Todo list */ .todo-list { list-style: none; margin-bottom: 30px; } .todo-item { background: white; border: 1px solid #e2e8f0; border-radius: 12px; margin-bottom: 12px; padding: 20px; transition: all 0.3s ease; animation: slideIn 0.3s ease; } .todo-item:hover { box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05); transform: translateY(-1px); } .todo-item.completed { background: #f7fafc; opacity: 0.7; } .todo-item.completed .todo-text { text-decoration: line-through; color: #718096; } .todo-content { display: flex; align-items: center; gap: 16px; } .todo-checkbox { width: 20px; height: 20px; border: 2px solid #cbd5e0; border-radius: 4px; cursor: pointer; display: flex; align-items: center; justify-content: center; transition: all 0.2s ease; flex-shrink: 0; } .todo-checkbox:hover { border-color: #4f46e5; } .todo-checkbox.checked { background: #4f46e5; border-color: #4f46e5; } .todo-checkbox.checked::after { content: '✓'; color: white; font-size: 14px; font-weight: bold; } .todo-text { flex: 1; font-size: 1rem; color: #2d3748; word-wrap: break-word; } .todo-actions { display: flex; gap: 8px; } .action-btn { background: transparent; border: none; cursor: pointer; padding: 6px; border-radius: 6px; color: #718096; transition: all 0.2s ease; display: flex; align-items: center; justify-content: center; } .action-btn:hover { background: #f7fafc; color: #4a5568; } .action-btn.delete:hover { background: #fed7d7; color: #e53e3e; } .action-btn svg { width: 16px; height: 16px; } /* Empty state */ .empty-state { text-align: center; padding: 60px 20px; color: #718096; } .empty-icon { margin-bottom: 20px; opacity: 0.6; } .empty-state h3 { font-size: 1.5rem; margin-bottom: 8px; color: #4a5568; } .empty-state p { font-size: 1rem; } /* Footer */ .footer { background: #f7fafc; padding: 20px 30px; text-align: center; border-top: 1px solid #e2e8f0; color: #4a5568; font-size: 0.9rem; } .footer a { color: #4f46e5; text-decoration: none; font-weight: 500; } .footer a:hover { text-decoration: underline; } /* Animations */ @keyframes slideIn { from { opacity: 0; transform: translateY(-10px); } to { opacity: 1; transform: translateY(0); } } @keyframes fadeOut { from { opacity: 1; transform: scale(1); } to { opacity: 0; transform: scale(0.95); } } .todo-item.removing { animation: fadeOut 0.3s ease forwards; } /* Responsive design */ @media (max-width: 768px) { body { padding: 10px; } .container { border-radius: 12px; } .header { padding: 30px 20px 20px; } .header h1 { font-size: 2rem; } .main { padding: 20px; } .todo-stats { flex-direction: column; gap: 12px; align-items: stretch; } .filter-btn { flex: 1; text-align: center; } } @media (max-width: 480px) { .header h1 { font-size: 1.8rem; } .tagline { font-size: 1rem; } .todo-item { padding: 16px; } .todo-content { gap: 12px; } .action-btn { padding: 4px; } } /* Focus styles for accessibility */ button:focus, input:focus { outline: 2px solid #4f46e5; outline-offset: 2px; } /* High contrast mode support */ @media (prefers-contrast: high) { .container { border: 2px solid #000; } .todo-item { border: 2px solid #000; } } /* Reduced motion support */ @media (prefers-reduced-motion: reduce) { * { animation-duration: 0.01ms !important; animation-iteration-count: 1 !important; transition-duration: 0.01ms !important; } } === assets/js/app.js === class TodoApp { constructor() { this.todos = JSON.parse(localStorage.getItem('todos')) || []; this.currentFilter = 'all'; this.init(); } init() { this.bindEvents(); this.render(); } bindEvents() { // Add todo const todoInput = document.getElementById('todoInput'); const addBtn = document.getElementById('addTodoBtn'); todoInput.addEventListener('keypress', (e) => { if (e.key === 'Enter') { this.addTodo(); } }); addBtn.addEventListener('click', () => { this.addTodo(); }); // Filters const filterBtns = document.querySelectorAll('.filter-btn'); filterBtns.forEach(btn => { btn.addEventListener('click', () => { this.setFilter(btn.dataset.filter); }); }); // Clear completed const clearCompleted = document.getElementById('clearCompleted'); clearCompleted.addEventListener('click', () => { this.clearCompleted(); }); // Todo list events (delegated) const todoList = document.getElementById('todoList'); todoList.addEventListener('click', (e) => { const todoItem = e.target.closest('.todo-item'); if (!todoItem) return; const todoId = parseInt(todoItem.dataset.id); if (e.target.classList.contains('todo-checkbox')) { this.toggleTodo(todoId); } else if (e.target.closest('.delete')) { this.deleteTodo(todoId); } }); } addTodo() { const todoInput = document.getElementById('todoInput'); const text = todoInput.value.trim(); if (!text) return; const todo = { id: Date.now(), text: text, completed: false, createdAt: new Date().toISOString() }; this.todos.unshift(todo); this.saveTodos(); this.render(); todoInput.value = ''; todoInput.focus(); } toggleTodo(id) { const todo = this.todos.find(t => t.id === id); if (todo) { todo.completed = !todo.completed; this.saveTodos(); this.render(); } } deleteTodo(id) { const todoItem = document.querySelector(`[data-id="${id}"]`); if (todoItem) { todoItem.classList.add('removing'); setTimeout(() => { this.todos = this.todos.filter(t => t.id !== id); this.saveTodos(); this.render(); }, 300); } else { this.todos = this.todos.filter(t => t.id !== id); this.saveTodos(); this.render(); } } setFilter(filter) { this.currentFilter = filter; this.render(); // Update active filter button document.querySelectorAll('.filter-btn').forEach(btn => { btn.classList.remove('active'); }); document.querySelector(`[data-filter="${filter}"]`).classList.add('active'); } 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; } } clearCompleted() { this.todos = this.todos.filter(todo => !todo.completed); this.saveTodos(); this.render(); } getItemsLeft() { return this.todos.filter(todo => !todo.completed).length; } saveTodos() { localStorage.setItem('todos', JSON.stringify(this.todos)); } render() { const todoList = document.getElementById('todoList'); const itemsLeft = document.getElementById('itemsLeft'); const emptyState = document.getElementById('emptyState'); const clearCompletedBtn = document.getElementById('clearCompleted'); const filteredTodos = this.getFilteredTodos(); // Update items left counter const leftCount = this.getItemsLeft(); itemsLeft.textContent = `${leftCount} item${leftCount !== 1 ? 's' : ''} left`; // Show/hide clear completed button const completedCount = this.todos.filter(todo => todo.completed).length; clearCompletedBtn.style.display = completedCount > 0 ? 'block' : 'none'; // Show/hide empty state if (this.todos.length === 0) { emptyState.style.display = 'block'; todoList.style.display = 'none'; } else { emptyState.style.display = 'none'; todoList.style.display = 'block'; } // Render todos if (filteredTodos.length === 0 && this.todos.length > 0) { todoList.innerHTML = `
  • No ${this.currentFilter} todos found

  • `; return; } todoList.innerHTML = filteredTodos.map(todo => `
  • ${this.escapeHtml(todo.text)}
  • `).join(''); } escapeHtml(text) { const div = document.createElement('div'); div.textContent = text; return div.innerHTML; } } // Initialize the app when DOM is loaded document.addEventListener('DOMContentLoaded', () => { new TodoApp(); }); // Add some keyboard shortcuts document.addEventListener('keydown', (e) => { // Ctrl/Cmd + / to focus input if ((e.ctrlKey || e.metaKey) && e.key === '/') { e.preventDefault(); document.getElementById('todoInput').focus(); } // Escape to clear input if (e.key === 'Escape') { const input = document.getElementById('todoInput'); if (input.value) { input.value = ''; } } });