// Enhanced Mobile-First Application Initialization document.addEventListener('DOMContentLoaded', function() { initializeMobileApp(); }); // Check if running on mobile device const isMobileDevice = () => { return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent) || (window.innerWidth <= 768 && 'ontouchstart' in window); }; // Check if device supports touch const isTouchDevice = () => { return 'ontouchstart' in window || navigator.maxTouchPoints > 0 || navigator.msMaxTouchPoints > 0; }; function initializeMobileApp() { console.log('đ Initializing Mobile-First RelevantReflex System...'); console.log(`đą Device: ${isMobileDevice() ? 'Mobile' : 'Desktop'}`); console.log(`đ Touch: ${isTouchDevice() ? 'Supported' : 'Not Supported'}`); initializeMobileDetection(); initializeTouchInteractions(); initializeMobileNavigation(); initializeSwipeGestures(); initializeMobileAnimations(); initializePerformanceOptimizations(); initializeMobileAccessibility(); initializeOrientationHandling(); initializePWAFeatures(); initializeOfflineSupport(); console.log('â Mobile application initialization complete'); } // Mobile Device Detection and Setup function initializeMobileDetection() { // Add device classes to body const body = document.body; if (isMobileDevice()) { body.classList.add('is-mobile'); } if (isTouchDevice()) { body.classList.add('is-touch'); // Remove hover styles on touch devices const style = document.createElement('style'); style.textContent = ` @media (hover: none) and (pointer: coarse) { .nav-item:hover, .action-btn:hover, .card:hover { transform: none !important; background: inherit !important; box-shadow: inherit !important; } } `; document.head.appendChild(style); } // Detect safe area support if (CSS.supports('padding: env(safe-area-inset-top)')) { body.classList.add('has-safe-area'); } // Set viewport height CSS custom property for mobile browsers const setVH = () => { const vh = window.innerHeight * 0.01; document.documentElement.style.setProperty('--vh', `${vh}px`); }; setVH(); window.addEventListener('resize', setVH); window.addEventListener('orientationchange', () => { setTimeout(setVH, 100); // Delay for orientation change }); } // Enhanced Touch Interactions function initializeTouchInteractions() { console.log('đ Initializing touch interactions...'); // Prevent 300ms click delay on mobile let touchStartTime = 0; let touchStartTarget = null; document.addEventListener('touchstart', function(e) { touchStartTime = Date.now(); touchStartTarget = e.target; }, { passive: true }); document.addEventListener('touchend', function(e) { const touchEndTime = Date.now(); const touchDuration = touchEndTime - touchStartTime; // Fast tap detection (under 200ms) if (touchDuration < 200 && e.target === touchStartTarget) { e.target.classList.add('fast-tap'); setTimeout(() => { e.target.classList.remove('fast-tap'); }, 150); } }, { passive: true }); // Enhanced touch feedback for interactive elements const interactiveElements = document.querySelectorAll( '.nav-item, .mobile-nav-item, .action-btn, .profile-btn, ' + '.card-action, .quick-action-btn, .survey-item, .activity-item, ' + '.member-item, .status-item' ); interactiveElements.forEach(element => { // Touch start feedback element.addEventListener('touchstart', function(e) { this.classList.add('touch-active'); // Create touch ripple effect if (!this.querySelector('.touch-ripple')) { createTouchRipple(this, e.touches[0]); } }, { passive: true }); // Touch end cleanup element.addEventListener('touchend', function() { setTimeout(() => { this.classList.remove('touch-active'); }, 150); }, { passive: true }); // Touch cancel cleanup element.addEventListener('touchcancel', function() { this.classList.remove('touch-active'); }, { passive: true }); }); // Add CSS for touch states const touchStyles = document.createElement('style'); touchStyles.textContent = ` .touch-active { transform: scale(0.98) !important; opacity: 0.8 !important; transition: all 0.1s ease !important; } .fast-tap { animation: fastTapFeedback 0.15s ease-out; } @keyframes fastTapFeedback { 0% { transform: scale(1); } 50% { transform: scale(0.95); } 100% { transform: scale(1); } } .touch-ripple { position: absolute; border-radius: 50%; background: rgba(255, 255, 255, 0.6); transform: scale(0); animation: touchRipple 0.6s ease-out; pointer-events: none; z-index: 1; } @keyframes touchRipple { to { transform: scale(4); opacity: 0; } } `; document.head.appendChild(touchStyles); } // Create touch ripple effect function createTouchRipple(element, touch) { const ripple = document.createElement('div'); const rect = element.getBoundingClientRect(); const size = Math.max(rect.width, rect.height); const x = touch.clientX - rect.left - size / 2; const y = touch.clientY - rect.top - size / 2; ripple.className = 'touch-ripple'; ripple.style.cssText = ` width: ${size}px; height: ${size}px; left: ${x}px; top: ${y}px; `; element.style.position = 'relative'; element.style.overflow = 'hidden'; element.appendChild(ripple); setTimeout(() => { ripple.remove(); }, 600); } // Enhanced Mobile Navigation function initializeMobileNavigation() { console.log('đą Initializing mobile navigation...'); const mobileMenuToggle = document.querySelector('.mobile-menu-toggle'); const mobileNav = document.querySelector('.mobile-nav'); const body = document.body; let isMenuOpen = false; if (mobileMenuToggle && mobileNav) { // Prevent body scroll when menu is open const toggleBodyScroll = (disable) => { if (disable) { body.style.position = 'fixed'; body.style.top = `-${window.scrollY}px`; body.style.width = '100%'; } else { const scrollY = body.style.top; body.style.position = ''; body.style.top = ''; body.style.width = ''; window.scrollTo(0, parseInt(scrollY || '0') * -1); } }; // Enhanced menu toggle with proper animations const toggleMenu = () => { isMenuOpen = !isMenuOpen; // Update toggle button const icon = mobileMenuToggle.querySelector('i'); icon.style.transform = isMenuOpen ? 'rotate(90deg)' : 'rotate(0deg)'; mobileMenuToggle.setAttribute('aria-expanded', isMenuOpen); // Update menu visibility if (isMenuOpen) { mobileNav.classList.add('show'); mobileNav.setAttribute('aria-hidden', 'false'); toggleBodyScroll(true); // Focus first nav item for accessibility setTimeout(() => { const firstNavItem = mobileNav.querySelector('.mobile-nav-item'); if (firstNavItem) firstNavItem.focus(); }, 100); // Stagger animation for nav items const navItems = mobileNav.querySelectorAll('.mobile-nav-item'); navItems.forEach((item, index) => { item.style.opacity = '0'; item.style.transform = 'translateY(20px)'; setTimeout(() => { item.style.transition = 'all 0.3s ease'; item.style.opacity = '1'; item.style.transform = 'translateY(0)'; }, index * 50); }); } else { mobileNav.classList.remove('show'); mobileNav.setAttribute('aria-hidden', 'true'); toggleBodyScroll(false); // Return focus to toggle button mobileMenuToggle.focus(); } }; // Touch-optimized menu toggle let touchStartY = 0; let isSwiping = false; mobileMenuToggle.addEventListener('touchstart', (e) => { touchStartY = e.touches[0].clientY; isSwiping = false; }, { passive: true }); mobileMenuToggle.addEventListener('touchmove', (e) => { const touchY = e.touches[0].clientY; const deltaY = Math.abs(touchY - touchStartY); if (deltaY > 10) isSwiping = true; }, { passive: true }); mobileMenuToggle.addEventListener('touchend', (e) => { if (!isSwiping) { e.preventDefault(); toggleMenu(); } }); mobileMenuToggle.addEventListener('click', (e) => { e.preventDefault(); toggleMenu(); }); // Close menu when clicking outside (for larger screens) document.addEventListener('click', (e) => { if (isMenuOpen && !mobileNav.contains(e.target) && !mobileMenuToggle.contains(e.target)) { toggleMenu(); } }); // Keyboard navigation mobileNav.addEventListener('keydown', (e) => { if (e.key === 'Escape' && isMenuOpen) { toggleMenu(); } }); // Enhanced mobile nav item interactions const mobileNavItems = document.querySelectorAll('.mobile-nav-item'); mobileNavItems.forEach(item => { item.addEventListener('click', function(e) { if (!this.classList.contains('active')) { e.preventDefault(); // Visual feedback this.style.transform = 'scale(0.95)'; setTimeout(() => { this.style.transform = 'scale(1)'; }, 150); // Remove active from all items mobileNavItems.forEach(i => i.classList.remove('active')); // Add active to clicked item this.classList.add('active'); // Close menu after navigation setTimeout(() => { if (isMenuOpen) toggleMenu(); simulatePageNavigation(this.getAttribute('href')); }, 300); } }); }); } } // Swipe Gesture Support function initializeSwipeGestures() { console.log('đ Initializing swipe gestures...'); let touchStartX = 0; let touchStartY = 0; let touchEndX = 0; let touchEndY = 0; // Swipe detection on cards const swipeableElements = document.querySelectorAll( '.survey-item, .activity-item, .member-item' ); swipeableElements.forEach(element => { element.addEventListener('touchstart', (e) => { touchStartX = e.touches[0].clientX; touchStartY = e.touches[0].clientY; }, { passive: true }); element.addEventListener('touchend', (e) => { touchEndX = e.changedTouches[0].clientX; touchEndY = e.changedTouches[0].clientY; handleSwipe(element); }, { passive: true }); }); function handleSwipe(element) { const deltaX = touchEndX - touchStartX; const deltaY = touchEndY - touchStartY; const minSwipeDistance = 50; // Horizontal swipe detection if (Math.abs(deltaX) > Math.abs(deltaY) && Math.abs(deltaX) > minSwipeDistance) { if (deltaX > 0) { handleSwipeRight(element); } else { handleSwipeLeft(element); } } } function handleSwipeRight(element) { // Swipe right action (e.g., mark as read, like) element.style.transform = 'translateX(20px)'; element.style.background = 'rgba(5, 150, 105, 0.1)'; setTimeout(() => { element.style.transform = 'translateX(0)'; element.style.background = ''; }, 300); console.log('đ Swipe right on:', element); } function handleSwipeLeft(element) { // Swipe left action (e.g., archive, delete) element.style.transform = 'translateX(-20px)'; element.style.background = 'rgba(220, 38, 38, 0.1)'; setTimeout(() => { element.style.transform = 'translateX(0)'; element.style.background = ''; }, 300); console.log('đ Swipe left on:', element); } } // Mobile-Optimized Animations function initializeMobileAnimations() { console.log('đŦ Initializing mobile animations...'); // Respect user's motion preferences const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches; if (prefersReducedMotion) { console.log('⥠Reduced motion preferred - using minimal animations'); document.body.classList.add('reduced-motion'); return; } // Intersection Observer for mobile-optimized scroll animations const observerOptions = { threshold: [0.1, 0.25], rootMargin: '0px 0px -50px 0px' }; const animationObserver = new IntersectionObserver((entries) => { entries.forEach(entry => { if (entry.isIntersecting) { const element = entry.target; element.style.opacity = '1'; element.style.transform = 'translateY(0)'; element.classList.add('animate-in'); // Reduced animation duration for mobile if (isMobileDevice()) { element.style.animationDuration = '0.4s'; } } }); }, observerOptions); // Observe elements with animation classes document.querySelectorAll('.fade-in-up, .slide-in-right').forEach(el => { el.style.opacity = '0'; el.style.transform = 'translateY(30px)'; el.style.transition = 'all 0.4s cubic-bezier(0.4, 0, 0.2, 1)'; animationObserver.observe(el); }); // Staggered animations with mobile optimization document.querySelectorAll('[class*="stagger-"]').forEach((el, index) => { const delay = isMobileDevice() ? (index * 0.05) : (index * 0.1); el.style.animationDelay = `${delay}s`; }); } // Performance Optimizations for Mobile function initializePerformanceOptimizations() { console.log('⥠Initializing performance optimizations...'); // Lazy loading for images const lazyImages = document.querySelectorAll('img[data-src]'); if ('IntersectionObserver' in window) { const imageObserver = new IntersectionObserver((entries) => { entries.forEach(entry => { if (entry.isIntersecting) { const img = entry.target; img.src = img.dataset.src; img.classList.remove('lazy'); imageObserver.unobserve(img); } }); }); lazyImages.forEach(img => { img.classList.add('lazy'); imageObserver.observe(img); }); } // Debounced scroll handler let scrollTimer = null; const scrollHandler = () => { document.body.classList.add('is-scrolling'); if (scrollTimer !== null) { clearTimeout(scrollTimer); } scrollTimer = setTimeout(() => { document.body.classList.remove('is-scrolling'); }, 150); }; window.addEventListener('scroll', scrollHandler, { passive: true }); // Resource hints for critical resources const preloadCriticalResources = () => { const criticalCSS = document.createElement('link'); criticalCSS.rel = 'preload'; criticalCSS.as = 'style'; criticalCSS.href = 'assets/css/critical.css'; document.head.appendChild(criticalCSS); }; // Preload on user interaction let userInteracted = false; const preloadOnInteraction = () => { if (!userInteracted) { userInteracted = true; preloadCriticalResources(); } }; document.addEventListener('touchstart', preloadOnInteraction, { once: true, passive: true }); document.addEventListener('click', preloadOnInteraction, { once: true }); // Memory usage monitoring if (performance.memory) { const checkMemoryUsage = () => { const memoryInfo = performance.memory; const usedMB = memoryInfo.usedJSHeapSize / 1048576; const totalMB = memoryInfo.totalJSHeapSize / 1048576; console.log(`đ Memory usage: ${usedMB.toFixed(1)}MB / ${totalMB.toFixed(1)}MB`); // Warn if memory usage is high if (usedMB > 50) { console.warn('â ī¸ High memory usage detected'); } }; setInterval(checkMemoryUsage, 30000); // Check every 30 seconds } } // Mobile Accessibility Enhancements function initializeMobileAccessibility() { console.log('âŋ Initializing mobile accessibility...'); // Enhanced focus management for mobile let focusedBeforeModal = null; // Skip links for mobile navigation const createSkipLinks = () => { const skipNav = document.createElement('a'); skipNav.href = '#main-content'; skipNav.textContent = 'Skip to main content'; skipNav.className = 'skip-link'; skipNav.style.cssText = ` position: absolute; top: -40px; left: 10px; background: var(--primary); color: white; padding: 8px 16px; text-decoration: none; border-radius: 4px; z-index: 100000; font-size: 14px; transition: top 0.3s ease; `; skipNav.addEventListener('focus', () => { skipNav.style.top = '10px'; }); skipNav.addEventListener('blur', () => { skipNav.style.top = '-40px'; }); document.body.insertBefore(skipNav, document.body.firstChild); }; createSkipLinks(); // Touch target size enforcement const enforceMinTouchTargets = () => { const interactiveElements = document.querySelectorAll( 'button, a, input, select, textarea, [role="button"], [tabindex="0"]' ); interactiveElements.forEach(element => { const rect = element.getBoundingClientRect(); const minSize = 44; // WCAG minimum if (rect.width < minSize || rect.height < minSize) { element.style.minWidth = `${minSize}px`; element.style.minHeight = `${minSize}px`; element.style.display = 'inline-flex'; element.style.alignItems = 'center'; element.style.justifyContent = 'center'; } }); }; // Run after DOM is fully loaded setTimeout(enforceMinTouchTargets, 1000); // Announce page changes for screen readers const announcePageChange = (message) => { const announcement = document.createElement('div'); announcement.setAttribute('aria-live', 'polite'); announcement.setAttribute('aria-atomic', 'true'); announcement.style.cssText = ` position: absolute; left: -10000px; width: 1px; height: 1px; overflow: hidden; `; announcement.textContent = message; document.body.appendChild(announcement); setTimeout(() => { document.body.removeChild(announcement); }, 1000); }; // Export for use in navigation window.announcePageChange = announcePageChange; } // Orientation Change Handling function initializeOrientationHandling() { console.log('đ Initializing orientation handling...'); let currentOrientation = screen.orientation?.angle || window.orientation; const handleOrientationChange = () => { const newOrientation = screen.orientation?.angle || window.orientation; if (newOrientation !== currentOrientation) { currentOrientation = newOrientation; // Add orientation class to body document.body.classList.remove('portrait', 'landscape'); if (Math.abs(newOrientation) === 90) { document.body.classList.add('landscape'); console.log('đą Switched to landscape mode'); } else { document.body.classList.add('portrait'); console.log('đą Switched to portrait mode'); } // Trigger resize event for components that need to reflow setTimeout(() => { window.dispatchEvent(new Event('resize')); }, 100); // Re-calculate viewport height const vh = window.innerHeight * 0.01; document.documentElement.style.setProperty('--vh', `${vh}px`); } }; // Listen for orientation changes if (screen.orientation) { screen.orientation.addEventListener('change', handleOrientationChange); } else { window.addEventListener('orientationchange', handleOrientationChange); } // Initial orientation setup handleOrientationChange(); } // Progressive Web App Features function initializePWAFeatures() { console.log('đą Initializing PWA features...'); // Add to homescreen prompt let deferredPrompt = null; window.addEventListener('beforeinstallprompt', (e) => { console.log('đž PWA install prompt available'); e.preventDefault(); deferredPrompt = e; // Show custom install button showInstallPrompt(); }); const showInstallPrompt = () => { const installBanner = document.createElement('div'); installBanner.className = 'install-banner'; installBanner.innerHTML = `