// Simple hash-based SPA router const routes = {}; let currentCleanup = null; export function route(path, handler) { routes[path] = handler; } export function navigate(path) { window.location.hash = path; } export function currentPath() { return window.location.hash.slice(1) || '/'; } export function getParam(name) { const path = currentPath(); // Match patterns like /services/:id for (const pattern of Object.keys(routes)) { const paramNames = []; const regex = pattern.replace(/:(\w+)/g, (_, name) => { paramNames.push(name); return '([^/]+)'; }); const match = path.match(new RegExp(`^${regex}$`)); if (match) { const idx = paramNames.indexOf(name); if (idx >= 0) return match[idx + 1]; } } return null; } export function startRouter(container) { function handleRoute() { const path = currentPath(); // Cleanup previous page if (currentCleanup) { currentCleanup(); currentCleanup = null; } // Find matching route for (const [pattern, handler] of Object.entries(routes)) { const paramNames = []; const regex = pattern.replace(/:(\w+)/g, (_, n) => { paramNames.push(n); return '([^/]+)'; }); const match = path.match(new RegExp(`^${regex}$`)); if (match) { const params = {}; paramNames.forEach((n, i) => params[n] = match[i + 1]); const cleanup = handler(container, params); if (typeof cleanup === 'function') currentCleanup = cleanup; updateActiveLink(path); return; } } // Default: redirect to / navigate('/'); } window.addEventListener('hashchange', handleRoute); handleRoute(); } function updateActiveLink(path) { document.querySelectorAll('.sidebar-link[data-path]').forEach(el => { const linkPath = el.dataset.path; if (linkPath === '/') { el.classList.toggle('active', path === '/'); } else { el.classList.toggle('active', path.startsWith(linkPath)); } }); }