mirror of
https://github.com/tiennm99/loto.git
synced 2026-09-10 02:17:36 +00:00
feat(player): center "Chờ N" chip + pulse the awaited cell
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.
This commit is contained in:
+25
@@ -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. */
|
||||
|
||||
@@ -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<string>} */
|
||||
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}
|
||||
<!-- Empty cell. Dark-mode dim is an overlay div, not a CSS
|
||||
@@ -418,7 +437,7 @@
|
||||
{:else}
|
||||
<button
|
||||
type="button"
|
||||
aria-label="Số {num}{isCrossed ? ', đã đánh dấu' : ''}"
|
||||
aria-label="Số {num}{isCrossed ? ', đã đánh dấu' : ''}{isWaitingCell ? ', đang chờ' : ''}"
|
||||
aria-pressed={isCrossed}
|
||||
onclick={() => handleCellClick(row, col)}
|
||||
class="tan-tan-num relative flex items-center justify-center
|
||||
@@ -427,6 +446,7 @@
|
||||
border border-slate-400/50 dark:border-slate-600/40
|
||||
transition-all select-none cursor-pointer active:scale-90
|
||||
focus:outline-none focus:ring-2 focus:ring-inset focus:ring-rose-400
|
||||
{isWaitingCell ? 'cell-waiting' : ''}
|
||||
{isCrossed
|
||||
? rowComplete
|
||||
? 'bg-emerald-100 dark:bg-emerald-900/60 text-emerald-700 dark:text-emerald-200'
|
||||
@@ -464,19 +484,20 @@
|
||||
</div>
|
||||
|
||||
{#if toast}
|
||||
<!-- Anchor toast ABOVE the grid (not centered over playable cells)
|
||||
so it announces without blocking row taps. Pointer-events
|
||||
still routed to the dismiss button only. -->
|
||||
<!-- Centered overlay over the card. Lower opacity + pointer-events
|
||||
routing keeps cell taps unblocked while the chip is visible.
|
||||
Cell-pulse on the awaited number is the persistent visual
|
||||
anchor; this chip is the textual fresh-event cue. -->
|
||||
<div
|
||||
role="status"
|
||||
aria-live="polite"
|
||||
class="absolute left-1/2 -translate-x-1/2 -top-3 sm:-top-4 z-10 pointer-events-none"
|
||||
class="absolute inset-0 z-10 flex items-center justify-center pointer-events-none"
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
onclick={dismissToast}
|
||||
aria-label="Đóng thông báo"
|
||||
class="pointer-events-auto px-5 py-2.5 rounded-2xl bg-amber-500/95 dark:bg-amber-600/95 text-white text-lg sm:text-xl font-black shadow-xl animate-toast"
|
||||
class="pointer-events-auto px-6 py-3 rounded-2xl bg-amber-500/75 dark:bg-amber-600/75 text-white text-2xl sm:text-3xl font-black shadow-2xl animate-toast backdrop-blur-sm"
|
||||
>
|
||||
{toast}
|
||||
</button>
|
||||
|
||||
Reference in New Issue
Block a user