shivam2104's picture
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Monthly Tracker</title> <script src="https://cdn.tailwindcss.com"></script> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> <style> /* Custom scrollbar */ ::-webkit-scrollbar { width: 8px; height: 8px; } ::-webkit-scrollbar-track { background: #f1f1f1; } ::-webkit-scrollbar-thumb { background: #888; border-radius: 4px; } ::-webkit-scrollbar-thumb:hover { background: #555; } /* Animation for checkmark */ @keyframes checkmark { 0% { transform: scale(0); opacity: 0; } 80% { transform: scale(1.2); opacity: 1; } 100% { transform: scale(1); opacity: 1; } } .checkmark-animate { animation: checkmark 0.3s ease-out; } /* Pulse animation for streaks */ @keyframes pulse { 0% { transform: scale(1); } 50% { transform: scale(1.05); } 100% { transform: scale(1); } } .pulse-animate { animation: pulse 1.5s infinite; } </style> </head> <body class="bg-gray-50 min-h-screen"> <!-- Login Modal --> <div id="login-modal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50"> <div class="bg-white rounded-lg shadow-xl p-6 w-full max-w-md"> <h2 class="text-2xl font-bold text-gray-800 mb-6 text-center">Monthly Tracker Login</h2> <form id="login-form" class="space-y-4"> <div> <label for="username" class="block text-sm font-medium text-gray-700 mb-1">Username</label> <input type="text" id="username" class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-indigo-500" placeholder="Enter your username" required> </div> <div> <label for="password" class="block text-sm font-medium text-gray-700 mb-1">Password</label> <input type="password" id="password" class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-indigo-500" placeholder="Enter your password" required> </div> <div class="pt-2"> <button type="submit" class="w-full bg-indigo-600 hover:bg-indigo-700 text-white py-2 px-4 rounded-md transition-colors"> Login / Register </button> </div> </form> </div> </div> <div id="app-content" class="container mx-auto px-4 py-8 max-w-6xl hidden"> <!-- Header --> <header class="flex flex-col md:flex-row justify-between items-center mb-8 gap-4"> <div> <h1 class="text-3xl font-bold text-indigo-700">Monthly Tracker</h1> <p class="text-gray-600">Track your habits and goals</p> </div> <div class="flex items-center gap-4"> <div class="relative"> <select id="month-select" class="appearance-none bg-white border border-gray-300 rounded-md py-2 pl-4 pr-8 text-gray-700 focus:outline-none focus:ring-2 focus:ring-indigo-500"> <!-- Months will be populated by JavaScript --> </select> <div class="pointer-events-none absolute inset-y-0 right-0 flex items-center px-2 text-gray-700"> <i class="fas fa-chevron-down"></i> </div> </div> <button id="add-tracker-btn" class="bg-indigo-600 hover:bg-indigo-700 text-white px-4 py-2 rounded-md flex items-center gap-2 transition-colors"> <i class="fas fa-plus"></i> <span>Add Tracker</span> </button> </div> </header> <!-- Stats Summary --> <div class="grid grid-cols-1 md:grid-cols-3 gap-4 mb-8"> <div class="bg-white p-6 rounded-lg shadow-md border-l-4 border-indigo-500"> <h3 class="text-gray-500 font-medium">Current Streak</h3> <p class="text-3xl font-bold text-indigo-700">0 days</p> </div> <div class="bg-white p-6 rounded-lg shadow-md border-l-4 border-green-500"> <h3 class="text-gray-500 font-medium">Completed</h3> <p class="text-3xl font-bold text-green-700">0%</p> </div> <div class="bg-white p-6 rounded-lg shadow-md border-l-4 border-blue-500"> <h3 class="text-gray-500 font-medium">Trackers</h3> <p class="text-3xl font-bold text-blue-700">0</p> </div> </div> <!-- Trackers Container --> <div id="trackers-container" class="space-y-6"> <!-- Trackers will be added here dynamically --> <div class="text-center py-12 bg-white rounded-lg shadow-sm border border-dashed border-gray-300"> <i class="fas fa-calendar-alt text-4xl text-gray-300 mb-4"></i> <h3 class="text-xl text-gray-500 font-medium">No trackers yet</h3> <p class="text-gray-400">Click "Add Tracker" to get started</p> </div> </div> <!-- Add Tracker Modal --> <div id="add-tracker-modal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 hidden"> <div class="bg-white rounded-lg shadow-xl p-6 w-full max-w-md"> <div class="flex justify-between items-center mb-4"> <h3 class="text-xl font-bold text-gray-800">Add New Tracker</h3> <button id="close-modal" class="text-gray-500 hover:text-gray-700"> <i class="fas fa-times"></i> </button> </div> <form id="tracker-form" class="space-y-4"> <div> <label for="tracker-name" class="block text-sm font-medium text-gray-700 mb-1">Tracker Name</label> <input type="text" id="tracker-name" class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-indigo-500" placeholder="e.g. Exercise, Reading" required> </div> <div> <label for="tracker-icon" class="block text-sm font-medium text-gray-700 mb-1">Icon</label> <div class="grid grid-cols-6 gap-2"> <button type="button" class="icon-option p-2 border rounded-md hover:bg-gray-100 focus:outline-none focus:ring-2 focus:ring-indigo-500" data-icon="dumbbell"> <i class="fas fa-dumbbell text-lg"></i> </button> <button type="button" class="icon-option p-2 border rounded-md hover:bg-gray-100 focus:outline-none focus:ring-2 focus:ring-indigo-500" data-icon="book"> <i class="fas fa-book text-lg"></i> </button> <button type="button" class="icon-option p-2 border rounded-md hover:bg-gray-100 focus:outline-none focus:ring-2 focus:ring-indigo-500" data-icon="running"> <i class="fas fa-running text-lg"></i> </button> <button type="button" class="icon-option p-2 border rounded-md hover:bg-gray-100 focus:outline-none focus:ring-2 focus:ring-indigo-500" data-icon="medal"> <i class="fas fa-medal text-lg"></i> </button> <button type="button" class="icon-option p-2 border rounded-md hover:bg-gray-100 focus:outline-none focus:ring-2 focus:ring-indigo-500" data-icon="water"> <i class="fas fa-tint text-lg"></i> </button> <button type="button" class="icon-option p-2 border rounded-md hover:bg-gray-100 focus:outline-none focus:ring-2 focus:ring-indigo-500" data-icon="moon"> <i class="fas fa-moon text-lg"></i> </button> </div> <input type="hidden" id="tracker-icon" value="dumbbell"> </div> <div> <label for="tracker-color" class="block text-sm font-medium text-gray-700 mb-1">Color</label> <div class="flex gap-2"> <button type="button" class="color-option w-8 h-8 rounded-full bg-indigo-500 border-2 border-white focus:outline-none focus:ring-2 focus:ring-gray-400" data-color="indigo"></button> <button type="button" class="color-option w-8 h-8 rounded-full bg-green-500 border-2 border-white focus:outline-none focus:ring-2 focus:ring-gray-400" data-color="green"></button> <button type="button" class="color-option w-8 h-8 rounded-full bg-red-500 border-2 border-white focus:outline-none focus:ring-2 focus:ring-gray-400" data-color="red"></button> <button type="button" class="color-option w-8 h-8 rounded-full bg-yellow-500 border-2 border-white focus:outline-none focus:ring-2 focus:ring-gray-400" data-color="yellow"></button> <button type="button" class="color-option w-8 h-8 rounded-full bg-purple-500 border-2 border-white focus:outline-none focus:ring-2 focus:ring-gray-400" data-color="purple"></button> <button type="button" class="color-option w-8 h-8 rounded-full bg-pink-500 border-2 border-white focus:outline-none focus:ring-2 focus:ring-gray-400" data-color="pink"></button> </div> <input type="hidden" id="tracker-color" value="indigo"> </div> <div class="pt-2"> <button type="submit" class="w-full bg-indigo-600 hover:bg-indigo-700 text-white py-2 px-4 rounded-md transition-colors"> Create Tracker </button> </div> </form> </div> </div> </div> <script> document.addEventListener('DOMContentLoaded', function() { // Initialize the app const loginModal = document.getElementById('login-modal'); const loginForm = document.getElementById('login-form'); const appContent = document.getElementById('app-content'); const trackersContainer = document.getElementById('trackers-container'); let currentUser = null; let users = JSON.parse(localStorage.getItem('monthlyTrackerUsers')) || {}; const addTrackerBtn = document.getElementById('add-tracker-btn'); const addTrackerModal = document.getElementById('add-tracker-modal'); const closeModalBtn = document.getElementById('close-modal'); const trackerForm = document.getElementById('tracker-form'); const monthSelect = document.getElementById('month-select'); // Handle login form submission loginForm.addEventListener('submit', (e) => { e.preventDefault(); const username = document.getElementById('username').value.trim(); const password = document.getElementById('password').value.trim(); if (!username) return; // Simple "authentication" - in a real app you'd want proper hashing if (!users[username]) { // New user registration users[username] = { password, trackers: [] }; showNotification(`Welcome new user ${username}!`); } else if (users[username].password !== password) { showNotification('Invalid password', 'error'); return; } // Login successful currentUser = username; localStorage.setItem('monthlyTrackerUsers', JSON.stringify(users)); loginModal.classList.add('hidden'); appContent.classList.remove('hidden'); loadTrackers(); }); let trackers = []; let currentMonth = new Date().getMonth(); let currentYear = new Date().getFullYear(); // Populate months dropdown const months = [ 'January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December' ]; months.forEach((month, index) => { const option = document.createElement('option'); option.value = index; option.textContent = month; if (index === currentMonth) { option.selected = true; } monthSelect.appendChild(option); }); // Load trackers for current month loadTrackers(); // Event listeners addTrackerBtn.addEventListener('click', () => { addTrackerModal.classList.remove('hidden'); }); closeModalBtn.addEventListener('click', () => { addTrackerModal.classList.add('hidden'); }); // Close modal when clicking outside addTrackerModal.addEventListener('click', (e) => { if (e.target === addTrackerModal) { addTrackerModal.classList.add('hidden'); } }); // Icon selection document.querySelectorAll('.icon-option').forEach(btn => { btn.addEventListener('click', () => { document.querySelectorAll('.icon-option').forEach(b => { b.classList.remove('border-indigo-500', 'bg-indigo-50'); }); btn.classList.add('border-indigo-500', 'bg-indigo-50'); document.getElementById('tracker-icon').value = btn.dataset.icon; }); }); // Color selection document.querySelectorAll('.color-option').forEach(btn => { btn.addEventListener('click', () => { document.querySelectorAll('.color-option').forEach(b => { b.classList.remove('ring-2', 'ring-offset-2', 'ring-gray-400'); }); btn.classList.add('ring-2', 'ring-offset-2', 'ring-gray-400'); document.getElementById('tracker-color').value = btn.dataset.color; }); }); // Set first icon and color as selected by default document.querySelector('.icon-option').classList.add('border-indigo-500', 'bg-indigo-50'); document.querySelector('.color-option').classList.add('ring-2', 'ring-offset-2', 'ring-gray-400'); // Form submission trackerForm.addEventListener('submit', (e) => { e.preventDefault(); const name = document.getElementById('tracker-name').value; const icon = document.getElementById('tracker-icon').value; const color = document.getElementById('tracker-color').value; const newTracker = { id: Date.now(), name, icon, color, month: currentMonth, year: currentYear, days: Array(getDaysInMonth(currentMonth + 1, currentYear)).fill(false) }; trackers.push(newTracker); saveTrackers(); loadTrackers(); // Reset form and close modal trackerForm.reset(); addTrackerModal.classList.add('hidden'); // Show success message showNotification(`Tracker "${name}" added successfully!`); }); // Month selection change monthSelect.addEventListener('change', () => { currentMonth = parseInt(monthSelect.value); currentYear = new Date().getFullYear(); // You could add year selection too loadTrackers(); }); // Functions function loadTrackers() { if (!currentUser) return; trackers = users[currentUser].trackers || []; const monthTrackers = trackers.filter(tracker => tracker.month === currentMonth && tracker.year === currentYear ); if (monthTrackers.length === 0) { trackersContainer.innerHTML = ` <div class="text-center py-12 bg-white rounded-lg shadow-sm border border-dashed border-gray-300"> <i class="fas fa-calendar-alt text-4xl text-gray-300 mb-4"></i> <h3 class="text-xl text-gray-500 font-medium">No trackers for ${months[currentMonth]}</h3> <p class="text-gray-400">Click "Add Tracker" to get started</p> </div> `; updateStats(); return; } trackersContainer.innerHTML = ''; monthTrackers.forEach(tracker => { const trackerElement = document.createElement('div'); trackerElement.className = 'bg-white rounded-lg shadow-md overflow-hidden'; trackerElement.innerHTML = ` <div class="flex items-center justify-between p-4 border-b" style="background-color: ${getColorShade(tracker.color, 50)}"> <div class="flex items-center gap-3"> <div class="w-10 h-10 rounded-full flex items-center justify-center text-white" style="background-color: ${getColorShade(tracker.color, 500)}"> <i class="fas fa-${tracker.icon}"></i> </div> <h3 class="font-bold text-lg">${tracker.name}</h3> </div> <button class="delete-tracker text-gray-500 hover:text-red-500" data-id="${tracker.id}"> <i class="fas fa-trash"></i> </button> </div> <div class="p-4"> <div class="grid grid-cols-7 gap-2 mb-4"> ${['S', 'M', 'T', 'W', 'T', 'F', 'S'].map(day => ` <div class="text-center text-sm font-medium text-gray-500">${day}</div> `).join('')} </div> <div class="grid grid-cols-7 gap-2" id="days-${tracker.id}"> ${tracker.days.map((completed, index) => { const day = index + 1; const today = new Date(); const isToday = today.getDate() === day && today.getMonth() === tracker.month && today.getFullYear() === tracker.year; return ` <button class="day-cell w-full h-10 rounded-md flex items-center justify-center ${completed ? `bg-${tracker.color}-500 text-white` : 'bg-gray-100 hover:bg-gray-200'} ${isToday ? 'ring-2 ring-offset-1 ring-' + tracker.color + '-400' : ''} transition-colors" data-id="${tracker.id}" data-day="${index}"> ${day} </button> `; }).join('')} </div> <div class="mt-4 flex justify-between items-center"> <div class="text-sm text-gray-500"> <span class="font-medium">${getCompletedCount(tracker.days)}</span> of ${tracker.days.length} days </div> <div class="text-sm ${getStreakClass(tracker)}"> <span class="font-medium">${getCurrentStreak(tracker.days)}</span> day streak </div> </div> </div> `; trackersContainer.appendChild(trackerElement); }); // Add event listeners to day cells document.querySelectorAll('.day-cell').forEach(cell => { cell.addEventListener('click', function() { const trackerId = parseInt(this.dataset.id); const dayIndex = parseInt(this.dataset.day); const tracker = trackers.find(t => t.id === trackerId); if (tracker) { tracker.days[dayIndex] = !tracker.days[dayIndex]; saveTrackers(); loadTrackers(); // Add animation to the clicked cell if (tracker.days[dayIndex]) { const checkmark = document.createElement('div'); checkmark.className = 'checkmark-animate absolute'; checkmark.innerHTML = '<i class="fas fa-check"></i>'; this.appendChild(checkmark); setTimeout(() => { checkmark.remove(); }, 500); } } }); }); // Add event listeners to delete buttons document.querySelectorAll('.delete-tracker').forEach(btn => { btn.addEventListener('click', function(e) { e.stopPropagation(); const trackerId = parseInt(this.dataset.id); if (confirm('Are you sure you want to delete this tracker?')) { trackers = trackers.filter(t => t.id !== trackerId); saveTrackers(); loadTrackers(); showNotification('Tracker deleted successfully!'); } }); }); updateStats(); } function saveTrackers() { if (!currentUser) return; users[currentUser].trackers = trackers; localStorage.setItem('monthlyTrackerUsers', JSON.stringify(users)); } function getDaysInMonth(month, year) { return new Date(year, month, 0).getDate(); } function getCompletedCount(days) { return days.filter(day => day).length; } function getCurrentStreak(days) { const today = new Date().getDate(); let streak = 0; for (let i = today - 1; i >= 0; i--) { if (days[i]) { streak++; } else { break; } } return streak; } function getStreakClass(tracker) { const streak = getCurrentStreak(tracker.days); if (streak >= 7) return 'text-green-600 pulse-animate'; if (streak >= 3) return 'text-yellow-600'; return 'text-gray-500'; } function getColorShade(color, shade) { // This is a workaround since we can't use dynamic Tailwind classes in JS const colorMap = { indigo: { 50: '#eef2ff', 500: '#6366f1' }, green: { 50: '#f0fdf4', 500: '#22c55e' }, red: { 50: '#fef2f2', 500: '#ef4444' }, yellow: { 50: '#fefce8', 500: '#eab308' }, purple: { 50: '#f5f3ff', 500: '#a855f7' }, pink: { 50: '#fdf2f8', 500: '#ec4899' } }; return colorMap[color][shade]; } function updateStats() { const monthTrackers = trackers.filter(tracker => tracker.month === currentMonth && tracker.year === currentYear ); // Update trackers count document.querySelectorAll('.bg-blue-500 + p').forEach(el => { el.textContent = monthTrackers.length; }); if (monthTrackers.length === 0) { document.querySelectorAll('.bg-indigo-500 + p').forEach(el => { el.textContent = '0 days'; }); document.querySelectorAll('.bg-green-500 + p').forEach(el => { el.textContent = '0%'; }); return; } // Calculate overall completion percentage let totalDays = 0; let completedDays = 0; monthTrackers.forEach(tracker => { totalDays += tracker.days.length; completedDays += getCompletedCount(tracker.days); }); const completionPercentage = Math.round((completedDays / totalDays) * 100); document.querySelectorAll('.bg-green-500 + p').forEach(el => { el.textContent = `${completionPercentage}%`; }); // Calculate best streak let bestStreak = 0; monthTrackers.forEach(tracker => { const streak = getCurrentStreak(tracker.days); if (streak > bestStreak) { bestStreak = streak; } }); document.querySelectorAll('.bg-indigo-500 + p').forEach(el => { el.textContent = `${bestStreak} days`; }); } function showNotification(message, type = 'success') { const notification = document.createElement('div'); const bgColor = type === 'error' ? 'bg-red-500' : 'bg-green-500'; notification.className = `fixed bottom-4 right-4 ${bgColor} text-white px-4 py-2 rounded-md shadow-lg flex items-center gap-2 animate-fade-in`; notification.innerHTML = ` <i class="fas fa-check-circle"></i> <span>${message}</span> `; document.body.appendChild(notification); setTimeout(() => { notification.classList.add('animate-fade-out'); setTimeout(() => { notification.remove(); }, 300); }, 3000); } }); </script> </body> </html> - Follow Up Deployment
e67458b verified