RETRO // NEXT

Setup Guide

One-time setup for using RETRO // NEXT components. Add these to your project, then copy any component from the theme.

1
Install dependencies

The cn() utility combines clsx and tailwind-merge for conditional class names. Install both packages.

BASH
npm install clsx tailwind-merge
2
Add utility function

Create lib/utils.ts with the cn() helper. Every component imports this for class name merging.

TS
import { type ClassValue, clsx } from 'clsx'
import { twMerge } from 'tailwind-merge'

export function cn(...inputs: ClassValue[]): string {
  return twMerge(clsx(inputs))
}
3
Add design tokens

Add the RETRO // NEXT design tokens to your globals.css. These define colors, fonts, and animation tokens used by every component in the theme.

CSS
@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;
}
4
Add component styles

Each component may need its own CSS classes in your globals.css. Copy the styles for the components you use.

CSS
/* ─── 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); }
CSS
/* ─── 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);
}
CSS
@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;
}
CSS
@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; }
}
CSS
/* ─── 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;
}
CSS
/* ─── 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;
}
RETRO // NEXT Components
RETRO // NEXTUI

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.

RETRO // NEXTUI

Badge

Neon-glow badge with magenta, cyan, yellow, and green color variants. Used for category tags and status labels in the arcade aesthetic.

RETRO // NEXTUI

Card

Game-card component with neon-colored icon, tag, title, description, and a five-star rating display. Supports a "coming soon" locked overlay state.

RETRO // NEXTUI

PixelCharacter

SVG pixel-art sprite with three variants: hero (neon green), ghost (magenta), and coin (yellow). Supports a CSS float animation and configurable size.

RETRO // NEXTUI

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.

RETRO // NEXTUI

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.

RETRO // NEXTUI

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.

RETRO // NEXTUI

Text

Polymorphic text primitive with body, caption, label, and code variants styled for the RETRO dark arcade palette.