Spaces:
Running
Running
| // Initialize app | |
| let currentView = 'dashboardView'; | |
| let selectedMood = null; | |
| let charts = {}; | |
| // View management | |
| function showView(viewId) { | |
| // Hide all views | |
| document.querySelectorAll('.view').forEach(view => { | |
| view.classList.add('hidden'); | |
| }); | |
| // Show selected view | |
| document.getElementById(viewId).classList.remove('hidden'); | |
| // Update navigation | |
| document.querySelectorAll('.nav-btn').forEach(btn => { | |
| btn.classList.remove('active'); | |
| if (btn.dataset.view === viewId) { | |
| btn.classList.add('active'); | |
| } | |
| }); | |
| currentView = viewId; | |
| // Initialize charts for specific views | |
| if (viewId === 'strainView') { | |
| initializeStrainCharts(); | |
| } else if (viewId === 'sleepView') { | |
| initializeSleepCharts(); | |
| } else if (viewId === 'recoveryView') { | |
| initializeRecoveryChart(); | |
| } | |
| // Re-initialize feather icons | |
| feather.replace(); | |
| } | |
| function showJournal() { | |
| showView('journalView'); | |
| } | |
| function showProfile() { | |
| // Placeholder for profile modal | |
| alert('Profile feature coming soon!'); | |
| } | |
| // Initialize charts | |
| function initializeStrainCharts() { | |
| // Strain Gauge | |
| const strainGaugeCtx = document.getElementById('strainGauge'); | |
| if (strainGaugeCtx && !charts.strainGauge) { | |
| charts.strainGauge = new Chart(strainGaugeCtx, { | |
| type: 'doughnut', | |
| data: { | |
| datasets: [{ | |
| data: [41, 59], | |
| backgroundColor: ['#eab308', '#fef3c7'], | |
| borderWidth: 0 | |
| }] | |
| }, | |
| options: { | |
| responsive: true, | |
| maintainAspectRatio: false, | |
| cutout: '70%', | |
| plugins: { | |
| legend: { display: false }, | |
| tooltip: { enabled: false } | |
| } | |
| } | |
| }); | |
| } | |
| // Hourly Strain Chart | |
| const hourlyStrainCtx = document.getElementById('hourlyStrainChart'); | |
| if (hourlyStrainCtx && !charts.hourlyStrain) { | |
| const hours = Array.from({length: 24}, (_, i) => `${i}:00`); | |
| const strainData = [ | |
| 2, 2, 1, 1, 2, 3, 8, 12, 5, 4, 3, 5, 8, 6, 4, 3, 4, 7, 3, 2, 2, 2, 1, 1 | |
| ]; | |
| charts.hourlyStrain = new Chart(hourlyStrainCtx, { | |
| type: 'line', | |
| data: { | |
| labels: hours, | |
| datasets: [{ | |
| label: 'Strain', | |
| data: strainData, | |
| borderColor: '#eab308', | |
| backgroundColor: 'rgba(234, 179, 8, 0.1)', | |
| fill: true, | |
| tension: 0.4, | |
| pointRadius: 2, | |
| pointHoverRadius: 4 | |
| }] | |
| }, | |
| options: { | |
| responsive: true, | |
| maintainAspectRatio: false, | |
| scales: { | |
| y: { | |
| beginAtZero: true, | |
| max: 21, | |
| grid: { | |
| color: 'rgba(0, 0, 0, 0.05)' | |
| } | |
| }, | |
| x: { | |
| grid: { | |
| display: false | |
| } | |
| } | |
| }, | |
| plugins: { | |
| legend: { display: false } | |
| } | |
| } | |
| }); | |
| } | |
| } | |
| function initializeSleepCharts() { | |
| // Sleep Stages Chart | |
| const sleepStagesCtx = document.getElementById('sleepStagesChart'); | |
| if (sleepStagesCtx && !charts.sleepStages) { | |
| const timeLabels = Array.from({length: 24}, (_, i) => { | |
| const hour = i < 10 ? `0${i}:00` : `${i}:00`; | |
| return hour; | |
| }); | |
| const sleepData = timeLabels.map((_, i) => { | |
| if (i < 22 || i > 23) return 0; | |
| if (i === 22) return 2; | |
| if (i === 23) return 3; | |
| return i % 4 + 1; | |
| }); | |
| charts.sleepStages = new Chart(sleepStagesCtx, { | |
| type: 'bar', | |
| data: { | |
| labels: timeLabels, | |
| datasets: [{ | |
| label: 'Sleep Stage', | |
| data: sleepData, | |
| backgroundColor: sleepData.map(value => { | |
| if (value === 0) return '#e5e7eb'; | |
| if (value === 1) return '#ddd6fe'; | |
| if (value === 2) return '#93c5fd'; | |
| if (value === 3) return '#60a5fa'; | |
| return '#8b5cf6'; | |
| }), | |
| borderWidth: 0 | |
| }] | |
| }, | |
| options: { | |
| responsive: true, | |
| maintainAspectRatio: false, | |
| scales: { | |
| y: { | |
| display: false, | |
| max: 4 | |
| }, | |
| x: { | |
| ticks: { | |
| maxTicksLimit: 12 | |
| }, | |
| grid: { | |
| display: false | |
| } | |
| } | |
| }, | |
| plugins: { | |
| legend: { display: false }, | |
| tooltip: { | |
| callbacks: { | |
| label: function(context) { | |
| const stages = ['', 'Light Sleep', 'Deep Sleep', 'REM Sleep', 'Awake']; | |
| return stages[context.parsed.y] || 'Awake'; | |
| } | |
| } | |
| } | |
| } | |
| } | |
| }); | |
| } | |
| } | |
| function initializeRecoveryChart() { | |
| // Recovery Ring Chart | |
| const recoveryRingCtx = document.getElementById('recoveryRing'); | |
| if (recoveryRingCtx && !charts.recoveryRing) { | |
| charts.recoveryRing = new Chart(recoveryRingCtx, { | |
| type: 'doughnut', | |
| data: { | |
| datasets: [{ | |
| data: [78, 22], | |
| backgroundColor: ['#10b981', '#d1fae5'], | |
| borderWidth: 0 | |
| }] | |
| }, | |
| options: { | |
| responsive: true, | |
| maintainAspectRatio: false, | |
| cutout: '75%', | |
| plugins: { | |
| legend: { display: false }, | |
| tooltip: { enabled: false } | |
| } | |
| } | |
| }); | |
| } | |
| } | |
| // Journal functionality | |
| document.addEventListener('DOMContentLoaded', function() { | |
| // Mood buttons | |
| document.querySelectorAll('.mood-btn').forEach(btn => { | |
| btn.addEventListener('click', function() { | |
| document.querySelectorAll('.mood-btn').forEach(b => b.classList.remove('selected')); | |
| this.classList.add('selected'); | |
| selectedMood = parseInt(this.dataset.mood); | |
| }); | |
| }); | |
| // Energy slider | |
| const energySlider = document.getElementById('energyLevel'); | |
| const energyValue = document.getElementById('energyValue'); | |
| if (energySlider && energyValue) { | |
| energySlider.addEventListener('input', function() { | |
| energyValue.textContent = this.value; | |
| }); | |
| } | |
| // Stress slider | |
| const stressSlider = document.getElementById('stressLevel'); | |
| const stressValue = document.getElementById('stressValue'); | |
| if (stressSlider && stressValue) { | |
| stressSlider.addEventListener('input', function() { | |
| stressValue.textContent = this.value; | |
| }); | |
| } | |
| // Journal form submission | |
| const journalForm = document.getElementById('journalForm'); | |
| if (journalForm) { | |
| journalForm.addEventListener('submit', function(e) { | |
| e.preventDefault(); | |
| const formData = { | |
| mood: selectedMood, | |
| energy: document.getElementById('energyLevel').value, | |
| stress: document.getElementById('stressLevel').value, | |
| activities: Array.from(document.querySelectorAll('.activity-checkbox:checked')).map(cb => cb.value), | |
| nutrition: Array.from(document.querySelectorAll('.nutrition-checkbox:checked')).map(cb => cb.value), | |
| notes: document.getElementById('journalNotes').value, | |
| timestamp: new Date().toISOString() | |
| }; | |
| // Save to localStorage (in real app, would send to server) | |
| let journalEntries = JSON.parse(localStorage.getItem('journalEntries') || '[]'); | |
| journalEntries.push(formData); | |
| localStorage.setItem('journalEntries', JSON.stringify(journalEntries)); | |
| // Show success message | |
| showNotification('Journal entry saved successfully!'); | |
| // Reset form | |
| journalForm.reset(); | |
| document.querySelectorAll('.mood-btn').forEach(b => b.classList.remove('selected')); | |
| selectedMood = null; | |
| // Go back to dashboard after a short delay | |
| setTimeout(() => showView('dashboardView'), 1500); | |
| }); | |
| } | |
| }); | |
| // Notification system | |
| function showNotification(message, type = 'success') { | |
| const notification = document.createElement('div'); | |
| notification.className = `fixed top-20 left-4 right-4 max-w-md mx-auto px-4 py-3 rounded-lg shadow-lg z-50 transform transition-all duration-300 ${ | |
| type === 'success' ? 'bg-emerald-500 text-white' : 'bg-red-500 text-white' | |
| }`; | |
| notification.innerHTML = ` | |
| <div class="flex items-center space-x-2"> | |
| <i data-feather="${type === 'success' ? 'check-circle' : 'alert-circle'}" class="w-5 h-5"></i> | |
| <span>${message}</span> | |
| </div> | |
| `; | |
| document.body.appendChild(notification); | |
| feather.replace(); | |
| // Animate in | |
| setTimeout(() => { | |
| notification.classList.add('translate-y-0'); | |
| }, 100); | |
| // Remove after 3 seconds | |
| setTimeout(() => { | |
| notification.classList.add('translate-y-full', 'opacity-0'); | |
| setTimeout(() => notification.remove(), 300); | |
| }, 3000); | |
| } | |
| // Simulate real-time data updates | |
| function updateMetrics() { | |
| // Update heart rate with slight variation | |
| const heartRateElements = document.querySelectorAll('[data-metric="heart-rate"]'); | |
| heartRateElements.forEach(el => { | |
| const currentRate = parseInt(el.textContent); | |
| const variation = Math.floor(Math.random() * 3) - 1; | |
| const newRate = Math.max(50, Math.min(60, currentRate + variation)); | |
| el.textContent = newRate + ' bpm'; | |
| }); | |
| } | |
| // Initialize app on load | |
| window.addEventListener('load', function() { | |
| showView('dashboardView'); | |
| // Update metrics every 30 seconds | |
| setInterval(updateMetrics, 30000); | |
| }); | |
| // Handle page visibility changes | |
| document.addEventListener('visibilitychange', function() { | |
| if (!document.hidden) { | |
| // Refresh data when page becomes visible again | |
| if (currentView === 'strainView') { | |
| initializeStrainCharts(); | |
| } else if (currentView === 'sleepView') { | |
| initializeSleepCharts(); | |
| } else if (currentView === 'recoveryView') { | |
| initializeRecoveryChart(); | |
| } | |
| } | |
| }); | |
| // Touch gestures for mobile | |
| let touchStartX = 0; | |
| let touchEndX = 0; | |
| document.addEventListener('touchstart', function(e) { | |
| touchStartX = e.changedTouches[0].screenX; | |
| }); | |
| document.addEventListener('touchend', function(e) { | |
| touchEndX = e.changedTouches[0].screenX; | |
| handleSwipe(); | |
| }); | |
| function handleSwipe() { | |
| const swipeThreshold = 50; | |
| const diff = touchStartX - touchEndX; | |
| if (Math.abs(diff) > swipeThreshold) { | |
| const views = ['dashboardView', 'strainView', 'sleepView', 'recoveryView', 'journalView']; | |
| const currentIndex = views.indexOf(currentView); | |
| if (diff > 0 && currentIndex < views.length - 1) { | |
| // Swipe left - next view | |
| showView(views[currentIndex + 1]); | |
| } else if (diff < 0 && currentIndex > 0) { | |
| // Swipe right - previous view | |
| showView(views[currentIndex - 1]); | |
| } | |
| } | |
| } |