mini-games
Design System
Shared components, tokens, and patterns used across all games. Copy the CSS snippets directly into each game's
<style> block.
Colors
All game UIs are built on this dark slate palette. Use these hex values when writing per-game
<style> blocks.
Backgrounds
Text
Accent & Game Colors
Typography
Overlay title ยท 1.6rem bold
Game Over
Section heading ยท 1.5rem bold
How to Play
Stat value ยท 1.2rem monospace bold
4280
Stat label ยท 0.65rem uppercase spaced
Best Score
Body / info ยท 0.875rem ยท color #94a3b8
Flip cards to find matching pairs. Train your memory and beat the clock.
Controls hint ยท 0.75rem ยท color #64748b
Arrow keys ยท D-pad ยท or swipe
Global Components
Defined in src/css/main.css via Tailwind
@layer components. Available on all pages without additional CSS.
Buttons โ .btn-primary /
.btn-secondary
Used for CTAs on the homepage and marketing sections. Not the same as in-game buttons โ see Buttons below.
Card โ .card
<a href="/games/snake/" class="card p-6 group">...</a>
Badge โ .badge
<span class="badge bg-violet-500/20 text-violet-300 border-violet-500/30">Arcade</span>
Category & Difficulty
Applied via Eleventy filters categoryColor,
categoryLabel, and
difficultyColor defined in .eleventy.js.
Category badges
arcade โ bg-violet-500/20 text-violet-300 border-violet-500/30
puzzle โ bg-emerald-500/20 text-emerald-300 border-emerald-500/30
board โ bg-amber-500/20 text-amber-300 border-amber-500/30
word โ bg-sky-500/20 text-sky-300 border-sky-500/30
{{- in Nunjucks templates -}}
<span class="badge {{ game.category | categoryColor }}">
{{ game.category | categoryLabel }}
</span>
{{- in game.njk front matter / layout -}}
Difficulty indicators
<span class="text-xs {{ game.difficulty | difficultyColor }} font-medium capitalize">
{{ game.difficulty }}
</span>
Stat Cards
Used in score rows during gameplay. Copy the CSS block into each game's
<style>.
Standard โ .stat
.stat { text-align:center; background:#1e293b; border:1px solid #334155; border-radius:8px; padding:0.5rem 1rem; }
.stat-label { font-size:0.65rem; text-transform:uppercase; letter-spacing:0.1em; color:#94a3b8; }
.stat-value { font-size:1.2rem; font-weight:bold; color:#e2e8f0; font-family:monospace; }
Game-over summary โ .summary-stat
.summary-row { display:flex; gap:0.75rem; flex-wrap:wrap; justify-content:center; }
.summary-stat { text-align:center; background:#1e293b; border:1px solid #334155; border-radius:8px; padding:0.5rem 0.9rem; min-width:72px; }
.summary-label { font-size:0.65rem; text-transform:uppercase; letter-spacing:0.1em; color:#94a3b8; }
.summary-value { font-size:1.2rem; font-weight:bold; color:#e2e8f0; font-family:monospace; }
Same visual as .stat but with min-width:72px and slightly tighter padding. Used
exclusively in the #gameover-overlay.
D-pad
Virtual directional controller for touch devices. Required for Snake and Tetris; shown only on small screens via
@media (max-width: 640px).
2-row layout (2048)
3-row layout (Snake)
.dpad { display:grid; grid-template-columns:repeat(3,52px); grid-template-rows:repeat(2,52px); gap:6px; }
.dpad-btn { background:#1e293b; border:1px solid #334155; border-radius:8px; color:white; font-size:1.3rem;
cursor:pointer; display:flex; align-items:center; justify-content:center;
touch-action:manipulation; -webkit-tap-highlight-color:transparent; }
.dpad-btn:active { background:#334155; }
Overlays
Two separate overlays per canvas game. See Game Wrapper for positioning context.
Start overlay โ #overlay
Shown on page load. Background is semi-transparent (rgba(15,23,42,0.82)) so the live demo canvas is
visible behind it. The "Live Demo" label is hidden once the real game starts.
Game-over overlay โ #gameover-overlay
#gameover-overlay { position:absolute; inset:0; display:flex; flex-direction:column; align-items:center;
justify-content:center; background:rgba(15,23,42,0.96); border-radius:4px; gap:0.75rem; }
#gameover-overlay h2 { font-size:1.6rem; font-weight:bold; color:white; }
Background is nearly opaque (rgba(15,23,42,0.96)) so the frozen last game frame shows through
slightly. Do NOT call startDemo() here. Use icon ๐ for loss, ๐ for win.
Info Section
Collapsible How to Play + FAQ placed below the game wrapper. Required in every game.
How to Play
Goal
Reach the 2048 tile by merging tiles with the same number.
Controls
- Arrow keys โ slide all tiles (desktop)
- Swipe โ swipe on the board (mobile)
FAQ
Is my best score saved?
Yes โ saved automatically in your browser's local storage.
Can I undo a move?
Not currently. Think carefully before each swipe!
.info-details { background:#1e293b; border:1px solid #334155; border-radius:8px;
margin-bottom:0.75rem; overflow:hidden; }
.info-details summary { padding:0.75rem 1rem; font-weight:600; color:#e2e8f0; cursor:pointer;
list-style:none; display:flex; align-items:center;
justify-content:space-between; user-select:none; }
.info-details summary::-webkit-details-marker { display:none; }
.info-details summary::after { content:"+"; font-size:1.1rem; color:#64748b; }
.info-details[open] summary::after { content:"โ"; }
.info-body { padding:0.75rem 1rem 1rem; color:#94a3b8; font-size:0.875rem;
line-height:1.6; border-top:1px solid #334155; }
.info-body h4 { color:#e2e8f0; font-weight:600; margin:0.75rem 0 0.25rem; font-size:0.875rem; }
.info-body h4:first-child { margin-top:0; }
.info-body ul { padding-left:1.25rem; margin:0.25rem 0; }
.info-body li { margin-bottom:0.25rem; }
Always include two <details> blocks: How to Play (Goal, Controls desktop +
mobile, Scoring/Levels) and FAQ (min. 3 questions).
Game Wrapper
Standard layout shell for all game pages. The canvas or grid goes inside a
.canvas-wrapper which positions overlays.
#game-wrapper {
display: flex;
flex-direction: column;
align-items: center;
justify-content: flex-start; /* โ always flex-start, never center */
min-height: calc(100vh - 60px);
gap: 1rem;
padding: 1rem;
}
Control layout order inside #game-wrapper
The d-pad must sit immediately below the board โ never put a button between the board and the d-pad, as it creates visual distance on mobile.
Canvas wrapper โ .canvas-wrapper
.canvas-wrapper {
position: relative; /* anchors #overlay and #gameover-overlay */
touch-action: none;
line-height: 0; /* removes inline-block gap under canvas */
}
/* Canvas sizing (responsive) */
const SIZE = Math.min(480, window.innerWidth - 32);
canvas.width = SIZE;
canvas.height = SIZE;
const S = SIZE / 480; /* scale factor for all game constants */