| | <!DOCTYPE html> |
| | <html lang="en"> |
| | <head> |
| | <meta charset="UTF-8"> |
| | <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| | <title>ZenHabit | Minimal Habit Tracker</title> |
| | <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> |
| | <style> |
| | :root { |
| | --primary: #6366f1; |
| | --primary-light: #818cf8; |
| | --text: #1e293b; |
| | --text-light: #64748b; |
| | --bg: #f8fafc; |
| | --card: #ffffff; |
| | --border: #e2e8f0; |
| | --shadow: 0 1px 3px rgba(0, 0, 0, 0.1); |
| | --success: #10b981; |
| | --warning: #f59e0b; |
| | --danger: #ef4444; |
| | } |
| | |
| | .dark-mode { |
| | --primary: #818cf8; |
| | --primary-light: #a5b4fc; |
| | --text: #e2e8f0; |
| | --text-light: #94a3b8; |
| | --bg: #0f172a; |
| | --card: #1e293b; |
| | --border: #334155; |
| | --shadow: 0 1px 3px rgba(0, 0, 0, 0.3); |
| | } |
| | |
| | * { |
| | margin: 0; |
| | padding: 0; |
| | box-sizing: border-box; |
| | transition: background-color 0.3s, color 0.3s; |
| | } |
| | |
| | body { |
| | font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; |
| | background-color: var(--bg); |
| | color: var(--text); |
| | line-height: 1.6; |
| | } |
| | |
| | .container { |
| | max-width: 800px; |
| | margin: 0 auto; |
| | padding: 2rem; |
| | } |
| | |
| | header { |
| | display: flex; |
| | justify-content: space-between; |
| | align-items: center; |
| | margin-bottom: 2rem; |
| | } |
| | |
| | h1 { |
| | font-size: 1.8rem; |
| | font-weight: 700; |
| | color: var(--primary); |
| | display: flex; |
| | align-items: center; |
| | gap: 0.5rem; |
| | } |
| | |
| | .theme-toggle { |
| | background: none; |
| | border: none; |
| | color: var(--text-light); |
| | font-size: 1.2rem; |
| | cursor: pointer; |
| | transition: transform 0.3s; |
| | } |
| | |
| | .theme-toggle:hover { |
| | transform: rotate(30deg); |
| | color: var(--primary); |
| | } |
| | |
| | .stats { |
| | display: grid; |
| | grid-template-columns: repeat(3, 1fr); |
| | gap: 1rem; |
| | margin-bottom: 2rem; |
| | } |
| | |
| | .stat-card { |
| | background-color: var(--card); |
| | border-radius: 0.5rem; |
| | padding: 1rem; |
| | box-shadow: var(--shadow); |
| | text-align: center; |
| | } |
| | |
| | .stat-card h3 { |
| | font-size: 0.9rem; |
| | color: var(--text-light); |
| | margin-bottom: 0.5rem; |
| | } |
| | |
| | .stat-card p { |
| | font-size: 1.5rem; |
| | font-weight: 700; |
| | color: var(--primary); |
| | } |
| | |
| | .habits { |
| | margin-bottom: 2rem; |
| | } |
| | |
| | .habits-header { |
| | display: flex; |
| | justify-content: space-between; |
| | align-items: center; |
| | margin-bottom: 1rem; |
| | } |
| | |
| | .habits-header h2 { |
| | font-size: 1.3rem; |
| | } |
| | |
| | .add-habit { |
| | background-color: var(--primary); |
| | color: white; |
| | border: none; |
| | border-radius: 0.3rem; |
| | padding: 0.5rem 1rem; |
| | font-size: 0.9rem; |
| | cursor: pointer; |
| | display: flex; |
| | align-items: center; |
| | gap: 0.5rem; |
| | transition: background-color 0.3s; |
| | } |
| | |
| | .add-habit:hover { |
| | background-color: var(--primary-light); |
| | } |
| | |
| | .habit-list { |
| | display: flex; |
| | flex-direction: column; |
| | gap: 0.5rem; |
| | } |
| | |
| | .habit-item { |
| | background-color: var(--card); |
| | border-radius: 0.5rem; |
| | padding: 1rem; |
| | box-shadow: var(--shadow); |
| | display: flex; |
| | align-items: center; |
| | gap: 1rem; |
| | position: relative; |
| | overflow: hidden; |
| | } |
| | |
| | .habit-item::before { |
| | content: ''; |
| | position: absolute; |
| | top: 0; |
| | left: 0; |
| | height: 100%; |
| | width: 0.3rem; |
| | background-color: var(--primary); |
| | } |
| | |
| | .habit-check { |
| | width: 1.5rem; |
| | height: 1.5rem; |
| | border: 2px solid var(--border); |
| | border-radius: 0.3rem; |
| | cursor: pointer; |
| | display: flex; |
| | align-items: center; |
| | justify-content: center; |
| | transition: all 0.3s; |
| | } |
| | |
| | .habit-check.checked { |
| | background-color: var(--primary); |
| | border-color: var(--primary); |
| | color: white; |
| | } |
| | |
| | .habit-info { |
| | flex: 1; |
| | } |
| | |
| | .habit-name { |
| | font-weight: 600; |
| | margin-bottom: 0.2rem; |
| | } |
| | |
| | .habit-streak { |
| | font-size: 0.8rem; |
| | color: var(--text-light); |
| | display: flex; |
| | align-items: center; |
| | gap: 0.3rem; |
| | } |
| | |
| | .habit-streak i { |
| | color: var(--warning); |
| | } |
| | |
| | .habit-progress { |
| | width: 100px; |
| | height: 0.3rem; |
| | background-color: var(--border); |
| | border-radius: 1rem; |
| | overflow: hidden; |
| | } |
| | |
| | .progress-bar { |
| | height: 100%; |
| | background-color: var(--primary); |
| | border-radius: 1rem; |
| | transition: width 0.5s ease; |
| | } |
| | |
| | .calendar { |
| | background-color: var(--card); |
| | border-radius: 0.5rem; |
| | padding: 1rem; |
| | box-shadow: var(--shadow); |
| | } |
| | |
| | .calendar-header { |
| | display: flex; |
| | justify-content: space-between; |
| | align-items: center; |
| | margin-bottom: 1rem; |
| | } |
| | |
| | .calendar-nav { |
| | display: flex; |
| | gap: 1rem; |
| | } |
| | |
| | .calendar-nav button { |
| | background: none; |
| | border: none; |
| | color: var(--text-light); |
| | cursor: pointer; |
| | font-size: 1rem; |
| | } |
| | |
| | .calendar-nav button:hover { |
| | color: var(--primary); |
| | } |
| | |
| | .calendar-grid { |
| | display: grid; |
| | grid-template-columns: repeat(7, 1fr); |
| | gap: 0.5rem; |
| | } |
| | |
| | .calendar-day-header { |
| | text-align: center; |
| | font-size: 0.8rem; |
| | color: var(--text-light); |
| | padding: 0.5rem 0; |
| | } |
| | |
| | .calendar-day { |
| | aspect-ratio: 1; |
| | display: flex; |
| | flex-direction: column; |
| | align-items: center; |
| | justify-content: center; |
| | border-radius: 0.3rem; |
| | cursor: pointer; |
| | position: relative; |
| | } |
| | |
| | .calendar-day:hover { |
| | background-color: var(--border); |
| | } |
| | |
| | .calendar-day.today { |
| | background-color: var(--primary); |
| | color: white; |
| | } |
| | |
| | .day-number { |
| | font-size: 0.9rem; |
| | font-weight: 500; |
| | } |
| | |
| | .day-habits { |
| | position: absolute; |
| | bottom: 0.2rem; |
| | display: flex; |
| | gap: 0.2rem; |
| | } |
| | |
| | .day-habit-dot { |
| | width: 0.3rem; |
| | height: 0.3rem; |
| | border-radius: 50%; |
| | background-color: var(--success); |
| | } |
| | |
| | .modal { |
| | position: fixed; |
| | top: 0; |
| | left: 0; |
| | width: 100%; |
| | height: 100%; |
| | background-color: rgba(0, 0, 0, 0.5); |
| | display: flex; |
| | align-items: center; |
| | justify-content: center; |
| | z-index: 1000; |
| | opacity: 0; |
| | pointer-events: none; |
| | transition: opacity 0.3s; |
| | } |
| | |
| | .modal.active { |
| | opacity: 1; |
| | pointer-events: all; |
| | } |
| | |
| | .modal-content { |
| | background-color: var(--card); |
| | border-radius: 0.5rem; |
| | padding: 1.5rem; |
| | width: 90%; |
| | max-width: 400px; |
| | box-shadow: 0 5px 15px rgba(0, 0, 0, 0.2); |
| | transform: translateY(-20px); |
| | transition: transform 0.3s; |
| | } |
| | |
| | .modal.active .modal-content { |
| | transform: translateY(0); |
| | } |
| | |
| | .modal-header { |
| | display: flex; |
| | justify-content: space-between; |
| | align-items: center; |
| | margin-bottom: 1rem; |
| | } |
| | |
| | .modal-header h3 { |
| | font-size: 1.2rem; |
| | } |
| | |
| | .close-modal { |
| | background: none; |
| | border: none; |
| | font-size: 1.2rem; |
| | color: var(--text-light); |
| | cursor: pointer; |
| | } |
| | |
| | .form-group { |
| | margin-bottom: 1rem; |
| | } |
| | |
| | .form-group label { |
| | display: block; |
| | margin-bottom: 0.5rem; |
| | font-size: 0.9rem; |
| | color: var(--text-light); |
| | } |
| | |
| | .form-group input, |
| | .form-group select { |
| | width: 100%; |
| | padding: 0.5rem; |
| | border: 1px solid var(--border); |
| | border-radius: 0.3rem; |
| | background-color: var(--bg); |
| | color: var(--text); |
| | } |
| | |
| | .modal-actions { |
| | display: flex; |
| | justify-content: flex-end; |
| | gap: 0.5rem; |
| | margin-top: 1rem; |
| | } |
| | |
| | .btn { |
| | padding: 0.5rem 1rem; |
| | border-radius: 0.3rem; |
| | cursor: pointer; |
| | font-size: 0.9rem; |
| | border: none; |
| | transition: background-color 0.3s; |
| | } |
| | |
| | .btn-primary { |
| | background-color: var(--primary); |
| | color: white; |
| | } |
| | |
| | .btn-primary:hover { |
| | background-color: var(--primary-light); |
| | } |
| | |
| | .btn-secondary { |
| | background-color: var(--border); |
| | color: var(--text); |
| | } |
| | |
| | .btn-secondary:hover { |
| | background-color: #d1d5db; |
| | } |
| | |
| | @media (max-width: 600px) { |
| | .stats { |
| | grid-template-columns: 1fr; |
| | } |
| | |
| | .container { |
| | padding: 1rem; |
| | } |
| | } |
| | |
| | |
| | @keyframes pulse { |
| | 0% { transform: scale(1); } |
| | 50% { transform: scale(1.1); } |
| | 100% { transform: scale(1); } |
| | } |
| | |
| | .habit-check.checked { |
| | animation: pulse 0.3s ease; |
| | } |
| | </style> |
| | </head> |
| | <body> |
| | <div class="container"> |
| | <header> |
| | <h1><i class="fas fa-leaf"></i> ZenHabit</h1> |
| | <button class="theme-toggle" id="themeToggle"> |
| | <i class="fas fa-moon"></i> |
| | </button> |
| | </header> |
| |
|
| | <div class="stats"> |
| | <div class="stat-card"> |
| | <h3>Current Streak</h3> |
| | <p id="currentStreak">5</p> |
| | </div> |
| | <div class="stat-card"> |
| | <h3>Habits Tracked</h3> |
| | <p id="habitsTracked">8</p> |
| | </div> |
| | <div class="stat-card"> |
| | <h3>Completion Rate</h3> |
| | <p id="completionRate">72%</p> |
| | </div> |
| | </div> |
| |
|
| | <div class="habits"> |
| | <div class="habits-header"> |
| | <h2>Today's Habits</h2> |
| | <button class="add-habit" id="addHabitBtn"> |
| | <i class="fas fa-plus"></i> Add Habit |
| | </button> |
| | </div> |
| | <div class="habit-list" id="habitList"> |
| | |
| | </div> |
| | </div> |
| |
|
| | <div class="calendar"> |
| | <div class="calendar-header"> |
| | <h3 id="currentMonth">June 2023</h3> |
| | <div class="calendar-nav"> |
| | <button id="prevMonth"><i class="fas fa-chevron-left"></i></button> |
| | <button id="nextMonth"><i class="fas fa-chevron-right"></i></button> |
| | </div> |
| | </div> |
| | <div class="calendar-grid" id="calendarGrid"> |
| | |
| | </div> |
| | </div> |
| | </div> |
| |
|
| | |
| | <div class="modal" id="addHabitModal"> |
| | <div class="modal-content"> |
| | <div class="modal-header"> |
| | <h3>Add New Habit</h3> |
| | <button class="close-modal" id="closeModal">×</button> |
| | </div> |
| | <form id="habitForm"> |
| | <div class="form-group"> |
| | <label for="habitName">Habit Name</label> |
| | <input type="text" id="habitName" placeholder="e.g. Drink water" required> |
| | </div> |
| | <div class="form-group"> |
| | <label for="habitFrequency">Frequency</label> |
| | <select id="habitFrequency" required> |
| | <option value="daily">Daily</option> |
| | <option value="weekly">Weekly</option> |
| | <option value="monthly">Monthly</option> |
| | </select> |
| | </div> |
| | <div class="modal-actions"> |
| | <button type="button" class="btn btn-secondary" id="cancelHabit">Cancel</button> |
| | <button type="submit" class="btn btn-primary">Add Habit</button> |
| | </div> |
| | </form> |
| | </div> |
| | </div> |
| |
|
| | <script> |
| | |
| | let habits = [ |
| | { id: 1, name: "Morning Meditation", streak: 7, frequency: "daily", progress: 85, checked: false }, |
| | { id: 2, name: "Drink 2L water", streak: 14, frequency: "daily", progress: 60, checked: true }, |
| | { id: 3, name: "Read 30 minutes", streak: 21, frequency: "daily", progress: 90, checked: false }, |
| | { id: 4, name: "Workout", streak: 5, frequency: "weekly", progress: 75, checked: false }, |
| | { id: 5, name: "Journaling", streak: 10, frequency: "daily", progress: 50, checked: true } |
| | ]; |
| | |
| | |
| | const themeToggle = document.getElementById('themeToggle'); |
| | const addHabitBtn = document.getElementById('addHabitBtn'); |
| | const addHabitModal = document.getElementById('addHabitModal'); |
| | const closeModal = document.getElementById('closeModal'); |
| | const cancelHabit = document.getElementById('cancelHabit'); |
| | const habitForm = document.getElementById('habitForm'); |
| | const habitList = document.getElementById('habitList'); |
| | const currentStreak = document.getElementById('currentStreak'); |
| | const habitsTracked = document.getElementById('habitsTracked'); |
| | const completionRate = document.getElementById('completionRate'); |
| | const currentMonth = document.getElementById('currentMonth'); |
| | const calendarGrid = document.getElementById('calendarGrid'); |
| | const prevMonthBtn = document.getElementById('prevMonth'); |
| | const nextMonthBtn = document.getElementById('nextMonth'); |
| | |
| | |
| | let currentDate = new Date(); |
| | let currentYear = currentDate.getFullYear(); |
| | let currentMonthIndex = currentDate.getMonth(); |
| | |
| | |
| | document.addEventListener('DOMContentLoaded', () => { |
| | renderHabits(); |
| | renderCalendar(); |
| | updateStats(); |
| | |
| | |
| | if (localStorage.getItem('theme') === 'dark') { |
| | document.body.classList.add('dark-mode'); |
| | themeToggle.innerHTML = '<i class="fas fa-sun"></i>'; |
| | } |
| | }); |
| | |
| | |
| | themeToggle.addEventListener('click', () => { |
| | document.body.classList.toggle('dark-mode'); |
| | if (document.body.classList.contains('dark-mode')) { |
| | localStorage.setItem('theme', 'dark'); |
| | themeToggle.innerHTML = '<i class="fas fa-sun"></i>'; |
| | } else { |
| | localStorage.setItem('theme', 'light'); |
| | themeToggle.innerHTML = '<i class="fas fa-moon"></i>'; |
| | } |
| | }); |
| | |
| | |
| | addHabitBtn.addEventListener('click', () => { |
| | addHabitModal.classList.add('active'); |
| | }); |
| | |
| | closeModal.addEventListener('click', () => { |
| | addHabitModal.classList.remove('active'); |
| | }); |
| | |
| | cancelHabit.addEventListener('click', () => { |
| | addHabitModal.classList.remove('active'); |
| | }); |
| | |
| | |
| | habitForm.addEventListener('submit', (e) => { |
| | e.preventDefault(); |
| | |
| | const name = document.getElementById('habitName').value; |
| | const frequency = document.getElementById('habitFrequency').value; |
| | |
| | const newHabit = { |
| | id: habits.length + 1, |
| | name: name, |
| | streak: 0, |
| | frequency: frequency, |
| | progress: 0, |
| | checked: false |
| | }; |
| | |
| | habits.push(newHabit); |
| | renderHabits(); |
| | updateStats(); |
| | renderCalendar(); |
| | |
| | |
| | habitForm.reset(); |
| | addHabitModal.classList.remove('active'); |
| | }); |
| | |
| | |
| | function renderHabits() { |
| | habitList.innerHTML = ''; |
| | |
| | habits.forEach(habit => { |
| | const habitItem = document.createElement('div'); |
| | habitItem.className = 'habit-item'; |
| | |
| | habitItem.innerHTML = ` |
| | <div class="habit-check ${habit.checked ? 'checked' : ''}" data-id="${habit.id}"> |
| | ${habit.checked ? '<i class="fas fa-check"></i>' : ''} |
| | </div> |
| | <div class="habit-info"> |
| | <div class="habit-name">${habit.name}</div> |
| | <div class="habit-streak"> |
| | <i class="fas fa-fire"></i> ${habit.streak} day streak |
| | </div> |
| | </div> |
| | <div class="habit-progress"> |
| | <div class="progress-bar" style="width: ${habit.progress}%"></div> |
| | </div> |
| | `; |
| | |
| | habitList.appendChild(habitItem); |
| | }); |
| | |
| | |
| | document.querySelectorAll('.habit-check').forEach(checkbox => { |
| | checkbox.addEventListener('click', function() { |
| | const habitId = parseInt(this.getAttribute('data-id')); |
| | const habit = habits.find(h => h.id === habitId); |
| | |
| | habit.checked = !habit.checked; |
| | |
| | if (habit.checked) { |
| | habit.streak += 1; |
| | habit.progress = Math.min(habit.progress + 20, 100); |
| | } else { |
| | habit.streak = Math.max(habit.streak - 1, 0); |
| | habit.progress = Math.max(habit.progress - 20, 0); |
| | } |
| | |
| | renderHabits(); |
| | updateStats(); |
| | }); |
| | }); |
| | } |
| | |
| | |
| | function updateStats() { |
| | const totalHabits = habits.length; |
| | const completedHabits = habits.filter(h => h.checked).length; |
| | const completionPercentage = Math.round((completedHabits / totalHabits) * 100); |
| | |
| | |
| | const longestStreak = habits.reduce((max, habit) => Math.max(max, habit.streak), 0); |
| | |
| | currentStreak.textContent = longestStreak; |
| | habitsTracked.textContent = totalHabits; |
| | completionRate.textContent = `${completionPercentage}%`; |
| | } |
| | |
| | |
| | function renderCalendar() { |
| | |
| | calendarGrid.innerHTML = ''; |
| | |
| | |
| | const monthNames = ["January", "February", "March", "April", "May", "June", |
| | "July", "August", "September", "October", "November", "December"]; |
| | currentMonth.textContent = `${monthNames[currentMonthIndex]} ${currentYear}`; |
| | |
| | |
| | const firstDay = new Date(currentYear, currentMonthIndex, 1).getDay(); |
| | const daysInMonth = new Date(currentYear, currentMonthIndex + 1, 0).getDate(); |
| | |
| | |
| | const dayNames = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]; |
| | dayNames.forEach(day => { |
| | const dayHeader = document.createElement('div'); |
| | dayHeader.className = 'calendar-day-header'; |
| | dayHeader.textContent = day; |
| | calendarGrid.appendChild(dayHeader); |
| | }); |
| | |
| | |
| | for (let i = 0; i < firstDay; i++) { |
| | const emptyDay = document.createElement('div'); |
| | emptyDay.className = 'calendar-day'; |
| | calendarGrid.appendChild(emptyDay); |
| | } |
| | |
| | |
| | for (let i = 1; i <= daysInMonth; i++) { |
| | const dayElement = document.createElement('div'); |
| | dayElement.className = 'calendar-day'; |
| | |
| | |
| | const today = new Date(); |
| | if (i === today.getDate() && currentMonthIndex === today.getMonth() && currentYear === today.getFullYear()) { |
| | dayElement.classList.add('today'); |
| | } |
| | |
| | |
| | const dayNumber = document.createElement('div'); |
| | dayNumber.className = 'day-number'; |
| | dayNumber.textContent = i; |
| | dayElement.appendChild(dayNumber); |
| | |
| | |
| | const habitsCompleted = Math.floor(Math.random() * 3); |
| | if (habitsCompleted > 0) { |
| | const dayHabits = document.createElement('div'); |
| | dayHabits.className = 'day-habits'; |
| | |
| | for (let j = 0; j < habitsCompleted; j++) { |
| | const habitDot = document.createElement('div'); |
| | habitDot.className = 'day-habit-dot'; |
| | dayHabits.appendChild(habitDot); |
| | } |
| | |
| | dayElement.appendChild(dayHabits); |
| | } |
| | |
| | calendarGrid.appendChild(dayElement); |
| | } |
| | } |
| | |
| | |
| | prevMonthBtn.addEventListener('click', () => { |
| | currentMonthIndex--; |
| | if (currentMonthIndex < 0) { |
| | currentMonthIndex = 11; |
| | currentYear--; |
| | } |
| | renderCalendar(); |
| | }); |
| | |
| | nextMonthBtn.addEventListener('click', () => { |
| | currentMonthIndex++; |
| | if (currentMonthIndex > 11) { |
| | currentMonthIndex = 0; |
| | currentYear++; |
| | } |
| | renderCalendar(); |
| | }); |
| | |
| | |
| | window.addEventListener('click', (e) => { |
| | if (e.target === addHabitModal) { |
| | addHabitModal.classList.remove('active'); |
| | } |
| | }); |
| | </script> |
| | <p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <a href="https://enzostvs-deepsite.hf.space" style="color: #fff;" target="_blank" >DeepSite</a> <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;"></p></body> |
| | </html> |