mirror of
https://github.com/tiennm99/loto.git
synced 2026-09-02 00:19:40 +00:00
feat(player-board): persistent Chờ ring + livelier confetti
Three player-board polish tweaks: 1. Per-section Chờ indicator. Each section's label band glows amber while any of its 3 rows is one cell from bingo. Uses the same amber as the toast so colour stays consistent. Animation respects prefers-reduced-motion. 2. Confetti threshold drops from 3+ bingos to: 2nd bingo OR 1st bingo while another row is in Chờ. The old threshold rarely fired on a 9-row card so most wins felt under-celebrated. 3. Confetti emoji set adds 🥢 🎋 🏮 (chopsticks, bamboo, lantern) for hội-chợ flavour, plus per-piece size jitter (1.5–2.4rem) via a --size CSS variable.
This commit is contained in:
@@ -1,9 +1,10 @@
|
||||
---
|
||||
name: Per-row "Chờ" indicator
|
||||
phase: 5
|
||||
status: todo
|
||||
status: completed
|
||||
priority: medium
|
||||
effort: 1h
|
||||
completed: 2026-04-28
|
||||
---
|
||||
|
||||
# Phase 5 — Per-row "Chờ" visual indicator
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
---
|
||||
name: Confetti polish
|
||||
phase: 6
|
||||
status: todo
|
||||
status: completed
|
||||
priority: low
|
||||
effort: 30m
|
||||
completed: 2026-04-28
|
||||
---
|
||||
|
||||
# Phase 6 — Confetti polish
|
||||
|
||||
@@ -21,8 +21,8 @@ YAGNI — parking-lot features and upstream-blocked items skipped.
|
||||
| 2 | CI inline-script guard ✅ | `phase-02-ci-inline-script-guard.md` |
|
||||
| 3 | Mode picker glyph redesign ✅ | `phase-03-mode-picker-glyphs.md` |
|
||||
| 4 | Settings modal sticky on small screens ✅ | `phase-04-settings-modal-sticky.md` |
|
||||
| 5 | Per-row "Chờ" indicator | `phase-05-cho-row-indicator.md` |
|
||||
| 6 | Confetti polish (threshold + variety) | `phase-06-confetti-polish.md` |
|
||||
| 5 | Per-row "Chờ" indicator ✅ | `phase-05-cho-row-indicator.md` |
|
||||
| 6 | Confetti polish (threshold + variety) ✅ | `phase-06-confetti-polish.md` |
|
||||
| 7 | Strict CSP via hashed inline script | `phase-07-strict-csp-hashed.md` |
|
||||
| 8 | Audio cache LRU rule | `phase-08-audio-cache-lru.md` |
|
||||
| 9 | PWA install verification checklist | `phase-09-pwa-verify-install.md` |
|
||||
|
||||
+19
-1
@@ -133,6 +133,24 @@ body {
|
||||
);
|
||||
}
|
||||
|
||||
/* Persistent visual cue when any of the section's 3 rows is in "Chờ"
|
||||
(one cell from bingo). Amber matches the toast — `bg-amber-500/95`
|
||||
in PlayerBoard — so users associate the same colour with the same
|
||||
meaning. Inset ring so the label band keeps its natural width. */
|
||||
.section-label-waiting {
|
||||
box-shadow: inset 0 0 0 2px rgb(245 158 11 / 0.6);
|
||||
animation: section-pulse 2.4s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes section-pulse {
|
||||
0%, 100% { box-shadow: inset 0 0 0 2px rgb(245 158 11 / 0.45); }
|
||||
50% { box-shadow: inset 0 0 0 2px rgb(245 158 11 / 0.85); }
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.section-label-waiting { animation: none; }
|
||||
}
|
||||
|
||||
/* 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. */
|
||||
@@ -187,7 +205,7 @@ body {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: var(--x);
|
||||
font-size: 2rem;
|
||||
font-size: var(--size, 2rem);
|
||||
animation: confetti-fall 1.6s ease-in var(--delay) forwards;
|
||||
will-change: transform, opacity;
|
||||
}
|
||||
|
||||
@@ -32,7 +32,9 @@
|
||||
// 12 confetti emoji indices. Stable per-render — values don't matter,
|
||||
// only the count drives the {#each}.
|
||||
const CONFETTI = Array.from({ length: 12 }, (_, i) => i);
|
||||
const CONFETTI_EMOJI = ["🎊", "✨", "🎉", "🥳"];
|
||||
// Hội-chợ flavour added (lantern, bamboo, chopsticks) so the
|
||||
// celebration set reads as Vietnamese fair, not generic party.
|
||||
const CONFETTI_EMOJI = ["🎊", "✨", "🎉", "🥳", "🥢", "🎋", "🏮"];
|
||||
|
||||
// Plain refs (not reactive)
|
||||
/** @type {ReturnType<typeof setTimeout> | null} */
|
||||
@@ -51,6 +53,18 @@
|
||||
: []
|
||||
);
|
||||
|
||||
// Per-row "Chờ" flag — one cell away AND not already complete. Reuses
|
||||
// rowCompleteness so we don't re-walk the row twice.
|
||||
const waitingRows = $derived(
|
||||
grid && crossed.length
|
||||
? grid.map(
|
||||
(_, r) =>
|
||||
!rowCompleteness[r] &&
|
||||
getWaitingNumber(grid, crossed, r) !== null,
|
||||
)
|
||||
: []
|
||||
);
|
||||
|
||||
function dismissToast() {
|
||||
toast = null;
|
||||
if (toastTimer) {
|
||||
@@ -110,7 +124,19 @@
|
||||
notifiedWaitingRows.add(i);
|
||||
congratsRow = i + 1;
|
||||
showCongrats = true;
|
||||
celebrationTier = celebratedRows.size >= 3 ? 2 : 1;
|
||||
// Tier 2 confetti: 2nd bingo, OR 1st bingo while another row
|
||||
// is one cell away. The previous "3+ bingos" threshold rarely
|
||||
// fired on a 9-row card so most wins felt under-celebrated.
|
||||
const hasActiveCho = grid.some(
|
||||
(_, r) =>
|
||||
!celebratedRows.has(r) &&
|
||||
getWaitingNumber(grid, crossed, r) !== null,
|
||||
);
|
||||
celebrationTier =
|
||||
celebratedRows.size >= 2 ||
|
||||
(celebratedRows.size >= 1 && hasActiveCho)
|
||||
? 2
|
||||
: 1;
|
||||
if (announce) playBingo();
|
||||
break;
|
||||
}
|
||||
@@ -219,6 +245,14 @@
|
||||
"TN1 (2014-2017)",
|
||||
"Độc-Đỉnh-Điên",
|
||||
];
|
||||
|
||||
// Persistent ring on the section band when any of its 3 rows is
|
||||
// waiting on a single number. Reduces reliance on the 5s toast.
|
||||
const sectionHasWaiting = $derived(
|
||||
SECTIONS.map((startRow) =>
|
||||
waitingRows.slice(startRow, startRow + 3).some(Boolean)
|
||||
)
|
||||
);
|
||||
</script>
|
||||
|
||||
<div class="flex flex-wrap justify-center gap-3 mb-6">
|
||||
@@ -257,7 +291,11 @@
|
||||
<div class="section-divider-vertical" aria-hidden="true"></div>
|
||||
<div class="flex-1">
|
||||
{#each SECTIONS as startRow, sectionIdx (sectionIdx)}
|
||||
<div class="section-label">{SECTION_LABELS[sectionIdx]}</div>
|
||||
<div
|
||||
class="section-label {sectionHasWaiting[sectionIdx] ? 'section-label-waiting' : ''}"
|
||||
>
|
||||
{SECTION_LABELS[sectionIdx]}
|
||||
</div>
|
||||
<div class="loto-grid">
|
||||
{#each grid.slice(startRow, startRow + 3).flat() as num, idx (idx)}
|
||||
{@const row = startRow + Math.floor(idx / 9)}
|
||||
@@ -378,6 +416,7 @@
|
||||
style:--x="{(i * 8.3 + (i % 3) * 11) % 100}%"
|
||||
style:--delay="{(i * 37) % 400}ms"
|
||||
style:--rot="{(i * 67) % 360}deg"
|
||||
style:--size="{(1.5 + ((i * 13) % 11) / 10).toFixed(2)}rem"
|
||||
>
|
||||
{CONFETTI_EMOJI[i % CONFETTI_EMOJI.length]}
|
||||
</span>
|
||||
|
||||
Reference in New Issue
Block a user