/* ============================================================ components.js — Component loader + header/footer init Loads shared HTML components, initializes sticky header, mobile nav, and active link state. ============================================================ */ (function () { 'use strict'; /* --- Component Loader ----------------------------------- */ async function loadComponent(targetId, url) { const el = document.getElementById(targetId); if (!el) return; try { const res = await fetch(url); if (!res.ok) throw new Error(res.status); const html = await res.text(); el.innerHTML = html; } catch (err) { console.warn('[components.js] Could not load', url, err.message); } } /* --- Active nav link ------------------------------------ */ function markActiveNav() { const path = window.location.pathname.replace(/\/$/, '') || '/'; document.querySelectorAll('.header-nav a, .mobile-nav-links a').forEach(a => { const href = a.getAttribute('href').replace(/\/$/, '') || '/'; if (path === href || (href !== '/' && path.startsWith(href))) { a.classList.add('active'); } }); } /* --- Sticky header -------------------------------------- */ function initStickyHeader() { const header = document.querySelector('.site-header'); if (!header) return; const toggle = () => { header.classList.toggle('scrolled', window.scrollY > 60); }; toggle(); window.addEventListener('scroll', toggle, { passive: true }); } /* --- Mobile nav ---------------------------------------- */ function initMobileNav() { const btn = document.querySelector('.header-menu-btn'); const nav = document.querySelector('.mobile-nav'); const overlay = document.querySelector('.mobile-nav-overlay'); const close = document.querySelector('.mobile-nav-close'); if (!btn || !nav) return; const open = () => { nav.classList.add('open'); btn.classList.add('open'); document.body.style.overflow = 'hidden'; }; const shut = () => { nav.classList.remove('open'); btn.classList.remove('open'); document.body.style.overflow = ''; }; btn.addEventListener('click', open); if (overlay) overlay.addEventListener('click', shut); if (close) close.addEventListener('click', shut); document.addEventListener('keydown', e => { if (e.key === 'Escape') shut(); }); } /* --- Init on DOM ready ---------------------------------- */ async function init() { const base = document.querySelector('meta[name="site-root"]')?.content || '/'; await Promise.all([ loadComponent('site-header', base + 'components/header.html'), loadComponent('site-footer', base + 'components/footer.html'), ]); initStickyHeader(); initMobileNav(); markActiveNav(); } if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', init); } else { init(); } })();