Soft visual constraint: no row has cols n, n+1, n+2 all filled.
Implementation = constraint-aware per-row picker (uniformly samples
triple-free completions of the forced+candidate set) + whole-grid
rejection sampling (up to 200 attempts). Hard invariants (5 per row,
5 per col, ascending column values) are never sacrificed; if the soft
constraint can't be met, the generator returns the best attempt.
- src/lib/game-logic.js: hasThreeInARow, combinations,
pickFilledColsOnce, pickFilledCols rejection wrapper
- src/lib/game-logic.test.js: 300-trial strict assertion
- docs/codebase-summary.md, project-overview-pdr.md: note the rule
Lets the player wipe all crossed cells on the current card without
generating a new grid. Shown next to "Tạo bảng mới" only when a grid
exists. Confirms before clearing if any cell is marked.
- src/lib/PlayerBoard.svelte: handleClear + secondary outline button
- docs/codebase-summary.md, project-overview-pdr.md: reflect new action
Previous commit misread "remove master board" — restore the 11x9
ones-digit-aligned tracking grid (with circular tokens + draw-order
overlay) inside MasterPanel; remove the host's own player card
("Bảng của quản trò") instead. The host can use the player board
above it; no need for a duplicate card.
Delete /master route entirely (single-page app now). Cloudflare
Pages redirect added via static/_redirects: any unknown path
301-redirects to /, so old /master bookmarks land on the homepage.
Static assets are served first so the rule only fires on misses.
Storage keys loto_master_card_grid / loto_master_card_crossed are
no longer written but old saved data stays in users' localStorage
(harmless leftover).
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.
Match the BAMBOORAFT physical sheet:
- Cell aspect changed from square to 3:5 (taller than wide), matching
the printed paper proportions
- Number cells rendered with .tan-tan-num: condensed system-font stack
(Arial Narrow / Avenir Next Condensed / Roboto Condensed), font-weight
900, negative letter-spacing for tight fall-back on platforms without
a true condensed face. Sized text-2xl / sm:text-3xl to fill the
taller cells. Black number on white cell.
- Default empty-cell color flipped from Minh Tân brown (#7a4a2b) to
BAMBOORAFT blue (#1e88e5). Settings store + test updated. Users who
picked a different color in settings keep their choice.
- Section dividers and labels recolored from orange to matching blue.
- Section labels relabelled to the BAMBOORAFT trio:
"Tân Tân" / "An khang thịnh vượng" / "Tân Tân tốt nhất"
Replaces the orange/red filled-square cells on the master tracking
board with a circular token treatment inspired by the Hội chợ "Các
số đã ra" panel. Each numbered cell now renders a centered circle
with a colored ring — pink for 1–49, green for 50–90 — over a
cream-yellow fill when the number has been called, or dimmed/empty
when it has not. The most recent draw keeps its red accent via a
ring-offset highlight + slight scale, so the host can still find
"vừa xổ" at a glance. Draw-order superscript stays at the cell's
top-right corner, now in muted slate (visible against both the
cream-filled and dim-empty token states).
Adds a gear button + modal on /, /master with a native color picker
and 8 preset swatches for the empty-cell background. The selected
color repaints both the player card AND the master tracking grid via
the --empty-cell-bg CSS variable. Default brown matches the physical
Minh Tân paper card.
settings-store.svelte.js: Svelte 5 rune-based reactive store, state
hydrated on layout mount, persisted to localStorage key loto_settings.
Validates hex (#rrggbb regex) before applying — keeps the CSS sink
safe and ignores corrupt or shorthand-3-digit payloads.
SettingsButton.svelte: gear icon, modal with picker + presets + reset
+ close, escape-to-close via window keydown listener (the trigger
button retains focus on open, so a dialog-scoped handler would miss).
eslint.config.mjs: declares Svelte 5 rune globals so .svelte.js stores
lint clean.
Docs synced: PDR, codebase-summary, system-architecture, roadmap.
`app/` should hold route segments only. Game logic and the player-card
component are imported by both routes, so they belong outside:
app/loto-game-logic.ts -> lib/game-logic.ts
app/loto-player-board.tsx -> components/player-board.tsx
Imports use the existing @/* alias. Drops the redundant `loto-` prefix.
Also fixes the package name from the scaffold default.
Adds the standard ./docs/ structure (overview, codebase summary,
architecture, code standards, design guidelines, deployment guide,
roadmap) and the code-review report under ./plans/reports/.
README now points at the docs and covers the codeserver dev profile.