diff --git a/src/components/frontend/minigames/blackjack/BlackjackFeltCanvas.tsx b/src/components/frontend/minigames/blackjack/BlackjackFeltCanvas.tsx index 47a7c77..b996e93 100644 --- a/src/components/frontend/minigames/blackjack/BlackjackFeltCanvas.tsx +++ b/src/components/frontend/minigames/blackjack/BlackjackFeltCanvas.tsx @@ -2,7 +2,7 @@ import { useEffect, useRef, useState } from "react"; import { cn } from "@/lib/utils"; -import { handValue, type Card, type PublicPlayerView } from "@/lib/minigames/blackjack"; +import { handValue, type Card, type PublicPlayerView, type Rank } from "@/lib/minigames/blackjack"; import type { BlackjackSnapshot } from "@/app/(frontend)/minigames/blackjack/actions"; import { Badge } from "@/components/ui/badge"; import { CardView } from "./CardView"; @@ -40,6 +40,15 @@ function signedAmount(value: number): string { return `${value >= 0 ? "+" : ""}${value.toLocaleString()}`; } +// Upcard label reads the card's point value, not its rank: face cards (J/Q/K) +// count as 10, aces read "ace", and 2-10 show their pip count. +function upcardDisplay(rank: Rank | undefined): string { + if (!rank) return ""; + if (rank === 14) return "ace"; + if (rank >= 11) return "10"; + return String(rank); +} + export function BlackjackFeltCanvas({ snapshot, onSnapshot, @@ -196,8 +205,7 @@ export function BlackjackFeltCanvas({ )} {view.dealer.holeHidden && view.dealer.cards.length > 0 && ( - Dealer showing{" "} - {view.dealer.cards[0]?.rank === 14 ? "ace" : (view.dealer.cards[0]?.rank ?? "")} + Dealer showing {upcardDisplay(view.dealer.cards[0]?.rank)} )} diff --git a/src/components/frontend/minigames/blackjack/BlackjackLeaderboard.tsx b/src/components/frontend/minigames/blackjack/BlackjackLeaderboard.tsx index 89efad6..702809e 100644 --- a/src/components/frontend/minigames/blackjack/BlackjackLeaderboard.tsx +++ b/src/components/frontend/minigames/blackjack/BlackjackLeaderboard.tsx @@ -3,6 +3,7 @@ import { useEffect, useState } from "react"; import { Trophy } from "lucide-react"; import type { PresenceParticipant } from "@/hooks/useRealtimePresence"; +import { PROFILE_UPDATE_EVENT } from "@/hooks/useGameTick"; import { cn } from "@/lib/utils"; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; import { Skeleton } from "@/components/ui/skeleton"; @@ -84,6 +85,20 @@ export function BlackjackLeaderboard({ }: BlackjackLeaderboardProps) { const [rows, setRows] = useState(null); const [failed, setFailed] = useState(false); + const [refreshTick, setRefreshTick] = useState(0); + + // Read-only surface: keep it fresh without a page refresh. A profile update + // (a fellow player's result) and a 30s poll both bump refreshTick, which + // re-runs the fetch below. Rows only update on success, so no loading flash. + useEffect(() => { + const handleProfileUpdate = () => setRefreshTick((tick) => tick + 1); + window.addEventListener(PROFILE_UPDATE_EVENT, handleProfileUpdate); + const poll = window.setInterval(() => setRefreshTick((tick) => tick + 1), 30_000); + return () => { + window.removeEventListener(PROFILE_UPDATE_EVENT, handleProfileUpdate); + window.clearInterval(poll); + }; + }, []); useEffect(() => { let cancelled = false; @@ -135,7 +150,7 @@ export function BlackjackLeaderboard({ return () => { cancelled = true; }; - }, [currentUserId, refreshKey]); + }, [currentUserId, refreshKey, refreshTick]); return (