1
0
Fork 0
polaris-task-force/src/components/frontend/minigames/poker/CardView.tsx
Z8MB1E 076a631fe8 feat(poker): drop the center suit glyph from mini cards
Mini-sized cards show the rank and corner suit only, keeping the guide's example rows quiet at small sizes.

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-09-11 17:30:11 -04:00

123 lines
3.4 KiB
TypeScript

"use client";
import { cn } from "@/lib/utils";
import type { Card, Rank, Suit } from "@/lib/minigames/poker";
import { mapCardToSvg, useCardStyle } from "./cardStyle";
const RANK_LABELS: Record<Rank, string> = {
2: "2",
3: "3",
4: "4",
5: "5",
6: "6",
7: "7",
8: "8",
9: "9",
10: "10",
11: "J",
12: "Q",
13: "K",
14: "A",
};
const SUIT_SYMBOLS: Record<Suit, string> = {
s: "♠",
h: "♥",
d: "♦",
c: "♣",
};
const RED_SUITS = new Set<Suit>(["h", "d"]);
interface CardViewProps {
readonly card: Card | null;
readonly faceUp?: boolean;
readonly size?: "mini" | "sm" | "md";
readonly dealIndex?: number;
readonly className?: string;
readonly highlighted?: boolean;
readonly dimmed?: boolean;
}
export function CardView({
card,
faceUp = true,
size = "md",
dealIndex = 0,
className,
highlighted = false,
dimmed = false,
}: CardViewProps) {
const { style: cardStyle } = useCardStyle();
const lightFace = cardStyle === "light";
const classicFace = cardStyle === "classic";
const revealed = card !== null && faceUp;
const redSuitClass = lightFace ? "text-rose-600" : "text-rose-400";
const label = card
? `${RANK_LABELS[card.rank]} of ${
card.suit === "s"
? "spades"
: card.suit === "h"
? "hearts"
: card.suit === "d"
? "diamonds"
: "clubs"
}`
: "Face-down card";
return (
<span
className={cn(
"poker-card inline-block shrink-0",
size === "mini" && "h-10 w-7",
size === "sm" && "h-14 w-10",
size === "md" && "h-16 w-12 sm:h-20 sm:w-14",
dealIndex > 0 && "poker-card-deal",
highlighted &&
"relative z-10 brightness-110 ring-2 ring-amber-300 shadow-lg shadow-amber-300/35",
dimmed && "opacity-40",
className,
)}
style={dealIndex > 0 ? { animationDelay: `${dealIndex * 60}ms` } : undefined}
aria-label={label}
>
<span className="poker-card-motion">
<span className={cn("poker-card-inner", revealed && "poker-card-is-face-up")}>
<span
className={cn(
"poker-card-face poker-card-front gap-1",
lightFace && "poker-card-face--light",
classicFace && "poker-card-front--svg",
)}
aria-hidden={!revealed}
>
{card && classicFace && (
<img src={mapCardToSvg(card)} alt="" draggable={false} className="poker-card-svg" />
)}
{card && !classicFace && (
<>
<span
className={cn(
"flex flex-col items-center leading-none",
RED_SUITS.has(card.suit) && redSuitClass,
)}
>
<span className="font-semibold">{RANK_LABELS[card.rank]}</span>
<span>{SUIT_SYMBOLS[card.suit]}</span>
</span>
{size !== "mini" && (
<span
className={cn("poker-card-center", RED_SUITS.has(card.suit) && redSuitClass)}
>
{SUIT_SYMBOLS[card.suit]}
</span>
)}
</>
)}
</span>
<span className="poker-card-face poker-card-back" aria-hidden={revealed} />
</span>
</span>
</span>
);
}