Stage 7 (wip): UI shell, libs, mock transport, screens (lobby->game), e2e smoke

- plain Svelte 5 + TS + Vite (no SvelteKit); CSS-token design system (Telegram-ready), hash router, IndexedDB session
- pure libs: domain model, premium/value maps ported from solver, board replay, placement state machine, i18n en/ru
- in-memory mock transport + seed data; pnpm start runs lobby->active game->board with no backend
- board: pointer-drag + tap placement, MakeMove (popup / 1s-hold commit), two-state zoom, blank chooser, exchange, hint, word-check, chat
- Playwright smoke (mock) green; svelte-check clean; mock bundle ~37 KB gzip
This commit is contained in:
Ilia Denisov
2026-06-03 00:32:50 +02:00
parent 19ae8f04a2
commit 453ddc5e94
48 changed files with 5696 additions and 0 deletions
+60
View File
@@ -0,0 +1,60 @@
// 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'
| 'profile'
| 'settings'
| 'about'
| 'notfound';
export interface Route {
name: RouteName;
params: Record<string, string>;
}
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':
return seg[1] ? { name: 'game', params: { id: seg[1] } } : { name: 'notfound', params: {} };
case 'profile':
return { name: 'profile', params: {} };
case 'settings':
return { name: 'settings', params: {} };
case 'about':
return { name: 'about', 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;
}
}