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