Files
loto/web/README.md

147 lines
4.9 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Lô Tô
Web version of *Lô tô hội chợ* — Vietnamese fairground bingo. Built with SvelteKit
(static export). The caller draws numbers one at a time; a host panel manages the
game and called numbers are announced aloud in Vietnamese using bundled audio clips.
No runtime TTS or server required.
Live: [tiennm99.github.io/loto](https://tiennm99.github.io/loto)
Android port: [`android/`](../android) — a native Kotlin + Jetpack Compose
app at feature parity with this one (this app is the behavioral spec). It
mounts `static/audio/` straight into its APK, so the voice clips exist once.
---
## Features
- **Number board** — 1–90 grid; called numbers highlight as they are drawn
- **Host panel** — enable "Chế độ quản trò" (host mode) in settings to reveal the
master draw panel inline below your player card
- **Vietnamese voice callouts** — pre-generated MP3 clips for every number using
Microsoft Edge TTS (`edge-tts`); multiple voices selectable in settings; no API
key or internet connection needed at runtime
- **Auto-cross player card** — optional automatic crossing of numbers on the player's
bingo card as they are called
- **Auto-countdown** — configurable delay between number draws for unattended calling
- **PWA** — installable as a progressive web app; works offline after first load
- **Static export** — no server; deploys to any static host or GitHub Pages
---
## Requirements
[Node.js](https://nodejs.org) 18+ and npm.
---
## Quick Start
```sh
git clone https://github.com/tiennm99/loto
cd loto
npm install
npm run dev # dev server at http://localhost:5173
```
---
## Commands
| Command | Description |
|---|---|
| `npm run dev` | Start development server |
| `npm run build` | Production build (root base path) |
| `npm run build:gh` | Production build for GitHub Pages (`/loto` base path) |
| `npm run preview` | Preview production build locally |
| `npm test` | Run unit tests |
| `npm run lint` | ESLint check |
---
## Configuration
Copy `.env.example` to `.env.local` and set:
| Variable | Description |
|---|---|
| `CODESERVER_HOST` | Hostname for code-server reverse-proxy dev (optional) |
| `CODESERVER_PORT` | Port for code-server proxy (optional) |
When running inside code-server use `npm run dev:codeserver` and open
`https://<CODESERVER_HOST>/absproxy/<CODESERVER_PORT>/` (use `/absproxy/`, not
`/proxy/` — the latter strips the path prefix and breaks SvelteKit routing).
---
## Architecture
Single-page SvelteKit app, statically exported. No backend.
```
src/
├── routes/ # Single route: /
├── lib/
│ ├── game-logic.js # Draw state, number pool, called-number tracking
│ ├── master-store.svelte.js # Host panel state (Svelte 5 runes store)
│ ├── settings-store.svelte.js
│ ├── voice.js # Audio manifest loader + clip player
│ ├── player-auto-cross.js # Auto-cross logic
│ ├── MasterPanel.svelte # Host draw UI
│ ├── PlayerBoard.svelte # Bingo card + number grid
│ └── AutoCountdown.svelte # Countdown timer between draws
static/
└── audio/{voiceId}/ # Pre-generated MP3 clips (one per number per voice)
scripts/
└── generate-audio.py # Regenerates audio clips via edge-tts
```
Game state lives entirely in Svelte 5 rune stores; no server round-trips.
Audio manifest is generated once at build prep time — the app reads a JSON manifest
on load and plays the appropriate clip for each drawn number.
---
## Regenerating Audio
Voice clips in `static/audio/` were generated with
[edge-tts](https://github.com/rany2/edge-tts) (free, no API key). To regenerate
(e.g. to add a new Microsoft `vi-*` voice):
```sh
pip install edge-tts
python3 scripts/generate-audio.py
```
The script auto-discovers all available `vi-*` voices and writes an updated manifest.
---
## Regenerating Icons
`static/icons/source.svg` is the source for `icon-192.png` and `icon-512.png`;
`static/icons/source-maskable.svg` (full-bleed background, wordmark inside the
80% safe zone) is the source for `icon-maskable-512.png`. Render them with the
real **Roboto Condensed Black Italic** TTF loaded explicitly — a fallback font is
wider and clips the wordmark. Any SVG rasterizer that accepts a font file works,
e.g. [`@resvg/resvg-js`](https://github.com/thx/resvg-js) with
`font: { fontFiles: [...], loadSystemFonts: false }` and `fitTo` set to each
PNG's exact pixel width. Open the results and check nothing touches the edges.
---
## Deployment
Deployed to GitHub Pages by the `deploy-pages` job in
`.github/workflows/ci.yml` on push to `main`. Build command: `npm run build:gh`
(sets `/loto` base path). Static output in `build/`.
The same workflow also deploys the base-`""` build to Firebase Hosting and
bundles it into the debug APK, so `web/` is built once per base path per run.
---
## License
Apache-2.0 — see [`LICENSE`](../LICENSE) at the repository root.