/* global marked */ const DOCS = [ { id: "submission", title: "Submission Edition (Clean)", file: "../DANNY_STOCKER_INFRAFABRIC_DOSSIER_SUBMISSION_EDITION.md", }, { id: "submission-full", title: "Submission Edition (Clean, Full) v2.0", file: "../DANNY_STOCKER_INFRAFABRIC_DOSSIER_SUBMISSION_EDITION_FULL.md", }, { id: "data", title: "Data‑Driven Technical Report (Microlab)", file: "../DANNY_STOCKER_INFRAFABRIC_DOSSIER_DATA_DRIVEN_EDITION.md", }, { id: "data-full", title: "Data‑Driven Edition (Full) v2.0", file: "../DANNY_STOCKER_INFRAFABRIC_DOSSIER_DATA_DRIVEN_EDITION_FULL.md", }, { id: "uncut", title: "Uncut Dossier (Full)", file: "../DANNY_STOCKER_INFRAFABRIC_DOSSIER_UNCUT.md", }, ]; function slugify(value) { return String(value) .replace(/<[^>]+>/g, "") .trim() .toLowerCase() .replace(/[^\w\s-]/g, "") .replace(/\s+/g, "-") .replace(/-+/g, "-"); } function createSlugger() { const seen = new Map(); return (headingText) => { const base = slugify(headingText) || "section"; const current = seen.get(base) || 0; seen.set(base, current + 1); return current === 0 ? base : `${base}-${current + 1}`; }; } function setStatus(text) { const statusEl = document.getElementById("status"); statusEl.textContent = text || ""; } function buildToc() { const tocEl = document.getElementById("toc"); tocEl.innerHTML = ""; const title = document.createElement("div"); title.className = "toc-title"; title.textContent = "On this page"; tocEl.appendChild(title); const headings = Array.from(document.querySelectorAll("#content h1, #content h2, #content h3, #content h4")); for (const heading of headings) { const level = Number(heading.tagName.slice(1)); if (!heading.id) continue; const a = document.createElement("a"); a.href = `#${heading.id}`; a.textContent = heading.textContent || heading.id; a.className = `depth-${level}`; tocEl.appendChild(a); } } function scrollToHash() { const id = (location.hash || "").replace(/^#/, ""); if (!id) return; const el = document.getElementById(id); if (el) el.scrollIntoView({ block: "start" }); } function setQueryParam(key, value) { const url = new URL(location.href); if (value == null) url.searchParams.delete(key); else url.searchParams.set(key, value); history.replaceState(null, "", url.toString()); } async function loadDoc(docId) { const doc = DOCS.find((d) => d.id === docId) || DOCS[0]; setQueryParam("doc", doc.id); const rawLink = document.getElementById("openRaw"); rawLink.href = doc.file; setStatus(`Loading: ${doc.title}…`); const slugger = createSlugger(); const renderer = new marked.Renderer(); renderer.heading = (text, level) => { const id = slugger(text); return `${text}`; }; marked.setOptions({ renderer, gfm: true, breaks: false, mangle: false, headerIds: false, }); try { const res = await fetch(doc.file, { cache: "no-cache" }); if (!res.ok) throw new Error(`HTTP ${res.status}`); const markdown = await res.text(); const html = marked.parse(markdown); const contentEl = document.getElementById("content"); contentEl.innerHTML = html; buildToc(); setStatus(`Loaded: ${doc.title}`); scrollToHash(); } catch (err) { setStatus(`Failed to load ${doc.title}: ${String(err)}`); document.getElementById("content").textContent = ""; } } function init() { const select = document.getElementById("docSelect"); for (const doc of DOCS) { const opt = document.createElement("option"); opt.value = doc.id; opt.textContent = doc.title; select.appendChild(opt); } const initial = new URLSearchParams(location.search).get("doc") || DOCS[0].id; select.value = DOCS.some((d) => d.id === initial) ? initial : DOCS[0].id; select.addEventListener("change", () => { location.hash = ""; loadDoc(select.value); }); window.addEventListener("hashchange", () => scrollToHash()); loadDoc(select.value); } init();