ScoreBoard
High-scores leaderboard that renders as a table on desktop and as stacked cards on mobile. Highlights ranks 1-3 with distinct neon color classes.
<ScoreBoard scores={defaultHighScores.scores} />import { cn } from '@/lib/utils'
import type { ScoreEntry } from '@/types'
interface ScoreBoardProps {
scores: ScoreEntry[]
className?: string
}
const RANK_CLASSES: Record<number, string> = {
1: 'rank-1',
2: 'rank-2',
3: 'rank-3',
}
function formatScore(score: number): string {
return score.toLocaleString()
}
export function ScoreBoard({ scores, className }: ScoreBoardProps): React.JSX.Element {
return (
<div className={cn('bg-retro-deep border-2 border-retro-green-dim max-w-[900px] mx-auto relative pt-6', className)}>
<div className="scores-board-label">HIGH SCORES</div>
{/* Desktop table */}
<table className="scores-table">
<thead>
<tr>
<th>RANK</th>
<th>PLAYER</th>
<th>STAGE</th>
<th>SCORE</th>
<th>DATE</th>
</tr>
</thead>
<tbody>
{scores.map((entry) => (
<tr
key={entry.rank}
className={cn(RANK_CLASSES[entry.rank])}
>
<td>{String(entry.rank).padStart(2, '0')}</td>
<td>
{entry.player}
{entry.isNew && <span className="new-tag">NEW</span>}
</td>
<td>{entry.stage}</td>
<td className="score-num">{formatScore(entry.score)}</td>
<td>{entry.date}</td>
</tr>
))}
</tbody>
</table>
{/* Mobile cards */}
<div className="scores-cards">
{scores.map((entry) => (
<div key={entry.rank} className="score-card">
<div className={cn('score-card-rank', RANK_CLASSES[entry.rank])}>
#{String(entry.rank).padStart(2, '0')}
</div>
<div className="score-card-player">
{entry.player}
{entry.isNew && <span className="new-tag">NEW</span>}
</div>
<div className="score-card-score">{formatScore(entry.score)}</div>
<div className="score-card-meta">
<div className="score-card-meta-item">
<span className="score-card-meta-label">STAGE</span>
<span className="score-card-meta-val">{entry.stage}</span>
</div>
<div className="score-card-meta-item">
<span className="score-card-meta-label">DATE</span>
<span className="score-card-meta-val">{entry.date}</span>
</div>
</div>
</div>
))}
</div>
</div>
)
}
Button
Arcade-styled button with neon green, magenta, cyan, and yellow variants in three sizes. Renders as a Next.js Link when an href is provided.
Badge
Neon-glow badge with magenta, cyan, yellow, and green color variants. Used for category tags and status labels in the arcade aesthetic.
Card
Game-card component with neon-colored icon, tag, title, description, and a five-star rating display. Supports a "coming soon" locked overlay state.
PixelCharacter
SVG pixel-art sprite with three variants: hero (neon green), ghost (magenta), and coin (yellow). Supports a CSS float animation and configurable size.
SectionHeader
Section heading block with a numbered label, a two-part heading where the last word is accented in neon, and an optional subtitle. Supports left or center alignment.
StatBlock
Single-stat display with a large pixel-font value in neon cyan and a label below. Wrapped in a pixel-border container for the arcade aesthetic.
Text
Polymorphic text primitive with body, caption, label, and code variants styled for the RETRO dark arcade palette.