cravecontrol / index.html
Cingy's picture
undefined - Initial Deployment
4dec5bf verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Dashboard | CraveControl</title>
<link rel="icon" type="image/x-icon" href="/static/favicon.ico">
<script src="https://cdn.tailwindcss.com"></script>
<link href="https://unpkg.com/aos@2.3.1/dist/aos.css" rel="stylesheet">
<script src="https://unpkg.com/aos@2.3.1/dist/aos.js"></script>
<script src="https://cdn.jsdelivr.net/npm/feather-icons/dist/feather.min.js"></script>
<script src="https://unpkg.com/feather-icons"></script>
<style>
@import url('https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600;700&display=swap');
@keyframes confetti {
0% { transform: translateY(0) rotate(0deg); opacity: 1; }
100% { transform: translateY(100vh) rotate(360deg); opacity: 0; }
}
.confetti {
position: fixed;
width: 10px;
height: 10px;
background-color: #f00;
opacity: 0;
z-index: 9999;
animation: confetti 2s ease-out forwards;
}
.goal-modal,
.celebration-modal,
.reminder-modal {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background-color: white;
padding: 2rem;
border-radius: 12px;
box-shadow: 0 10px 25px rgba(0,0,0,0.1);
z-index: 100;
text-align: center;
max-width: 90%;
display: none;
animation: pulse 0.5s ease-in-out;
}
.celebration-modal h3,
.reminder-modal h3 {
font-size: 1.5rem;
margin-bottom: 1rem;
}
.celebration-modal h3 {
color: #4f46e5;
}
.reminder-modal h3 {
color: #ef4444;
}
.celebration-close,
.reminder-close {
position: absolute;
top: 0.5rem;
right: 0.5rem;
background: none;
border: none;
font-size: 1.2rem;
cursor: pointer;
color: #6b7280;
}
.goal-overlay,
.celebration-overlay,
.reminder-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0,0,0,0.5);
z-index: 99;
display: none;
}
@keyframes pulse {
0% { transform: translate(-50%, -50%) scale(1); }
50% { transform: translate(-50%, -50%) scale(1.05); }
100% { transform: translate(-50%, -50%) scale(1); }
}
.shake {
animation: shake 0.5s ease-in-out;
}
@keyframes shake {
0%, 100% { transform: translate(-50%, -50%); }
20%, 60% { transform: translate(-52%, -50%); }
40%, 80% { transform: translate(-48%, -50%); }
}
body {
font-family: 'Poppins', sans-serif;
position: relative;
overflow-x: hidden;
}
.progress-ring__circle {
transition: stroke-dashoffset 0.5s;
transform: rotate(-90deg);
transform-origin: 50% 50%;
}
.candy-stripe {
background: repeating-linear-gradient(
45deg,
rgba(255,255,255,0.1),
rgba(255,255,255,0.1) 10px,
rgba(255,255,255,0.15) 10px,
rgba(255,255,255,0.15) 20px
);
}
.sidebar {
transition: transform 0.3s ease-in-out;
z-index: 30;
overflow-y: auto;
-webkit-overflow-scrolling: touch;
}
.sidebar-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0,0,0,0.5);
z-index: 20;
opacity: 0;
visibility: hidden;
transition: opacity 0.3s ease, visibility 0.3s ease;
}
.sidebar-overlay.active {
opacity: 1;
visibility: visible;
}
@media (max-width: 768px) {
.sidebar {
transform: translateX(-100%);
position: fixed;
height: 100vh;
top: 0;
left: 0;
}
.sidebar.active {
transform: translateX(0);
}
body.sidebar-open {
overflow: hidden;
position: fixed;
width: 100%;
height: 100%;
}
}
</style>
</head>
<body class="bg-gray-50">
<!-- Fixed Header with Title and Menu Button -->
<header class="bg-indigo-600 text-white fixed w-full z-10">
<div class="p-4 flex items-center justify-center relative">
<button id="menu-toggle" class="absolute left-4 focus:outline-none">
<i data-feather="menu" class="w-6 h-6"></i>
</button>
<h1 class="text-xl font-bold">CraveControl</h1>
</div>
</header>
<div id="sidebar-overlay" class="sidebar-overlay"></div>
<!-- Sidebar -->
<div class="sidebar fixed inset-y-0 left-0 w-64 bg-white shadow-lg z-20 md:relative md:translate-x-0" style="overflow-y: auto;">
<div class="p-6 border-b border-gray-200">
<h1 class="text-2xl font-bold text-indigo-600 flex items-center">
<i data-feather="zap" class="mr-2"></i>
CraveControl
</h1>
</div>
<nav class="p-4">
<div class="mb-8">
<div class="flex items-center mb-6">
<img src="http://static.photos/people/200x200/4" alt="User" class="w-10 h-10 rounded-full mr-3">
<div>
<p class="font-medium">Welcome back,</p>
<p class="font-bold">Alex M.</p>
</div>
</div>
<div class="bg-indigo-50 rounded-lg p-3 mb-4">
<div class="flex justify-between items-center mb-1">
<span class="text-xs font-medium text-indigo-600">Weekly Goal</span>
<span class="text-xs text-indigo-600">€12/€20</span>
</div>
<div class="w-full bg-indigo-100 rounded-full h-2">
<div class="bg-indigo-600 h-2 rounded-full" style="width: 60%"></div>
</div>
</div>
<div class="flex items-center justify-between bg-indigo-600 text-white rounded-lg p-3">
<div>
<p class="text-xs">Current Streak</p>
<p class="font-bold">0 days</p>
</div>
<i data-feather="award" class="w-5 h-5"></i>
</div>
</div>
<ul class="space-y-2">
<li>
<a href="#" class="flex items-center p-3 rounded-lg bg-indigo-100 text-indigo-700 font-medium">
<i data-feather="home" class="w-5 h-5 mr-3"></i>
Dashboard
</a>
</li>
<li>
<a href="#" class="flex items-center p-3 rounded-lg text-gray-700 hover:bg-gray-100 transition">
<i data-feather="dollar-sign" class="w-5 h-5 mr-3"></i>
Savings
</a>
</li>
<li>
<a href="#" class="flex items-center p-3 rounded-lg text-gray-700 hover:bg-gray-100 transition">
<i data-feather="target" class="w-5 h-5 mr-3"></i>
Goals
</a>
</li>
<li>
<a href="#" class="flex items-center p-3 rounded-lg text-gray-700 hover:bg-gray-100 transition">
<i data-feather="award" class="w-5 h-5 mr-3"></i>
Achievements
</a>
</li>
<li>
<a href="#" class="flex items-center p-3 rounded-lg text-gray-700 hover:bg-gray-100 transition">
<i data-feather="users" class="w-5 h-5 mr-3"></i>
Community
</a>
</li>
<li>
<a href="#" class="flex items-center p-3 rounded-lg text-gray-700 hover:bg-gray-100 transition">
<i data-feather="bar-chart-2" class="w-5 h-5 mr-3"></i>
Insights
</a>
</li>
<li>
<a href="#" class="flex items-center p-3 rounded-lg text-gray-700 hover:bg-gray-100 transition">
<i data-feather="settings" class="w-5 h-5 mr-3"></i>
Settings
</a>
</li>
<li>
<button onclick="resetApp()" class="w-full flex items-center p-3 rounded-lg text-gray-700 hover:bg-gray-100 transition">
<i data-feather="refresh-cw" class="w-5 h-5 mr-3"></i>
Reset App
</button>
</li>
</ul>
</nav>
</div>
<!-- Main Content -->
<div class="md:ml-64 pt-16 md:pt-0">
<!-- Desktop Header -->
<header class="hidden md:flex items-center justify-between p-6 bg-white border-b border-gray-200">
<h2 class="text-2xl font-bold text-gray-800">Dashboard</h2>
<div class="flex items-center space-x-4">
<button class="p-2 rounded-full bg-gray-100 text-gray-600 hover:bg-gray-200 transition">
<i data-feather="bell" class="w-5 h-5"></i>
</button>
<div class="flex items-center">
<img src="http://static.photos/people/200x200/4" alt="User" class="w-8 h-8 rounded-full mr-2">
<span class="font-medium">Alex</span>
</div>
</div>
</header>
<main class="p-6 mt-4 md:mt-0"> <!-- Add margin top for mobile -->
<!-- Quick Log Section -->
<div class="bg-white rounded-xl shadow-sm p-6 mb-6" data-aos="fade-up" id="dashboard-stats">
<div class="flex justify-between items-center mb-4">
<h3 class="text-lg font-semibold text-gray-800">Log a Resisted Craving</h3>
<button class="text-sm text-indigo-600 font-medium hover:text-indigo-800 transition">
View History
</button>
</div>
<form class="space-y-4">
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">What did you resist?</label>
<input type="text" placeholder="e.g. Afternoon coffee" class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500">
</div>
<div class="grid grid-cols-2 gap-4">
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Amount Saved</label>
<div class="relative">
<span class="absolute inset-y-0 left-0 pl-3 flex items-center text-gray-500"></span>
<input type="number" step="0.01" placeholder="0.00" class="w-full pl-8 px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 transition">
</div>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Goal</label>
<select id="goal-select" class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500">
<option value="general">General</option>
</select>
</div>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Notes (optional)</label>
<textarea rows="2" placeholder="Why did you have this craving? How did you resist?" class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500"></textarea>
</div>
<div class="flex justify-end">
<button type="submit" id="log-craving-btn" class="bg-indigo-600 text-white px-6 py-2 rounded-lg font-medium hover:bg-indigo-700 transition flex items-center">
<i data-feather="plus" class="w-4 h-4 mr-2"></i>
Add to Savings
</button>
</div>
</form>
</div>
<!-- Stats Overview -->
<div class="grid md:grid-cols-4 gap-6 mb-6">
<div class="bg-white rounded-xl shadow-sm p-6" data-aos="fade-up" data-aos-delay="100">
<div class="flex justify-between items-start">
<div>
<p class="text-sm font-medium text-gray-500">Total Saved</p>
<p class="text-2xl font-bold text-gray-800">€0.00</p>
</div>
<div class="p-2 bg-indigo-100 rounded-lg text-indigo-600">
<i data-feather="dollar-sign" class="w-5 h-5"></i>
</div>
</div>
<div class="mt-4">
<p class="text-xs text-gray-500">Start tracking your savings</p>
</div>
</div>
<div class="bg-white rounded-xl shadow-sm p-6" data-aos="fade-up" data-aos-delay="200">
<div class="flex justify-between items-start">
<div>
<p class="text-sm font-medium text-gray-500">Current Streak</p>
<p class="text-2xl font-bold text-gray-800">5 days</p>
</div>
<div class="p-2 bg-green-100 rounded-lg text-green-600">
<i data-feather="calendar" class="w-5 h-5"></i>
</div>
</div>
<div class="mt-4">
<p class="text-xs text-gray-500">Start your streak today</p>
</div>
</div>
<div class="bg-white rounded-xl shadow-sm p-6" data-aos="fade-up" data-aos-delay="300">
<div class="flex justify-between items-start">
<div>
<p class="text-sm font-medium text-gray-500">Cravings Resisted</p>
<p class="text-2xl font-bold text-gray-800">0</p>
</div>
<div class="p-2 bg-purple-100 rounded-lg text-purple-600">
<i data-feather="check" class="w-5 h-5"></i>
</div>
</div>
<div class="mt-4">
<p class="text-xs text-gray-500">Log your first craving</p>
</div>
</div>
<div class="bg-white rounded-xl shadow-sm p-6" data-aos="fade-up" data-aos-delay="400">
<div class="flex justify-between items-start">
<div>
<p class="text-sm font-medium text-gray-500">Badges Earned</p>
<p class="text-2xl font-bold text-gray-800">0</p>
</div>
<div class="p-2 bg-yellow-100 rounded-lg text-yellow-600">
<i data-feather="award" class="w-5 h-5"></i>
</div>
</div>
<div class="mt-4">
<p class="text-xs text-gray-500">Earn your first badge</p>
</div>
</div>
</div>
<!-- Goals Progress -->
<div class="grid md:grid-cols-2 gap-6 mb-6">
<div class="bg-white rounded-xl shadow-sm p-6" data-aos="fade-up">
<div class="flex justify-between items-center mb-4">
<h3 class="text-lg font-semibold text-gray-800">Active Goals</h3>
<button class="text-sm text-indigo-600 font-medium hover:text-indigo-800 transition">
View All
</button>
</div>
<div class="space-y-4" id="goals-container">
<div class="text-center py-8 text-gray-500">
<i data-feather="target" class="w-12 h-12 mx-auto mb-2"></i>
<p>No active goals</p>
</div>
<button onclick="showGoalModal()" class="w-full border-2 border-dashed border-gray-300 rounded-lg p-4 text-gray-500 hover:border-indigo-300 hover:text-indigo-600 transition flex items-center justify-center">
<i data-feather="plus" class="w-4 h-4 mr-2"></i>
Add New Goal
</button>
</div>
</div>
<div class="bg-white rounded-xl shadow-sm p-6" data-aos="fade-up" data-aos-delay="200">
<div class="flex justify-between items-center mb-4">
<h3 class="text-lg font-semibold text-gray-800">Recent Achievements</h3>
<button class="text-sm text-indigo-600 font-medium hover:text-indigo-800 transition">
View All
</button>
</div>
<div class="space-y-4" id="achievements-container">
<div class="text-center py-8 text-gray-500">
<i data-feather="award" class="w-12 h-12 mx-auto mb-2"></i>
<p>No achievements yet</p>
</div>
</div>
</div>
</div>
<!-- Activity Feed -->
<div class="bg-white rounded-xl shadow-sm p-6" data-aos="fade-up">
<div class="flex justify-between items-center mb-4">
<h3 class="text-lg font-semibold text-gray-800">Recent Activity</h3>
<button class="text-sm text-indigo-600 font-medium hover:text-indigo-800 transition">
View All
</button>
</div>
<div class="space-y-4" id="activity-feed">
<div class="text-center py-8 text-gray-500">
<i data-feather="inbox" class="w-12 h-12 mx-auto mb-2"></i>
<p>No activity yet</p>
</div>
</div>
</div>
</main>
</div>
<!-- Goal Creation Modal -->
<div class="goal-overlay" id="goal-overlay"></div>
<div class="goal-modal" id="goal-modal">
<button class="goal-close" id="goal-close">×</button>
<h3>Create New Goal</h3>
<form id="goal-form" class="space-y-4 mt-4">
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Goal Name</label>
<input type="text" id="goal-name" maxlength="50" placeholder="e.g. Coffee Savings" class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500">
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Target Amount (€)</label>
<input type="number" id="goal-amount" step="0.01" min="0.01" placeholder="0.00" class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500">
</div>
<div class="flex justify-end space-x-3">
<button type="button" onclick="closeGoalModal()" class="px-4 py-2 border border-gray-300 rounded-lg font-medium hover:bg-gray-50 transition">Cancel</button>
<button type="submit" class="bg-indigo-600 text-white px-6 py-2 rounded-lg font-medium hover:bg-indigo-700 transition">Save Goal</button>
</div>
</form>
</div>
<!-- Celebration Modal -->
<div class="celebration-overlay" id="celebration-overlay"></div>
<div class="celebration-modal" id="celebration-modal">
<button class="celebration-close" id="celebration-close">×</button>
<h3>You resisted a craving! 🎉</h3>
<p>Way to go! You're building great habits.</p>
</div>
<!-- Reminder Modal -->
<div class="reminder-overlay" id="reminder-overlay"></div>
<div class="reminder-modal" id="reminder-modal">
<button class="reminder-close" id="reminder-close">×</button>
<h3>Missing Information! ❗</h3>
<p>Please fill out all required fields to log your craving.</p>
</div>
<script>
// Celebration functions
function createConfetti() {
const colors = ['#f44336', '#e91e63', '#9c27b0', '#673ab7', '#3f51b5', '#2196f3', '#03a9f4', '#00bcd4', '#009688', '#4CAF50', '#8BC34A', '#CDDC39', '#FFEB3B', '#FFC107', '#FF9800', '#FF5722'];
for (let i = 0; i < 100; i++) {
const confetti = document.createElement('div');
confetti.className = 'confetti';
confetti.style.left = Math.random() * 100 + 'vw';
confetti.style.backgroundColor = colors[Math.floor(Math.random() * colors.length)];
confetti.style.animationDelay = Math.random() * 2 + 's';
document.body.appendChild(confetti);
// Remove confetti after animation
setTimeout(() => {
confetti.remove();
}, 2000);
}
}
function showCelebration() {
const modal = document.getElementById('celebration-modal');
const overlay = document.getElementById('celebration-overlay');
modal.style.display = 'block';
overlay.style.display = 'block';
createConfetti();
// Close modal when clicking overlay
overlay.addEventListener('click', closeCelebration);
// Close modal when clicking close button
document.getElementById('celebration-close').addEventListener('click', closeCelebration);
}
function closeCelebration() {
const modal = document.getElementById('celebration-modal');
const overlay = document.getElementById('celebration-overlay');
modal.style.display = 'none';
overlay.style.display = 'none';
// Remove event listeners to prevent memory leaks
overlay.removeEventListener('click', closeCelebration);
document.getElementById('celebration-close').removeEventListener('click', closeCelebration);
}
// Goal Management
let goals = [
{ id: 'general', name: 'General', target: 0, saved: 0 }
];
function showGoalModal() {
const modal = document.getElementById('goal-modal');
const overlay = document.getElementById('goal-overlay');
modal.style.display = 'block';
overlay.style.display = 'block';
// Close modal when clicking overlay
overlay.addEventListener('click', closeGoalModal);
// Close modal when clicking close button
document.getElementById('goal-close').addEventListener('click', closeGoalModal);
}
function closeGoalModal() {
const modal = document.getElementById('goal-modal');
const overlay = document.getElementById('goal-overlay');
modal.style.display = 'none';
overlay.style.display = 'none';
document.getElementById('goal-form').reset();
// Remove event listeners to prevent memory leaks
overlay.removeEventListener('click', closeGoalModal);
document.getElementById('goal-close').removeEventListener('click', closeGoalModal);
}
function addGoal(name, target) {
// Validate input
if (!name || !target || target <= 0) {
alert('Please enter a valid goal name and target amount');
return;
}
// Trim name to 50 chars if needed
name = name.substring(0, 50);
const id = 'goal-' + Date.now();
goals.push({ id, name, target, saved: 0 });
// Update dropdown
const select = document.getElementById('goal-select');
const option = document.createElement('option');
option.value = id;
option.textContent = name;
select.appendChild(option);
// Update goals container
updateGoalsDisplay();
closeGoalModal();
// Focus on the new goal in dropdown
select.value = id;
}
function updateGoalsDisplay() {
const container = document.getElementById('goals-container');
const emptyState = container.querySelector('.text-center');
if (emptyState) {
emptyState.remove();
}
// Clear existing goals (except add button)
document.querySelectorAll('#goals-container > div:not(button)').forEach(el => el.remove());
// Add each goal
goals.forEach(goal => {
const goalEl = document.createElement('div');
goalEl.className = 'bg-gray-50 rounded-lg p-4 border border-gray-200';
goalEl.innerHTML = `
<div class="flex justify-between items-center mb-2">
<h4 class="font-medium">${goal.name}</h4>
${goal.id !== 'general' ?
`<button onclick="deleteGoal('${goal.id}')" class="text-gray-400 hover:text-red-500">
<i data-feather="trash-2" class="w-4 h-4"></i>
</button>` : ''}
</div>
<div class="flex justify-between items-center text-xs mb-1">
<span class="text-gray-500">Saved: €${goal.saved.toFixed(2)}</span>
<span class="text-gray-500">Target: €${goal.target.toFixed(2)}</span>
</div>
<div class="w-full bg-gray-200 rounded-full h-2">
<div class="bg-indigo-600 h-2 rounded-full" style="width: ${Math.min(100, (goal.saved / goal.target) * 100)}%"></div>
</div>
`;
container.insertBefore(goalEl, container.lastElementChild);
feather.replace();
});
}
function deleteGoal(id) {
if (id === 'general') return; // Prevent deleting General goal
if (confirm('Are you sure you want to delete this goal?')) {
// Move any savings from this goal to General
const goalToDelete = goals.find(g => g.id === id);
if (goalToDelete) {
const generalGoal = goals.find(g => g.id === 'general');
if (generalGoal) {
generalGoal.saved += goalToDelete.saved;
}
}
goals = goals.filter(goal => goal.id !== id);
// Remove from dropdown
const select = document.getElementById('goal-select');
const option = select.querySelector(`option[value="${id}"]`);
if (option) option.remove();
// Reset selection to General
select.value = 'general';
updateGoalsDisplay();
}
}
// Initialize app in reset state
function resetApp() {
// Clear all stored data
localStorage.clear();
sessionStorage.clear();
// Clear all UI elements
document.querySelectorAll('#activity-feed > div:not(.text-center)').forEach(el => el.remove());
document.querySelectorAll('#goals-container > div:not(.text-center, button)').forEach(el => el.remove());
document.querySelectorAll('#achievements-container > div:not(.text-center)').forEach(el => el.remove());
// Ensure empty state messages are shown
if (!document.querySelector('#activity-feed .text-center')) {
const emptyActivity = document.createElement('div');
emptyActivity.className = 'text-center py-8 text-gray-500';
emptyActivity.innerHTML = `
<i data-feather="inbox" class="w-12 h-12 mx-auto mb-2"></i>
<p>No activity yet</p>
`;
document.getElementById('activity-feed').appendChild(emptyActivity);
}
// Reset all stats to zero
document.querySelectorAll('[data-stat="total-saved"]').forEach(el => el.textContent = '€0.00');
document.querySelectorAll('[data-stat="streak"]').forEach(el => el.textContent = '0 days');
document.querySelectorAll('[data-stat="cravings-resisted"]').forEach(el => el.textContent = '0');
document.querySelectorAll('[data-stat="badges-earned"]').forEach(el => el.textContent = '0');
// Reset weekly goal progress
document.querySelector('.progress-ring__circle')?.style.setProperty('stroke-dashoffset', '0');
document.querySelector('.bg-indigo-600.h-2')?.style.setProperty('width', '0%');
// Reset form fields
document.querySelector('form').reset();
// Refresh icons
feather.replace();
}
// Initialize animations and icons
AOS.init({
duration: 800,
easing: 'ease-in-out',
once: true
});
feather.replace();
// Initialize app with General goal
document.addEventListener('DOMContentLoaded', function() {
// Ensure General goal exists
if (!goals.some(g => g.id === 'general')) {
goals.unshift({ id: 'general', name: 'General', target: 0, saved: 0 });
}
// Reset app state
resetApp();
// Update goals display
updateGoalsDisplay();
});
// Handle form submission
document.querySelector('form').addEventListener('submit', function(e) {
e.preventDefault();
const form = e.target;
const cravingInput = form.querySelector('input[type="text"]');
const amountInput = form.querySelector('input[type="number"]');
if (!cravingInput.value.trim() || !amountInput.value) {
showReminder();
document.getElementById('reminder-modal').classList.add('shake');
setTimeout(() => {
document.getElementById('reminder-modal').classList.remove('shake');
}, 500);
// Highlight empty fields
if (!cravingInput.value.trim()) {
cravingInput.classList.add('border-red-500');
cravingInput.focus();
} else if (!amountInput.value) {
amountInput.classList.add('border-red-500');
amountInput.focus();
}
return;
}
// Handle goal form submission
document.getElementById('goal-form').addEventListener('submit', function(e) {
e.preventDefault();
const name = document.getElementById('goal-name').value.trim();
const amount = parseFloat(document.getElementById('goal-amount').value);
addGoal(name, amount);
});
// Simulate form submission
const activityFeed = document.getElementById('activity-feed');
const emptyState = activityFeed.querySelector('.text-center');
if (emptyState) {
emptyState.remove();
}
// Add new activity
const newActivity = document.createElement('div');
newActivity.className = 'flex items-start p-3 border-b border-gray-100';
newActivity.innerHTML = `
<div class="p-2 bg-indigo-100 rounded-full mr-3">
<i data-feather="dollar-sign" class="w-4 h-4 text-indigo-600"></i>
</div>
<div class="flex-1">
<p class="text-sm">
<span class="font-medium">You</span> resisted <span class="font-medium">${cravingInput.value}</span> and saved <span class="font-medium">€${amountInput.value}</span>
</p>
<p class="text-xs text-gray-500 mt-1">Just now</p>
</div>
`;
activityFeed.prepend(newActivity);
// Update goal progress
const goalId = document.getElementById('goal-select').value;
const amount = parseFloat(amountInput.value);
const goal = goals.find(g => g.id === goalId);
if (goal) {
goal.saved += amount;
updateGoalsDisplay();
// Update total saved for General goal if this wasn't it
if (goalId !== 'general') {
const generalGoal = goals.find(g => g.id === 'general');
if (generalGoal) {
generalGoal.saved += amount;
}
}
}
// Update stats
const totalSaved = document.querySelector('[data-stat="total-saved"]');
const currentTotal = parseFloat(totalSaved.textContent.replace('€', '')) || 0;
totalSaved.textContent = `€${(currentTotal + parseFloat(amountInput.value)).toFixed(2)}`;
const cravingsResisted = document.querySelector('[data-stat="cravings-resisted"]');
cravingsResisted.textContent = parseInt(cravingsResisted.textContent) + 1;
showCelebration();
// Reset form
this.reset();
cravingInput.classList.remove('border-red-500');
amountInput.classList.remove('border-red-500');
});
// Reminder functions
function showReminder() {
const modal = document.getElementById('reminder-modal');
const overlay = document.getElementById('reminder-overlay');
modal.style.display = 'block';
overlay.style.display = 'block';
// Close modal when clicking overlay
overlay.addEventListener('click', closeReminder);
// Close modal when clicking close button
document.getElementById('reminder-close').addEventListener('click', closeReminder);
}
function closeReminder() {
const modal = document.getElementById('reminder-modal');
const overlay = document.getElementById('reminder-overlay');
modal.style.display = 'none';
overlay.style.display = 'none';
// Remove event listeners to prevent memory leaks
overlay.removeEventListener('click', closeReminder);
document.getElementById('reminder-close').removeEventListener('click', closeReminder);
}
// Mobile menu toggle
const menuToggle = document.getElementById('menu-toggle');
const sidebar = document.querySelector('.sidebar');
const overlay = document.getElementById('sidebar-overlay');
function toggleMenu() {
const isOpening = !sidebar.classList.contains('active');
sidebar.classList.toggle('active');
overlay.classList.toggle('active');
if (isOpening) {
document.body.classList.add('sidebar-open');
} else {
document.body.classList.remove('sidebar-open');
}
}
menuToggle.addEventListener('click', toggleMenu);
// Close menu when clicking outside
overlay.addEventListener('click', toggleMenu);
// Swipe to close functionality
let touchStartX = 0;
let touchEndX = 0;
sidebar.addEventListener('touchstart', e => {
touchStartX = e.changedTouches[0].screenX;
}, false);
sidebar.addEventListener('touchend', e => {
touchEndX = e.changedTouches[0].screenX;
if (touchEndX - touchStartX > 50 && sidebar.classList.contains('active')) {
toggleMenu();
}
}, false);
</script>
</body>
</html>