diff --git a/web/docs/code-standards.md b/web/docs/code-standards.md
index f72e2e6..eecbfbe 100644
--- a/web/docs/code-standards.md
+++ b/web/docs/code-standards.md
@@ -127,8 +127,8 @@ function randomANumberInRow(weights) {
## Internal Navigation
-- Use `import { base } from '$app/paths'` for internal links to preserve basePath across deployments.
-- Example: `Host page` works on root (`""`) and subpath (`/loto`) equally.
+- Use `import { base } from '$app/paths'` for internal links/asset URLs to preserve basePath across deployments.
+- Example: `
` resolves on both root (`""`) and subpath (`/loto`) builds.
## Import Organization
diff --git a/web/docs/codebase-summary.md b/web/docs/codebase-summary.md
index a498b08..9f9ef1b 100644
--- a/web/docs/codebase-summary.md
+++ b/web/docs/codebase-summary.md
@@ -12,9 +12,9 @@
| File | Purpose |
|------|---------|
| `src/lib/PlayerBoard.svelte` | Reusable player card (9×9 grid rendered as 3 stacked 3×9 mini-cards: Tân Tân / An khang thịnh vượng / Tân Tân tốt nhất). Tall (3:5 on mobile; wider on sm+) cells with condensed bold black numbers (`tan-tan-num` font stack), white number cells, purple empty cells by default. Handles crossed state, bingo popup, "Chờ X" toast. Accepts `storagePrefix` prop for multi-card isolation. Empty cells use `--empty-cell-bg` CSS var from settings store. |
-| `src/lib/SettingsButton.svelte` | Gear icon + modal. 4 fieldsets: Giao diện (theme auto/light/dark), Chế độ quản trò (master mode toggle), Tự động xổ (auto-call + speed 1–10s), Màu ô trống (10 Excel color swatches). Reset-to-default button. Mounted on both `/` and `/master` headers. |
+| `src/lib/SettingsButton.svelte` | Gear icon + modal. 4 fieldsets: Giao diện (theme auto/light/dark), Chế độ quản trò (master mode toggle), Tự động xổ (auto-call + speed 1–10s), Màu ô trống (10 Excel color swatches). Reset-to-default button. Mounted on `/`. |
| `src/lib/MasterPanel.svelte` | Host controls. New game / draw, "Số vừa xổ" hero token, "Thứ tự đã xổ" history list, 11×9 last-digit-aligned tracking grid (with circular tokens + draw-order overlay). No host's own player card (the player already has one above). "Xổ số" / "Bắt đầu / Dừng" button bound to auto-call. Mounted conditionally on `/` when `settings.masterMode === true`. |
-| `src/lib/PageFooter.svelte` | Footer with tagline ("Made by miti99 with ❤️ SVG icon") + link. Mounted on `/` and `/master`. |
+| `src/lib/PageFooter.svelte` | Footer with tagline ("Made by miti99 with ❤️ SVG icon") + link. Mounted on `/`. |
### Game Logic
| File | Purpose |
diff --git a/web/docs/deployment-guide.md b/web/docs/deployment-guide.md
index 631e016..645b86d 100644
--- a/web/docs/deployment-guide.md
+++ b/web/docs/deployment-guide.md
@@ -35,9 +35,11 @@ No GitHub Actions involved; no repo secrets needed.
## GitHub Pages (redirect-only)
`.github/workflows/deploy-github-pages.yml` no longer builds the app.
-It generates two tiny HTML pages (`/loto/index.html` and
-`/loto/master/index.html`) that immediately redirect to the canonical
-URL on Cloudflare Pages.
+It generates a tiny HTML page at `/loto/index.html` that immediately
+redirects to the canonical URL on Cloudflare Pages, plus a copy at
+`/loto/master/index.html` to catch legacy `/master` bookmarks (the app
+itself is single-page now; Cloudflare's `_redirects` collapses unknown
+paths back to `/`).
The redirect uses both `` (no-JS fallback)
and a tiny inline script that preserves path / query / hash:
@@ -46,8 +48,9 @@ and a tiny inline script that preserves path / query / hash:
location.replace("https://loto.miti99.com" + path + search + hash);
```
-So `tiennm99.github.io/loto/` → `loto.miti99.com/` and
-`tiennm99.github.io/loto/master` → `loto.miti99.com/master`.
+So `tiennm99.github.io/loto/` → `loto.miti99.com/` and any legacy
+`tiennm99.github.io/loto/master` link → `loto.miti99.com/` (after the
+intermediate `/master` redirect is collapsed by `_redirects`).
The redirect runs on every push to `main`. If you want full GH Pages
serving back (instead of the redirect), restore the prior version of
diff --git a/web/docs/development-roadmap.md b/web/docs/development-roadmap.md
index 3632c07..0564c6a 100644
--- a/web/docs/development-roadmap.md
+++ b/web/docs/development-roadmap.md
@@ -1,35 +1,8 @@
# Development Roadmap
-This document tracks **future work only**. Completed features live in git commit history, not here.
-
-## Currently Implemented Features
-
-The app is fully functional for core gameplay (Lô tô hội chợ Tân Tân variant):
-- 9×9 player card with **exactly 5 per row and 5 per column**, columns
- ascending top-to-bottom
-- Cell marking (toggle crossed state); player can keep playing after Kinh
-- Bingo detection and "Kinh!" celebration popup
-- "Chờ X" waiting notifications
-- Host number drawing from 1–90 deck
-- 11×9 last-digit-aligned master board with draw-order overlay for fast
- Kinh verification
-- Player card rendered as 3 stacked Tân Tân mini-cards
- (Minh Tân / Loại đặc biệt / Tấn tài tấn lộc) with cross-hatch dividers
-- Settings modal: 4 fieldsets (theme auto/light/dark, master mode toggle,
- auto-call speed 1–10s, 10 color presets), persisted to `loto_settings`
-- Theme switcher (auto detects OS pref, explicit light/dark modes)
-- Master mode toggle (shows MasterPanel on `/`)
-- Auto-call timer with speed slider
-- Mobile-responsive cells (aspect-ratio + text scaling)
-- MasterPanel extracted component (reused on `/` and `/master`)
-- PageFooter with tagline + creator link
-- Host's own player card (isolated instance)
-- localStorage persistence
-- Dark mode
-- Mobile responsive
-- Offline capable
-- Unit tests (Vitest: 26 game-logic tests + 27 settings-store tests, 53 total passing)
- covering constraint validation, persistence, theme/master-mode/auto-call logic, and error handling
+This document tracks **future work only**. Completed features live in
+git commit history and `plans/`, not here. For "what's in the app right
+now", see `docs/project-overview-pdr.md` and `docs/codebase-summary.md`.
## Idea Phase
@@ -63,7 +36,7 @@ Full undo/redo stack with history navigation. Adds complexity to state managemen
### Component Tests (Planned)
- PlayerBoard with mocked localStorage, $state/$derived verification
-- Master page with different game states
+- MasterPanel with different game states
### E2E Tests
- Player flow: generate card → click cells → verify bingo popup
diff --git a/web/docs/project-overview-pdr.md b/web/docs/project-overview-pdr.md
index 0e331db..ee51966 100644
--- a/web/docs/project-overview-pdr.md
+++ b/web/docs/project-overview-pdr.md
@@ -71,11 +71,11 @@ That format is intentionally out of scope.
## Architecture Overview
-Two public pages:
-1. **`/`** — Player page. Generate a card, click cells to mark them, see bingo popup and waiting toasts.
-2. **`/master`** — Host page. Control number drawing, view 9×10 master board (tracking called vs uncalled), and host's own player card.
+Single page (`/`):
+- Always renders the player card (generate, mark, bingo popup, "Chờ N" toasts).
+- When `settings.masterMode === true`, mounts MasterPanel below the player card — host controls, draw history, and the host's own player card.
-State is entirely client-side. Each page/card instance uses a unique localStorage prefix (e.g., `"loto"` for player, `"loto_master"` for host's state, `"loto_master_card"` for host's player card).
+State is entirely client-side. Each card / panel instance uses a unique localStorage prefix (`"loto"` for the player card, `"loto_master"` for host draw state, `"loto_master_card"` for the host's own card).
## Deployment
@@ -104,8 +104,8 @@ State is entirely client-side. Each page/card instance uses a unique localStorag
- [x] Master mode toggle to show MasterPanel on player page.
- [x] Auto-call timer (1–10s speed) with start/stop button.
- [x] Mobile-responsive layout (aspect-square sm:aspect-[3/5] cells, text scaling).
-- [x] MasterPanel extracted; mounted conditionally on `/` and directly on `/master`.
-- [x] PageFooter with tagline + link on both pages.
+- [x] MasterPanel extracted; mounted conditionally on `/` when master mode is on.
+- [x] PageFooter with tagline + link.
## Visual Language
diff --git a/web/docs/system-architecture.md b/web/docs/system-architecture.md
index d76f557..bcebb57 100644
--- a/web/docs/system-architecture.md
+++ b/web/docs/system-architecture.md
@@ -7,20 +7,15 @@ Entry (+layout.svelte)
├─ onMount: loadSettings() — restore all 5 settings keys from loto_settings,
│ apply CSS vars, toggle on theme/OS pref, setup auto-call effect
│
- ├─ / (Player Page)
- │ ├─ Load loto_grid, loto_crossed from localStorage
- │ ├─ Display 9×9 PlayerBoard (empty cells use --empty-cell-bg from settings)
- │ ├─ Generate new grid on button click
- │ ├─ Mark/unmark cells on click
- │ ├─ Show bingo popup + "Chờ X" toasts
- │ ├─ [if settings.masterMode === true]
- │ │ └─ Mount MasterPanel (host controls + draw history)
- │ └─ PageFooter (tagline + miti99 link)
- │
- └─ /master (Host Page)
- ├─ MasterPanel (state, controls, draw history; same as above)
- ├─ Back link to /
- └─ PageFooter
+ └─ / (single page)
+ ├─ Load loto_grid, loto_crossed from localStorage
+ ├─ Display 9×9 PlayerBoard (empty cells use --empty-cell-bg from settings)
+ ├─ Generate new grid on button click
+ ├─ Mark/unmark cells on click
+ ├─ Show bingo popup + "Chờ X" toasts
+ ├─ [if settings.masterMode === true]
+ │ └─ Mount MasterPanel (host controls + draw history)
+ └─ PageFooter (tagline + miti99 link)
```
## State Model
@@ -67,32 +62,38 @@ All keys are JSON stringified. Corruption is silent (returns null).
## basePath & Asset Resolution
-### Production Mode
+### Production (Cloudflare Pages, default)
```
-NODE_ENV=production
+npm run build
+basePath="" (root)
+Output: build/index.html
+Deploy: loto.miti99.com (root domain)
+```
+
+### Production (GitHub Pages, manual)
+```
+npm run build:gh
basePath="/loto"
-Output: /loto/index.html, /loto/_next/...
-GitHub Pages serves: https://user.github.io/loto
+Output: build/index.html with /loto prefix
+Deploy: https://tiennm99.github.io/loto
```
-### Development Mode (Local)
+### Development (Local)
```
-NEXT_DEV_PROFILE not set
+npm run dev
basePath="" (empty)
-Dev server: http://localhost:3000
+Dev server: http://localhost:5173
```
-### Code-Server Mode
+### Development (Code-Server)
```
-NEXT_DEV_PROFILE=codeserver
-CODESERVER_HOST=
-CODESERVER_PORT=3000 (or env)
+npm run dev:codeserver
+.env.local: CODESERVER_HOST + CODESERVER_PORT
basePath="/absproxy/{PORT}"
-HMR Origin: CODESERVER_HOST
-Access: https:///absproxy/3000/
+Access: https:///absproxy/{PORT}/
```
-**Note**: `/absproxy/{port}` preserves the basePath through the proxy. `/proxy/{port}` strips it before forwarding, breaking HMR.
+**Note**: Use `/absproxy/{port}` — `/proxy/{port}` strips the path prefix and breaks the SvelteKit base path.
## Client-Only Architecture
@@ -102,9 +103,9 @@ All pages are client-only (no SSR) because:
- State initialization (grid, crossed) must run in browser
Files that are client-only:
-- `src/routes/+page.svelte` (player page)
-- `src/routes/master/+page.svelte` (host page)
-- `src/lib/PlayerBoard.svelte` (shared component)
+- `src/routes/+page.svelte` (single page)
+- `src/lib/PlayerBoard.svelte` (player card component)
+- `src/lib/MasterPanel.svelte` (host panel, mounted when `settings.masterMode`)
## Data Flow: Mark a Cell
diff --git a/web/jsconfig.json b/web/jsconfig.json
index 457c304..a8ef6e9 100644
--- a/web/jsconfig.json
+++ b/web/jsconfig.json
@@ -1,14 +1,7 @@
{
+ "extends": "./.svelte-kit/tsconfig.json",
"compilerOptions": {
- "module": "esnext",
- "moduleResolution": "bundler",
- "target": "es2022",
"allowJs": true,
- "checkJs": false,
- "paths": {
- "$lib": ["./src/lib"],
- "$lib/*": ["./src/lib/*"]
- }
- },
- "include": ["src/**/*", "vite.config.js", "svelte.config.js"]
+ "checkJs": false
+ }
}