// Minimal dependency-free hash router. Hash routing survives a reload and works on // a file:// origin (Capacitor native packaging), where there is no server to honour // deep paths. The route is a reactive rune so screens re-render on navigation. export type RouteName = | 'login' | 'lobby' | 'new' | 'game' | 'gameChat' | 'gameCheck' | 'profile' | 'settings' | 'about' | 'friends' | 'stats' | 'notfound'; export interface Route { name: RouteName; params: Record; } function parse(hash: string): Route { const path = (hash.replace(/^#/, '') || '/').split('?')[0]; const seg = path.split('/').filter(Boolean); if (seg.length === 0) return { name: 'lobby', params: {} }; switch (seg[0]) { case 'login': return { name: 'login', params: {} }; case 'new': return { name: 'new', params: {} }; case 'game': if (!seg[1]) return { name: 'notfound', params: {} }; if (seg[2] === 'chat') return { name: 'gameChat', params: { id: seg[1] } }; if (seg[2] === 'check') return { name: 'gameCheck', params: { id: seg[1] } }; return { name: 'game', params: { id: seg[1] } }; case 'profile': return { name: 'profile', params: {} }; case 'settings': return { name: 'settings', params: {} }; case 'about': return { name: 'about', params: {} }; case 'friends': return { name: 'friends', params: {} }; case 'stats': return { name: 'stats', params: {} }; default: return { name: 'notfound', params: {} }; } } export const router = $state<{ route: Route }>({ route: parse(typeof location !== 'undefined' ? location.hash : ''), }); if (typeof window !== 'undefined') { window.addEventListener('hashchange', () => { router.route = parse(location.hash); }); } /** navigate switches the hash route (and forces a re-parse if it is unchanged). */ export function navigate(path: string): void { const target = '#' + path; if (location.hash === target) { router.route = parse(target); } else { location.hash = path; } }