|
<!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> |