Streakify / index.html
uumerrr684's picture
Rename app.py to index.html
1e13925 verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Streakly - Beautiful Habit Tracker</title>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
padding: 20px;
}
.container {
max-width: 900px;
margin: 0 auto;
background: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(20px);
border-radius: 24px;
padding: 32px;
box-shadow: 0 32px 64px rgba(0, 0, 0, 0.1);
border: 1px solid rgba(255, 255, 255, 0.2);
}
.header {
display: flex;
justify-content: space-between;
align-items: flex-start;
margin-bottom: 32px;
}
.greeting h1 {
font-size: 28px;
font-weight: 700;
color: #1a202c;
margin-bottom: 4px;
}
.greeting p {
color: #718096;
font-size: 16px;
}
.date-section {
text-align: right;
}
.current-date {
font-size: 18px;
font-weight: 600;
color: #2d3748;
margin-bottom: 4px;
}
.progress-text {
color: #718096;
font-size: 14px;
}
.progress-card {
background: linear-gradient(135deg, #f7fafc 0%, #edf2f7 100%);
border-radius: 16px;
padding: 24px;
margin-bottom: 32px;
text-align: center;
border: 1px solid rgba(255, 255, 255, 0.5);
}
.progress-title {
font-size: 20px;
font-weight: 600;
color: #2d3748;
margin-bottom: 12px;
}
.progress-bar-container {
background: rgba(255, 255, 255, 0.8);
height: 12px;
border-radius: 6px;
overflow: hidden;
margin-bottom: 8px;
}
.progress-bar {
height: 100%;
background: linear-gradient(90deg, #48bb78, #38a169);
border-radius: 6px;
transition: width 0.6s cubic-bezier(0.4, 0, 0.2, 1);
box-shadow: 0 2px 4px rgba(72, 187, 120, 0.3);
}
.add-habit-section {
display: flex;
gap: 12px;
margin-bottom: 32px;
}
.habit-input {
flex: 1;
padding: 16px 20px;
border: 2px solid #e2e8f0;
border-radius: 12px;
font-size: 16px;
font-weight: 500;
background: rgba(255, 255, 255, 0.9);
transition: all 0.3s ease;
outline: none;
}
.habit-input:focus {
border-color: #4299e1;
box-shadow: 0 0 0 3px rgba(66, 153, 225, 0.1);
transform: translateY(-1px);
}
.add-btn {
padding: 16px 24px;
background: linear-gradient(135deg, #4299e1, #3182ce);
color: white;
border: none;
border-radius: 12px;
font-size: 16px;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
box-shadow: 0 4px 12px rgba(66, 153, 225, 0.3);
}
.add-btn:hover {
transform: translateY(-2px);
box-shadow: 0 8px 24px rgba(66, 153, 225, 0.4);
}
.habits-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
gap: 20px;
margin-bottom: 40px;
}
.habit-card {
background: rgba(255, 255, 255, 0.9);
border: 2px solid #e2e8f0;
border-radius: 16px;
padding: 24px;
cursor: pointer;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
position: relative;
overflow: hidden;
}
.habit-card::before {
content: '';
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 4px;
background: linear-gradient(90deg, #e2e8f0, #cbd5e0);
transition: background 0.3s ease;
}
.habit-card:hover {
transform: translateY(-4px);
box-shadow: 0 12px 32px rgba(0, 0, 0, 0.1);
border-color: #4299e1;
}
.habit-card.completed {
background: linear-gradient(135deg, #48bb78, #38a169);
color: white;
border-color: #48bb78;
}
.habit-card.completed::before {
background: linear-gradient(90deg, rgba(255,255,255,0.3), rgba(255,255,255,0.1));
}
.habit-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 12px;
}
.habit-name {
font-size: 20px;
font-weight: 600;
}
.habit-icon {
font-size: 24px;
opacity: 0.8;
}
.habit-status {
font-size: 14px;
opacity: 0.9;
font-weight: 500;
}
.delete-btn {
position: absolute;
top: 12px;
right: 12px;
background: rgba(255, 255, 255, 0.2);
border: none;
border-radius: 8px;
width: 32px;
height: 32px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
opacity: 0;
transition: all 0.3s ease;
color: #e53e3e;
font-size: 16px;
}
.habit-card:hover .delete-btn {
opacity: 1;
}
.delete-btn:hover {
background: rgba(229, 62, 62, 0.1);
transform: scale(1.1);
}
.week-section {
background: rgba(247, 250, 252, 0.8);
border-radius: 20px;
padding: 28px;
backdrop-filter: blur(10px);
border: 1px solid rgba(255, 255, 255, 0.3);
}
.week-header {
font-size: 22px;
font-weight: 700;
color: #2d3748;
margin-bottom: 24px;
text-align: center;
}
.week-grid {
display: grid;
grid-template-columns: 120px repeat(7, 1fr);
gap: 12px;
align-items: center;
}
.day-header {
text-align: center;
font-size: 14px;
font-weight: 600;
color: #718096;
padding: 12px 4px;
}
.habit-label {
font-size: 16px;
font-weight: 500;
color: #4a5568;
padding: 12px 0;
border-right: 2px solid #e2e8f0;
text-align: right;
padding-right: 16px;
}
.day-cell {
width: 40px;
height: 40px;
border-radius: 12px;
display: flex;
align-items: center;
justify-content: center;
font-size: 14px;
font-weight: 600;
margin: 0 auto;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
cursor: pointer;
}
.day-cell:hover {
transform: scale(1.1);
}
.day-empty {
background: rgba(226, 232, 240, 0.6);
color: #a0aec0;
border: 2px solid transparent;
}
.day-completed {
background: linear-gradient(135deg, #48bb78, #38a169);
color: white;
box-shadow: 0 4px 12px rgba(72, 187, 120, 0.3);
border: 2px solid transparent;
}
.day-today {
border: 2px solid #4299e1;
animation: pulse 2s infinite;
}
@keyframes pulse {
0% { box-shadow: 0 0 0 0 rgba(66, 153, 225, 0.4); }
70% { box-shadow: 0 0 0 8px rgba(66, 153, 225, 0); }
100% { box-shadow: 0 0 0 0 rgba(66, 153, 225, 0); }
}
.streak-info {
text-align: center;
margin-top: 24px;
padding: 20px;
background: rgba(255, 255, 255, 0.5);
border-radius: 12px;
border: 1px solid rgba(255, 255, 255, 0.3);
}
.streak-number {
font-size: 32px;
font-weight: 700;
color: #48bb78;
margin-bottom: 4px;
}
.streak-label {
color: #718096;
font-size: 14px;
font-weight: 500;
}
.empty-state {
text-align: center;
padding: 60px 20px;
color: #718096;
}
.empty-state h3 {
font-size: 24px;
font-weight: 600;
margin-bottom: 12px;
color: #4a5568;
}
.floating-add {
position: fixed;
bottom: 32px;
right: 32px;
width: 64px;
height: 64px;
background: linear-gradient(135deg, #4299e1, #3182ce);
border: none;
border-radius: 50%;
color: white;
font-size: 24px;
cursor: pointer;
box-shadow: 0 8px 24px rgba(66, 153, 225, 0.4);
transition: all 0.3s ease;
display: none;
}
.floating-add:hover {
transform: scale(1.1);
box-shadow: 0 12px 32px rgba(66, 153, 225, 0.5);
}
@media (max-width: 768px) {
.container {
margin: 10px;
padding: 24px;
}
.header {
flex-direction: column;
gap: 16px;
text-align: center;
}
.habits-grid {
grid-template-columns: 1fr;
}
.week-grid {
grid-template-columns: 80px repeat(7, 1fr);
gap: 8px;
}
.day-cell {
width: 32px;
height: 32px;
font-size: 12px;
}
.floating-add {
display: flex;
align-items: center;
justify-content: center;
}
.add-habit-section {
display: none;
}
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<div class="greeting">
<h1>Hey there! 👋</h1>
<p>Keep building those amazing habits</p>
</div>
<div class="date-section">
<div class="current-date" id="currentDate"></div>
<div class="progress-text" id="progressText">0% of daily goals achieved</div>
</div>
</div>
<div class="progress-card">
<div class="progress-title" id="progressTitle">Today's Progress: 0/0 habits completed</div>
<div class="progress-bar-container">
<div class="progress-bar" id="progressBar" style="width: 0%"></div>
</div>
</div>
<div class="add-habit-section">
<input type="text" class="habit-input" id="habitInput" placeholder="Add a new habit..." maxlength="50">
<button class="add-btn" onclick="addHabit()">+ Add Habit</button>
</div>
<div class="habits-grid" id="habitsGrid"></div>
<div class="week-section">
<div class="week-header" id="weekHeader">This Week</div>
<div class="week-grid" id="weekGrid"></div>
<div class="streak-info">
<div class="streak-number" id="streakNumber">0</div>
<div class="streak-label">day current streak</div>
</div>
</div>
</div>
<button class="floating-add" onclick="showAddHabitPrompt()">+</button>
<script>
// Sample data - in a real app, this would be stored in localStorage or a database
let habits = [
{ id: 1, name: 'Exercise', icon: '💪' },
{ id: 2, name: 'Read', icon: '📚' },
{ id: 3, name: 'Meditate', icon: '🧘' },
{ id: 4, name: 'Drink Water', icon: '💧' }
];
let completedHabits = {}; // Format: { 'YYYY-MM-DD': [habitId1, habitId2, ...] }
// Initialize with some sample data
const today = new Date();
const yesterday = new Date(today);
yesterday.setDate(yesterday.getDate() - 1);
completedHabits[formatDate(today)] = [1, 3]; // Exercise and Meditate completed today
completedHabits[formatDate(yesterday)] = [1, 2, 4]; // Exercise, Read, Water completed yesterday
function formatDate(date) {
return date.toISOString().split('T')[0];
}
function formatDateForDisplay(date) {
return date.toLocaleDateString('en-US', {
weekday: 'short',
month: 'short',
day: 'numeric'
});
}
function updateHeader() {
const now = new Date();
document.getElementById('currentDate').textContent = formatDateForDisplay(now);
const todayCompleted = getTodayCompletedCount();
const totalHabits = habits.length;
const percentage = totalHabits > 0 ? Math.round((todayCompleted / totalHabits) * 100) : 0;
document.getElementById('progressText').textContent = `${percentage}% of daily goals achieved`;
document.getElementById('progressTitle').textContent = `Today's Progress: ${todayCompleted}/${totalHabits} habits completed`;
document.getElementById('progressBar').style.width = `${percentage}%`;
}
function getTodayCompletedCount() {
const today = formatDate(new Date());
return completedHabits[today] ? completedHabits[today].length : 0;
}
function isHabitCompletedToday(habitId) {
const today = formatDate(new Date());
return completedHabits[today] && completedHabits[today].includes(habitId);
}
function toggleHabit(habitId) {
const today = formatDate(new Date());
if (!completedHabits[today]) {
completedHabits[today] = [];
}
const index = completedHabits[today].indexOf(habitId);
if (index > -1) {
completedHabits[today].splice(index, 1);
if (completedHabits[today].length === 0) {
delete completedHabits[today];
}
} else {
completedHabits[today].push(habitId);
}
renderHabits();
renderWeekView();
updateHeader();
updateStreak();
}
function addHabit() {
const input = document.getElementById('habitInput');
const habitName = input.value.trim();
if (habitName && habitName.length > 0) {
const newId = Math.max(...habits.map(h => h.id), 0) + 1;
const icons = ['🎯', '⭐', '🔥', '✨', '🌟', '💎', '🎊', '🏆', '⚡', '🌈'];
const randomIcon = icons[Math.floor(Math.random() * icons.length)];
habits.push({
id: newId,
name: habitName,
icon: randomIcon
});
input.value = '';
renderHabits();
updateHeader();
renderWeekView();
}
}
function deleteHabit(habitId) {
habits = habits.filter(h => h.id !== habitId);
// Remove from completed habits
Object.keys(completedHabits).forEach(date => {
completedHabits[date] = completedHabits[date].filter(id => id !== habitId);
if (completedHabits[date].length === 0) {
delete completedHabits[date];
}
});
renderHabits();
updateHeader();
renderWeekView();
}
function renderHabits() {
const grid = document.getElementById('habitsGrid');
if (habits.length === 0) {
grid.innerHTML = `
<div class="empty-state" style="grid-column: 1 / -1;">
<h3>No habits yet! 🌱</h3>
<p>Add your first habit to start building amazing streaks</p>
</div>
`;
return;
}
grid.innerHTML = habits.map(habit => {
const isCompleted = isHabitCompletedToday(habit.id);
return `
<div class="habit-card ${isCompleted ? 'completed' : ''}" onclick="toggleHabit(${habit.id})">
<button class="delete-btn" onclick="event.stopPropagation(); deleteHabit(${habit.id})" title="Delete habit">×</button>
<div class="habit-header">
<div class="habit-name">${habit.name}</div>
<div class="habit-icon">${habit.icon}</div>
</div>
<div class="habit-status">${isCompleted ? '✓ Completed today!' : 'Click to mark complete'}</div>
</div>
`;
}).join('');
}
function renderWeekView() {
const weekGrid = document.getElementById('weekGrid');
const today = new Date();
const startOfWeek = new Date(today);
startOfWeek.setDate(today.getDate() - today.getDay() + 1); // Monday
const days = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'];
const weekDates = [];
for (let i = 0; i < 7; i++) {
const date = new Date(startOfWeek);
date.setDate(startOfWeek.getDate() + i);
weekDates.push(date);
}
// Update week header
document.getElementById('weekHeader').textContent =
`This Week - ${startOfWeek.toLocaleDateString('en-US', { month: 'short', day: 'numeric' })} to ${weekDates[6].toLocaleDateString('en-US', { month: 'short', day: 'numeric' })}`;
let gridHTML = '<div></div>'; // Empty corner cell
// Day headers
days.forEach((day, index) => {
gridHTML += `<div class="day-header">${day}<br><span style="font-size: 12px; opacity: 0.7;">${weekDates[index].getDate()}</span></div>`;
});
// Habit rows
habits.forEach(habit => {
gridHTML += `<div class="habit-label">${habit.name}</div>`;
weekDates.forEach(date => {
const dateStr = formatDate(date);
const isCompleted = completedHabits[dateStr] && completedHabits[dateStr].includes(habit.id);
const isToday = formatDate(date) === formatDate(today);
let classes = 'day-cell ';
classes += isCompleted ? 'day-completed' : 'day-empty';
classes += isToday ? ' day-today' : '';
gridHTML += `<div class="${classes}" onclick="toggleHabitForDate(${habit.id}, '${dateStr}')">${isCompleted ? '●' : '○'}</div>`;
});
});
weekGrid.innerHTML = gridHTML;
}
function toggleHabitForDate(habitId, dateStr) {
if (!completedHabits[dateStr]) {
completedHabits[dateStr] = [];
}
const index = completedHabits[dateStr].indexOf(habitId);
if (index > -1) {
completedHabits[dateStr].splice(index, 1);
if (completedHabits[dateStr].length === 0) {
delete completedHabits[dateStr];
}
} else {
completedHabits[dateStr].push(habitId);
}
renderWeekView();
if (dateStr === formatDate(new Date())) {
renderHabits();
updateHeader();
}
updateStreak();
}
function updateStreak() {
// Calculate current streak (simplified - just counts consecutive days with any habit completed)
let streak = 0;
const today = new Date();
for (let i = 0; i < 30; i++) {
const date = new Date(today);
date.setDate(today.getDate() - i);
const dateStr = formatDate(date);
if (completedHabits[dateStr] && completedHabits[dateStr].length > 0) {
streak++;
} else {
break;
}
}
document.getElementById('streakNumber').textContent = streak;
}
function showAddHabitPrompt() {
const habitName = prompt('Enter new habit name:');
if (habitName && habitName.trim()) {
document.getElementById('habitInput').value = habitName.trim();
addHabit();
}
}
// Handle Enter key in input
document.getElementById('habitInput').addEventListener('keypress', function(e) {
if (e.key === 'Enter') {
addHabit();
}
});
// Initialize app
updateHeader();
renderHabits();
renderWeekView();
updateStreak();
</script>
</body>
</html>