mirror of
https://github.com/tiennm99/loto.git
synced 2026-10-14 10:18:50 +00:00
feat: UI polish v2 + installable PWA with offline audio
Phase 1 — Vietnamese-safe font + master empty state:
- Add @fontsource/roboto-condensed (700 weight, all subsets including
Vietnamese). font-display:swap. tan-tan-num now resolves the bundled
face on Android instead of system Arial Narrow.
- New MasterEmptyState.svelte: ghost 11×9 grid + "Chế độ Quản trò"
pill + readiness microcopy. Replaces the bare line of text in
MasterPanel's no-game state.
Phase 2 — Mode picker icons, color picker layout, header polish:
- Inline SVG glyphs above each mode button (player card / megaphone /
two stacked cards). Communicates role at a glance.
- Color picker wrapped in a single bordered card with "Tuỳ chỉnh"
and "Mẫu sẵn" sub-headers.
- "Mặc định" → "Đặt lại" with a bordered chip style — clearer
affordance than the previous near-invisible footer link.
- Header subline: drop tracking-[0.28em] all-caps SaaS look; replace
with dash-flanked lantern band ("— 🏮 Hội chợ TN1 —") for
fairground mood.
Phase 3 — Installable PWA + offline audio:
- @vite-pwa/sveltekit with autoUpdate. Precache app shell (~353 KB
→ 213 entries) PLUS the default voice's 92 clips so first-install
is fully offline-capable. Alternate voices fall through to a
CacheFirst runtime rule (cached on first play, 30-day TTL,
maxEntries: 400 for future-proofing).
- New static/manifest.webmanifest (Lô tô — Hội chợ TN1, theme #1565c0,
background #0a0f1f, standalone, vi).
- Icons: 192/512 standard + 512 maskable, generated from a single
rose-amber-gradient SVG source.
- app.html: manifest link, dual theme-color meta (light + dark),
apple-touch-icon, apple-mobile-web-app-capable for proper
standalone launch on iOS Safari.
- _headers: add manifest-src + worker-src to CSP; no-cache on /sw.js
and /manifest.webmanifest so deploys propagate.
Tests: 115/115 pass. Build clean (305 precache entries, 0 glob
warnings).
Reviewer concerns (addressed):
- maxEntries bumped 200 → 400 (was barely enough for 2 voices).
- Default voice precached so offline-first promise holds without
requiring users to play every clip online first.
- "do NOT add skipWaiting" comment added next to autoUpdate.
This commit is contained in:
1 parent
f7cbb6e1c7
commit
f899b09631
24 files changed
+5756
-68
No files matched your search
@@ -11,8 +11,9 @@
|
||||
### 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:4 on mobile; 3:5 on sm+) cells with condensed bold black numbers (`tan-tan-num` font stack), white number cells, purple empty cells (dark mode dims via `filter:brightness(0.85)`). Handles crossed state, animated cross-out (200 ms `cross-draw` keyframe), `active:scale-90` press, 10 ms haptic on tap. Two header actions: "Tạo bảng mới" / "Xoá đánh dấu". First-run state shows a faded preview card. Bingo popup tiers: row 1 = standard celebration; row 3+ = falling-emoji confetti rain via CSS `confetti-fall`. Toast "Chờ N" + audio. Accepts `storagePrefix` prop for multi-card isolation. |
|
||||
| `src/lib/SettingsButton.svelte` | Gear icon + modal (responsive `max-w-sm sm:max-w-md`). 5 fieldsets: Giao diện (theme pills), Chế độ quản trò (switch row), Tự động xổ (switch + speed slider), Âm thanh (two switches + voice picker), Màu ô trống (10 Excel color swatches + custom picker). Boolean toggles use a shared `switchRow` snippet (`role="switch"` + keyboard support). Reset-to-default button. Mounted on `/`. |
|
||||
| `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:4 on mobile; 3:5 on sm+) cells with condensed bold black numbers (`tan-tan-num` font stack w/ self-hosted Roboto Condensed), white number cells, purple empty cells (dark mode dims via `filter:brightness(0.85)`). Handles crossed state, animated cross-out (200 ms `cross-draw` keyframe), `active:scale-90` press, 10 ms haptic on tap. Two header actions: "Tạo bảng mới" / "Xoá đánh dấu". First-run state shows a faded preview card. Bingo popup tiers: row 1 = standard celebration; row 3+ = falling-emoji confetti rain via CSS `confetti-fall`. Toast "Chờ N" + audio. Accepts `storagePrefix` prop for multi-card isolation. |
|
||||
| `src/lib/SettingsButton.svelte` | Gear icon + modal (responsive `max-w-sm sm:max-w-md`). 6 fieldsets: Giao diện (theme pills), Chế độ (3-way mode picker w/ SVG glyphs: player/master/both), Chế độ quản trò (switch row), Tự động xổ (switch + speed slider), Âm thanh (two switches + voice picker), Màu ô trống (10 Excel swatches + custom input in bordered card w/ "Tuỳ chỉnh"/"Mẫu" sub-headers). Boolean toggles use a shared `switchRow` snippet (`role="switch"` + keyboard support). Reset-to-default button. Mounted on `/`. |
|
||||
| `src/lib/MasterEmptyState.svelte` | Empty board placeholder for first-run master (mirrors PlayerBoard's preview UX). Displays faded 11×9 grid with "Ấn để bắt đầu ván mới" hint. |
|
||||
| `src/lib/MasterPanel.svelte` | Host controls. New game / draw, large "Số vừa xổ" hero token (160 px mobile, 224 px sm+) with `aria-live="assertive"` + auto `scrollIntoView` on each new draw, "Thứ tự đã xổ" history list, 11×9 last-digit-aligned tracking grid (with circular tokens + draw-order overlay). Publishes draws to `call-bus` for player auto-tick. "Xổ số" / "Bắt đầu / Dừng" button bound to auto-call. Mounted conditionally on `/` when `settings.mode !== "player"`; the wrapping section uses `transition:slide` for smooth toggle-in. |
|
||||
| `src/lib/PageFooter.svelte` | Footer with tagline ("Made by miti99 with ❤️ SVG icon") + link. Mounted on `/`. |
|
||||
|
||||
@@ -38,20 +39,23 @@
|
||||
| `src/lib/settings-store.test.js` | 31 unit tests: defaults (incl. voice keys), loadSettings (restore 8 keys, apply CSS vars, toggle dark class, handle empty/corrupt), saveSettings, resetSettings, theme toggle (auto → OS pref detection), master mode, auto-call + speed, color validation, voice round-trip + invalid-id fallback. |
|
||||
| `src/lib/vietnamese-number.test.js` | 40 unit tests: ones (0–9), teens (10–19 incl. mười lăm), 20–90 incl. mốt and lăm exceptions, out-of-range fall-through. |
|
||||
|
||||
### Configuration
|
||||
### Configuration & PWA
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
| `svelte.config.js` | adapter-static (HTML export), dual basePath via BUILD_PROFILE env. |
|
||||
| `vite.config.js` | Tailwind + SvelteKit plugins. codeserver HMR config (port, allowedHosts, hmr). |
|
||||
| `package.json` | SvelteKit 2, Svelte 5 (runes), Tailwind 4, Vite. Scripts: dev, dev:codeserver, build, build:gh, lint, test, test:watch. |
|
||||
| `eslint.config.mjs` | ESLint 9 flat config (@eslint/js + eslint-plugin-svelte). Declares Svelte 5 rune globals (lines 16–22). |
|
||||
| `svelte.config.js` | adapter-static (HTML export), dual basePath via BUILD_PROFILE env, SvelteKit PWA plugin config. |
|
||||
| `vite.config.js` | Tailwind + SvelteKit + PWA plugins. codeserver HMR config (port, allowedHosts, hmr). |
|
||||
| `package.json` | SvelteKit 2, Svelte 5 (runes), Tailwind 4, Vite, @vite-pwa/sveltekit. Scripts: dev, dev:codeserver, build, build:gh, lint, test, test:watch. |
|
||||
| `eslint.config.mjs` | ESLint 9 flat config (@eslint/js + eslint-plugin-svelte). Declares Svelte 5 rune globals. |
|
||||
| `jsconfig.json` | Path alias `$lib`, no checkJs. |
|
||||
| `.gitignore` | Excludes node_modules, build, .env.local, etc. |
|
||||
| `.env.example` | codeserver profile vars (CODESERVER_HOST, CODESERVER_PORT). |
|
||||
| `static/_redirects` | Cloudflare Pages: `/* / 301` — every unknown path 301-redirects to homepage. Static assets are served first so this only fires on misses. |
|
||||
| `static/audio/{voiceId}/*.mp3` | Pre-generated Vietnamese voice clips (1–90 + cho + kinh per voice). Generated once by `scripts/generate-audio.py` (edge-tts). Runtime never calls TTS. |
|
||||
| `static/audio/manifest.json` | Voice list `{ id, edgeName, label, gender }[]`. Written by the generator, imported by `audio-manifest.js`. |
|
||||
| `scripts/generate-audio.py` | One-shot Python script: discovers every `vi-*` edge-tts voice, generates 92 clips per voice, writes manifest. Run after voice/wording changes. |
|
||||
| `static/_redirects` | Cloudflare Pages: `/* / 301` — every unknown path 301-redirects to homepage. |
|
||||
| `static/manifest.webmanifest` | PWA manifest: name, short_name, icons (192/512px), theme colors, display: standalone, scope, start_url. |
|
||||
| `static/icons/{192,512}.png` | PWA icons (auto-generated by @vite-pwa/sveltekit from source). |
|
||||
| `static/audio/{voiceId}/*.mp3` | Pre-generated Vietnamese voice clips (1–90 + cho + kinh per voice). Generated by `scripts/generate-audio.py`. SW caches at runtime (CacheFirst). |
|
||||
| `static/audio/manifest.json` | Voice list `{ id, edgeName, label, gender }[]`. |
|
||||
| `scripts/generate-audio.py` | One-shot Python script: generates 92 clips per voice, writes manifest. |
|
||||
| Service Worker (auto) | @vite-pwa/sveltekit generates `/pwa-manifest.json`, `/sw.js`. App shell precache ~353 KB. Auto-update mode with reload toast. |
|
||||
|
||||
## Key Data Structures
|
||||
|
||||
@@ -94,4 +98,4 @@ RootLayout
|
||||
| `saveGrid()` / `loadGrid()` | game-logic.js | localStorage with prefix-based keys. |
|
||||
|
||||
Last reviewed: 2026-04-27
|
||||
Last synced: 2026-04-27 (6-phase refactor)
|
||||
Last synced: 2026-04-27 (UI polish v2 + PWA: font, mode picker, color picker, empty state, PWA stack)
|
||||
@@ -35,6 +35,7 @@ All colors have corresponding `dark:` variants. Tailwind's `prefers-color-scheme
|
||||
## Typography
|
||||
|
||||
- **Font**: Geist Sans (Google Fonts), fallback to Arial/Helvetica.
|
||||
- **Grid numbers**: Roboto Condensed (self-hosted via @fontsource/roboto-condensed, font-display: swap) in `tan-tan-num` stack, bold weight.
|
||||
- **Headings**: Extrabold (font-extrabold) for main title, bold (font-bold) for secondary.
|
||||
- **Body**: Regular weight, slate-600 (light) / slate-400 (dark).
|
||||
- **Emphasis**: `<strong>` for important copy (e.g., button labels in instructions).
|
||||
@@ -65,12 +66,19 @@ All colors have corresponding `dark:` variants. Tailwind's `prefers-color-scheme
|
||||
## Settings & UI Toggles
|
||||
|
||||
### Mode Picker (SettingsButton)
|
||||
Three-way toggle in settings modal:
|
||||
- **Player mode** (`mode: "player"`) — PlayerBoard only, default UX for players
|
||||
- **Master mode** (`mode: "master"`) — MasterPanel only, host-only view (e.g., projector)
|
||||
- **Both mode** (`mode: "both"`) — PlayerBoard + MasterPanel stacked inline, master auto-ticks player board on draw
|
||||
Three-way segmented picker in settings modal with inline SVG glyphs:
|
||||
- **Player mode** (rectangle icon) — PlayerBoard only, default UX for players
|
||||
- **Master mode** (rounded ellipse icon) — MasterPanel only, host-only view (e.g., projector)
|
||||
- **Both mode** (double-box icon) — PlayerBoard + MasterPanel stacked inline, master auto-ticks player board on draw
|
||||
|
||||
Mode selection is persisted to localStorage and applied immediately.
|
||||
SVG icons hand-drawn from primitives (no icon library). Selection persisted to localStorage.
|
||||
|
||||
### Color Picker (SettingsButton)
|
||||
Grouped card with two sub-sections:
|
||||
- **Mẫu** (Presets): 10 Excel color swatches in a flex row (purple, blue, cyan, green, yellow, orange, red, pink, gray, black)
|
||||
- **Tuỳ chỉnh** (Custom): Native `<input type="color">` for custom hex entry
|
||||
|
||||
Card has rounded border, shadow, padding. Compact layout fits mobile.
|
||||
|
||||
## Component Patterns
|
||||
|
||||
@@ -159,5 +167,12 @@ Keep emojis rare; use for celebration only.
|
||||
- Focus states: Implicit via Tailwind (`focus:ring-2`), add if extending components.
|
||||
- Contrast: Tailwind slate/indigo/emerald combos meet WCAG AA.
|
||||
- Dark mode: Respects `prefers-color-scheme`, not forced.
|
||||
- SVG glyphs: Simple, high-contrast shapes (no fill patterns); keyboard-navigable via fieldset + label.
|
||||
|
||||
Last reviewed: 2026-04-26
|
||||
## PWA Features
|
||||
|
||||
- **Install Prompt**: Native browser-driven (no custom banner). Manifest enables Android/iOS install.
|
||||
- **Offline**: App shell precache enables instant load. Audio cached on first play.
|
||||
- **Auto-Update**: SW detects new version; user sees reload toast (non-intrusive).
|
||||
|
||||
Last reviewed: 2026-04-27
|
||||
@@ -12,15 +12,19 @@ now", see `docs/project-overview-pdr.md` and `docs/codebase-summary.md`.
|
||||
- "Quản trò đọc số" voice now speaks Chờ/Kinh on master in "both" mode
|
||||
- One-shot migration: `masterMode: true` → `mode: "both"`
|
||||
|
||||
### UI Polish v2 + Installable PWA (2026-04-27)
|
||||
- **Font**: Roboto Condensed self-hosted via @fontsource (Latin + Vietnamese subsets), swapped into `tan-tan-num` stack
|
||||
- **Empty State**: New `MasterEmptyState.svelte` component for first-run host (mirrors player UX)
|
||||
- **Mode Picker**: 3-way segmented toggle with inline SVG glyphs (player/master/both)
|
||||
- **Color Picker**: Bordered card with "Mẫu" (presets) + "Tuỳ chỉnh" (custom input) sections
|
||||
- **Header Polish**: Decorative dashes flanking subline (replaced all-caps tracking)
|
||||
- **PWA**: @vite-pwa/sveltekit generates SW + manifest. App shell precache (~353 KB). Audio runtime caching (CacheFirst). Auto-update w/ reload toast. Works offline at fairground (no signal venues)
|
||||
|
||||
## Idea Phase
|
||||
|
||||
### Undo Last Cell
|
||||
Allow player to undo the most recent cross/uncross action. Requires change history or state snapshot. **Status**: Idea (low priority)
|
||||
|
||||
|
||||
### PWA Install
|
||||
Add service worker and manifest for "Install App" prompt on Android/iOS. **Status**: Idea (would require server-side components)
|
||||
|
||||
## Considered Phase
|
||||
|
||||
### Multiplayer Sync (Real-time)
|
||||
@@ -86,4 +90,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)
|
||||
Last synced: 2026-04-27 (UI polish v2 + PWA shipped)
|
||||
@@ -177,9 +177,27 @@ PlayerBoard:
|
||||
|
||||
Each draw creates a new object (even repeat numbers) to ensure reactive re-fire.
|
||||
|
||||
## Offline Capability
|
||||
## Offline Capability & PWA
|
||||
|
||||
All state is localStorage. No API calls. Fully functional offline after initial load.
|
||||
**Static Storage**: All game state in localStorage. No API calls. Fully functional offline.
|
||||
|
||||
**Service Worker**: @vite-pwa/sveltekit auto-generates `/sw.js` with:
|
||||
- App shell precache (~353 KB): HTML, CSS, JS bundles for instant load
|
||||
- Audio runtime caching: Voice clips cached on-demand (CacheFirst strategy)
|
||||
- Navigation: NetworkFirst (try network, fallback to cache)
|
||||
- Auto-update: Detects new versions, shows reload toast, skipWaiting on user action
|
||||
|
||||
**Manifest**: `/manifest.webmanifest` enables install prompt on Android/iOS:
|
||||
- Display: `standalone` (fullscreen app-like mode, no browser chrome)
|
||||
- Theme colors: Indigo primary, white background
|
||||
- Icons: 192px + 512px PNG (auto-generated)
|
||||
- Start URL: `/` with basePath awareness
|
||||
|
||||
**Installation Flow**:
|
||||
1. App loads, registers SW (production only, not dev)
|
||||
2. Browser detects manifest → shows install prompt (native, no custom banner)
|
||||
3. User installs → app appears on home screen, launches in standalone mode
|
||||
4. Updates: SW checks for new version; if found, toast appears; user can reload
|
||||
|
||||
Last reviewed: 2026-04-27
|
||||
Last synced: 2026-04-27 (three-mode + auto-tick feature)
|
||||
Last synced: 2026-04-27 (UI polish v2 + PWA: font, mode picker, color picker, empty state, PWA stack)
|
||||
Reference in new issue
Block a user