import { authAPI, moodAPI, thoughtAPI, gratitudeAPI, progressAPI, notificationAPI, exerciseAPI, isAuthenticated, initializeAPI } from './offline-api.js'; import { translations } from './translations.js'; // Language Management let currentLang = localStorage.getItem('appLang') || 'en'; function t(key) { const langObj = translations[currentLang] || translations['en']; return langObj[key] || key; } function setLanguage(lang) { currentLang = lang; localStorage.setItem('appLang', lang); document.documentElement.lang = lang; document.documentElement.dir = lang === 'he' ? 'rtl' : 'ltr'; // Update Header updateHeaderTranslations(); // Re-render current section const activeNav = document.querySelector('.nav-item.active'); if (activeNav) { // Identify section from onclick attribute or class // Simple re-render of home if unsure const onclick = activeNav.getAttribute('onclick'); if (onclick && onclick.includes("'")) { const section = onclick.split("'")[1]; showSection(section); } else { showSection('home'); } } else { showSection('home'); } // Update nav labels document.querySelectorAll('.nav-label').forEach((el, index) => { const keys = ['nav_home', 'nav_mood', 'nav_thoughts', 'nav_gratitude', 'nav_progress']; if (keys[index]) el.textContent = t(keys[index]); }); // Update Proactive Badge const badge = document.getElementById('proactive-badge'); if (badge) badge.textContent = t('proactive_badge'); } function updateHeaderTranslations() { // Update app title if dynamic, currently static in HTML but let's allow JS update // const title = document.querySelector('.app-title'); // if (title) title.innerHTML = `self_improvement ${t('app_title')}`; } // Sound Manager using Web Audio API class SoundManager { constructor() { this.ctx = new (window.AudioContext || window.webkitAudioContext)(); this.enabled = true; } playTone(freq, type, duration, vol = 0.1) { if (!this.enabled) return; const osc = this.ctx.createOscillator(); const gain = this.ctx.createGain(); osc.type = type; osc.frequency.setValueAtTime(freq, this.ctx.currentTime); gain.gain.setValueAtTime(vol, this.ctx.currentTime); gain.gain.exponentialRampToValueAtTime(0.01, this.ctx.currentTime + duration); osc.connect(gain); gain.connect(this.ctx.destination); osc.start(); osc.stop(this.ctx.currentTime + duration); } playSuccess() { // Happy ascending chime this.playTone(523.25, 'sine', 0.3, 0.1); // C5 setTimeout(() => this.playTone(659.25, 'sine', 0.3, 0.1), 100); // E5 setTimeout(() => this.playTone(783.99, 'sine', 0.6, 0.1), 200); // G5 } playBreathIn() { // Gentle swelling sound if (!this.enabled) return; const osc = this.ctx.createOscillator(); const gain = this.ctx.createGain(); osc.frequency.setValueAtTime(200, this.ctx.currentTime); osc.frequency.linearRampToValueAtTime(300, this.ctx.currentTime + 4); gain.gain.setValueAtTime(0, this.ctx.currentTime); gain.gain.linearRampToValueAtTime(0.1, this.ctx.currentTime + 2); gain.gain.linearRampToValueAtTime(0, this.ctx.currentTime + 4); osc.connect(gain); gain.connect(this.ctx.destination); osc.start(); osc.stop(this.ctx.currentTime + 4); } playBreathOut() { // Gentle descending sound if (!this.enabled) return; const osc = this.ctx.createOscillator(); const gain = this.ctx.createGain(); osc.frequency.setValueAtTime(300, this.ctx.currentTime); osc.frequency.linearRampToValueAtTime(200, this.ctx.currentTime + 4); gain.gain.setValueAtTime(0, this.ctx.currentTime); gain.gain.linearRampToValueAtTime(0.1, this.ctx.currentTime + 2); gain.gain.linearRampToValueAtTime(0, this.ctx.currentTime + 4); osc.connect(gain); gain.connect(this.ctx.destination); osc.start(); osc.stop(this.ctx.currentTime + 4); } } const soundManager = new SoundManager(); // Initialize app when DOM is loaded document.addEventListener('DOMContentLoaded', async function() { try { console.log('App initialization started'); // Initial Language Setup document.documentElement.lang = currentLang; document.documentElement.dir = currentLang === 'he' ? 'rtl' : 'ltr'; setLanguage(currentLang); // Updates Nav labels immediately // Check authentication if (!isAuthenticated()) { console.log('User not authenticated, showing login modal'); showLoginModal(); // Hide initial loader if login modal is shown const loader = document.getElementById('initial-loader'); if (loader) loader.style.display = 'none'; return; } // Initialize API await initializeAPI(); // Create floating particles createParticles(); // Add fade-in animation to cards document.querySelectorAll('.card').forEach((card, index) => { card.style.animationDelay = `${index * 0.1}s`; card.classList.add('fade-in'); }); // Add motivational quotes rotation startQuoteRotation(); // Initialize emotion sliders initializeEmotionSliders(); // Initialize belief rating slider initializeBeliefSlider(); // Load saved data from API await loadSavedData(); // Render Home Page initially showSection('home'); // Initialize inactivity tracker initInactivityTracker(); console.log('App initialization complete'); } catch (error) { console.error('Initialization error:', error); const loader = document.getElementById('initial-loader'); if (loader) { loader.innerHTML = `

${t('init_error')}

${error.message}

`; } } }); // Inactivity Tracker let inactivityTimer; function initInactivityTracker() { const resetTimer = () => { clearTimeout(inactivityTimer); const badge = document.getElementById('proactive-badge'); if (badge) badge.classList.remove('visible'); inactivityTimer = setTimeout(() => { if (badge) badge.classList.add('visible'); }, 30000); // 30 seconds }; document.addEventListener('mousemove', resetTimer); document.addEventListener('keydown', resetTimer); document.addEventListener('click', resetTimer); resetTimer(); } // Success Ping Animation function triggerSuccessPing() { soundManager.playSuccess(); const ping = document.getElementById('success-ping'); if (!ping) return; // Clone and replace to restart animation const newPing = ping.cloneNode(true); newPing.innerHTML = '
'; ping.parentNode.replaceChild(newPing, ping); } // Quick Action Menu & Language Selector function showQuickActionMenu() { const menu = document.createElement('div'); menu.className = 'exercise-modal'; menu.style.display = 'block'; menu.innerHTML = `

${t('quick_title')}

📝 ${t('home_log_mood')}
🌬️ ${t('home_breathe')}
🧘 ${t('quick_relax_now')}
`; document.body.appendChild(menu); } // Login Modal function showLoginModal() { const loginModal = document.createElement('div'); loginModal.id = 'login-modal'; loginModal.innerHTML = ` `; document.body.appendChild(loginModal); // Add event listeners document.getElementById('login-form').addEventListener('submit', handleLogin); document.getElementById('register-form').addEventListener('submit', handleRegister); } async function handleLogin(e) { e.preventDefault(); const email = document.getElementById('email').value; const password = document.getElementById('password').value; try { await authAPI.login(email, password); document.getElementById('login-modal').remove(); location.reload(); } catch (error) { showToast(t('auth_login_failed') + ': ' + error.message, 'error'); } } async function handleRegister(e) { e.preventDefault(); const name = document.getElementById('reg-name').value; const email = document.getElementById('reg-email').value; const password = document.getElementById('reg-password').value; try { await authAPI.register(name, email, password); document.getElementById('login-modal').remove(); location.reload(); } catch (error) { showToast(t('auth_reg_failed') + ': ' + error.message, 'error'); } } function showRegisterForm() { document.getElementById('login-form').style.display = 'none'; document.getElementById('register-form').style.display = 'block'; } function showLoginForm() { document.getElementById('register-form').style.display = 'none'; document.getElementById('login-form').style.display = 'block'; } // Mood tracking with API async function selectMood(element, mood) { // Remove previous selection document.querySelectorAll('.mood-card').forEach(card => { card.classList.remove('selected'); }); // Add selection to clicked mood element.classList.add('selected'); // Store selected mood localStorage.setItem('selectedMood', mood); } async function updateIntensity(value) { document.getElementById('intensityValue').textContent = value; localStorage.setItem('moodIntensity', value); } async function saveMoodEntry() { // Try to get mood from DOM first (more reliable than localStorage if user just clicked) const selectedCard = document.querySelector('.mood-card.selected'); let moodType = selectedCard ? selectedCard.dataset.mood : localStorage.getItem('selectedMood'); // Default intensity to 5 if not set let intensity = localStorage.getItem('moodIntensity'); if (!intensity) { const slider = document.querySelector('.slider'); intensity = slider ? slider.value : '5'; } const notesInput = document.getElementById('moodNotes'); const notes = notesInput ? notesInput.value : ''; if (!moodType) { showToast(t('mood_select_warning'), 'warning'); return; } try { await moodAPI.trackMood(moodType, parseInt(intensity), notes); triggerSuccessPing(); // Alive Feedback showToast(t('mood_saved_success')); // Clear form document.querySelectorAll('.mood-card').forEach(card => { card.classList.remove('selected'); }); // Reset slider const slider = document.querySelector('.slider'); if (slider) slider.value = 5; document.getElementById('intensityValue').textContent = '5'; if (document.getElementById('moodNotes')) { document.getElementById('moodNotes').value = ''; } localStorage.removeItem('selectedMood'); localStorage.removeItem('moodIntensity'); // Update progress await updateProgress(); } catch (error) { showToast(t('mood_save') + ' failed: ' + error.message, 'error'); } } // Thought record with API async function saveThoughtRecord() { const situation = document.getElementById('situation') ? document.getElementById('situation').value : ''; const thoughts = document.getElementById('thoughts') ? document.getElementById('thoughts').value : ''; const evidence = document.getElementById('evidence') ? document.getElementById('evidence').value : ''; const alternative = document.getElementById('alternative') ? document.getElementById('alternative').value : ''; if (!situation || !thoughts) { showToast(t('thought_fill_warning'), 'warning'); return; } // Get emotions const emotions = []; document.querySelectorAll('.emotion-inputs').forEach(input => { const nameInput = input.querySelector('.emotion-name'); const valueInput = input.querySelector('.emotion-slider'); const name = nameInput ? nameInput.value : ''; const value = valueInput ? valueInput.value : ''; if (name && value) { emotions.push({ name, intensity: parseInt(value) }); } }); try { await thoughtAPI.saveThoughtRecord({ situation, thoughts, emotions, evidence, alternativeThought: alternative, date: new Date().toISOString() }); triggerSuccessPing(); // Alive Feedback showToast(t('thought_saved_success')); closeExercise('thought-record-exercise'); // Update progress await updateProgress(); } catch (error) { showToast(t('thought_save') + ' failed: ' + error.message, 'error'); } } // Gratitude entry with API async function saveGratitudeEntry() { const entries = []; document.querySelectorAll('.gratitude-input').forEach(input => { if (input.value.trim()) { entries.push(input.value.trim()); } }); if (entries.length === 0) { showToast(t('gratitude_empty_warning'), 'warning'); return; } try { await gratitudeAPI.saveGratitudeEntry({ entries, date: new Date().toISOString() }); triggerSuccessPing(); // Alive Feedback showToast(t('gratitude_saved_success')); closeExercise('gratitude-exercise'); // Update progress await updateProgress(); } catch (error) { showToast(t('gratitude_save') + ' failed: ' + error.message, 'error'); } } // Load saved data from API async function loadSavedData() { try { // Initialize notification system initializeNotifications(); // Update progress based on saved data await updateProgress(); } catch (error) { console.error('Failed to load saved data:', error); } } // Progress with API async function updateProgress() { console.log('updateProgress: Starting update...'); try { // Add timeout to prevent infinite loading const timeoutPromise = new Promise((_, reject) => setTimeout(() => reject(new Error('Timeout loading stats')), 5000) ); const statsPromise = progressAPI.getProgressStats(); const historyPromise = progressAPI.getProgressHistory(); const [stats, history] = await Promise.race([ Promise.all([statsPromise, historyPromise]), timeoutPromise ]); console.log('updateProgress: Data received', stats); const statsHTML = `
${(stats.today && stats.today.mood_score) ? stats.today.mood_score : '-'}
${t('home_stats_mood')}
${(stats.totals && stats.totals.totalSessions) ? stats.totals.totalSessions : 0}
${t('home_stats_sessions')}
${(stats.week && stats.week.avgMood) ? (Math.round(stats.week.avgMood * 10) / 10) : '-'}
${t('home_stats_avg')}
${(stats.totals && stats.totals.totalGratitude) ? stats.totals.totalGratitude : 0}
${t('home_stats_gratitude')}
`; // Update progress page stats const progressContainer = document.getElementById('progress-stats'); if (progressContainer) { progressContainer.innerHTML = statsHTML; } // Update home page stats const homeStatsContainer = document.getElementById('home-stats-container'); if (homeStatsContainer) { homeStatsContainer.innerHTML = `
${statsHTML}
`; } // Draw weekly chart drawWeeklyChart(history); console.log('updateProgress: Update complete'); } catch (error) { console.error('Failed to update progress:', error); // Fallback UI to remove spinner const errorHTML = `
⚠️
${t('home_retry')}
`; const homeStatsContainer = document.getElementById('home-stats-container'); if (homeStatsContainer) { homeStatsContainer.innerHTML = errorHTML; } } } // Analytics with API async function loadAnalyticsData() { try { const moodHistory = await moodAPI.getMoodHistory(); const thoughtRecords = await thoughtAPI.getThoughtRecords(); const gratitudeEntries = await gratitudeAPI.getGratitudeEntries(); // Update analytics displays updateAnalyticsDisplay(moodHistory, thoughtRecords, gratitudeEntries); } catch (error) { console.error('Failed to load analytics data:', error); } } function updateAnalyticsDisplay(moodHistory, thoughtRecords, gratitudeEntries) { // Implementation for updating analytics charts and displays // This would use the data from the API to populate charts console.log('Updating analytics with:', { moodHistory, thoughtRecords, gratitudeEntries }); } // Export these functions to global scope for HTML onclick handlers window.selectMood = selectMood; window.updateIntensity = updateIntensity; window.saveMoodEntry = saveMoodEntry; window.saveThoughtRecord = saveThoughtRecord; window.saveGratitudeEntry = saveGratitudeEntry; window.showLoginModal = showLoginModal; window.showRegisterForm = showRegisterForm; window.showLoginForm = showLoginForm; window.handleLogin = handleLogin; window.handleRegister = handleRegister; window.deleteNotification = deleteNotification; window.markNotificationAsRead = markNotificationAsRead; window.addNotification = addNotification; window.logout = logout; window.toggleNotifications = toggleNotifications; window.renderHistory = renderHistory; window.setLanguage = setLanguage; // Logout function async function logout() { try { await authAPI.logout(); location.reload(); } catch (error) { console.error('Logout failed:', error); location.reload(); // Reload anyway to clear state } } // Toggle Notifications function toggleNotifications() { const list = document.getElementById('notification-list'); if (list) { list.style.display = list.style.display === 'block' ? 'none' : 'block'; } } // Enhanced Toast Notification function showToast(message, type = 'success') { const toast = document.createElement('div'); toast.className = 'success-message'; // Keep class for animation toast.textContent = message; const bgColor = type === 'error' ? 'var(--error)' : type === 'warning' ? 'var(--warning)' : 'var(--success)'; toast.style.cssText = ` position: fixed; top: 20px; right: 20px; background: ${bgColor}; color: white; padding: 16px 24px; border-radius: 12px; box-shadow: 0 8px 24px rgba(0,0,0,0.2); z-index: 2100; animation: slideInDown 0.3s ease; font-weight: 500; display: flex; align-items: center; gap: 8px; `; // Add icon based on type const icon = type === 'error' ? '⚠️' : type === 'warning' ? '🔔' : '✅'; toast.innerHTML = `${icon} ${message}`; document.body.appendChild(toast); setTimeout(() => { toast.style.animation = 'fadeOut 0.3s ease forwards'; setTimeout(() => toast.remove(), 300); }, 3000); } // Replace showSuccessMessage with showToast alias for backward compatibility const showSuccessMessage = (msg) => showToast(msg, 'success'); function createParticles() { const particlesContainer = document.getElementById('particles'); if (!particlesContainer) return; const particleCount = 20; for (let i = 0; i < particleCount; i++) { const particle = document.createElement('div'); particle.className = 'particle'; particle.style.left = Math.random() * 100 + '%'; particle.style.animationDelay = Math.random() * 6 + 's'; particle.style.animationDuration = (Math.random() * 3 + 3) + 's'; particlesContainer.appendChild(particle); } } function startQuoteRotation() { // Quotes could also be translated, but keeping them static or random is fine for now // Ideally, add quotes to translations.js } function initializeEmotionSliders() { document.querySelectorAll('.emotion-slider').forEach(slider => { slider.addEventListener('input', function() { const valueDisplay = this.nextElementSibling; if (valueDisplay && valueDisplay.classList.contains('emotion-value')) { valueDisplay.textContent = this.value; } }); }); } function initializeBeliefSlider() { const beliefSlider = document.getElementById('belief-slider'); const beliefValue = document.getElementById('belief-value'); if (beliefSlider && beliefValue) { beliefSlider.addEventListener('input', function() { beliefValue.textContent = this.value; }); } } function drawWeeklyChart(history) { const canvas = document.getElementById('weeklyChart'); if (!canvas || !history) return; const ctx = canvas.getContext('2d'); const width = canvas.width; const height = canvas.height; // Clear canvas ctx.clearRect(0, 0, width, height); // Get last 7 days from history const days = []; const scores = []; for (let i = 6; i >= 0; i--) { const date = new Date(); date.setDate(date.getDate() - i); const dateStr = date.toISOString().split('T')[0]; const dayEntry = history.find(entry => entry.date === dateStr); const score = dayEntry ? dayEntry.mood_score : 0; days.push(date.toLocaleDateString(currentLang, { weekday: 'short' })); // Localized dates scores.push(score); } // Draw chart const maxValue = 10; // Mood intensity max is 10 const barWidth = width / 7 * 0.6; const spacing = width / 7; ctx.fillStyle = '#FF6B6B'; ctx.font = '12px sans-serif'; ctx.textAlign = 'center'; scores.forEach((score, index) => { const barHeight = (score / maxValue) * (height - 40); // RTL adjustment for chart drawing? No, canvas is coordinate based. // But the order of days might need to be right-to-left for Hebrew visually? // Standard charts usually go left-to-right (past -> future) even in RTL. const x = index * spacing + (spacing - barWidth) / 2; const y = height - barHeight - 20; // Draw bar // Color based on score if (score >= 7) ctx.fillStyle = '#66BB6A'; // Green else if (score >= 4) ctx.fillStyle = '#FFB74D'; // Orange else ctx.fillStyle = '#FF5252'; // Red if (score > 0) { ctx.fillRect(x, y, barWidth, barHeight); ctx.fillStyle = '#666'; ctx.fillText(score, x + barWidth / 2, y - 5); } // Draw day label ctx.fillStyle = '#666'; ctx.fillText(days[index], x + barWidth / 2, height - 5); }); } // Notification system with API integration async function initializeNotifications() { try { // Load notifications from API const notifications = await notificationAPI.getNotifications(); // Update notification badge const badge = document.getElementById('notification-badge'); if (badge) { const unreadCount = notifications.filter(n => !n.read).length; badge.textContent = unreadCount; badge.style.display = unreadCount > 0 ? 'block' : 'none'; } // Display notifications in dropdown const dropdown = document.getElementById('notification-list'); if (dropdown) { dropdown.innerHTML = notifications.length > 0 ? notifications.map(n => `
${n.title}
${n.message}
${formatTime(n.timestamp)}
`).join('') : `
${t('notifications_empty')}
`; } } catch (error) { console.error('Failed to load notifications:', error); // Fallback to localStorage if API fails const savedNotifications = JSON.parse(localStorage.getItem('notifications') || '[]'); const notifications = savedNotifications; // Update notification badge const badge = document.getElementById('notification-badge'); if (badge) { const unreadCount = notifications.filter(n => !n.read).length; badge.textContent = unreadCount; badge.style.display = unreadCount > 0 ? 'block' : 'none'; } // Display notifications in dropdown const dropdown = document.getElementById('notification-list'); if (dropdown) { dropdown.innerHTML = notifications.length > 0 ? notifications.map(n => `
${n.title}
${n.message}
${formatTime(n.timestamp)}
`).join('') : `
${t('notifications_empty')}
`; } } } function formatTime(timestamp) { const date = new Date(timestamp); const now = new Date(); const diff = now - date; if (diff < 60000) return t('just_now'); if (diff < 3600000) return `${Math.floor(diff / 60000)}${t('ago_m')}`; if (diff < 86400000) return `${Math.floor(diff / 3600000)}${t('ago_h')}`; return date.toLocaleDateString(currentLang); } async function deleteNotification(id) { try { await notificationAPI.deleteNotification(id); await initializeNotifications(); } catch (error) { console.error('Failed to delete notification:', error); // Fallback to localStorage let notifications = JSON.parse(localStorage.getItem('notifications') || '[]'); notifications = notifications.filter(n => n.id !== id); localStorage.setItem('notifications', JSON.stringify(notifications)); initializeNotifications(); } } async function markNotificationAsRead(id) { try { await notificationAPI.markAsRead(id); await initializeNotifications(); } catch (error) { console.error('Failed to mark notification as read:', error); } } async function addNotification(title, message, type = 'info') { try { const notification = { title, message, type, timestamp: new Date().toISOString(), read: false }; await notificationAPI.addNotification(notification); await initializeNotifications(); } catch (error) { console.error('Failed to add notification:', error); // Fallback to localStorage let notifications = JSON.parse(localStorage.getItem('notifications') || '[]'); const newNotification = { id: Date.now().toString(), title, message, type, timestamp: new Date().toISOString(), read: false }; notifications.push(newNotification); localStorage.setItem('notifications', JSON.stringify(notifications)); initializeNotifications(); } } // Render History async function renderHistory(type) { const container = document.getElementById('history-container'); if (!container) return; // Clear existing content container.innerHTML = '
'; try { let data = []; let content = ''; switch(type) { case 'mood': data = await moodAPI.getMoodHistory(); if (data.length === 0) { content = `
${t('history_empty_mood')}
`; } else { content = data.map(entry => `
${getMoodEmoji(entry.mood_type)} ${t('mood_' + entry.mood_type)} ${formatDate(entry.created_at)}
${t('mood_intensity')}: ${entry.intensity}/10 ${entry.notes ? `

"${entry.notes}"

` : ''}
`).join(''); } break; case 'thoughts': data = await thoughtAPI.getThoughtRecords(); if (data.length === 0) { content = `
${t('history_empty_thoughts')}
`; } else { content = data.map(entry => `
${t('thought_title')} ${formatDate(entry.created_at)}
${t('thought_situation').split('(')[0]}: ${entry.situation}
${t('thought_automatic').split('(')[0]}: ${entry.automatic_thought}
${t('thought_emotions')}: ${entry.emotion} (${entry.emotion_intensity}%)
`).join(''); } break; case 'gratitude': data = await gratitudeAPI.getGratitudeEntries(); if (data.length === 0) { content = `
${t('history_empty_gratitude')}
`; } else { content = data.map(entry => `
${t('nav_gratitude')} ${formatDate(entry.created_at)}

"${entry.entry}"

`).join(''); } break; } container.innerHTML = content; // Animate items document.querySelectorAll('.history-item').forEach((item, index) => { item.style.opacity = '0'; item.style.animation = `slideInUp 0.3s ease forwards ${index * 0.05}s`; }); } catch (error) { container.innerHTML = `
${error.message}
`; } } function getMoodEmoji(type) { const map = { 'joy': '😊', 'peace': '😌', 'energy': '⚡', 'anxiety': '😰', 'sadness': '😢', 'anger': '😠' }; return map[type] || '😐'; } function capitalize(str) { return str.charAt(0).toUpperCase() + str.slice(1); } function formatDate(dateStr) { const date = new Date(dateStr); return date.toLocaleDateString(currentLang) + ' ' + date.toLocaleTimeString(currentLang, {hour: '2-digit', minute:'2-digit'}); } // Render Dynamic Content function showSection(sectionId) { const mainContent = document.getElementById('main-content'); // Update active nav document.querySelectorAll('.nav-item').forEach(item => { item.classList.remove('active'); if (item.onclick && item.onclick.toString().includes(sectionId)) { item.classList.add('active'); } }); switch(sectionId) { case 'home': mainContent.innerHTML = `

${t('home_welcome')}

${t('home_subtitle')}

📝 ${t('home_log_mood')}
🧠 ${t('home_record_thought')}
🙏 ${t('home_gratitude')}

${t('home_daily_vibe')}

${t('home_quick_relief')}

🌬️ ${t('home_breathe')}
🧘 ${t('home_relax')}
`; updateProgress(); // Refresh stats break; case 'mood': mainContent.innerHTML = `

${t('mood_title')}

😊 ${t('mood_joy')}
😌 ${t('mood_peace')}
${t('mood_energy')}
😰 ${t('mood_anxiety')}
😢 ${t('mood_sadness')}
😠 ${t('mood_anger')}
${t('mood_intensity')} 5
`; break; case 'thoughts': mainContent.innerHTML = `

${t('thought_title')}

50
`; initializeEmotionSliders(); break; case 'gratitude': mainContent.innerHTML = `

${t('gratitude_title')}

${t('gratitude_intro')}

`; break; case 'progress': mainContent.innerHTML = `

${t('progress_title')}

${t('progress_weekly')}

${t('progress_history')}

${t('history_select_prompt')}
`; updateProgress(); renderHistory('mood'); // Default to mood break; } } function quickRelax() { // Launch the new Guided Grounding Experience startGuidedRelaxation(); } // --- Guided Relaxation (Mindfulness System) --- let relaxationState = { mode: null, // 'grounding', 'body_scan', 'visualization' step: 0, isActive: false, isPaused: false, steps: [], timer: null, duration: 0 }; // Data Providers function getGroundingSteps() { return [ { title: t('guided_sight_title'), instruction: t('guided_sight_instruction'), sub: t('guided_sight_sub'), count: 5, icon: "👁️", color: "#64B5F6" }, { title: t('guided_touch_title'), instruction: t('guided_touch_instruction'), sub: t('guided_touch_sub'), count: 4, icon: "✋", color: "#81C784" }, { title: t('guided_sound_title'), instruction: t('guided_sound_instruction'), sub: t('guided_sound_sub'), count: 3, icon: "👂", color: "#FFB74D" }, { title: t('guided_smell_title'), instruction: t('guided_smell_instruction'), sub: t('guided_smell_sub'), count: 2, icon: "👃", color: "#BA68C8" }, { title: t('guided_taste_title'), instruction: t('guided_taste_instruction'), sub: t('guided_taste_sub'), count: 1, icon: "👅", color: "#E57373" } ]; } function getBodyScanSteps() { return [ { instruction: t('scan_intro'), duration: 5000 }, { instruction: t('scan_feet'), duration: 8000 }, { instruction: t('scan_legs'), duration: 8000 }, { instruction: t('scan_stomach'), duration: 8000 }, { instruction: t('scan_chest'), duration: 8000 }, { instruction: t('scan_shoulders'), duration: 8000 }, { instruction: t('scan_face'), duration: 8000 }, { instruction: t('scan_outro'), duration: 6000 } ]; } function getVisualizationSteps() { return [ { instruction: t('vis_intro'), duration: 6000 }, { instruction: t('vis_step1'), duration: 10000 }, { instruction: t('vis_step2'), duration: 10000 }, { instruction: t('vis_step3'), duration: 10000 }, { instruction: t('vis_step4'), duration: 10000 }, { instruction: t('vis_outro'), duration: 8000 } ]; } // Entry Point function startGuidedRelaxation() { relaxationState = { mode: null, step: 0, isActive: true, isPaused: false, steps: [], timer: null, duration: 0 }; const overlay = document.createElement('div'); overlay.id = 'guided-relaxation-overlay'; overlay.className = 'guided-overlay'; document.body.appendChild(overlay); // Add styles dynamically if not present (using the styles we wrote to guided-styles.css but injecting here for simplicity in single file context if needed, but best to rely on CSS file) // Assuming guided-styles.css is linked or merged. For safety, we rely on the styles.css update. renderModeSelection(); } function renderModeSelection() { const overlay = document.getElementById('guided-relaxation-overlay'); if (!overlay) return; overlay.innerHTML = `

${t('guided_select_mode')}

🌿 ${t('mode_grounding')}
🧘 ${t('mode_body_scan')}
🌄 ${t('mode_visualization')}
`; } function startSession(mode) { relaxationState.mode = mode; relaxationState.step = 0; relaxationState.isActive = true; const overlay = document.getElementById('guided-relaxation-overlay'); if (overlay) overlay.className = `guided-overlay mode-${mode}`; if (mode === 'grounding') { relaxationState.steps = getGroundingSteps(); renderGroundingStep(); } else if (mode === 'body_scan') { relaxationState.steps = getBodyScanSteps(); runAutoSession(); } else if (mode === 'visualization') { relaxationState.steps = getVisualizationSteps(); runAutoSession(); } } // --- Grounding Logic (Interactive) --- function renderGroundingStep() { const overlay = document.getElementById('guided-relaxation-overlay'); if (!overlay || !relaxationState.isActive) return; const currentStep = relaxationState.steps[relaxationState.step]; const progressDots = Array(currentStep.count).fill('
').join(''); overlay.innerHTML = `
${currentStep.icon}
${currentStep.instruction}
${currentStep.sub}
${progressDots}
`; speakText(`${currentStep.instruction} ${currentStep.sub}`); } let currentDotIndex = 0; function nextGroundingSubStep() { const dots = document.querySelectorAll('.progress-dot'); if (currentDotIndex < dots.length) { dots[currentDotIndex].classList.add('active'); if (navigator.vibrate) navigator.vibrate(50); soundManager.playTone(400 + (currentDotIndex * 50), 'sine', 0.1, 0.1); currentDotIndex++; if (currentDotIndex === dots.length) { setTimeout(() => { currentDotIndex = 0; relaxationState.step++; if (relaxationState.step < relaxationState.steps.length) { renderGroundingStep(); } else { finishSession(); } }, 1000); } } } // --- Auto Session Logic (Body Scan & Visualization) --- function runAutoSession() { const overlay = document.getElementById('guided-relaxation-overlay'); if (!overlay || !relaxationState.isActive) return; const step = relaxationState.steps[relaxationState.step]; const totalSteps = relaxationState.steps.length; const progress = ((relaxationState.step) / totalSteps) * 100; // Visuals based on mode let visualHTML = ''; if (relaxationState.mode === 'body_scan') { visualHTML = `
🧘
`; } else { visualHTML = `
🌄
`; } overlay.innerHTML = `
${visualHTML}
${step.instruction}
`; // Speak and advance speakText(step.instruction, () => { // On end of speech, wait remainder of duration if (!relaxationState.isActive) return; relaxationState.timer = setTimeout(() => { relaxationState.step++; if (relaxationState.step < relaxationState.steps.length) { runAutoSession(); } else { finishSession(); } }, 2000); // Short pause after speech }); // Animate progress bar setTimeout(() => { const bar = document.querySelector('.guided-progress-bar'); if (bar) bar.style.width = `${((relaxationState.step + 1) / totalSteps) * 100}%`; }, 100); } function finishSession() { const overlay = document.getElementById('guided-relaxation-overlay'); if (overlay) { overlay.innerHTML = `
🌟
${t('guided_complete_title')}
${t('guided_complete_sub')}
`; speakText(`${t('guided_complete_title')} ${t('guided_complete_sub')}`); triggerSuccessPing(); // Log stats const duration = relaxationState.mode === 'grounding' ? 180 : relaxationState.mode === 'body_scan' ? 300 : 240; exerciseAPI.logSession(relaxationState.mode, duration).then(() => updateProgress()); } } function closeGuidedRelaxation() { relaxationState.isActive = false; if (relaxationState.timer) clearTimeout(relaxationState.timer); currentDotIndex = 0; const overlay = document.getElementById('guided-relaxation-overlay'); if (overlay) overlay.remove(); if (window.speechSynthesis) { window.speechSynthesis.cancel(); } } // Enhanced TTS Wrapper function speakText(text, onEndCallback) { if ('speechSynthesis' in window) { window.speechSynthesis.cancel(); const utterance = new SpeechSynthesisUtterance(text); utterance.rate = 0.85; // Slightly slower for relaxation utterance.pitch = 1.0; const langMap = { 'en': 'en-US', 'ru': 'ru-RU', 'he': 'he-IL' }; utterance.lang = langMap[currentLang] || 'en-US'; if (onEndCallback) { utterance.onend = onEndCallback; } window.speechSynthesis.speak(utterance); } else { // Fallback if no TTS if (onEndCallback) setTimeout(onEndCallback, 3000); } } function finishRelaxSession(btn) { btn.closest('.exercise-modal').remove(); exerciseAPI.logSession('relaxation', 120).then(() => { triggerSuccessPing(); showToast(t('mood_saved_success')); // Reuse success message or create new updateProgress(); }); } function breathingExercise() { startBreathing(); } function emergencyHelp() { alert('If you\'re in crisis, please call emergency services or a crisis hotline.'); } function startThoughtRecord() { showSection('thoughts'); } function startMindfulness() { // Implement or route } function startGratitude() { showSection('gratitude'); } function closeExercise(exerciseId) { document.getElementById(exerciseId).style.display = 'none'; showSection('home'); } function addEmotionInput() { const emotionContainer = document.querySelector('.emotion-inputs').parentElement; const newEmotionInput = document.createElement('div'); newEmotionInput.className = 'emotion-inputs'; newEmotionInput.innerHTML = ` 50 `; emotionContainer.insertBefore(newEmotionInput, emotionContainer.lastElementChild); initializeEmotionSliders(); } function addGratitudeInput() { const gratitudeContainer = document.querySelector('.gratitude-inputs'); const newInput = document.createElement('input'); newInput.type = 'text'; newInput.className = 'form-input gratitude-input'; newInput.placeholder = t('gratitude_placeholder'); gratitudeContainer.appendChild(newInput); } // --- Smart Breathing System --- let breathingState = { isActive: false, technique: 'balance', timer: null }; function getBreathingTechniques() { return { balance: { name: 'Balance', label: t('breath_balance'), phases: [ { name: 'inhale', duration: 5500, label: t('breath_in'), scale: 1.8 }, { name: 'exhale', duration: 5500, label: t('breath_out'), scale: 1.0 } ] }, relax: { name: 'Relax', label: t('breath_relax'), phases: [ { name: 'inhale', duration: 4000, label: t('breath_in'), scale: 1.8 }, { name: 'hold', duration: 7000, label: t('breath_hold'), scale: 1.8 }, { name: 'exhale', duration: 8000, label: t('breath_out'), scale: 1.0 } ] }, focus: { name: 'Focus', label: t('breath_focus'), phases: [ { name: 'inhale', duration: 4000, label: t('breath_in'), scale: 1.8 }, { name: 'hold', duration: 4000, label: t('breath_hold'), scale: 1.8 }, { name: 'exhale', duration: 4000, label: t('breath_out'), scale: 1.0 }, { name: 'hold', duration: 4000, label: t('breath_hold'), scale: 1.0 } ] } }; } function startBreathing() { // Create immersive overlay const overlay = document.createElement('div'); overlay.id = 'smart-breathing-overlay'; overlay.className = 'breathing-overlay'; // Render techniques dynamically const techniques = getBreathingTechniques(); overlay.innerHTML = `
${t('breath_ready')}
${t('breath_sit')}
`; document.body.appendChild(overlay); // Start with default or last used setBreathingTechnique('balance'); } function setBreathingTechnique(tech) { breathingState.technique = tech; const techniques = getBreathingTechniques(); // Update buttons document.querySelectorAll('.technique-btn').forEach(btn => { btn.classList.remove('active'); // Match by label content if(btn.textContent === techniques[tech].label) { btn.classList.add('active'); } }); stopSmartBreathingLoop(); startSmartBreathingLoop(); } function startSmartBreathingLoop() { breathingState.isActive = true; let currentPhaseIndex = 0; const loop = async () => { if (!breathingState.isActive) return; const techniques = getBreathingTechniques(); const technique = techniques[breathingState.technique]; const phase = technique.phases[currentPhaseIndex]; updateBreathingUI(phase.name, phase.label, phase.duration, phase.scale); // Audio & Haptics if (phase.name === 'inhale') { soundManager.playBreathIn(); if (navigator.vibrate) navigator.vibrate(50); } else if (phase.name === 'exhale') { soundManager.playBreathOut(); if (navigator.vibrate) navigator.vibrate([30, 30]); } else { // Hold if (navigator.vibrate) navigator.vibrate(20); } // Wait for phase duration await new Promise(resolve => { breathingState.timer = setTimeout(resolve, phase.duration); }); // Move to next phase currentPhaseIndex = (currentPhaseIndex + 1) % technique.phases.length; if (breathingState.isActive) loop(); }; loop(); } function updateBreathingUI(phaseName, label, duration, scale) { const circle = document.getElementById('breath-circle'); const text = document.getElementById('breath-instruction'); const sub = document.getElementById('breath-sub'); if (!circle) return; // Update Text text.textContent = label; text.style.animation = 'none'; text.offsetHeight; // Trigger reflow text.style.animation = 'fadeIn 0.5s'; sub.textContent = (duration / 1000) + 's'; // Update Visuals circle.className = 'breath-circle-main ' + phaseName; circle.style.transition = `transform ${duration}ms linear`; circle.style.transform = `scale(${scale})`; } function stopSmartBreathingLoop() { breathingState.isActive = false; if (breathingState.timer) clearTimeout(breathingState.timer); } function closeSmartBreathing() { stopSmartBreathingLoop(); const overlay = document.getElementById('smart-breathing-overlay'); if (overlay) overlay.remove(); // Log session exerciseAPI.logSession('breathing', 60).then(() => { triggerSuccessPing(); showToast(t('breath_complete')); updateProgress(); }); } // Make global window.setBreathingTechnique = setBreathingTechnique; window.closeSmartBreathing = closeSmartBreathing; // Legacy wrappers function startBreathingExercise() { startBreathing(); } function stopBreathingExercise() { closeSmartBreathing(); } // Export additional functions window.addEmotionInput = addEmotionInput; window.addGratitudeInput = addGratitudeInput; window.startBreathingExercise = startBreathingExercise; window.stopBreathingExercise = stopBreathingExercise; window.deleteNotification = deleteNotification; window.quickRelax = quickRelax; window.startBreathing = breathingExercise; // Alias for startBreathing in HTML window.showQuickActionMenu = showQuickActionMenu; window.navigateTo = showSection; // Alias for navigateTo in HTML window.finishRelaxSession = finishRelaxSession; function showLanguageModal() { const menu = document.createElement('div'); menu.className = 'exercise-modal'; menu.style.display = 'block'; menu.innerHTML = `

Language / שפה / Язык

`; document.body.appendChild(menu); } window.showLanguageModal = showLanguageModal;