/** * MealMood Main JavaScript * Handles dark mode, navigation, and dynamic content */ (function() { 'use strict'; function trackEvent(eventName, props) { if (window.plausible) { window.plausible(eventName, { props: props || {} }); } } // Dark Mode Management const DarkMode = { STORAGE_KEY: 'pj-theme', init() { const saved = localStorage.getItem(this.STORAGE_KEY); const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches; if (saved === 'dark' || (!saved && prefersDark)) { document.documentElement.setAttribute('data-theme', 'dark'); } else { document.documentElement.setAttribute('data-theme', 'light'); } // Listen for system preference changes window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', (e) => { if (!localStorage.getItem(this.STORAGE_KEY)) { document.documentElement.setAttribute('data-theme', e.matches ? 'dark' : 'light'); } }); }, toggle() { const current = document.documentElement.getAttribute('data-theme'); const next = current === 'dark' ? 'light' : 'dark'; document.documentElement.setAttribute('data-theme', next); localStorage.setItem(this.STORAGE_KEY, next); this.updateToggleButton(); }, updateToggleButton() { const btn = document.querySelector('.theme-toggle'); if (btn) { const isDark = document.documentElement.getAttribute('data-theme') === 'dark'; btn.setAttribute('aria-label', isDark ? 'Switch to light mode' : 'Switch to dark mode'); btn.innerHTML = isDark ? this.getSunIcon() : this.getMoonIcon(); } }, getSunIcon() { return ``; }, getMoonIcon() { return ``; } }; // Navigation Management const Navigation = { init() { this.setActiveLink(); this.setupMobileMenu(); this.setupKeyboardNav(); }, setActiveLink() { const currentPath = window.location.pathname; const links = document.querySelectorAll('.nav-link'); links.forEach(link => { const href = link.getAttribute('href'); const isActive = currentPath.endsWith(href) || (href === 'index.html' && (currentPath === '/' || currentPath.endsWith('/'))); if (isActive) { link.classList.add('active'); link.setAttribute('aria-current', 'page'); } }); }, setupMobileMenu() { const toggle = document.querySelector('.mobile-menu-toggle'); const nav = document.querySelector('.nav-links'); if (toggle && nav) { toggle.addEventListener('click', () => { const isOpen = nav.classList.toggle('open'); toggle.setAttribute('aria-expanded', isOpen); toggle.innerHTML = isOpen ? this.getCloseIcon() : this.getMenuIcon(); }); // Close menu on link click nav.querySelectorAll('a').forEach(link => { link.addEventListener('click', () => { nav.classList.remove('open'); toggle.setAttribute('aria-expanded', 'false'); toggle.innerHTML = this.getMenuIcon(); }); }); } }, setupKeyboardNav() { document.addEventListener('keydown', (e) => { if (e.key === 'Escape') { const nav = document.querySelector('.nav-links'); const toggle = document.querySelector('.mobile-menu-toggle'); if (nav && nav.classList.contains('open')) { nav.classList.remove('open'); toggle.setAttribute('aria-expanded', 'false'); toggle.innerHTML = this.getMenuIcon(); } } }); }, getMenuIcon() { return ``; }, getCloseIcon() { return ``; } }; // Dynamic Content const Content = { init() { this.insertYear(); this.insertConfig(); this.insertEffectiveDate(); }, insertYear() { document.querySelectorAll('[data-year]').forEach(el => { el.textContent = CONFIG.COPYRIGHT_YEAR; }); }, insertConfig() { document.querySelectorAll('[data-config]').forEach(el => { const key = el.getAttribute('data-config'); if (CONFIG[key]) { if (el.tagName === 'A') { el.href = CONFIG[key]; } else { el.textContent = CONFIG[key]; } } }); }, insertEffectiveDate() { document.querySelectorAll('[data-effective-date]').forEach(el => { const dateValue = CONFIG.EFFECTIVE_DATE === 'today' ? new Date() : new Date(CONFIG.EFFECTIVE_DATE); const date = Number.isNaN(dateValue.getTime()) ? new Date() : dateValue; el.textContent = date.toLocaleDateString('en-US', { year: 'numeric', month: 'long', day: 'numeric' }); }); } }; const ImageOptimization = { init() { document.querySelectorAll('img').forEach(img => { if (img.closest('.hero')) return; if (!img.hasAttribute('loading')) { img.setAttribute('loading', 'lazy'); } }); } }; // Smooth Scroll const SmoothScroll = { init() { document.querySelectorAll('a[href^="#"]').forEach(anchor => { anchor.addEventListener('click', (e) => { const targetId = anchor.getAttribute('href'); if (targetId === '#') return; const target = document.querySelector(targetId); if (target) { e.preventDefault(); target.scrollIntoView({ behavior: 'smooth', block: 'start' }); target.focus({ preventScroll: true }); } }); }); } }; // A/B Test: Hero Variants const HeroABTest = { STORAGE_KEY: 'pj-hero-variant', init() { if (!document.querySelector('.hero-variant-a-only, .hero-variant-b-only')) { return; } let variant = 'a'; try { variant = localStorage.getItem(this.STORAGE_KEY); if (!variant) { variant = Math.random() < 0.5 ? 'a' : 'b'; localStorage.setItem(this.STORAGE_KEY, variant); } } catch (err) { variant = 'a'; } document.body.classList.remove('hero-variant-a', 'hero-variant-b'); document.body.classList.add(`hero-variant-${variant}`); trackEvent('AB Test', { variant }); } }; // Analytics Events const Analytics = { init() { this.trackAppStoreClicks(); this.trackScrollDepth(); this.trackFooterLinks(); }, trackAppStoreClicks() { document.querySelectorAll('a[href*="apps.apple.com"]').forEach(link => { link.addEventListener('click', () => { let campaign = 'unknown'; try { const url = new URL(link.href); campaign = url.searchParams.get('ct') || 'unknown'; } catch (err) { campaign = 'unknown'; } trackEvent('AppStore Click', { location: campaign }); }); }); }, trackScrollDepth() { const scrollDepths = [25, 50, 75, 100]; const tracked = new Set(); window.addEventListener('scroll', () => { const scrollPercent = ((window.scrollY + window.innerHeight) / document.body.offsetHeight) * 100; scrollDepths.forEach(depth => { if (scrollPercent >= depth && !tracked.has(depth)) { tracked.add(depth); trackEvent('Deep Scroll', { depth: `${depth}%` }); } }); }); }, trackFooterLinks() { document.querySelectorAll('footer a').forEach(link => { link.addEventListener('click', () => { const destination = link.textContent.trim() || link.getAttribute('href'); trackEvent('Footer Link Click', { destination }); }); }); } }; const ExitIntent = { STORAGE_KEY: 'pj-exit-intent-shown', init() { this.modal = document.getElementById('exit-intent-modal'); if (!this.modal) return; const closeBtn = this.modal.querySelector('[data-exit-close]'); if (closeBtn) { closeBtn.addEventListener('click', () => this.close()); } this.modal.addEventListener('click', (event) => { if (event.target === this.modal) { this.close(); } }); document.addEventListener('mouseleave', (event) => { if (event.clientY < 0) { this.show(); } }); }, show() { try { if (localStorage.getItem(this.STORAGE_KEY)) return; } catch (err) { return; } this.modal.classList.add('is-visible'); this.modal.setAttribute('aria-hidden', 'false'); try { localStorage.setItem(this.STORAGE_KEY, 'true'); } catch (err) { // ignore storage errors } trackEvent('Exit Intent', { action: 'shown' }); }, close() { this.modal.classList.remove('is-visible'); this.modal.setAttribute('aria-hidden', 'true'); trackEvent('Exit Intent', { action: 'closed' }); } }; const StickyCTA = { init() { this.el = document.getElementById('sticky-mobile-cta'); if (!this.el) return; window.addEventListener('scroll', () => this.update()); this.update(); }, update() { const scrollPercent = ((window.scrollY + window.innerHeight) / document.body.offsetHeight) * 100; const isMobile = window.innerWidth <= 768; const shouldShow = isMobile && scrollPercent > 50; this.el.style.display = shouldShow ? 'block' : 'none'; this.el.setAttribute('aria-hidden', shouldShow ? 'false' : 'true'); } }; const CTAActions = { init() { document.querySelectorAll('[data-cta="learn-more"]').forEach(button => { button.addEventListener('click', () => { const target = document.querySelector('.features-section'); if (target) { target.scrollIntoView({ behavior: 'smooth', block: 'start' }); trackEvent('CTA Click', { action: 'learn_more' }); } }); }); } }; // Cookie Notice (only if analytics enabled) const CookieNotice = { STORAGE_KEY: 'pj-cookies-accepted', init() { if (!CONFIG.ENABLE_ANALYTICS) return; if (localStorage.getItem(this.STORAGE_KEY)) return; this.show(); }, show() { const notice = document.createElement('div'); notice.className = 'cookie-notice'; notice.innerHTML = ` `; document.body.appendChild(notice); }, accept() { localStorage.setItem(this.STORAGE_KEY, 'true'); this.hide(); // Initialize analytics here if needed }, decline() { localStorage.setItem(this.STORAGE_KEY, 'false'); this.hide(); }, hide() { const notice = document.querySelector('.cookie-notice'); if (notice) notice.remove(); } }; // FAQ Accordion const FAQ = { init() { document.querySelectorAll('.faq-question').forEach(question => { question.addEventListener('click', () => { trackEvent('FAQ Click', { question: question.textContent.trim() }); const item = question.parentElement; const isOpen = item.classList.contains('open'); // Close all others document.querySelectorAll('.faq-item.open').forEach(openItem => { openItem.classList.remove('open'); openItem.querySelector('.faq-question').setAttribute('aria-expanded', 'false'); }); // Toggle current if (!isOpen) { item.classList.add('open'); question.setAttribute('aria-expanded', 'true'); } }); // Keyboard support question.addEventListener('keydown', (e) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); question.click(); } }); }); } }; // Initialize everything on DOM ready document.addEventListener('DOMContentLoaded', () => { DarkMode.init(); DarkMode.updateToggleButton(); Navigation.init(); Content.init(); ImageOptimization.init(); SmoothScroll.init(); HeroABTest.init(); Analytics.init(); FAQ.init(); CookieNotice.init(); ExitIntent.init(); StickyCTA.init(); CTAActions.init(); // Setup theme toggle button const themeToggle = document.querySelector('.theme-toggle'); if (themeToggle) { themeToggle.addEventListener('click', () => DarkMode.toggle()); } }); // Expose necessary functions globally window.DarkMode = DarkMode; window.CookieNotice = CookieNotice; window.trackEvent = trackEvent; })();