/** * Mobile Navigation Handler * Clean, accessible mobile navigation implementation */ class MobileNavigation { constructor() { this.menuToggle = document.querySelector('.mobile-menu-toggle'); this.mobileMenu = document.querySelector('.nav-menu-mobile'); this.overlay = document.querySelector('.mobile-menu-overlay'); this.menuLinks = document.querySelectorAll('.nav-menu-mobile a'); this.closeButton = document.querySelector('.mobile-menu-close'); this.isOpen = false; this.init(); } init() { if (!this.menuToggle || !this.mobileMenu) return; // Toggle button click this.menuToggle.addEventListener('click', () => this.toggleMenu()); // Close button click if (this.closeButton) { this.closeButton.addEventListener('click', () => this.closeMenu()); } // Overlay click closes menu this.overlay.addEventListener('click', () => this.closeMenu()); // Menu links click closes menu this.menuLinks.forEach(link => { link.addEventListener('click', () => this.closeMenu()); }); // ESC key closes menu document.addEventListener('keydown', (e) => { if (e.key === 'Escape' && this.isOpen) { this.closeMenu(); } }); // Prevent body scroll when menu is open this.handleBodyScroll(); } toggleMenu() { this.isOpen ? this.closeMenu() : this.openMenu(); } openMenu() { this.isOpen = true; this.menuToggle.classList.add('active'); this.mobileMenu.classList.add('active'); this.overlay.classList.add('active'); // Update ARIA attributes this.menuToggle.setAttribute('aria-expanded', 'true'); this.mobileMenu.setAttribute('aria-hidden', 'false'); // Prevent body scroll document.body.style.overflow = 'hidden'; // Focus management setTimeout(() => { const firstLink = this.mobileMenu.querySelector('a'); if (firstLink) firstLink.focus(); }, 300); } closeMenu() { this.isOpen = false; this.menuToggle.classList.remove('active'); this.mobileMenu.classList.remove('active'); this.overlay.classList.remove('active'); // Update ARIA attributes this.menuToggle.setAttribute('aria-expanded', 'false'); this.mobileMenu.setAttribute('aria-hidden', 'true'); // Restore body scroll document.body.style.overflow = ''; // Return focus to toggle button this.menuToggle.focus(); } handleBodyScroll() { // Save scroll position when menu opens let scrollPosition = 0; const observer = new MutationObserver(() => { if (this.isOpen) { scrollPosition = window.pageYOffset; document.body.style.position = 'fixed'; document.body.style.top = `-${scrollPosition}px`; document.body.style.width = '100%'; } else { document.body.style.position = ''; document.body.style.top = ''; document.body.style.width = ''; window.scrollTo(0, scrollPosition); } }); observer.observe(this.mobileMenu, { attributes: true, attributeFilter: ['class'] }); } } // Smooth scroll for anchor links class SmoothScroll { constructor() { this.init(); } init() { document.querySelectorAll('a[href^="#"]').forEach(anchor => { anchor.addEventListener('click', (e) => { const href = anchor.getAttribute('href'); if (href === '#') return; e.preventDefault(); const target = document.querySelector(href); if (target) { const offset = 80; // Account for fixed navbar const targetPosition = target.offsetTop - offset; window.scrollTo({ top: targetPosition, behavior: 'smooth' }); } }); }); } } // Responsive image loading class ResponsiveImages { constructor() { this.init(); } init() { // Check if user prefers reduced motion const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches; if (prefersReducedMotion) { // Disable animations document.documentElement.style.setProperty('--animation-duration', '0.01s'); } // Lazy load images on mobile if ('IntersectionObserver' in window && window.innerWidth <= 768) { const images = document.querySelectorAll('img[data-src]'); const imageObserver = new IntersectionObserver((entries) => { entries.forEach(entry => { if (entry.isIntersecting) { const img = entry.target; img.src = img.dataset.src; img.removeAttribute('data-src'); imageObserver.unobserve(img); } }); }); images.forEach(img => imageObserver.observe(img)); } } } // Initialize on DOM load document.addEventListener('DOMContentLoaded', () => { new MobileNavigation(); new SmoothScroll(); new ResponsiveImages(); // Hide mobile menu button styles until JS loads document.documentElement.classList.add('js-loaded'); }); // Handle orientation change window.addEventListener('orientationchange', () => { // Close mobile menu on orientation change const mobileNav = document.querySelector('.nav-menu-mobile'); if (mobileNav && mobileNav.classList.contains('active')) { const event = new Event('click'); document.querySelector('.mobile-menu-overlay').dispatchEvent(event); } });