Habit Streak Tracker

Track your daily habits and build consistent streaks! Click "Done for Today" to increment, or "Reset Streak" if you missed a day.

No habits added yet. Start building your streaks!

No habits added yet. Start building your streaks!

'; downloadPdfButton.style.display = 'none'; return; } habits.forEach(habit => { const habitItem = document.createElement('div'); habitItem.className = 'habit-item'; habitItem.dataset.id = habit.id; const habitName = document.createElement('span'); habitName.className = 'habit-name'; habitName.textContent = habit.name; const streakDisplay = document.createElement('span'); streakDisplay.className = 'streak-display'; streakDisplay.innerHTML = `${habit.streakCount} days`; const controls = document.createElement('div'); controls.className = 'habit-controls'; const doneButton = document.createElement('button'); doneButton.className = 'control-button done-button'; doneButton.textContent = 'Done for Today'; doneButton.addEventListener('click', () => incrementStreak(habit.id)); const resetButton = document.createElement('button'); resetButton.className = 'control-button reset-button'; resetButton.textContent = 'Reset Streak'; resetButton.addEventListener('click', () => resetStreak(habit.id)); const deleteButton = document.createElement('button'); deleteButton.className = 'control-button delete-button'; deleteButton.textContent = 'Delete'; deleteButton.addEventListener('click', () => deleteHabit(habit.id)); controls.appendChild(doneButton); controls.appendChild(resetButton); controls.appendChild(deleteButton); habitItem.appendChild(habitName); habitItem.appendChild(streakDisplay); habitItem.appendChild(controls); habitList.appendChild(habitItem); }); downloadPdfButton.style.display = 'block'; // Show PDF button if there are habits } /** * Adds a new habit. */ function addHabit() { const name = newHabitInput.value.trim(); if (name === '') { showMessageBox("Habit name cannot be empty."); return; } if (habits.some(h => h.name.toLowerCase() === name.toLowerCase())) { showMessageBox("A habit with this name already exists."); return; } const newHabit = { id: generateUniqueId(), name: name, streakCount: 0, lastUpdatedDate: null // YYYY-MM-DD string }; habits.push(newHabit); saveHabits(); renderHabits(); newHabitInput.value = ''; showMessageBox(`"${name}" added to your habits!`); } /** * Increments a habit's streak. * @param {string} id - The ID of the habit to increment. */ function incrementStreak(id) { const habitIndex = habits.findIndex(h => h.id === id); if (habitIndex === -1) { showMessageBox("Habit not found."); return; } const habit = habits[habitIndex]; const today = getTodayDate(); const yesterday = getYesterdayDate(); if (habit.lastUpdatedDate === today) { showMessageBox(`"${habit.name}" already marked as done for today!`); } else if (habit.lastUpdatedDate === yesterday) { // Continued streak from yesterday habit.streakCount++; habit.lastUpdatedDate = today; saveHabits(); renderHabits(); showMessageBox(`"${habit.name}" streak is now ${habit.streakCount} days! Keep it up!`); } else { // Missed a day or first time marking done if (habit.lastUpdatedDate !== null) { // Only reset if it's not the very first time showMessageBox(`It looks like you missed a day for "${habit.name}". Your streak has been reset to 0. Starting a new streak of 1 day!`); } else { showMessageBox(`Great start for "${habit.name}"! Streak is 1 day!`); } habit.streakCount = 1; habit.lastUpdatedDate = today; saveHabits(); renderHabits(); } } /** * Resets a habit's streak to 0. * @param {string} id - The ID of the habit to reset. */ function resetStreak(id) { const habitIndex = habits.findIndex(h => h.id === id); if (habitIndex === -1) { showMessageBox("Habit not found."); return; } const habit = habits[habitIndex]; if (habit.streakCount === 0) { showMessageBox(`"${habit.name}" streak is already 0.`); return; } habit.streakCount = 0; habit.lastUpdatedDate = null; // Clear last updated date saveHabits(); renderHabits(); showMessageBox(`"${habit.name}" streak has been reset.`); } /** * Deletes a habit. * @param {string} id - The ID of the habit to delete. */ function deleteHabit(id) { const initialLength = habits.length; habits = habits.filter(habit => habit.id !== id); if (habits.length < initialLength) { saveHabits(); renderHabits(); showMessageBox("Habit deleted."); } else { showMessageBox("Habit not found for deletion."); } } /** * Generates and downloads a PDF summary of habits and their streaks. */ function downloadPdfSummary() { const jsPDF = window.jspdf.jsPDF; if (!jsPDF) { console.error("Error: jsPDF library not loaded. PDF generation aborted."); showMessageBox("PDF export is currently unavailable. Please try again later."); return; } const doc = new jsPDF(); const leftMargin = 20; const topMargin = 20; const contentWidth = doc.internal.pageSize.getWidth() - (2 * leftMargin); const pageHeight = doc.internal.pageSize.getHeight(); const footerHeight = 15; let currentY = topMargin; // Title doc.setFont('helvetica', 'bold'); doc.setFontSize(22); doc.setTextColor(44, 62, 80); /* #2c3e50 */ doc.text("Habit Streak Report", doc.internal.pageSize.getWidth() / 2, currentY, { align: 'center' }); currentY += 15; doc.setFont('helvetica', 'normal'); doc.setFontSize(10); doc.setTextColor(102, 126, 148); /* #667e94 */ doc.text(`Generated on: ${new Date().toLocaleDateString()} at ${new Date().toLocaleTimeString()}`, doc.internal.pageSize.getWidth() / 2, currentY, { align: 'center' }); currentY += 20; if (habits.length === 0) { doc.setFont('helvetica', 'italic'); doc.setFontSize(12); doc.setTextColor(153, 153, 153); doc.text("No habits recorded yet.", doc.internal.pageSize.getWidth() / 2, currentY, { align: 'center' }); currentY += 20; } else { doc.setFontSize(14); doc.setTextColor(44, 62, 80); doc.text("Your Habits & Streaks:", leftMargin, currentY); currentY += 10; // Table Headers (optional, but good for structured data) doc.setFont('helvetica', 'bold'); doc.setFontSize(11); doc.text("Habit Name", leftMargin, currentY); doc.text("Streak", leftMargin + contentWidth * 0.7, currentY, { align: 'right' }); currentY += 5; doc.setDrawColor(224, 230, 237); // Light line doc.line(leftMargin, currentY, leftMargin + contentWidth, currentY); currentY += 8; // Habit List doc.setFont('helvetica', 'normal'); doc.setFontSize(10); habits.forEach(habit => { const habitLine = `• ${habit.name}`; const streakText = `${habit.streakCount} days`; const splitHabitName = doc.splitTextToSize(habitLine, contentWidth * 0.65); const nameHeight = splitHabitName.length * 5; // Approximate line height if (currentY + nameHeight + 10 > pageHeight - footerHeight) { doc.addPage(); currentY = topMargin; doc.setFont('helvetica', 'bold'); doc.setFontSize(11); doc.text("Habit Name", leftMargin, currentY); doc.text("Streak", leftMargin + contentWidth * 0.7, currentY, { align: 'right' }); currentY += 5; doc.setDrawColor(224, 230, 237); doc.line(leftMargin, currentY, leftMargin + contentWidth, currentY); currentY += 8; doc.setFont('helvetica', 'normal'); doc.setFontSize(10); } doc.text(splitHabitName, leftMargin, currentY); doc.text(streakText, leftMargin + contentWidth * 0.7, currentY, { align: 'right' }); currentY += nameHeight + 5; // Space between items }); } // Footer doc.setFontSize(10); doc.setTextColor(150, 150, 150); doc.text("Habit Tracker by Gemini", doc.internal.pageSize.getWidth() / 2, pageHeight - footerHeight, { align: 'center' }); doc.save("Habit_Streaks_Report.pdf"); } // --- Event Listeners --- addHabitButton.addEventListener('click', addHabit); newHabitInput.addEventListener('keypress', function(event) { if (event.key === 'Enter') { event.preventDefault(); // Prevent default form submission addHabit(); } }); downloadPdfButton.addEventListener('click', downloadPdfSummary); // --- Initial Load --- loadHabits(); renderHabits(); });
Scroll to Top