Skip to content

Instantly share code, notes, and snippets.

@Aakash67
Created March 27, 2025 16:56
Show Gist options
  • Select an option

  • Save Aakash67/5eace83c62b606890e1f9372a4b4491e to your computer and use it in GitHub Desktop.

Select an option

Save Aakash67/5eace83c62b606890e1f9372a4b4491e to your computer and use it in GitHub Desktop.
Task Tracker by Gemini 2.5
<!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