mirror of
https://github.com/tiennm99/loto.git
synced 2026-10-13 18:15:53 +00:00
feat(ui): big bundle — settings (theme/master/auto), purple default, mobile fit, master extraction
Settings expansion: 4 new keys (theme auto/light/dark, masterMode, autoCallEnabled, autoCallSpeed 1-10) with per-key validators that preserve old saved data. Default empty-cell color flipped from Tân Tân blue to Excel Standard Purple #7030A0; preset palette swapped to Office's 10 standard colors (5x2 grid). 15 new tests, 53 total. Theme system: Tailwind v4 @variant dark (.dark *); applyTheme() toggles <html class="dark"> based on settings.theme; auto mode mirrors prefers-color-scheme via matchMedia listener (cleanly torn down when switching modes). Existing dark-mode CSS converted from @media to :where(.dark) selectors. Mobile fit: PlayerBoard cells aspect-square on mobile, sm:aspect-[3/5] desktop. Number text scales text-base sm:text-2xl md:text-3xl. Page padding tightened (px-2 py-4 sm:px-3 sm:py-12). Container bumped max-w-lg to max-w-2xl. Header / footer: removed instructions toggle and "Trang quản trò" link from player page. New PageFooter.svelte (tagline + Made by miti99 with [SVG heart] link); also duplicated in PlayerBoard's closing section-label band per request. Heart is inline SVG (red), not emoji. Master mode: extracted everything from /master route into reusable MasterPanel.svelte. /master route slimmed to header + MasterPanel + footer. / mounts MasterPanel conditionally when settings.masterMode. Storage prefixes unchanged. Master tracking grid removed: per request, the 11x9 ones-digit master board is gone. Host still gets controls, "Số vừa xổ" hero, draw history list, and their own player card. Player card is enough. Auto-call: single $effect lifecycle keyed on (autoRunning, settings.autoCallSpeed, settings.autoCallEnabled). Setup / clear setInterval cleanly across speed changes, master-mode toggle off, component unmount, and "user disabled auto" mid-run. Button toggles "Xổ số" -> "Bắt đầu / Dừng". Speed slider in Settings (only visible when master mode is on). aria-label / aria-valuetext on slider. Code-review nice-to-fixes applied: folded the two MasterPanel $effects into one, removed muddled onkeydown on the SettingsButton modal backdrop, added slider a11y attrs. Docs: PDR / codebase-summary / system-architecture / development-roadmap / code-standards all synced to the new state.
This commit is contained in:
1 parent
96a652f381
commit
0bc27f2897
23 files changed
+2056
-425
No files matched your search
@@ -41,8 +41,8 @@ Use the `.svelte.js` extension for modules that export rune-based reactive state
|
||||
### Utilities
|
||||
- Utility-first: `className="px-4 py-2 rounded-lg text-white"`.
|
||||
- Responsive: `sm:`, `md:`, `lg:` prefixes for breakpoints.
|
||||
- Dark mode: `dark:bg-slate-800`, `dark:text-white`.
|
||||
- Animations: Custom keyframes in `globals.css`, apply via `animate-fade-in`.
|
||||
- Dark mode: Explicit `@variant dark (.dark *)` in `app.css` declares dark-mode selector; use `dark:bg-slate-800 dark:text-white`. Settings store toggles `<html class="dark">` rather than relying on `@media (prefers-color-scheme: dark)`.
|
||||
- Animations: Custom keyframes in `app.css`, apply via `animate-fade-in`.
|
||||
|
||||
### Layout
|
||||
- Flexbox for alignment: `flex flex-col items-center justify-center`.
|
||||
@@ -167,8 +167,8 @@ Use inline event handlers (`onclick`, `onkeydown`). Svelte 5 handles click deleg
|
||||
|
||||
### Unit Tests (Implemented)
|
||||
- **Framework**: Vitest 4.1.5 with happy-dom
|
||||
- **Test Files**: `src/lib/game-logic.test.js` (26 tests), `src/lib/settings-store.test.js` (12 tests) — 38 total passing
|
||||
- **Coverage**: Game logic (generateGrid shape/constraints, isRowComplete, getWaitingNumber, persistence with validators), settings (load/save/reset with error handling)
|
||||
- **Test Files**: `src/lib/game-logic.test.js` (26 tests), `src/lib/settings-store.test.js` (27 tests) — 53 total passing
|
||||
- **Coverage**: Game logic (generateGrid shape/constraints, isRowComplete, getWaitingNumber, persistence with validators), settings (load/save/reset with error handling, theme detection, master mode toggle, auto-call speed, color validation)
|
||||
- **Scripts**: `npm test` (run once), `npm run test:watch` (continuous)
|
||||
- **Pattern**: Use vitest's `describe` / `it` blocks, `expect()` assertions. Test both happy path and error cases (corrupt JSON, missing localStorage).
|
||||
|
||||
@@ -192,3 +192,4 @@ Set in `.env.local` (not committed).
|
||||
- **basePath**: Dual-mode: `""` (Cloudflare, dev) or `/loto` (GitHub Pages via `BUILD_PROFILE=gh`).
|
||||
|
||||
Last reviewed: 2026-04-27
|
||||
Last synced: 2026-04-27 (6-phase refactor)
|
||||
@@ -6,31 +6,33 @@
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
| `src/routes/+layout.svelte` | Root HTML layout. Sets Vietnamese lang, imports Geist font, applies global flex layout. |
|
||||
| `src/routes/+page.svelte` | Player page (`/`). Instructions toggle, PlayerBoard component, indigo gradient branding. |
|
||||
| `src/routes/master/+page.svelte` | Host page (`/master`). Controls (new game, draw number), 11×9 last-digit-aligned master board with circular tokens (pink ring for 1–49, green for 50–90; cream fill when called, dim when uncalled; red ring + scale on last-called) and draw-order overlay, host's player card. |
|
||||
| `src/routes/+page.svelte` | Player page (`/`). Header + SettingsButton, PlayerBoard, conditional MasterPanel (when `settings.masterMode`), PageFooter. Indigo→purple gradient branding. |
|
||||
| `src/routes/master/+page.svelte` | Host page (`/master`). Thin shell: header + back link + MasterPanel + PageFooter. All host logic lives in `MasterPanel.svelte`. |
|
||||
|
||||
### Shared Components
|
||||
| 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) cells with condensed bold black numbers (`tan-tan-num` font stack), white number cells, blue empty cells. 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. Color picker + 8 preset swatches for empty-cell color. Reset-to-default button. Mounted on both `/` and `/master` headers. |
|
||||
| `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/MasterPanel.svelte` | Host controls. New game / draw, "Số vừa xổ" hero token, "Thứ tự đã xổ" history list, host's own player card. No 11×9 tracking board (player card is enough). "Xổ số" / "Bắt đầu / Dừng" button bound to auto-call. Mounted conditionally on `/` when `settings.masterMode === true`, or directly on `/master`. |
|
||||
| `src/lib/PageFooter.svelte` | Footer with tagline ("Made by miti99 with ❤️ SVG icon") + link. Mounted on `/` and `/master`. |
|
||||
|
||||
### Game Logic
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
| `src/lib/game-logic.js` | Stateless utilities: generateGrid (constraint-aware picker — exact 5 per row & per col, ascending-sorted columns), saveGrid, loadGrid, saveCrossedState, loadCrossedState, isRowComplete, getWaitingNumber. |
|
||||
| `src/lib/settings-store.svelte.js` | Reactive global UI settings via Svelte 5 runes. `emptyCellColor` (hex) persisted to localStorage `loto_settings`. Pushes value to `--empty-cell-bg` CSS var on `:root`. |
|
||||
| `src/lib/settings-store.svelte.js` | Reactive global UI settings via Svelte 5 runes. Stores 5 keys: `theme` (enum: "auto" / "light" / "dark"), `masterMode` (bool), `autoCallEnabled` (bool), `autoCallSpeed` (1–10), `emptyCellColor` (hex). Persisted to localStorage `loto_settings`. Pushes values to CSS vars and `<html class="dark">` on `:root`. Per-key validators preserve old data. |
|
||||
|
||||
### Styling
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
| `src/app.css` | Root styles: Tailwind @import, CSS variables (light/dark), `.loto-grid` & `.master-grid` (9-col), animations (fade-in, pop-in, bounce-slow, spin-slow, toast), `.cell-crossed` diagonal. |
|
||||
| `src/app.css` | Root styles: Tailwind @import, CSS variables (light/dark), Tailwind v4 `@variant dark (.dark *)` for explicit dark-mode class selector, `.loto-grid` & `.master-grid` (9-col), animations (fade-in, pop-in, bounce-slow, spin-slow, toast), `.cell-crossed` diagonal. |
|
||||
|
||||
### Tests
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
| `src/lib/game-logic.test.js` | 26 unit tests: generateGrid shape (9×9, 5 per row/col, no duplicates), column ranges & ascending sort, row completion, waiting number detection, persistence (saveGrid/loadGrid/saveCrossedState/loadCrossedState with validators). |
|
||||
| `src/lib/settings-store.test.js` | 12 unit tests: defaults, loadSettings (restore from localStorage, apply CSS var, handle empty/corrupt), saveSettings, resetSettings, color validation. |
|
||||
| `src/lib/settings-store.test.js` | 27 unit tests: defaults, loadSettings (restore 5 keys from localStorage, apply CSS vars, toggle dark class, handle empty/corrupt), saveSettings, resetSettings, theme toggle (auto → OS pref detection), master mode, auto-call + speed, color validation. |
|
||||
|
||||
### Configuration
|
||||
| File | Purpose |
|
||||
@@ -58,19 +60,21 @@
|
||||
| `loto_master` | Host's drawn/remaining numbers. |
|
||||
| `loto_master_card_grid` | Host's player card numbers. |
|
||||
| `loto_master_card_crossed` | Host's marked cells. |
|
||||
| `loto_settings` | Global UI settings: `{ emptyCellColor: "#rrggbb" }`. |
|
||||
| `loto_settings` | Global UI settings: `{ theme, masterMode, autoCallEnabled, autoCallSpeed, emptyCellColor }`. |
|
||||
|
||||
## Component Hierarchy
|
||||
|
||||
```
|
||||
RootLayout
|
||||
├── HomePage (/)
|
||||
│ ├── Instructions toggle
|
||||
│ └── PlayerBoard (storagePrefix="loto")
|
||||
│ ├── PlayerBoard (storagePrefix="loto")
|
||||
│ ├── [if settings.masterMode]
|
||||
│ │ └── MasterPanel (state, controls, draw history)
|
||||
│ └── PageFooter
|
||||
└── MasterPage (/master)
|
||||
├── Controls (new game, draw)
|
||||
├── Master board (11×9, last-digit aligned, draw-order overlay)
|
||||
└── PlayerBoard (storagePrefix="loto_master_card")
|
||||
├── MasterPanel (state, controls, draw history)
|
||||
├── Back link
|
||||
└── PageFooter
|
||||
```
|
||||
|
||||
## Key Functions
|
||||
@@ -81,8 +85,8 @@ RootLayout
|
||||
| `generateGrid()` | game-logic.js | Builds 9×9; ascending-sorted numbers per column. |
|
||||
| `isRowComplete()` | game-logic.js | Boolean: all non-zero cells in row crossed? |
|
||||
| `getWaitingNumber()` | game-logic.js | Returns the single uncrossed number in row, or null. |
|
||||
| `buildBoard()` | master/+page.svelte | Builds 11×9 master grid; row = ones-digit, col = tens-digit; col 0 holds 1–9, col 8 holds 80–90. |
|
||||
| `handleCellClick()` | PlayerBoard.svelte | Toggle crossed[row][col]. |
|
||||
| `saveGrid()` / `loadGrid()` | game-logic.js | localStorage with prefix-based keys. |
|
||||
|
||||
Last reviewed: 2026-04-27
|
||||
Last synced: 2026-04-27 (6-phase refactor)
|
||||
@@ -15,15 +15,21 @@ The app is fully functional for core gameplay (Lô tô hội chợ Tân Tân var
|
||||
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: empty-cell color picker (color picker + 8 presets +
|
||||
reset), applies to player + master grids, persisted to `loto_settings`
|
||||
- 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 + 12 settings-store tests, 38 total passing)
|
||||
covering constraint validation, persistence, and error handling
|
||||
- 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
|
||||
|
||||
## Idea Phase
|
||||
|
||||
@@ -33,8 +39,6 @@ Play celebratory chime or "Kinh!" voice snippet when row completes. Could use We
|
||||
### Undo Last Cell
|
||||
Allow player to undo the most recent cross/uncross action. Requires change history or state snapshot. **Status**: Idea (low priority)
|
||||
|
||||
### Theme Switcher
|
||||
Explicit light/dark toggle button instead of relying on OS preference. **Status**: Idea (Tailwind already supports OS toggle)
|
||||
|
||||
### PWA Install
|
||||
Add service worker and manifest for "Install App" prompt on Android/iOS. **Status**: Idea (would require server-side components)
|
||||
@@ -104,3 +108,4 @@ All decisions follow **YAGNI** (You Aren't Gonna Need It), **KISS** (Keep It Sim
|
||||
Future work gates on **real user demand**, not speculation.
|
||||
|
||||
Last reviewed: 2026-04-27
|
||||
Last synced: 2026-04-27 (6-phase refactor)
|
||||
@@ -18,8 +18,8 @@ That format is intentionally out of scope.
|
||||
- **Visual layout**: card displayed as 3 stacked 3×9 mini-cards with
|
||||
traditional separator labels — *Tân Tân*, *An khang thịnh vượng*,
|
||||
*Tân Tân tốt nhất* — matching the physical Tân Tân BAMBOORAFT paper
|
||||
sheet (tall non-square cells, condensed bold black numbers on white,
|
||||
blue empty-cell background by default).
|
||||
sheet (tall non-square cells on mobile, wider on sm+; condensed bold black numbers on white,
|
||||
purple empty-cell background by default — Excel Standard Purple `#7030A0`).
|
||||
- **Column ranges**: col 0 = 1–9, col 1 = 10–19, …, col 7 = 70–79,
|
||||
col 8 = 80–90.
|
||||
- **Within a column**: numbers placed top-to-bottom in **ascending** order.
|
||||
@@ -31,8 +31,11 @@ That format is intentionally out of scope.
|
||||
a master board with circular tokens (pink ring for 1–49, green for 50–90,
|
||||
cream fill on called, dim on uncalled, red ring + scale on the most
|
||||
recent draw), and may also play their own card.
|
||||
- **Settings**: gear icon in headers opens a modal with a color picker for
|
||||
the empty/blank cell color (default brown matches physical card).
|
||||
- **Settings**: gear icon in headers opens a modal with 4 fieldsets:
|
||||
- Giao diện: theme switcher (auto / light / dark; auto mirrors OS pref)
|
||||
- Chế độ quản trò: toggle to show MasterPanel on `/`
|
||||
- Tự động xổ: enable auto-call; speed slider (1–10 seconds)
|
||||
- Màu ô trống: color picker + 10 Excel preset swatches
|
||||
Persisted to `loto_settings`.
|
||||
|
||||
### Out of scope
|
||||
@@ -96,7 +99,13 @@ State is entirely client-side. Each page/card instance uses a unique localStorag
|
||||
- [x] Mobile-responsive (base + sm breakpoints).
|
||||
- [x] HMR works on code-server via proxy.
|
||||
- [x] Player card rendered as 3 stacked Tân Tân mini-cards with separator labels.
|
||||
- [x] Settings modal with empty-cell color picker; applies to both player and master grids; persisted.
|
||||
- [x] Settings modal with 4 fieldsets (theme, master mode, auto-call, color); applies to both grids; persisted.
|
||||
- [x] Theme switcher (auto / light / dark) with explicit dark class selector on `<html>`.
|
||||
- [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.
|
||||
|
||||
## Visual Language
|
||||
|
||||
@@ -115,4 +124,5 @@ State is entirely client-side. Each page/card instance uses a unique localStorag
|
||||
- Multiplayer sync (real-time via WebSocket)
|
||||
- i18n beyond Vietnamese
|
||||
|
||||
Last reviewed: 2026-04-26 (scope locked: Lô tô hội chợ Tân Tân)
|
||||
Last reviewed: 2026-04-27 (scope locked: Lô tô hội chợ Tân Tân + theme/master/auto-call)
|
||||
Last synced: 2026-04-27 (6-phase refactor)
|
||||
@@ -4,22 +4,23 @@
|
||||
|
||||
```
|
||||
Entry (+layout.svelte)
|
||||
├─ onMount: loadSettings() — restore emptyCellColor from loto_settings,
|
||||
│ apply to --empty-cell-bg CSS variable before pages render
|
||||
├─ onMount: loadSettings() — restore all 5 settings keys from loto_settings,
|
||||
│ apply CSS vars, toggle <html class="dark"> on theme/OS pref, setup auto-call effect
|
||||
│
|
||||
├─ / (Player Page)
|
||||
│ ├─ Load loto_grid, loto_crossed from localStorage
|
||||
│ ├─ Display 9×9 grid (empty cells use --empty-cell-bg from settings)
|
||||
│ ├─ 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
|
||||
│ ├─ Show bingo popup + "Chờ X" toasts
|
||||
│ ├─ [if settings.masterMode === true]
|
||||
│ │ └─ Mount MasterPanel (host controls + draw history)
|
||||
│ └─ PageFooter (tagline + miti99 link)
|
||||
│
|
||||
└─ /master (Host Page)
|
||||
├─ Load loto_master (called/remaining)
|
||||
├─ Display 11×9 master board (empty cells use --empty-cell-bg)
|
||||
├─ Draw button shows next called number (highlighted in red)
|
||||
├─ Display host's own card (loto_master_card prefix)
|
||||
└─ New Game button resets called/remaining
|
||||
├─ MasterPanel (state, controls, draw history; same as above)
|
||||
├─ Back link to /
|
||||
└─ PageFooter
|
||||
```
|
||||
|
||||
## State Model
|
||||
@@ -34,10 +35,20 @@ Each row has exactly 5 non-zero numbers AND each column has exactly 5
|
||||
(constraint-aware picker — no slack). Numbers within a column are sorted
|
||||
ascending top-to-bottom (lô tô hội chợ Tân Tân convention).
|
||||
|
||||
### Settings (`loto_settings`)
|
||||
```
|
||||
theme: "auto" | "light" | "dark" // Display mode
|
||||
masterMode: boolean // Show MasterPanel on /
|
||||
autoCallEnabled: boolean // Enable auto-call timer
|
||||
autoCallSpeed: number (1–10) // Speed in seconds
|
||||
emptyCellColor: "#rrggbb" // Hex color (default #7030A0 Excel Purple)
|
||||
```
|
||||
|
||||
### Host State (`storagePrefix="loto_master"`)
|
||||
```
|
||||
called: number[] // [5, 23, 67, ...] — drawn in order
|
||||
remaining: number[] // [1, 2, 3, ...] minus called — shuffled initially
|
||||
autoRunning: boolean // Auto-call timer active
|
||||
```
|
||||
|
||||
### Host's Card (`storagePrefix="loto_master_card"`)
|
||||
@@ -134,3 +145,4 @@ Files that are client-only:
|
||||
All state is localStorage. No API calls. Fully functional offline after initial load.
|
||||
|
||||
Last reviewed: 2026-04-27
|
||||
Last synced: 2026-04-27 (6-phase refactor)
|
||||
Reference in new issue
Block a user