diff --git a/web/docs/codebase-summary.md b/web/docs/codebase-summary.md index 73bad39..7067312 100644 --- a/web/docs/codebase-summary.md +++ b/web/docs/codebase-summary.md @@ -12,7 +12,7 @@ ### Shared Components | File | Purpose | |------|---------| -| `src/lib/PlayerBoard.svelte` | Reusable player card (9×9 grid rendered as 3 stacked 3×9 mini-cards: Minh Tân / Loại đặc biệt / Tấn tài tấn lộc). Handles crossed state, bingo popup, "Chờ X" toast. Accepts `storagePrefix` prop for multi-card isolation. Empty cells use `--empty-cell-bg` CSS var from settings store. | +| `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:5) cells with condensed bold black numbers (`tan-tan-num` font stack), white number cells, blue empty cells. Handles crossed state, bingo popup, "Chờ X" toast. Accepts `storagePrefix` prop for multi-card isolation. Empty cells use `--empty-cell-bg` CSS var from settings store. | | `src/lib/SettingsButton.svelte` | Gear icon + modal. Color picker + 8 preset swatches for empty-cell color. Reset-to-default button. Mounted on both `/` and `/master` headers. | ### Game Logic diff --git a/web/docs/project-overview-pdr.md b/web/docs/project-overview-pdr.md index 9016eeb..6cdc058 100644 --- a/web/docs/project-overview-pdr.md +++ b/web/docs/project-overview-pdr.md @@ -16,8 +16,10 @@ That format is intentionally out of scope. - **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 — *Minh Tân*, *Loại đặc biệt*, - *Tấn tài tấn lộc* — matching the physical Minh Tân paper sheet. + 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, condensed bold black numbers on white, + blue empty-cell background by default). - **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. diff --git a/web/src/app.css b/web/src/app.css index 3c6774f..4b0182e 100644 --- a/web/src/app.css +++ b/web/src/app.css @@ -3,9 +3,9 @@ :root { --background: #f8fafc; --foreground: #1e293b; - /* Default empty-cell color matches a Minh Tân paper card. Overridden - at runtime by the settings store via documentElement.style. */ - --empty-cell-bg: #7a4a2b; + /* Default empty-cell color matches a Tân Tân BAMBOORAFT paper card. + Overridden at runtime by the settings store via documentElement.style. */ + --empty-cell-bg: #1e88e5; } @media (prefers-color-scheme: dark) { @@ -34,41 +34,57 @@ body { } /* Decorative cross-hatch divider between mini-cards on the player sheet, - echoing the ✚✚✚ borders on a physical Minh Tân card. */ + echoing the ✚✚✚ blue borders on a Tân Tân BAMBOORAFT card. */ .section-divider { height: 8px; background-image: repeating-linear-gradient( 90deg, transparent 0 6px, - #c2410c 6px 8px, + #1e88e5 6px 8px, transparent 8px 14px ); - background-color: rgb(255 247 237 / 0.6); + background-color: rgb(227 242 253 / 0.7); } @media (prefers-color-scheme: dark) { .section-divider { - background-color: rgb(67 20 7 / 0.4); + background-color: rgb(13 71 161 / 0.4); } } .section-label { text-align: center; - font-size: 0.7rem; + font-size: 0.75rem; letter-spacing: 0.18em; text-transform: uppercase; - font-weight: 600; + font-weight: 700; padding: 6px 0; - color: #c2410c; - background-color: rgb(255 247 237 / 0.5); + color: #1565c0; + background-color: rgb(227 242 253 / 0.6); font-style: italic; } @media (prefers-color-scheme: dark) { .section-label { - color: #fb923c; - background-color: rgb(67 20 7 / 0.4); + color: #64b5f6; + background-color: rgb(13 71 161 / 0.4); } } +/* Tân Tân BAMBOORAFT-style number — tall, condensed, heavy black. Stack + prefers a system condensed face; falls back to negative letter-spacing + on platforms without one so the digits still read tall-and-tight. */ +.tan-tan-num { + font-family: + "Arial Narrow", + "Avenir Next Condensed", + "Roboto Condensed", + "Liberation Sans Narrow", + system-ui, + sans-serif; + font-weight: 900; + letter-spacing: -0.04em; + line-height: 1; +} + @keyframes fade-in { from { opacity: 0; } to { opacity: 1; } } @keyframes pop-in { 0% { opacity: 0; transform: scale(0.5); } 70% { transform: scale(1.05); } 100% { opacity: 1; transform: scale(1); } } @keyframes bounce-slow { 0%, 100% { transform: translateX(-50%) translateY(0); } 50% { transform: translateX(-50%) translateY(-12px); } } diff --git a/web/src/lib/PlayerBoard.svelte b/web/src/lib/PlayerBoard.svelte index 3a94174..93b235d 100644 --- a/web/src/lib/PlayerBoard.svelte +++ b/web/src/lib/PlayerBoard.svelte @@ -140,9 +140,9 @@ // visual segmentation. const SECTIONS = /** @type {const} */ ([0, 3, 6]); const SECTION_LABELS = [ - "Minh Tân", - "Loại đặc biệt", - "Tấn tài tấn lộc", + "Tân Tân", + "An khang thịnh vượng", + "Tân Tân tốt nhất", ]; @@ -180,7 +180,7 @@ {#if !hasNumber}
{:else} @@ -189,16 +189,16 @@ aria-label="Số {num}{isCrossed ? ', đã đánh dấu' : ''}" aria-pressed={isCrossed} onclick={() => handleCellClick(row, col)} - class="relative flex items-center justify-center - aspect-square text-base sm:text-xl font-bold - border-r border-b border-slate-200/80 dark:border-slate-700/60 + class="tan-tan-num relative flex items-center justify-center + aspect-[3/5] text-2xl sm:text-3xl + border-r border-b border-slate-300/60 dark:border-slate-700/60 transition-all select-none cursor-pointer focus:outline-none focus:ring-2 focus:ring-inset focus:ring-indigo-400 {isCrossed ? rowComplete - ? 'cell-crossed bg-emerald-100 dark:bg-emerald-900/40 text-emerald-500 dark:text-emerald-400' - : 'cell-crossed bg-red-50 dark:bg-red-950/30 text-red-400 dark:text-red-500' - : 'bg-white dark:bg-slate-800 text-slate-800 dark:text-slate-100 hover:bg-indigo-50 dark:hover:bg-indigo-950/30 hover:text-indigo-600 dark:hover:text-indigo-400'}" + ? 'cell-crossed bg-emerald-100 dark:bg-emerald-900/40 text-emerald-600 dark:text-emerald-400' + : 'cell-crossed bg-red-50 dark:bg-red-950/30 text-red-500 dark:text-red-500' + : 'bg-white dark:bg-slate-800 text-black dark:text-slate-100 hover:bg-indigo-50 dark:hover:bg-indigo-950/30 hover:text-indigo-600 dark:hover:text-indigo-400'}" > {num} diff --git a/web/src/lib/settings-store.svelte.js b/web/src/lib/settings-store.svelte.js index 1251b35..ed7802b 100644 --- a/web/src/lib/settings-store.svelte.js +++ b/web/src/lib/settings-store.svelte.js @@ -11,8 +11,8 @@ const STORAGE_KEY = "loto_settings"; const HEX6 = /^#[0-9a-fA-F]{6}$/; export const DEFAULT_SETTINGS = Object.freeze({ - /** Brown — matches a Minh Tân paper card. */ - emptyCellColor: "#7a4a2b", + /** Blue — matches a Tân Tân BAMBOORAFT paper card. */ + emptyCellColor: "#1e88e5", }); export const settings = $state({ ...DEFAULT_SETTINGS }); diff --git a/web/src/lib/settings-store.test.js b/web/src/lib/settings-store.test.js index 1cf1340..0282044 100644 --- a/web/src/lib/settings-store.test.js +++ b/web/src/lib/settings-store.test.js @@ -22,8 +22,8 @@ describe("settings-store — defaults", () => { expect(Object.isFrozen(DEFAULT_SETTINGS)).toBe(true); }); - it("default empty-cell color is brown (#7a4a2b)", () => { - expect(DEFAULT_SETTINGS.emptyCellColor).toBe("#7a4a2b"); + it("default empty-cell color is Tân Tân blue (#1e88e5)", () => { + expect(DEFAULT_SETTINGS.emptyCellColor).toBe("#1e88e5"); }); });