const navEl = document.getElementById("nav"); const contentEl = document.getElementById("content"); const menuToggle = document.querySelector(".menu-toggle"); const sidebar = document.querySelector(".sidebar"); const overlay = document.querySelector(".overlay"); function toggleMenu(open) { const isOpen = open ?? !sidebar.classList.contains("open"); sidebar.classList.toggle("open", isOpen); overlay?.classList.toggle("open", isOpen); menuToggle?.setAttribute("aria-expanded", isOpen); document.body.style.overflow = isOpen ? "hidden" : ""; } menuToggle?.addEventListener("click", () => toggleMenu()); overlay?.addEventListener("click", () => toggleMenu(false)); // Close menu when nav link clicked (mobile) navEl?.addEventListener("click", (e) => { if (e.target.closest("a")) toggleMenu(false); }); const buildId = new URL(import.meta.url).searchParams.get("v") || ""; function withBuild(url) { if (!buildId) return url; const sep = url.includes("?") ? "&" : "?"; return `${url}${sep}v=${encodeURIComponent(buildId)}`; } function escapeHtml(text) { return text .replaceAll("&", "&") .replaceAll("<", "<") .replaceAll(">", ">") .replaceAll('"', """) .replaceAll("'", "'"); } function normalizeDocPath(docPath) { let p = String(docPath || "").trim(); p = p.replaceAll("\\", "/"); p = p.replace(/^\/+/, ""); p = p.replace(/\.\.\//g, ""); if (!p.endsWith(".md")) p += ".md"; return p; } function getSelectedPath() { const hash = (location.hash || "").replace(/^#/, ""); if (!hash) return null; return normalizeDocPath(hash); } function setSelectedPath(docPath) { location.hash = normalizeDocPath(docPath); } async function fetchJson(path) { const res = await fetch(withBuild(path), { cache: "no-store" }); if (!res.ok) throw new Error(`Failed to fetch ${path}: ${res.status}`); return res.json(); } async function fetchText(path) { const res = await fetch(withBuild(path), { cache: "no-store" }); if (!res.ok) throw new Error(`Failed to fetch ${path}: ${res.status}`); return res.text(); } function renderNav(pages, activePath) { if (!pages.length) { navEl.innerHTML = ""; return; } navEl.innerHTML = pages .map((p) => { const path = normalizeDocPath(p.path); const title = escapeHtml(p.title || path); const current = activePath === path ? ` aria-current="page"` : ""; return `${title}`; }) .join(""); } function installContentLinkHandler() { contentEl.addEventListener("click", (e) => { const a = e.target?.closest?.("a"); if (!a) return; const href = a.getAttribute("href") || ""; if ( href.startsWith("http://") || href.startsWith("https://") || href.startsWith("mailto:") || href.startsWith("#") ) { return; } // Route relative markdown links through the SPA. if (href.endsWith(".md")) { e.preventDefault(); setSelectedPath(href); return; } }); } async function main() { if (!globalThis.marked) { contentEl.innerHTML = `
Markdown renderer failed to load.
`; return; } installContentLinkHandler(); let manifest; try { manifest = await fetchJson("./manifest.json"); } catch (e) { contentEl.innerHTML = `Missing manifest.json. Deploy the site via CI.
No docs yet. Add markdown files under zat/docs/ and push to main.
Failed to load ${escapeHtml(
activePath,
)}.