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 = `
${t('auth_welcome')}
`;
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 = `
`;
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 => `
${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_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 => `
`).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_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')}
`;
break;
case 'thoughts':
mainContent.innerHTML = `
${t('thought_title')}
`;
initializeEmotionSliders();
break;
case 'gratitude':
mainContent.innerHTML = `
`;
break;
case 'progress':
mainContent.innerHTML = `
${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;