/** * Loads shared nav/footer and applies active menu + copyright year. * Requires pages to be served over HTTP (see server.js). */ (function () { var NAV_URL = "/components/nav.html"; var FOOTER_URL = "/components/footer.html"; function normalizePath(pathname) { if (!pathname) return "/"; var p = pathname.split("?")[0].split("#")[0]; try { p = decodeURIComponent(p); } catch (e) { /* ignore */ } if (p.endsWith("/index.html")) { p = p.slice(0, -"/index.html".length) || "/"; } if (p.length > 1 && p.endsWith("/")) { p = p.slice(0, -1); } return p.toLowerCase(); } function hrefToPath(href) { if (!href || href === "#" || href.indexOf("javascript:") === 0) return null; try { var url = new URL(href, window.location.origin); if (url.origin !== window.location.origin) return null; return normalizePath(url.pathname); } catch (e) { return null; } } function pathsMatch(current, target) { if (!target) return false; if (current === target) return true; if ( (current === "/" || current === "/index.html") && (target === "/" || target === "/index.html") ) { return true; } return false; } function highlightActiveNav(container) { var current = normalizePath(window.location.pathname); var links = container.querySelectorAll("a[href]"); links.forEach(function (anchor) { var target = hrefToPath(anchor.getAttribute("href")); if (!pathsMatch(current, target)) return; anchor.classList.add("active"); var item = anchor.closest(".nav-item"); if (item) item.classList.add("active"); if (anchor.classList.contains("dropdown-item")) { var parentItem = anchor.closest(".nav-item.dropdown"); if (parentItem) { parentItem.classList.add("active"); var toggle = parentItem.querySelector(".nav-link.dropdown-toggle"); if (toggle) toggle.classList.add("active"); } } }); } function updateCopyrightYear(root) { var year = String(new Date().getFullYear()); root.querySelectorAll("#current-year").forEach(function (el) { el.textContent = year; }); } function fetchText(url) { return fetch(url, { credentials: "same-origin" }).then(function (res) { if (!res.ok) throw new Error("Failed to load " + url + " (" + res.status + ")"); return res.text(); }); } function injectHtml(containerId, html) { var el = document.getElementById(containerId); if (!el) return null; el.innerHTML = html; return el; } function initLayout() { var navContainer = document.getElementById("nav-container"); var footerContainer = document.getElementById("footer-container"); if (!navContainer && !footerContainer) return; var tasks = []; if (navContainer) tasks.push(fetchText(NAV_URL)); else tasks.push(Promise.resolve(null)); if (footerContainer) tasks.push(fetchText(FOOTER_URL)); else tasks.push(Promise.resolve(null)); Promise.all(tasks) .then(function (parts) { var navHtml = parts[0]; var footerHtml = parts[1]; if (navHtml) { var navRoot = injectHtml("nav-container", navHtml); if (navRoot) highlightActiveNav(navRoot); } if (footerHtml) { var footerRoot = injectHtml("footer-container", footerHtml); if (footerRoot) updateCopyrightYear(footerRoot); } if (navHtml && window.jQuery && window.jQuery.fn.dropdown) { window.jQuery("#nav-container [data-toggle=\"dropdown\"]").dropdown(); } document.dispatchEvent(new CustomEvent("aifo:layout-ready")); }) .catch(function (err) { console.error("[layout.js]", err); }); } if (document.readyState === "loading") { document.addEventListener("DOMContentLoaded", initLayout); } else { initLayout(); } })();