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 (