Files
loto/web/docs/project-overview-pdr.md
tiennm99 c6c839fd95 ci: consolidate web and android workflows into one pipeline
Both subprojects ship from the same commit, so ordinary CI is now a single
ci.yml; only the tag-driven release stands apart. The web app is built twice
per run — once per base path — and every consumer downloads the artifact
instead of rebuilding, replacing three redundant base-"" builds on main.

Nothing deploys unless the test job is green, and android-release runs the
suite before signing (ci.yml does not fire on tags, so it was the only gap).

Shared toolchain setup moves into composite actions, which puts the web build
and the APK on the same Node version for the first time. The Firebase PR path
was still on npm ci against a stale web/package-lock.json that could resolve a
different tree than pnpm-lock.yaml; drop the lockfile and the npm path with it.

Also: least-privilege permissions widened per job, persist-credentials off on
every checkout, concurrency groups that cancel superseded PRs but never a live
deploy, npm caching for android, and the Firebase action pinned by commit SHA
to match how the release actions were already pinned.
2026-08-09 15:21:23 +07:00

8.2 KiB
Raw Permalink Blame History

Lô Tô — Project Overview & PDR

What is Lô Tô?

This app replicates Lô tô hội chợ (Vietnamese fairground / carnival lô tô), specifically the Tân Tân style most familiar from southern Vietnam class reunions and Tết gatherings. Inspiration: TN1 class reunions (2014–2017) where players ran build of physical bingo cards.

It is not the British Bingo 90 / Italian Tombola 3×9 / 15-number ticket. That format is intentionally out of scope.

Game Variant & Scope

In scope (Lô tô hội chợ Tân Tân)

  • Card: 9 rows × 9 columns. Exactly 5 numbers per row AND exactly 5 numbers per column (45 numbers per card; 36 blanks).
  • 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 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.
  • Visual rhythm (soft): the generator avoids rows with 3 consecutive filled columns whenever possible (rejection-sampled per card). Hard invariants (5 per row + 5 per col) always win if the constraint can't be satisfied.
  • Number pool: 1–90.
  • Win condition: 1 row complete = "Kinh!". After winning, the player may keep playing further rows (game does not end).
  • Waiting state: when a row needs only 1 number, app announces "Chờ N".
  • Host (quản trò) draws numbers from a shuffled 1–90 deck, tracks them on a master board with circular tokens (light-blue ring and cream fill when called, dim when 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 5 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 thanh: voice toggles for master + player + Vietnamese voice picker (every vi-* edge-tts voice; default Hoài Mỹ)
    • Màu ô trống: color picker + 10 Excel preset swatches Persisted to loto_settings.

Out of scope

  • 3×9 / 15-number Bingo 90 / Tombola tickets (European format).
  • Two-line and full-house win tiers.
  • Custom number ranges (1–75 American bingo, etc.).
  • Stake / pot / payout logic.
  • Multiplayer real-time sync.

Core Mechanics

  • Players: Generate a randomized 9×9 card. The generator guarantees 5 per row and 5 per column (constraint-aware picker, not loose weighted random). Numbers within each column ascend top-to-bottom. Click cells to mark them.
  • Host: Draws numbers randomly from a shuffled 1–90 deck, shows the current number, and tracks all called numbers on an 11×9 master board aligned by ones-digit (col 0 = 1–9, col 8 = 80–90; 1 / 11 / 21 / … / 81 share row 1, etc.). Each called cell shows its 1-based draw order as a small superscript so the host can quickly verify a "Kinh!" claim by reading the order across the winning row.
  • Bingo: When a row is complete, a celebration popup shows "Kinh!" with confetti. Player keeps playing afterward — no game-end gate.
  • Waiting: "Chờ N" toast when a row is one number away.

Tech Stack

  • Framework: SvelteKit 2 with Svelte 5 (runes mode)
  • Runtime: Svelte 5 runes ($state, $derived, $effect, $props)
  • Styling: Tailwind CSS 4 (utility-first, animations)
  • Persistence: localStorage (no backend)
  • Audio: pre-generated MP3 clips bundled under static/audio/{voiceId}/. Built once by scripts/generate-audio.py using free edge-tts (Microsoft Neural). Runtime plays via plain HTML5 Audio — no TTS API at runtime.
  • Deploy: GitHub Pages at /loto (canonical, via GitHub Actions)
  • Dev Profile: code-server compatible via /absproxy/{port} basePath + HMR proxy config

Architecture Overview

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 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

  • Production: GitHub Pages at https://tiennm99.github.io/loto/ (canonical, basePath /loto). Deployed by the deploy-pages job in .github/workflows/ci.yml on push to main.
  • Development: npm run dev (local), npm run dev:codeserver (code-server via proxy).
  • Build: npm run build:gh generates static export to build/ directory with the /loto basePath.

Key Acceptance Criteria

  • Player card is 9×9 with exactly 5 per row and 5 per column.
  • Numbers within each column are ascending top-to-bottom.
  • Player can click cells to toggle crossed state.
  • Player can clear all marks on the current card without regenerating it (confirm prompt when marks exist).
  • No row has 3 consecutive filled columns (soft constraint, rejection-sampled).
  • Master speaks the called number aloud in Vietnamese (bundled MP3, no runtime TTS API).
  • Player hears "Chờ N" when a row is one away and "Kinh" on bingo.
  • Voice picker in settings — switch among every Vietnamese edge-tts voice.
  • Player numbers comfortably readable on a 360 px viewport.
  • Cell tap on mobile gives haptic + active-press + animated cross-out feedback.
  • Master "Số vừa xổ" hero is the page focal point and auto-scrolls into view on each draw.
  • Cold first paint shows a faded preview card + welcome line, not a gray placeholder.
  • Bingo celebration tiers: 3rd+ row triggers a CSS confetti rain on top of the popup.
  • Bingo popup triggers when row is complete, shows row number and "Kinh!" message.
  • Player may keep marking after a Kinh — no game-end lock.
  • Toast notifications show "Chờ X" before bingo (one number remaining).
  • Host can draw numbers and see them on the 11×9 last-digit-aligned master board.
  • Master board shows draw order on each called cell for Kinh verification.
  • Host has their own player card (isolated by localStorage prefix).
  • Offline persistence via localStorage (grid and crossed state).
  • Dark mode support (Tailwind dark classes).
  • Mobile-responsive (base + sm breakpoints).
  • HMR works on code-server via proxy.
  • Player card rendered as 3 stacked Tân Tân mini-cards with separator labels.
  • Settings modal with 4 fieldsets (theme, master mode, auto-call, color); applies to both grids; persisted.
  • Theme switcher (auto / light / dark) with explicit dark class selector on <html>.
  • Master mode toggle to show MasterPanel on player page.
  • Auto-call timer (1–10s speed) with start/stop button.
  • Mobile-responsive layout (aspect-square sm:aspect-[3/5] cells, text scaling).
  • MasterPanel extracted; mounted conditionally on / when master mode is on.
  • PageFooter with tagline + link.

Visual Language

  • Wordmark gradient: rose → amber → rose, italic + drop-shadow — vintage carnival marquee feel.
  • Player accent: indigo → purple (primary brand, positive action).
  • Host accent: orange (subordinate to wordmark; not gradient).
  • Completed rows: sky blue (success indicator).
  • Waiting toast: amber (attention, ephemeral).
  • Emojis: 🎉 ✨ 🎊 🥳 ❤️ (celebration, joy). Confetti rain on 3rd+ bingo per card.
  • Haptic: 10 ms vibration on cell tap (mobile only, where supported).

Future Considerations (Not Committed)

  • Undo last crossed cell
  • Sound effects on bingo
  • Theme switcher
  • PWA install
  • Multiplayer sync (real-time via WebSocket)
  • i18n beyond Vietnamese

Last reviewed: 2026-05-09 (scope locked: Lô tô hội chợ Tân Tân + theme/master/auto-call) Last synced: 2026-05-09 (deploy target switched to GitHub Pages)