1. Settings gear was unclickable because H1's `drop-shadow` filter created a stacking context that painted the H1 over the absolutely- positioned trigger. Bump gear wrapper to z-10. (src/routes/+page.svelte) 2. Switch rows in settings had a dead zone — `<label>` wrapped a `<span role=switch>` (not a labelable element), so taps on the text fell through. Move role/click/keydown to the wrapping div so the whole row toggles. Pill stays decorative. (src/lib/SettingsButton.svelte) 3. Empty cells used `dark:[filter:brightness(...)]` which creates a per-cell stacking context — same hazard class as bug #1. Replace with a pointer-events-none overlay span (`hidden dark:block`). (src/lib/PlayerBoard.svelte) 4. Audio cache never evicted on voice swap, leaking ~92 Audio elements per change over a session. New `clearAudioCache()` in voice.js, called from `pickVoice` when the id actually changes. (src/lib/voice.js, src/lib/SettingsButton.svelte) Tests 98/98 pass; build clean.
Lô tô
Bàn số của trò chơi "Lô tô" — SvelteKit app.
Single page (/). The host enables "Chế độ quản trò" in settings to
reveal the master panel inline below their player card; called numbers
get spoken aloud in Vietnamese using bundled MP3 clips (no runtime TTS).
See docs/ for architecture, code standards, and deployment.
Development
npm install
npm run dev
Inside code-server (reverse proxy)
cp .env.example .env.local
# edit .env.local: set CODESERVER_HOST and CODESERVER_PORT
npm run dev:codeserver
Open https://<CODESERVER_HOST>/absproxy/<CODESERVER_PORT>/.
Use /absproxy/{port}/, not /proxy/{port}/ — the latter strips the
path prefix and breaks the SvelteKit base path.
Build
npm run build # default — root basePath, for Cloudflare Pages
npm run build:gh # /loto basePath, for tiennm99.github.io/loto manual export
Static export to build/. Deployed to Cloudflare Pages from main
(set up via the CF dashboard — see docs/deployment-guide.md).
Regenerating audio
Vietnamese voice clips live in static/audio/{voiceId}/. Generated
once with edge-tts (free, no API
key) — runtime never calls TTS. To regenerate (e.g., to add a new voice
that Microsoft ships, or to change wording):
pip install edge-tts
python3 scripts/generate-audio.py
The script auto-discovers every vi-* voice and writes a manifest the
app reads on next reload.