Setup Guide
One-time setup for using PINBALL // 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 PINBALL // NEXT design tokens to your globals.css. These define colors, fonts, and animation tokens used by every component in the theme.
@theme {
/* ── Colors ─────────────────────────────────────────── */
--color-pinball-midnight: #0D0D2B;
--color-pinball-midnight-deep: #06061A;
--color-pinball-midnight-light: #151540;
--color-pinball-magenta: #FF2D8A;
--color-pinball-magenta-dim: #cc2470;
--color-pinball-yellow: #FFE814;
--color-pinball-yellow-dim: #d4c210;
--color-pinball-chrome-light: #e0e0f0;
--color-pinball-chrome-mid: #8888aa;
--color-pinball-chrome-dark: #44446a;
--color-pinball-white: #f0f0ff;
--color-pinball-white-dim: #a0a0c0;
--color-pinball-green: #44dd88;
--color-pinball-cyan: #44ddff;
/* ── Fonts ───────────────────────────────────────────── */
--font-display: var(--font-bungee);
--font-body: var(--font-space-grotesk);
/* ── Animations ──────────────────────────────────────── */
--animate-blink: blink-cursor 0.8s step-end infinite;
--animate-pulse-soft: pulse-soft 2.5s ease-in-out infinite;
}Each component may need its own CSS classes in your globals.css. Copy the styles for the components you use.
/* ─── Buttons ────────────────────────────────────────── */
.btn-base {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 8px;
font-family: var(--font-display);
font-weight: 400;
border: none;
cursor: pointer;
text-decoration: none;
line-height: 1;
letter-spacing: 2px;
transition: background 0.2s, transform 0.12s;
white-space: nowrap;
clip-path: polygon(10px 0, 100% 0, calc(100% - 10px) 100%, 0 100%);
}
.btn-base:hover { text-decoration: none; }
.btn-base:active { transform: scale(0.97); }
.btn-sm { font-size: 11px; padding: 10px 24px; }
.btn-md { font-size: 13px; padding: 14px 32px; }
.btn-lg { font-size: 14px; padding: 16px 40px; }
.btn-primary {
background: var(--color-pinball-magenta);
color: var(--color-pinball-white);
}
.btn-primary:hover {
background: var(--color-pinball-magenta-dim);
color: var(--color-pinball-white);
}
.btn-secondary {
background: transparent;
color: var(--color-pinball-white-dim);
border: 1px solid var(--color-pinball-chrome-dark);
clip-path: none;
font-family: var(--font-body);
font-weight: 500;
letter-spacing: 0.02em;
}
.btn-secondary:hover {
color: var(--color-pinball-yellow);
border-color: var(--color-pinball-yellow);
}
.btn-ghost {
background: transparent;
color: var(--color-pinball-white-dim);
clip-path: none;
font-family: var(--font-body);
font-weight: 500;
letter-spacing: 0.02em;
}
.btn-ghost:hover {
color: var(--color-pinball-yellow);
}/* ─── Badges ─────────────────────────────────────────── */
.badge {
display: inline-flex;
align-items: center;
gap: 4px;
font-weight: 700;
font-size: 10px;
letter-spacing: 2px;
text-transform: uppercase;
padding: 4px 12px;
clip-path: polygon(4px 0, 100% 0, calc(100% - 4px) 100%, 0 100%);
}
.badge--magenta { background: var(--color-pinball-magenta); color: var(--color-pinball-midnight-deep); }
.badge--yellow { background: var(--color-pinball-yellow); color: var(--color-pinball-midnight-deep); }
.badge--chrome { background: var(--color-pinball-chrome-mid); color: var(--color-pinball-midnight-deep); }
.badge--green { background: var(--color-pinball-green); color: var(--color-pinball-midnight-deep); }
.badge--cyan { background: var(--color-pinball-cyan); color: var(--color-pinball-midnight-deep); }/* ─── Text Variants ──────────────────────────────────── */
.text-body { font-size: 14px; line-height: 1.7; font-weight: 400; color: var(--color-pinball-white-dim); }
.text-caption { font-size: 12px; font-weight: 500; color: var(--color-pinball-chrome-mid); }
.text-label { font-size: 11px; font-weight: 500; letter-spacing: 3px; text-transform: uppercase; }
.text-code { font-family: monospace; font-size: 13px; }/* ─── Horizontal Scroll Features ─────────────────────── */
.pinball-card {
flex: 0 0 360px;
scroll-snap-align: start;
background: var(--color-pinball-midnight-light);
border: 2px solid transparent;
background-clip: padding-box;
position: relative;
padding: 36px 32px;
min-height: 320px;
display: flex;
flex-direction: column;
}
.pinball-card::before {
content: '';
position: absolute;
inset: -2px;
z-index: -1;
background: linear-gradient(135deg, var(--color-pinball-chrome-light) 0%, var(--color-pinball-chrome-dark) 40%, var(--color-pinball-chrome-mid) 60%, var(--color-pinball-chrome-light) 100%);
}
.card-number {
font-family: var(--font-display);
font-size: 48px;
color: var(--color-pinball-magenta);
opacity: 0.3;
line-height: 1;
margin-bottom: 16px;
}
.card-title {
font-family: var(--font-display);
font-size: 18px;
color: var(--color-pinball-yellow);
letter-spacing: 1px;
margin-bottom: 12px;
}
.card-description {
font-size: 14px;
line-height: 1.7;
color: var(--color-pinball-white-dim);
flex: 1;
}
@media (max-width: 768px) {
.features-header { padding: 0 20px 24px; }
.features-track { padding: 0 20px 60px; gap: 16px; }
.pinball-card { flex: 0 0 300px; }
.scroll-hint { padding: 12px 20px 24px; }
}Button
Arcade button with primary, secondary, and ghost variants in three sizes. Magenta accent, chrome borders, uppercase Bungee labels. Renders as a Next.js Link when an href is provided.
Badge
Arcade badge with magenta, yellow, chrome, green, and cyan variants. Bold uppercase labels on dark surfaces.
Text
Polymorphic text component with body, caption, label, and code variants. Bungee for display, Space Grotesk for body.
Card
Arcade card with numbered index, tag badge, title, and description. Chrome borders with parallelogram clip-path shapes.