CoolFace
Apppublic

Waifu112/undefined

sourceHugging Faceupdated 1y agoView on Hugging Face
0likes
index.html623 linesDownload Raw Back to root
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>