From c4e103cd58a13c5f4959b4a8b5483ad2fc0ebc48 Mon Sep 17 00:00:00 2001 From: tiennm99 Date: Thu, 30 Apr 2026 21:41:39 +0700 Subject: [PATCH] =?UTF-8?q?feat(player):=20center=20"Ch=E1=BB=9D=20N"=20ch?= =?UTF-8?q?ip=20+=20pulse=20the=20awaited=20cell?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The toast above the card was disconnected from where the user actually looks (the 9×9 grid). Two-part redesign per /ck:research + /ck:brainstorm: - Chip moved to the vertical center of the card with bg-amber-500/75 + backdrop-blur-sm + pointer-events-none on the wrapper. Taps still pass through to the cells underneath; auto-hide stays at 5s. - Persistent 1.6s breathing pulse on the cell holding the awaited number — amber inset ring + soft outer glow, dimmed in prefers-reduced-motion to a static ring. Material 3-style cadence picked over scale-bounce because the grid is dense and bouncing cells would smudge neighbours. Pulses scale to multiple simultaneous waiting rows (Set-keyed by "row,col"). aria-label gains "đang chờ" so screen readers pick it up. --- src/app.css | 25 +++++++++++++++++++++++++ src/lib/PlayerBoard.svelte | 33 +++++++++++++++++++++++++++------ 2 files changed, 52 insertions(+), 6 deletions(-) diff --git a/src/app.css b/src/app.css index d4b694b..19ea208 100644 --- a/src/app.css +++ b/src/app.css @@ -107,6 +107,31 @@ body { .section-label-waiting { animation: none; } } +/* Per-cell waiting pulse — applied to the specific board cell that holds + the awaited number for a "Chờ" row. Amber inset ring + outer glow + breathe at 1.6s (Material 3 standard pulse cadence) to attract the + eye without fatigue over a long round. */ +.cell-waiting { + animation: cell-waiting-pulse 1.6s ease-in-out infinite; +} +@keyframes cell-waiting-pulse { + 0%, 100% { + box-shadow: + inset 0 0 0 3px rgb(245 158 11 / 0.45); + } + 50% { + box-shadow: + inset 0 0 0 3px rgb(245 158 11 / 0.95), + 0 0 8px 2px rgb(245 158 11 / 0.5); + } +} +@media (prefers-reduced-motion: reduce) { + .cell-waiting { + animation: none; + box-shadow: inset 0 0 0 3px rgb(245 158 11 / 0.7); + } +} + /* 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. */ diff --git a/src/lib/PlayerBoard.svelte b/src/lib/PlayerBoard.svelte index 028b07d..017d71b 100644 --- a/src/lib/PlayerBoard.svelte +++ b/src/lib/PlayerBoard.svelte @@ -76,6 +76,24 @@ : [] ); + // "row,col" keys of cells holding the awaited number for any waiting + // row. Drives the per-cell pulse animation so the user can spot which + // number to find without reading text. Multiple rows may be waiting + // simultaneously — each gets its own pulsing cell. + const waitingCells = $derived.by(() => { + /** @type {Set} */ + const set = new Set(); + if (!grid || crossed.length === 0) return set; + for (let r = 0; r < grid.length; r++) { + if (rowCompleteness[r]) continue; + const num = getWaitingNumber(grid, crossed, r); + if (num === null) continue; + const c = grid[r].indexOf(num); + if (c >= 0) set.add(`${r},${c}`); + } + return set; + }); + function dismissToast() { toast = null; if (toastTimer) { @@ -400,6 +418,7 @@ {@const hasNumber = num > 0} {@const isCrossed = hasNumber && !!crossed[row]?.[col]} {@const rowComplete = hasNumber && rowCompleteness[row]} + {@const isWaitingCell = hasNumber && waitingCells.has(`${row},${col}`)} {#if !hasNumber} +