-
-
Save Aakash67/5eace83c62b606890e1f9372a4b4491e to your computer and use it in GitHub Desktop.
Task Tracker by Gemini 2.5
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <!DOCTYPE html> | |
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Task Tracker</title> | |
| <style> | |
| /* Basic Reset & Font */ | |
| * { | |
| margin: 0; | |
| padding: 0; | |
| box-sizing: border-box; | |
| } | |
| body { | |
| font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; | |
| background-color: #f0f2f5; /* Calm background */ | |
| color: #333; | |
| line-height: 1.6; | |
| } | |
| /* App Container - Flexbox for split screen */ | |
| .app-container { | |
| display: flex; | |
| min-height: 100vh; | |
| background-color: #fff; | |
| max-width: 1200px; /* Max width for larger screens */ | |
| margin: 20px auto; /* Center container */ | |
| border-radius: 12px; /* Rounded corners for the whole app */ | |
| box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1); /* Soft shadow */ | |
| overflow: hidden; /* Contain children */ | |
| } | |
| /* Sidebar - Filters */ | |
| .sidebar { | |
| width: 220px; | |
| background-color: #e9ecef; /* Slightly different bg for sidebar */ | |
| padding: 30px 20px; | |
| border-right: 1px solid #dee2e6; | |
| flex-shrink: 0; /* Prevent sidebar from shrinking */ | |
| } | |
| .sidebar h2 { | |
| font-size: 1.4em; | |
| margin-bottom: 25px; | |
| color: #495057; | |
| border-bottom: 2px solid #adb5bd; | |
| padding-bottom: 10px; | |
| } | |
| .filters { | |
| list-style: none; | |
| } | |
| .filters li { | |
| margin-bottom: 15px; | |
| } | |
| .filters button { | |
| background: none; | |
| border: none; | |
| color: #495057; | |
| font-size: 1em; | |
| cursor: pointer; | |
| padding: 8px 12px; | |
| width: 100%; | |
| text-align: left; | |
| border-radius: 6px; | |
| transition: background-color 0.2s ease, color 0.2s ease; | |
| } | |
| .filters button:hover { | |
| background-color: #dee2e6; | |
| } | |
| .filters button.active { | |
| background-color: #007bff; /* Calm primary color */ | |
| color: #fff; | |
| font-weight: bold; | |
| } | |
| /* Main Content Area - Tasks */ | |
| .main-content { | |
| flex-grow: 1; /* Take remaining space */ | |
| padding: 30px 40px; | |
| overflow-y: auto; /* Allow scrolling if content overflows */ | |
| } | |
| .main-content h1 { | |
| font-size: 2em; | |
| margin-bottom: 20px; | |
| color: #343a40; | |
| } | |
| /* Task Input Form */ | |
| #task-form { | |
| display: flex; | |
| margin-bottom: 30px; | |
| gap: 10px; /* Space between input and button */ | |
| } | |
| #task-input { | |
| flex-grow: 1; | |
| padding: 12px 15px; | |
| border: 1px solid #ced4da; | |
| border-radius: 6px; | |
| font-size: 1em; | |
| outline: none; | |
| transition: border-color 0.2s ease, box-shadow 0.2s ease; | |
| } | |
| #task-input:focus { | |
| border-color: #80bdff; | |
| box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25); | |
| } | |
| #add-task-btn { | |
| padding: 12px 20px; | |
| background-color: #28a745; /* Green for add */ | |
| color: white; | |
| border: none; | |
| border-radius: 6px; | |
| font-size: 1em; | |
| cursor: pointer; | |
| transition: background-color 0.2s ease; | |
| white-space: nowrap; /* Prevent button text wrapping */ | |
| } | |
| #add-task-btn:hover { | |
| background-color: #218838; | |
| } | |
| /* Task List */ | |
| #task-list { | |
| list-style: none; | |
| } | |
| .task-item { | |
| display: flex; | |
| align-items: center; | |
| background-color: #ffffff; | |
| padding: 15px; | |
| margin-bottom: 12px; | |
| border-radius: 8px; | |
| box-shadow: 0 2px 5px rgba(0, 0, 0, 0.08); | |
| transition: all 0.3s ease; /* For all transitions */ | |
| opacity: 1; | |
| transform: scale(1); | |
| } | |
| /* Task Item Animation Classes */ | |
| .task-item.entering { | |
| opacity: 0; | |
| transform: scale(0.95); | |
| } | |
| .task-item.leaving { | |
| opacity: 0; | |
| transform: scale(0.95); | |
| height: 0; /* Animate height out - might need adjustments */ | |
| padding-top: 0; | |
| padding-bottom: 0; | |
| margin-bottom: 0; | |
| overflow: hidden; /* Important for height animation */ | |
| border: none; | |
| box-shadow: none; | |
| } | |
| .task-item input[type="checkbox"] { | |
| margin-right: 15px; | |
| cursor: pointer; | |
| width: 18px; /* Custom checkbox size */ | |
| height: 18px; | |
| accent-color: #007bff; /* Color the check */ | |
| } | |
| .task-item .task-text { | |
| flex-grow: 1; | |
| margin-right: 15px; | |
| font-size: 1.05em; | |
| word-break: break-word; /* Prevent long words overflowing */ | |
| } | |
| .task-item.completed .task-text { | |
| text-decoration: line-through; | |
| color: #6c757d; /* Dim completed text */ | |
| font-style: italic; | |
| } | |
| .task-item .task-actions button { | |
| background: none; | |
| border: none; | |
| cursor: pointer; | |
| padding: 5px 8px; | |
| margin-left: 8px; | |
| font-size: 0.9em; | |
| border-radius: 4px; | |
| transition: background-color 0.2s ease; | |
| } | |
| .task-item .task-actions button.edit-btn { | |
| color: #ffc107; /* Yellow/Orange for edit */ | |
| } | |
| .task-item .task-actions button.edit-btn:hover { | |
| background-color: #fff3cd; | |
| } | |
| .task-item .task-actions button.delete-btn { | |
| color: #dc3545; /* Red for delete */ | |
| } | |
| .task-item .task-actions button.delete-btn:hover { | |
| background-color: #f8d7da; | |
| } | |
| /* Helper class for hidden elements */ | |
| .hidden { | |
| display: none; | |
| } | |
| /* Empty state message */ | |
| #empty-message { | |
| text-align: center; | |
| color: #6c757d; | |
| margin-top: 40px; | |
| font-style: italic; | |
| } | |
| /* Responsive Design */ | |
| @media (max-width: 768px) { | |
| .app-container { | |
| flex-direction: column; | |
| margin: 0; | |
| border-radius: 0; | |
| min-height: 100vh; /* Ensure it fills height on mobile */ | |
| } | |
| .sidebar { | |
| width: 100%; | |
| border-right: none; | |
| border-bottom: 1px solid #dee2e6; | |
| padding: 20px; | |
| } | |
| .sidebar h2 { | |
| margin-bottom: 15px; | |
| text-align: center; | |
| } | |
| .filters { | |
| display: flex; | |
| justify-content: center; /* Center filter buttons */ | |
| gap: 10px; | |
| } | |
| .filters li { | |
| margin-bottom: 0; | |
| } | |
| .filters button { | |
| width: auto; /* Fit content */ | |
| } | |
| .main-content { | |
| padding: 20px; | |
| } | |
| .main-content h1 { | |
| font-size: 1.8em; | |
| text-align: center; | |
| } | |
| #task-form { | |
| flex-direction: column; /* Stack input and button */ | |
| } | |
| #add-task-btn { | |
| width: 100%; /* Full width button */ | |
| } | |
| } | |
| @media (max-width: 480px) { | |
| .main-content { | |
| padding: 15px; | |
| } | |
| .main-content h1 { | |
| font-size: 1.6em; | |
| } | |
| .task-item { | |
| padding: 12px; | |
| } | |
| .task-item .task-text { | |
| font-size: 1em; | |
| } | |
| .task-item .task-actions button { | |
| padding: 4px 6px; | |
| font-size: 0.85em; | |
| } | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="app-container"> | |
| <!-- Sidebar --> | |
| <aside class="sidebar"> | |
| <h2>Filters</h2> | |
| <ul class="filters" id="filters"> | |
| <li><button data-filter="all" class="active">All Tasks</button></li> | |
| <li><button data-filter="active">Active</button></li> | |
| <li><button data-filter="completed">Completed</button></li> | |
| </ul> | |
| </aside> | |
| <!-- Main Content --> | |
| <main class="main-content"> | |
| <h1>Task Tracker</h1> | |
| <!-- Task Input Form --> | |
| <form id="task-form"> | |
| <input type="text" id="task-input" placeholder="Add a new task..." aria-label="New task input"> | |
| <button type="submit" id="add-task-btn">Add Task</button> | |
| </form> | |
| <!-- Task List --> | |
| <ul id="task-list"> | |
| <!-- Task items will be dynamically added here --> | |
| </ul> | |
| <p id="empty-message" class="hidden">No tasks here yet!</p> | |
| </main> | |
| </div> | |
| <script> | |
| document.addEventListener('DOMContentLoaded', () => { | |
| const taskForm = document.getElementById('task-form'); | |
| const taskInput = document.getElementById('task-input'); | |
| const taskList = document.getElementById('task-list'); | |
| const filterButtonsContainer = document.getElementById('filters'); | |
| const emptyMessage = document.getElementById('empty-message'); | |
| let tasks = []; | |
| let currentFilter = 'all'; // 'all', 'active', 'completed' | |
| // --- Local Storage --- | |
| function loadTasks() { | |
| const storedTasks = localStorage.getItem('tasks'); | |
| tasks = storedTasks ? JSON.parse(storedTasks) : []; | |
| } | |
| function saveTasks() { | |
| localStorage.setItem('tasks', JSON.stringify(tasks)); | |
| } | |
| // --- Rendering Tasks --- | |
| function renderTasks() { | |
| taskList.innerHTML = ''; // Clear current list | |
| const filteredTasks = tasks.filter(task => { | |
| if (currentFilter === 'active') return !task.completed; | |
| if (currentFilter === 'completed') return task.completed; | |
| return true; // 'all' filter | |
| }); | |
| if (filteredTasks.length === 0) { | |
| emptyMessage.classList.remove('hidden'); | |
| } else { | |
| emptyMessage.classList.add('hidden'); | |
| } | |
| filteredTasks.forEach(task => { | |
| const li = document.createElement('li'); | |
| li.className = `task-item ${task.completed ? 'completed' : ''}`; | |
| li.dataset.id = task.id; | |
| // Add 'entering' class for animation, remove after timeout | |
| li.classList.add('entering'); | |
| li.innerHTML = ` | |
| <input type="checkbox" ${task.completed ? 'checked' : ''} aria-label="Complete task"> | |
| <span class="task-text">${escapeHTML(task.text)}</span> | |
| <div class="task-actions"> | |
| <button class="edit-btn" aria-label="Edit task">Edit</button> | |
| <button class="delete-btn" aria-label="Delete task">Delete</button> | |
| </div> | |
| `; | |
| taskList.appendChild(li); | |
| // Remove 'entering' class after animation duration allows fade-in | |
| setTimeout(() => { | |
| if(li.classList.contains('entering')){ // Check if still exists | |
| li.classList.remove('entering'); | |
| } | |
| }, 10); // Small delay to allow initial render then transition | |
| }); | |
| } | |
| // --- Task Management --- | |
| function addTask(text) { | |
| if (text.trim() === '') return; // Don't add empty tasks | |
| const newTask = { | |
| id: Date.now().toString(), // Simple unique ID | |
| text: text.trim(), | |
| completed: false | |
| }; | |
| tasks.push(newTask); | |
| saveTasks(); | |
| renderTasks(); // Re-render with the new task | |
| taskInput.value = ''; // Clear input | |
| taskInput.focus(); // Keep focus on input | |
| } | |
| function toggleComplete(id) { | |
| tasks = tasks.map(task => | |
| task.id === id ? { ...task, completed: !task.completed } : task | |
| ); | |
| saveTasks(); | |
| renderTasks(); // Re-render to update style and filter potentially | |
| } | |
| function deleteTask(id) { | |
| const taskElement = taskList.querySelector(`[data-id="${id}"]`); | |
| // Add leaving animation class | |
| if (taskElement) { | |
| taskElement.classList.add('leaving'); | |
| // Wait for animation to finish before removing from DOM and data | |
| taskElement.addEventListener('transitionend', () => { | |
| tasks = tasks.filter(task => task.id !== id); | |
| saveTasks(); | |
| renderTasks(); // Re-render the list | |
| }, { once: true }); // Ensure listener runs only once | |
| // Fallback if transitionend doesn't fire (e.g., element removed before transition) | |
| setTimeout(() => { | |
| if (document.body.contains(taskElement)) { // Check if element still exists | |
| tasks = tasks.filter(task => task.id !== id); | |
| saveTasks(); | |
| renderTasks(); | |
| } | |
| }, 400); // Slightly longer than CSS transition duration | |
| } else { | |
| // If element not found (rare case), just update data and re-render | |
| tasks = tasks.filter(task => task.id !== id); | |
| saveTasks(); | |
| renderTasks(); | |
| } | |
| } | |
| function editTask(id) { | |
| const task = tasks.find(task => task.id === id); | |
| if (!task) return; | |
| const newText = prompt('Edit task:', task.text); | |
| if (newText !== null && newText.trim() !== '') { | |
| tasks = tasks.map(t => | |
| t.id === id ? { ...t, text: newText.trim() } : t | |
| ); | |
| saveTasks(); | |
| renderTasks(); | |
| } else if (newText !== null && newText.trim() === '') { | |
| alert("Task text cannot be empty. Edit cancelled."); | |
| } | |
| // If newText is null (user cancelled), do nothing. | |
| } | |
| // --- Event Listeners --- | |
| taskForm.addEventListener('submit', (e) => { | |
| e.preventDefault(); // Prevent page reload | |
| addTask(taskInput.value); | |
| }); | |
| taskList.addEventListener('click', (e) => { | |
| const target = e.target; | |
| const taskItem = target.closest('.task-item'); | |
| if (!taskItem) return; // Click was not inside a task item | |
| const taskId = taskItem.dataset.id; | |
| if (target.type === 'checkbox') { | |
| toggleComplete(taskId); | |
| } else if (target.classList.contains('edit-btn')) { | |
| editTask(taskId); | |
| } else if (target.classList.contains('delete-btn')) { | |
| // Optional: Add a confirmation dialog | |
| // if (confirm('Are you sure you want to delete this task?')) { | |
| deleteTask(taskId); | |
| // } | |
| } | |
| }); | |
| filterButtonsContainer.addEventListener('click', (e) => { | |
| if (e.target.tagName === 'BUTTON') { | |
| const filterValue = e.target.dataset.filter; | |
| if (filterValue === currentFilter) return; // Do nothing if already active | |
| currentFilter = filterValue; | |
| // Update active class on buttons | |
| filterButtonsContainer.querySelectorAll('button').forEach(btn => { | |
| btn.classList.remove('active'); | |
| }); | |
| e.target.classList.add('active'); | |
| renderTasks(); // Re-render with the new filter | |
| } | |
| }); | |
| // --- Utility --- | |
| function escapeHTML(str) { | |
| // Basic escaping to prevent simple HTML injection | |
| return str.replace(/</g, "<").replace(/>/g, ">"); | |
| } | |
| // --- Initial Load --- | |
| loadTasks(); | |
| renderTasks(); | |
| }); | |
| </script> | |
| </body> | |
| </html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment