Setup Guide
One-time setup for using RETRO // NEXT components. Add these to your project, then copy any component from the theme.
The cn() utility combines clsx and tailwind-merge for conditional class names. Install both packages.
npm install clsx tailwind-mergeCreate lib/utils.ts with the cn() helper. Every component imports this for class name merging.
import { type ClassValue, clsx } from 'clsx'
import { twMerge } from 'tailwind-merge'
export function cn(...inputs: ClassValue[]): string {
return twMerge(clsx(inputs))
}Add the RETRO // NEXT design tokens to your globals.css. These define colors, fonts, and animation tokens used by every component in the theme.
@theme {
/* Colors */
--color-retro-black: #070810;
--color-retro-deep: #0d0f1a;
--color-retro-green: #00ff41;
--color-retro-green-dim: #00aa2a;
--color-retro-green-glow: rgba(0, 255, 65, 0.2);
--color-retro-magenta: #ff00a8;
--color-retro-magenta-glow: rgba(255, 0, 168, 0.2);
--color-retro-cyan: #00e5ff;
--color-retro-cyan-glow: rgba(0, 229, 255, 0.2);
--color-retro-yellow: #ffe600;
--color-retro-white: #e8eaf0;
--color-retro-dim: rgba(232, 234, 240, 0.45);
/* Fonts */
--font-pixel: var(--font-press-start);
--font-crt: var(--font-vt323);
/* Animations */
--animate-blink: blink-cursor 0.8s step-end infinite;
--animate-blink-slow: blink-cursor 1s step-end infinite;
--animate-flicker: flicker-subtle 4s infinite;
--animate-float: character-float 3s ease-in-out infinite;
--animate-ticker: ticker-scroll 30s linear infinite;
--animate-grid-scroll: grid-scroll 20s linear infinite;
--animate-pulse-life: pulse-life 2s ease-in-out infinite;
--animate-fill-bar: fill-bar 2s steps(20, end) 1s forwards;
}Each component may need its own CSS classes in your globals.css. Copy the styles for the components you use.
/* ─── Button Component ───────────────────────────────────── */
.btn-base {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 8px;
font-family: var(--font-press-start), monospace;
letter-spacing: 2px;
text-transform: uppercase;
text-decoration: none;
cursor: none;
clip-path: polygon(
0 6px, 6px 0,
calc(100% - 6px) 0, 100% 6px,
100% calc(100% - 6px), calc(100% - 6px) 100%,
6px 100%, 0 calc(100% - 6px)
);
transition: transform 0.1s, box-shadow 0.15s, background 0.15s, color 0.15s, border-color 0.15s;
}
.btn-sm {
font-size: clamp(6px, 0.7vw, 7px);
padding: 10px clamp(14px, 1.5vw, 20px);
}
.btn-md {
font-size: clamp(7px, 0.85vw, 9px);
padding: clamp(10px, 1.5vw, 14px) clamp(18px, 2.5vw, 28px);
}
.btn-lg {
font-size: clamp(7px, 1vw, 10px);
padding: clamp(12px, 2vw, 18px) clamp(20px, 3vw, 36px);
}
.btn-primary {
background: var(--color-retro-green);
color: var(--color-retro-black);
border: 2px solid transparent;
}
.btn-primary:hover {
background: #33ff66;
box-shadow: 0 0 30px var(--color-retro-green), 0 0 60px var(--color-retro-green-glow);
}
.btn-primary:active { transform: translate(2px, 2px); }
.btn-secondary {
background: transparent;
color: var(--color-retro-cyan);
border: 2px solid var(--color-retro-cyan);
}
.btn-secondary:hover {
background: rgba(0, 229, 255, 0.1);
box-shadow: 0 0 20px rgba(0, 229, 255, 0.2);
}
.btn-ghost {
background: transparent;
color: rgba(232, 234, 240, 0.5);
border: 2px solid transparent;
}
.btn-ghost:hover { color: var(--color-retro-white); }/* ─── Badge Component ───────────────────────────────────── */
.badge {
display: inline-block;
font-family: var(--font-press-start), monospace;
font-size: clamp(7px, 0.8vw, 9px);
letter-spacing: 3px;
text-transform: uppercase;
padding: 6px 14px;
border: 1px solid currentColor;
}
.badge-green {
color: var(--color-retro-green);
text-shadow: 0 0 8px var(--color-retro-green-glow);
}
.badge-magenta {
color: var(--color-retro-magenta);
text-shadow: 0 0 8px var(--color-retro-magenta-glow);
}
.badge-cyan {
color: var(--color-retro-cyan);
text-shadow: 0 0 8px var(--color-retro-cyan-glow);
}
.badge-yellow {
color: var(--color-retro-yellow);
text-shadow: 0 0 8px rgba(255, 230, 0, 0.4);
}@keyframes card-icon-pulse {
0% { transform: scale(1); }
50% { transform: scale(1.1); }
100% { transform: scale(1); }
}
/* ─── Card ───────────────────────────────────────────── */
.retro-game-card {
background: #070810;
border: 2px solid var(--color-retro-green-dim);
padding: clamp(20px, 3vw, 32px);
position: relative;
transition: border-color 0.2s, transform 0.15s, box-shadow 0.15s;
cursor: none;
overflow: hidden;
height: 100%;
display: flex;
flex-direction: column;
}
.retro-game-card::before {
content: '';
position: absolute;
inset: 4px;
border: 1px solid rgba(0, 255, 65, 0.08);
pointer-events: none;
}
.retro-game-card::after {
content: '';
position: absolute;
inset: 0;
background: linear-gradient(135deg, transparent 60%, rgba(0, 255, 65, 0.04));
pointer-events: none;
}
.retro-game-card:not(.retro-game-card--locked):hover {
border-color: var(--color-retro-green);
transform: translate(-2px, -2px);
box-shadow: 4px 4px 0 var(--color-retro-green-dim), 0 0 30px var(--color-retro-green-glow);
}
.retro-game-card:not(.retro-game-card--locked):hover .card-icon-el {
animation: card-icon-pulse 0.4s steps(2) infinite;
}
.retro-game-card--locked {
border-style: dashed;
border-color: var(--color-retro-magenta);
}
.card-locked-overlay {
position: absolute;
inset: 0;
display: flex;
align-items: center;
justify-content: center;
background: rgba(7, 8, 16, 0.7);
z-index: 10;
}
.card-locked-label {
font-family: var(--font-pixel), monospace;
font-size: 8px;
color: rgba(232, 234, 240, 0.4);
letter-spacing: 4px;
}
.card-title {
font-family: var(--font-pixel), monospace;
font-size: clamp(8px, 1.2vw, 11px);
color: #e8eaf0;
margin-bottom: 12px;
letter-spacing: 1px;
line-height: 1.6;
}
.card-description {
font-family: var(--font-crt), monospace;
font-size: clamp(16px, 2vw, 20px);
color: rgba(232, 234, 240, 0.45);
line-height: 1.5;
flex: 1;
}
.card-stars {
margin-top: 20px;
display: flex;
gap: 4px;
font-size: 14px;
}@keyframes blink-cursor {
0%, 100% { opacity: 1; }
50% { opacity: 0; }
}
/* ─── ScoreBoard ─────────────────────────────────────── */
.scores-board-label {
position: absolute;
top: -1px;
left: 50%;
transform: translateX(-50%);
font-family: var(--font-press-start), monospace;
font-size: 8px;
color: #070810;
background: var(--color-retro-green);
padding: 4px 16px;
letter-spacing: 3px;
white-space: nowrap;
}
.new-tag {
font-family: var(--font-press-start), monospace;
font-size: 6px;
color: #070810;
background: var(--color-retro-magenta);
padding: 2px 6px;
margin-left: 8px;
animation: blink-cursor 0.5s step-end infinite;
text-shadow: none;
vertical-align: middle;
}
.scores-table {
width: 100%;
border-collapse: collapse;
margin-top: 32px;
}
.scores-table thead tr {
border-bottom: 1px solid var(--color-retro-green-dim);
}
.scores-table th {
font-family: var(--font-press-start), monospace;
font-size: 7px;
color: var(--color-retro-green-dim);
padding: 12px 24px;
text-align: left;
letter-spacing: 2px;
}
.scores-table td {
font-family: var(--font-vt323), monospace;
font-size: 22px;
color: #e8eaf0;
padding: 14px 24px;
border-bottom: 1px solid rgba(0, 255, 65, 0.07);
}
.scores-table tr:hover td { background: rgba(0, 255, 65, 0.04); }
.rank-1 td:first-child { color: #ffe600; text-shadow: 0 0 12px #ffe600; }
.rank-2 td:first-child { color: rgba(200, 200, 200, 0.9); }
.rank-3 td:first-child { color: #cd7f32; }
.score-num {
color: var(--color-retro-green) !important;
font-family: var(--font-press-start), monospace !important;
font-size: 14px !important;
text-shadow: 0 0 8px var(--color-retro-green);
letter-spacing: 1px;
}
.scores-cards {
display: none;
flex-direction: column;
gap: 12px;
margin-top: 32px;
padding: 0 4px;
}
.score-card {
border: 1px solid color-mix(in srgb, var(--color-retro-green) 20%, transparent);
background: rgba(0, 255, 65, 0.02);
padding: 14px 16px;
position: relative;
}
.score-card-rank {
position: absolute;
top: 10px;
right: 14px;
font-family: var(--font-press-start), monospace;
font-size: 20px;
opacity: 0.18;
letter-spacing: 1px;
}
.score-card-rank.rank-1 { color: #ffe600; opacity: 0.5; text-shadow: 0 0 16px #ffe600; }
.score-card-rank.rank-2 { color: #c8c8c8; opacity: 0.4; }
.score-card-rank.rank-3 { color: #cd7f32; opacity: 0.45; }
.score-card-player {
font-family: var(--font-vt323), monospace;
font-size: 26px;
color: #e8eaf0;
line-height: 1;
margin-bottom: 6px;
display: flex;
align-items: center;
gap: 6px;
}
.score-card-score {
font-family: var(--font-press-start), monospace;
font-size: 13px;
color: var(--color-retro-green);
text-shadow: 0 0 10px var(--color-retro-green);
letter-spacing: 1px;
margin-bottom: 8px;
}
.score-card-meta {
display: flex;
gap: 16px;
flex-wrap: wrap;
}
.score-card-meta-item {
display: flex;
flex-direction: column;
gap: 2px;
}
.score-card-meta-label {
font-family: var(--font-press-start), monospace;
font-size: 6px;
color: var(--color-retro-green-dim);
letter-spacing: 1px;
}
.score-card-meta-val {
font-family: var(--font-vt323), monospace;
font-size: 20px;
color: rgba(232, 234, 240, 0.6);
}
@media (max-width: 560px) {
.scores-table { display: none; }
.scores-cards { display: flex; }
}/* ─── SectionHeader ──────────────────────────────────── */
.section-header-label {
font-family: var(--font-pixel), monospace;
font-size: 8px;
color: var(--color-retro-magenta);
letter-spacing: 4px;
margin-bottom: 12px;
text-shadow: 0 0 10px var(--color-retro-magenta);
}
.section-header-title {
font-family: var(--font-pixel), monospace;
font-size: clamp(14px, 2.5vw, 24px);
color: #e8eaf0;
letter-spacing: 3px;
line-height: 1.4;
margin-bottom: 8px;
}
.section-header-title-accent {
color: var(--color-retro-green);
text-shadow: 0 0 16px var(--color-retro-green);
}
/* ─── Button Component ───────────────────────────────────── */
.theme-cyan .section-header-title-accent {
color: #00b4ff;
text-shadow: 0 0 16px #00b4ff;
}
/* ─── theme-pink ──────────────────────────────────────── */
.theme-pink .section-header-title-accent {
color: #ff2d78;
text-shadow: 0 0 16px #ff2d78;
}/* ─── Typography Utilities ──────────────────────────────── */
.text-body {
font-family: var(--font-vt323), monospace;
font-size: clamp(18px, 2vw, 22px);
color: rgba(232, 234, 240, 0.6);
letter-spacing: 2px;
line-height: 1.6;
}
.text-caption {
font-family: var(--font-vt323), monospace;
font-size: clamp(16px, 1.8vw, 20px);
color: rgba(232, 234, 240, 0.4);
letter-spacing: 2px;
line-height: 1.5;
}
.text-label {
font-family: var(--font-press-start), monospace;
font-size: 8px;
color: rgba(232, 234, 240, 0.4);
letter-spacing: 3px;
text-transform: uppercase;
}
.text-code {
font-family: var(--font-vt323), monospace;
font-size: clamp(15px, 1.6vw, 18px);
color: var(--color-retro-green);
letter-spacing: 3px;
}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.
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.
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.