1
0
Fork 0

feat(poker): rework the guide ranking table into tier columns

Rankings flow down the left column then the right with muted chevrons in the gaps marking the order, the text and example cards sit in their own centered columns, and each row wears its ladder tier.

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

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
Jason Fraley 2026-09-11 17:30:11 -04:00
parent 1b0e4ed074
commit 8634c17ff2

View file

@ -1,7 +1,8 @@
"use client"; "use client";
import { BookOpen } from "lucide-react"; import { BookOpen, ChevronDown } from "lucide-react";
import type { Card, Rank, Suit } from "@/lib/minigames/poker"; import type { Card, Rank, Suit } from "@/lib/minigames/poker";
import { cn } from "@/lib/utils";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { import {
Dialog, Dialog,
@ -17,7 +18,8 @@ interface PokerRanking {
readonly name: string; readonly name: string;
readonly example: readonly Card[]; readonly example: readonly Card[];
readonly description: string; readonly description: string;
readonly color?: string; /** 0 (no styling) through 9 (royal prismatic); see handRankStyle.ts. */
readonly tier: number;
} }
function card(rank: Rank, suit: Suit): Card { function card(rank: Rank, suit: Suit): Card {
@ -29,57 +31,61 @@ const RANKINGS: readonly PokerRanking[] = [
name: "High card", name: "High card",
example: [card(14, "s"), card(13, "h"), card(9, "d"), card(5, "c"), card(2, "s")], example: [card(14, "s"), card(13, "h"), card(9, "d"), card(5, "c"), card(2, "s")],
description: "No matching ranks, the highest card wins.", description: "No matching ranks, the highest card wins.",
tier: 0,
}, },
{ {
name: "One pair", name: "One pair",
example: [card(9, "s"), card(9, "h"), card(13, "d"), card(7, "c"), card(3, "s")], example: [card(9, "s"), card(9, "h"), card(13, "d"), card(7, "c"), card(3, "s")],
description: "Two cards share the same rank.", description: "Two cards share the same rank.",
tier: 1,
}, },
{ {
name: "Two pair", name: "Two pair",
example: [card(11, "s"), card(11, "h"), card(4, "d"), card(4, "c"), card(14, "s")], example: [card(11, "s"), card(11, "h"), card(4, "d"), card(4, "c"), card(14, "s")],
description: "Two different pairs plus a fifth card.", description: "Two different pairs plus a fifth card.",
tier: 2,
}, },
{ {
name: "Three of a kind", name: "Three of a kind",
example: [card(7, "s"), card(7, "h"), card(7, "d"), card(13, "c"), card(2, "s")], example: [card(7, "s"), card(7, "h"), card(7, "d"), card(13, "c"), card(2, "s")],
description: "Three cards share the same rank.", description: "Three cards share the same rank.",
tier: 3,
}, },
{ {
name: "Straight", name: "Straight",
example: [card(5, "s"), card(6, "h"), card(7, "d"), card(8, "c"), card(9, "s")], example: [card(5, "s"), card(6, "h"), card(7, "d"), card(8, "c"), card(9, "s")],
description: "Five cards run in rank order.", description: "Five cards run in rank order.",
color: "#ffff00", tier: 4,
}, },
{ {
name: "Flush", name: "Flush",
example: [card(14, "h"), card(10, "h"), card(7, "h"), card(4, "h"), card(2, "h")], example: [card(14, "h"), card(10, "h"), card(7, "h"), card(4, "h"), card(2, "h")],
description: "Five cards share the same suit.", description: "Five cards share the same suit.",
color: "#ffff00", tier: 5,
}, },
{ {
name: "Full house", name: "Full house",
example: [card(13, "s"), card(13, "h"), card(13, "d"), card(8, "c"), card(8, "s")], example: [card(13, "s"), card(13, "h"), card(13, "d"), card(8, "c"), card(8, "s")],
description: "Three of one rank and two of another.", description: "Three of one rank and two of another.",
color: "#ffff00", tier: 6,
}, },
{ {
name: "Four of a kind", name: "Four of a kind",
example: [card(3, "s"), card(3, "h"), card(3, "d"), card(3, "c"), card(14, "s")], example: [card(3, "s"), card(3, "h"), card(3, "d"), card(3, "c"), card(14, "s")],
description: "Four cards share the same rank.", description: "Four cards share the same rank.",
color: "#ffff00", tier: 7,
}, },
{ {
name: "Straight flush", name: "Straight flush",
example: [card(5, "s"), card(6, "s"), card(7, "s"), card(8, "s"), card(9, "s")], example: [card(5, "s"), card(6, "s"), card(7, "s"), card(8, "s"), card(9, "s")],
description: "Five cards run in order and share a suit.", description: "Five cards run in order and share a suit.",
color: "#ffff00", tier: 8,
}, },
{ {
name: "Royal flush", name: "Royal flush",
example: [card(10, "h"), card(11, "h"), card(12, "h"), card(13, "h"), card(14, "h")], example: [card(10, "h"), card(11, "h"), card(12, "h"), card(13, "h"), card(14, "h")],
description: "The ace-high straight flush, the strongest hand.", description: "The ace-high straight flush, the strongest hand.",
color: "#ffff00", tier: 9,
}, },
]; ];
@ -102,18 +108,27 @@ export function PokerGuide() {
<section className="flex flex-col gap-3"> <section className="flex flex-col gap-3">
<h2 className="text-sm font-semibold uppercase tracking-wider">Hand rankings</h2> <h2 className="text-sm font-semibold uppercase tracking-wider">Hand rankings</h2>
<ol className="grid gap-3 sm:grid-cols-2"> <ol className="grid gap-2 sm:grid-flow-col sm:grid-cols-2 sm:grid-rows-[auto_1rem_auto_1rem_auto_1rem_auto_1rem_auto]">
{RANKINGS.map((ranking, index) => ( {RANKINGS.flatMap((ranking, index) => {
<li key={ranking.name} className="flex items-center gap-3 border border-border p-3" style={ranking.color ? ({ const isLast = index === RANKINGS.length - 1;
borderColor: ranking.color const columnBreakAfter = index === 4;
}) : {}}> const nodes = [
<li
key={ranking.name}
className={`relative flex items-center gap-3 border border-border p-3${
ranking.tier > 0 ? ` poker-rank-tier-${ranking.tier}` : ""
}`}
>
<span className="w-5 shrink-0 font-mono text-xs text-muted-foreground"> <span className="w-5 shrink-0 font-mono text-xs text-muted-foreground">
{index + 1} {index + 1}
</span> </span>
<div className="flex min-w-0 flex-1 flex-col gap-1"> <div className="flex min-w-0 flex-1 flex-col justify-center gap-0">
<div className="flex flex-wrap items-center justify-between gap-2">
<span className="font-medium">{ranking.name}</span> <span className="font-medium">{ranking.name}</span>
<div className="flex gap-0.5" aria-label={`${ranking.name} example`}> <p className="text-xs leading-tight text-muted-foreground">
{ranking.description}
</p>
</div>
<div className="flex shrink-0 gap-0.5" aria-label={`${ranking.name} example`}>
{ranking.example.map((exampleCard, cardIndex) => ( {ranking.example.map((exampleCard, cardIndex) => (
<CardView <CardView
key={`${exampleCard.rank}-${exampleCard.suit}`} key={`${exampleCard.rank}-${exampleCard.suit}`}
@ -123,11 +138,24 @@ export function PokerGuide() {
/> />
))} ))}
</div> </div>
</div> </li>,
<p className="text-xs text-muted-foreground max-w-1/2">{ranking.description}</p> ];
</div> if (!isLast) {
</li> nodes.push(
))} <div
key={`${ranking.name}-arrow`}
aria-hidden="true"
className={cn(
"flex items-center justify-center -my-3",
columnBreakAfter && "sm:hidden",
)}
>
<ChevronDown className="size-3.5 text-muted-foreground/70" />
</div>,
);
}
return nodes;
})}
</ol> </ol>
</section> </section>