Waifu112/undefined
0
1<!DOCTYPE html>2<html lang="en">3<head>4 <meta charset="UTF-8">5 <meta name="viewport" content="width=device-width, initial-scale=1.0">6 <title>AquaPulse Reminder</title>7 <link rel="icon" type="image/x-icon" href="/static/favicon.ico">8 <script src="https://cdn.tailwindcss.com"></script>9 <script src="https://cdn.jsdelivr.net/npm/feather-icons/dist/feather.min.js"></script>10 <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>11 <style>12 @import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap');13 body {14 font-family: 'Inter', sans-serif;15 }16 .glass-effect {17 background: rgba(255, 255, 255, 0.1);18 backdrop-filter: blur(10px);19 -webkit-backdrop-filter: blur(10px);20 }21 .dark .glass-effect {22 background: rgba(30, 30, 30, 0.3);23 }24 .water-progress {25 transition: all 0.5s ease;26 }27 .history-item {28 transition: transform 0.2s ease;29 }30 .history-item:hover {31 transform: translateY(-2px);32 }33 </style>34</head>35<body class="bg-gradient-to-br from-blue-50 to-cyan-100 dark:from-gray-900 dark:to-gray-800 text-gray-900 dark:text-white transition-colors duration-300">36 <!-- Header -->37 <header class="sticky top-0 z-10 glass-effect border-b border-white/20 dark:border-gray-700">38 <div class="container mx-auto px-4 py-4 flex justify-between items-center">39 <div class="flex items-center space-x-2">40 <i data-feather="droplet" class="text-blue-500 dark:text-cyan-400"></i>41 <h1 class="text-xl font-bold">AquaPulse</h1>42 </div>43 <button id="theme-toggle" class="p-2 rounded-full bg-white/30 dark:bg-gray-700/50 text-gray-700 dark:text-gray-300">44 <i data-feather="moon" class="hidden dark:block"></i>45 <i data-feather="sun" class="block dark:hidden"></i>46 </button>47 </div>48 </header>49 50 <!-- Main Content -->51 <main class="container mx-auto px-4 py-6">52 <!-- Tab Navigation -->53 <div class="flex justify-center mb-8">54 <div class="inline-flex bg-white/30 dark:bg-gray-800/50 rounded-lg p-1 glass-effect">55 <button id="home-tab" class="px-4 py-2 rounded-md font-medium text-blue-600 dark:text-cyan-400 bg-white dark:bg-gray-700 shadow-sm">Home</button>56 <button id="history-tab" class="px-4 py-2 rounded-md font-medium text-gray-600 dark:text-gray-300 hover:text-blue-600 dark:hover:text-cyan-400">History</button>57 <button id="settings-tab" class="px-4 py-2 rounded-md font-medium text-gray-600 dark:text-gray-300 hover:text-blue-600 dark:hover:text-cyan-400">Settings</button>58 </div>59 </div>60 61 <!-- Home Tab -->62 <div id="home-content" class="tab-content">63 <div class="flex flex-col items-center">64 <!-- Water Progress Circle -->65 <div class="relative w-64 h-64 mb-8">66 <div class="absolute inset-0 flex items-center justify-center">67 <div class="relative w-60 h-60">68 <svg class="w-full h-full" viewBox="0 0 100 100">69 <circle cx="50" cy="50" r="45" fill="none" stroke="#e5e7eb" stroke-width="8" class="dark:stroke-gray-700"/>70 <circle id="water-progress-circle" cx="50" cy="50" r="45" fill="none" stroke="#0ea5e9" stroke-width="8" stroke-dasharray="283" stroke-dashoffset="283" class="dark:stroke-cyan-400" transform="rotate(-90 50 50)"/>71 </svg>72 <div class="absolute inset-0 flex flex-col items-center justify-center">73 <span id="water-intake" class="text-3xl font-bold">0</span>74 <span class="text-gray-500 dark:text-gray-400">/ 2000 ml</span>75 </div>76 </div>77 </div>78 </div>79 <!-- Add Water Button -->80 <button id="add-water" class="flex items-center justify-center w-20 h-20 rounded-full bg-blue-500 dark:bg-cyan-500 text-white shadow-lg hover:bg-blue-600 dark:hover:bg-cyan-600 transition-all mb-4">81 <i data-feather="plus" class="w-8 h-8"></i>82 </button>83 <p class="text-gray-600 dark:text-gray-300 mb-4">+250ml per glass</p>84 85 <!-- Reset Progress Button -->86 <button id="reset-progress" class="flex items-center justify-center space-x-2 px-4 py-2 rounded-full bg-red-500 dark:bg-red-600 text-white shadow hover:bg-red-600 dark:hover:bg-red-700 transition-all mb-8">87 <i data-feather="refresh-cw" class="w-4 h-4"></i>88 <span>Reset Progress</span>89 </button>90<!-- Today's Stats -->91 <div class="grid grid-cols-3 gap-4 w-full max-w-md mb-8">92 <div class="bg-white/50 dark:bg-gray-800/50 rounded-lg p-4 text-center glass-effect">93 <p class="text-2xl font-bold text-blue-500 dark:text-cyan-400" id="glasses-count">0</p>94 <p class="text-sm text-gray-600 dark:text-gray-400">Glasses</p>95 </div>96 <div class="bg-white/50 dark:bg-gray-800/50 rounded-lg p-4 text-center glass-effect">97 <p class="text-2xl font-bold text-blue-500 dark:text-cyan-400" id="percentage">0%</p>98 <p class="text-sm text-gray-600 dark:text-gray-400">Completed</p>99 </div>100 <div class="bg-white/50 dark:bg-gray-800/50 rounded-lg p-4 text-center glass-effect">101 <p class="text-2xl font-bold text-blue-500 dark:text-cyan-400" id="remaining">2000</p>102 <p class="text-sm text-gray-600 dark:text-gray-400">Remaining</p>103 </div>104 </div>105 106 <!-- Next Reminder -->107 <div class="bg-white/50 dark:bg-gray-800/50 rounded-lg p-4 w-full max-w-md glass-effect">108 <div class="flex items-center space-x-3">109 <i data-feather="clock" class="text-blue-500 dark:text-cyan-400"></i>110 <div>111 <p class="font-medium">Next reminder</p>112 <p class="text-gray-600 dark:text-gray-400" id="next-reminder">In 2 hours</p>113 </div>114 </div>115 </div>116 </div>117 </div>118 119 <!-- History Tab -->120 <div id="history-content" class="tab-content hidden">121 <div class="space-y-6">122 <h2 class="text-2xl font-bold mb-4">Consumption History</h2>123 124 <!-- Chart -->125 <div class="bg-white/50 dark:bg-gray-800/50 rounded-lg p-4 glass-effect">126 <div class="h-[200px] sm:h-[300px]">127 <canvas id="consumption-chart"></canvas>128 </div>129 </div>130<!-- History List -->131 <div class="space-y-4" id="history-list">132 <!-- History items will be populated by JS -->133 </div>134 </div>135 </div>136 137 <!-- Settings Tab -->138 <div id="settings-content" class="tab-content hidden">139 <div class="space-y-6">140 <div class="bg-white/50 dark:bg-gray-800/50 rounded-lg p-4 glass-effect">141 <h3 class="font-medium text-lg mb-4">Daily Goal</h3>142 <div class="flex items-center space-x-4">143 <input type="range" min="1000" max="5000" step="100" value="2000" id="water-goal-slider" class="w-full">144 <span id="water-goal-value" class="font-bold w-16">2000ml</span>145 </div>146 </div>147 148 <div class="bg-white/50 dark:bg-gray-800/50 rounded-lg p-4 glass-effect">149 <h3 class="font-medium text-lg mb-4">Reminder Settings</h3>150 <div class="space-y-4">151 <div>152 <label class="block text-gray-700 dark:text-gray-300 mb-2">Reminder Interval</label>153 <select id="reminder-interval" class="w-full bg-white dark:bg-gray-700 border border-gray-300 dark:border-gray-600 rounded-lg px-4 py-2">154 <option value="60">Every hour</option>155 <option value="120" selected>Every 2 hours</option>156 <option value="180">Every 3 hours</option>157 <option value="240">Every 4 hours</option>158 </select>159 </div>160 <div>161 <label class="flex items-center space-x-2">162 <input type="checkbox" id="activity-adjust" class="rounded text-blue-500 dark:text-cyan-400" checked>163 <span class="text-gray-700 dark:text-gray-300">Adjust based on activity</span>164 </label>165 </div>166 <div>167 <label class="flex items-center space-x-2">168 <input type="checkbox" id="weather-adjust" class="rounded text-blue-500 dark:text-cyan-400" checked>169 <span class="text-gray-700 dark:text-gray-300">Adjust based on weather</span>170 </label>171 </div>172 </div>173 </div>174 175 <div class="bg-white/50 dark:bg-gray-800/50 rounded-lg p-4 glass-effect">176 <h3 class="font-medium text-lg mb-4">About</h3>177 <p class="text-gray-600 dark:text-gray-400 mb-4">AquaPulse helps you stay hydrated by tracking your water intake and sending smart reminders based on your activity and weather conditions.</p>178 <div class="flex items-center space-x-4 text-gray-600 dark:text-gray-400">179 <a href="#" class="flex items-center space-x-1 hover:text-blue-500 dark:hover:text-cyan-400">180 <i data-feather="star"></i>181 <span>Rate App</span>182 </a>183 <a href="#" class="flex items-center space-x-1 hover:text-blue-500 dark:hover:text-cyan-400">184 <i data-feather="share-2"></i>185 <span>Share</span>186 </a>187 </div>188 </div>189 </div>190 </div>191 </main>192 193 <script>194 // Initialize feather icons195 feather.replace();196 // Theme toggle197 const themeToggle = document.getElementById('theme-toggle');198 const html = document.documentElement;199 200 // Check for saved theme preference201 if (localStorage.getItem('theme') === 'dark' || (!localStorage.getItem('theme') && window.matchMedia('(prefers-color-scheme: dark)').matches)) {202 html.classList.add('dark');203 }204 205 themeToggle.addEventListener('click', () => {206 html.classList.toggle('dark');207 localStorage.setItem('theme', html.classList.contains('dark') ? 'dark' : 'light');208 updateIcon();209 updateChart();210 });211 212 // Listen for system theme changes213 window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', () => {214 updateChart();215 });216function updateIcon() {217 const sunIcon = themeToggle.querySelector('[data-feather="sun"]');218 const moonIcon = themeToggle.querySelector('[data-feather="moon"]');219 if (html.classList.contains('dark')) {220 sunIcon.classList.add('hidden');221 moonIcon.classList.remove('hidden');222 } else {223 sunIcon.classList.remove('hidden');224 moonIcon.classList.add('hidden');225 }226 }227 228 // Tab switching229 const tabs = ['home', 'history', 'settings'];230 tabs.forEach(tab => {231 const tabBtn = document.getElementById(`${tab}-tab`);232 const tabContent = document.getElementById(`${tab}-content`);233 234 tabBtn.addEventListener('click', () => {235 // Update active tab button236 document.querySelectorAll('[id$="-tab"]').forEach(btn => {237 btn.classList.remove('text-blue-600', 'dark:text-cyan-400', 'bg-white', 'dark:bg-gray-700', 'shadow-sm');238 btn.classList.add('text-gray-600', 'dark:text-gray-300', 'hover:text-blue-600', 'dark:hover:text-cyan-400');239 });240 tabBtn.classList.add('text-blue-600', 'dark:text-cyan-400', 'bg-white', 'dark:bg-gray-700', 'shadow-sm');241 tabBtn.classList.remove('text-gray-600', 'dark:text-gray-300', 'hover:text-blue-600', 'dark:hover:text-cyan-400');242 243 // Show active tab content244 document.querySelectorAll('.tab-content').forEach(content => {245 content.classList.add('hidden');246 });247 tabContent.classList.remove('hidden');248 249 // Update chart if history tab250 if (tab === 'history') {251 updateChart();252 }253 });254 });255 // Water tracking256 let waterIntake = 0;257 let waterGoal = 2000; // ml258 const glassSize = 250; // ml259// DOM elements260 const waterIntakeEl = document.getElementById('water-intake');261 const glassesCountEl = document.getElementById('glasses-count');262 const percentageEl = document.getElementById('percentage');263 const remainingEl = document.getElementById('remaining');264 const progressCircle = document.getElementById('water-progress-circle');265 const addWaterBtn = document.getElementById('add-water');266 // Load data from localStorage267 function loadData() {268 const savedData = localStorage.getItem('aquaPulseData');269 const today = new Date().toDateString();270 271 // Reset if new day272 if (savedData) {273 const data = JSON.parse(savedData);274 waterIntake = data.waterIntake || 0;275 waterGoal = data.waterGoal || 2000;276 } else {277 waterGoal = 2000;278 waterIntake = 0;279 }280 281 waterGoalSlider.value = waterGoal;282 waterGoalValue.textContent = `${waterGoal}ml`;283 updateDisplay();284 saveData();285 }286 // Save data to localStorage287 function saveData() {288 const data = {289 waterIntake: waterIntake,290 waterGoal: waterGoal291 };292 localStorage.setItem('aquaPulseData', JSON.stringify(data));293 localStorage.setItem('aquaPulseLastUpdate', new Date().toDateString());294 }295// Update display296 function updateDisplay() {297 waterIntakeEl.textContent = waterIntake;298 waterIntakeEl.nextElementSibling.textContent = `/${waterGoal} ml`;299 300 // Calculate percentage301 const percentage = Math.min(100, Math.round((waterIntake / waterGoal) * 100));302 percentageEl.textContent = `${percentage}%`;303 glassesCountEl.textContent = Math.floor(waterIntake / glassSize);304 remainingEl.textContent = Math.max(0, waterGoal - waterIntake);305 306 // Update progress circle307 const radius = 45;308 const circumference = 2 * Math.PI * radius;309const offset = circumference - (percentage / 100) * circumference;310 progressCircle.style.strokeDashoffset = offset;311 312 // Force a reflow to ensure animation works313 void progressCircle.offsetHeight;314 }315// Add water316 addWaterBtn.addEventListener('click', () => {317 waterIntake += glassSize;318 updateDisplay();319 320 // Add to history321 const now = new Date();322 const historyItem = {323 id: Date.now(),324 amount: glassSize,325 timestamp: now.toISOString()326 };327 328 // Save to localStorage329 let history = JSON.parse(localStorage.getItem('waterHistory') || '[]');330 history.push(historyItem);331 localStorage.setItem('waterHistory', JSON.stringify(history));332 });333 334 // Settings335 const waterGoalSlider = document.getElementById('water-goal-slider');336 const waterGoalValue = document.getElementById('water-goal-value');337 waterGoalSlider.addEventListener('input', () => {338 waterGoal = parseInt(waterGoalSlider.value);339 waterGoalValue.textContent = `${waterGoal}ml`;340 updateDisplay();341 saveData();342 });343// Initialize344 loadData();345 updateIcon();346 347 // History and Chart348 const historyList = document.getElementById('history-list');349 let cachedHistory = null;350 let lastHistoryLoad = 0;351 352 function loadHistory() {353 // Use cached history if loaded within last 5 minutes354 if (cachedHistory && Date.now() - lastHistoryLoad < 300000) {355 renderHistory(cachedHistory);356 return;357 }358 359 let history = JSON.parse(localStorage.getItem('waterHistory') || '[]');360 361 // Clean up old history (keep last 30 days)362 const thirtyDaysAgo = new Date();363 thirtyDaysAgo.setDate(thirtyDaysAgo.getDate() - 30);364 365 history = history.filter(item => {366 return new Date(item.timestamp) > thirtyDaysAgo;367 });368 localStorage.setItem('waterHistory', JSON.stringify(history));369 370 // Cache the filtered history371 cachedHistory = history;372 lastHistoryLoad = Date.now();373 renderHistory(history);374 }375 376 function renderHistory(history) {377 historyList.innerHTML = '';378 379 // Group by date and only show last 14 days380 const grouped = {};381 const fourteenDaysAgo = new Date();382 fourteenDaysAgo.setDate(fourteenDaysAgo.getDate() - 14);383 384 history.forEach(item => {385 const itemDate = new Date(item.timestamp);386 if (itemDate > fourteenDaysAgo) {387 const dateStr = itemDate.toDateString();388 if (!grouped[dateStr]) {389 grouped[dateStr] = [];390 }391 grouped[dateStr].push(item);392 }393 });394 395 // Display history (most recent first)396 const sortedDates = Object.keys(grouped).sort((a, b) => new Date(b) - new Date(a));397 sortedDates.forEach(date => {398 const dateEl = document.createElement('div');399 dateEl.className = 'bg-white/50 dark:bg-gray-800/50 rounded-lg p-4 glass-effect';400 dateEl.innerHTML = `401 <h3 class="font-bold mb-2">${date}</h3>402 <ul class="space-y-2">403 ${grouped[date].map(item => {404 const time = new Date(item.timestamp).toLocaleTimeString([], {hour: '2-digit', minute:'2-digit'});405 return `<li class="flex justify-between">406 <span>+${item.amount}ml</span>407 <span class="text-gray-500 dark:text-gray-400">${time}</span>408 </li>`;409 }).join('')}410 </ul>411 `;412 historyList.appendChild(dateEl);413 });414}415 // Chart416 let consumptionChart;417 let lastChartUpdate = 0;418 419 function updateChart() {420 // Use cached data if updated within last 5 minutes421 if (Date.now() - lastChartUpdate < 300000 && consumptionChart) {422 return;423 }424 425 const history = cachedHistory || JSON.parse(localStorage.getItem('waterHistory') || '[]');426 427 // Group by date for last 7 days428 const dailyData = {};429 const today = new Date();430 for (let i = 6; i >= 0; i--) {431 const date = new Date();432 date.setDate(today.getDate() - i);433 const dateStr = date.toDateString();434 dailyData[dateStr] = 0;435 }436 437 history.forEach(item => {438 const date = new Date(item.timestamp).toDateString();439 if (dailyData.hasOwnProperty(date)) {440 dailyData[date] += item.amount;441 }442 });443 444 lastChartUpdate = Date.now();445const labels = Object.keys(dailyData);446 const data = Object.values(dailyData);447 448 const ctx = document.getElementById('consumption-chart').getContext('2d');449 450 if (consumptionChart) {451 consumptionChart.destroy();452 }453 454 const isDark = html.classList.contains('dark');455 consumptionChart = new Chart(ctx, {456 type: 'bar',457 data: {458 labels: labels,459 datasets: [{460 label: 'Water Intake (ml)',461 data: data,462 backgroundColor: isDark ? '#06b6d4' : '#0ea5e9',463 borderColor: isDark ? '#0891b2' : '#0284c7',464 borderWidth: 1465 }]466 },467 options: {468 responsive: true,469 maintainAspectRatio: false,470 aspectRatio: window.innerWidth < 768 ? 1.5 : 2,471 plugins: {472 legend: {473 display: false474 }475 },476 scales: {477 y: {478 beginAtZero: true,479 grid: {480 color: isDark ? 'rgba(255, 255, 255, 0.1)' : 'rgba(0, 0, 0, 0.1)'481 },482 ticks: {483 color: isDark ? '#e5e7eb' : '#6b7280'484 }485 },486 x: {487 grid: {488 display: false489 },490 ticks: {491 color: isDark ? '#e5e7eb' : '#6b7280'492 }493 }494 }495 }496 });497 }498 // Load history on history tab and update chart (debounced)499 let historyTabDebounce;500 document.getElementById('history-tab').addEventListener('click', () => {501 clearTimeout(historyTabDebounce);502 historyTabDebounce = setTimeout(() => {503 loadHistory();504 updateChart();505 }, 100);506 });507 // Invalidate cache when resetting progress508 document.getElementById('reset-progress').addEventListener('click', () => {509 cachedHistory = null;510 lastHistoryLoad = 0;511 lastChartUpdate = 0;512if (confirm('Are you sure you want to reset today\'s progress?')) {513 // Reset water intake and related values514 waterIntake = 0;515 const percentage = 0;516 const glassesCount = 0;517 const remaining = waterGoal;518 519 // Update DOM elements520 waterIntakeEl.textContent = waterIntake;521 waterIntakeEl.nextElementSibling.textContent = `/${waterGoal} ml`;522 percentageEl.textContent = `${percentage}%`;523 glassesCountEl.textContent = glassesCount;524 remainingEl.textContent = remaining;525 526 // Reset progress circle animation527 progressCircle.style.strokeDashoffset = 283;528 529 // Remove today's history530 const today = new Date().toDateString();531 let history = JSON.parse(localStorage.getItem('waterHistory') || '[]');532 history = history.filter(item => {533 return new Date(item.timestamp).toDateString() !== today;534 });535 localStorage.setItem('waterHistory', JSON.stringify(history));536 537 // Save updated data538 saveData();539 540 // Force update display541 updateDisplay();542 // Update history view regardless of active tab543 loadHistory();544 updateChart();545}546 });547 // Smart reminders with notifications548 let reminderTimeout;549 function scheduleNextReminder() {550 clearTimeout(reminderTimeout);551 552 let interval = parseInt(document.getElementById('reminder-interval').value);553 const activityAdjust = document.getElementById('activity-adjust').checked;554 const weatherAdjust = document.getElementById('weather-adjust').checked;555 556 // Adjust interval based on settings557 if (activityAdjust || weatherAdjust) {558 const adjustmentFactor = 0.8; // 20% more frequent559 interval = Math.floor(interval * adjustmentFactor);560 }561 562 const now = new Date();563 const nextReminder = new Date(now.getTime() + interval * 60000);564 565 document.getElementById('next-reminder').textContent = 566 `At ${nextReminder.toLocaleTimeString([], {hour: '2-digit', minute:'2-digit'})}`;567 568 // Schedule notification569 if ('Notification' in window && Notification.permission === 'granted') {570 reminderTimeout = setTimeout(() => {571 new Notification('AquaPulse Reminder', {572 body: `Time to drink water! You've had ${waterIntake}ml today.`,573 icon: '/static/favicon.ico'574 });575 scheduleNextReminder(); // Schedule next reminder576 }, interval * 60000);577 }578 }579 // Initialize reminder when settings change or tab changes580 document.getElementById('reminder-interval').addEventListener('change', scheduleNextReminder);581 document.getElementById('activity-adjust').addEventListener('change', scheduleNextReminder);582 document.getElementById('weather-adjust').addEventListener('change', scheduleNextReminder);583 584 // Also reschedule when switching to settings tab585 document.getElementById('settings-tab').addEventListener('click', scheduleNextReminder);586// Request notification permission587if ('Notification' in window) {588 if (Notification.permission !== 'granted' && Notification.permission !== 'denied') {589 Notification.requestPermission().then(permission => {590 if (permission === 'granted') {591 scheduleNextReminder();592 }593 });594 } else if (Notification.permission === 'granted') {595 scheduleNextReminder();596 }597 }598 // Check for daily reset and reschedule reminders599 function checkDailyReset() {600const today = new Date().toDateString();601 const lastUpdate = localStorage.getItem('aquaPulseLastUpdate');602 603 if (lastUpdate !== today) {604 waterIntake = 0;605 let history = JSON.parse(localStorage.getItem('waterHistory') || '[]');606 history = history.filter(item => new Date(item.timestamp).toDateString() !== today);607 localStorage.setItem('waterHistory', JSON.stringify(history));608 saveData();609 }610}611// Add event listeners for visibility changes612 document.addEventListener('visibilitychange', () => {613 if (document.visibilityState === 'visible') {614 checkDailyReset();615 loadData();616 }617 });618 // Initial check and schedule first reminder619 checkDailyReset();620 scheduleNextReminder();621</script>622</body>623</html>