// Hash-based SPA router. // Reads pages.json (generated by build.sh from components.yml). // Each page is an HTML fragment (index.html) + optional CSS + optional JS module. // // Load order enforced by index.html: // await import('./core/registry.js') ← defines all components // await import('./core/router.js') ← this file const response = await fetch('./pages.json') if (!response.ok) throw new Error('router: failed to load pages.json — run build.sh') const PAGES = await response.json() // Cache of imported JS modules — modules are only fetched once per session. const MODULE_CACHE = new Map() // Currently active page CSS — removed on navigation. let activeCSS = null function currentRoute() { const hash = window.location.hash.replace(/^#\//, '') || PAGES[0]?.route || '' return hash.split('?')[0] } async function render(routeName) { const page = PAGES.find(p => p.route === routeName) ?? PAGES[0] const main = document.querySelector('main') if (!main || !page) return // ── HTML ──────────────────────────────────────────────────────────────────── const htmlRes = await fetch(page.html) if (!htmlRes.ok) throw new Error(`router: failed to load ${page.html}`) main.innerHTML = await htmlRes.text() // ── CSS ───────────────────────────────────────────────────────────────────── activeCSS?.remove() activeCSS = null if (page.css) { const link = document.createElement('link') link.rel = 'stylesheet' link.href = page.css document.head.appendChild(link) activeCSS = link } // ── JS ────────────────────────────────────────────────────────────────────── if (page.js) { if (!MODULE_CACHE.has(page.js)) { MODULE_CACHE.set(page.js, await import(`../${page.js}`)) } MODULE_CACHE.get(page.js)?.init?.(main) } } await render(currentRoute()) window.addEventListener('hashchange', () => render(currentRoute()))