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

Deepest
#020617
Canvas
#0f172a
Card
#1e293b
Border/Hover
#334155
Muted
#475569

Text

Primary
#e2e8f0
Secondary
#94a3b8
Hint
#64748b

Accent & Game Colors

Violet (default)
#7c3aed
Green (Snake)
#16a34a
Orange (Breakout)
#ea580c
Success
#22c55e
Danger
#ef4444

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

Arcade Puzzle Board Word
<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 arcade โ†’ bg-violet-500/20 text-violet-300 border-violet-500/30
Puzzle puzzle โ†’ bg-emerald-500/20 text-emerald-300 border-emerald-500/30
Board board  โ†’ bg-amber-500/20 text-amber-300 border-amber-500/30
Word 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

easy medium hard
<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

Score
4280
Best
8160
Level
3
Time
42s
.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

Score
4280
Best
8160
Level
3
.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.

Buttons

In-game buttons defined per-game in each <style> block. Three variants with specific roles.

Primary action โ€” .play-btn

.play-btn       { padding:0.5rem 1.5rem; background:#7c3aed; color:white; border:none; border-radius:6px;
                  font-size:1rem; font-weight:600; cursor:pointer;
                  touch-action:manipulation; -webkit-tap-highlight-color:transparent; }
.play-btn:hover { background:#6d28d9; }

/* Game-specific accent overrides */
/* Snake   */ .play-btn { background:#16a34a; } .play-btn:hover { background:#15803d; }
/* Breakout*/ .play-btn { background:#ea580c; } .play-btn:hover { background:#c2410c; }

Always use padding: 0.5rem 1.5rem. Do not increase vertical padding beyond 0.5rem.

Secondary action โ€” .new-btn

.new-btn       { padding:0.4rem 1rem; background:#334155; color:#e2e8f0; border:none;
                 border-radius:6px; font-size:0.85rem; cursor:pointer; touch-action:manipulation; }
.new-btn:hover { background:#475569; }

Used for non-critical resets below the game board (e.g. 2048 New Game under the d-pad).

Selector / toggle โ€” .diff-btn

.diff-btn              { padding:0.3rem 0.8rem; background:#1e293b; color:#94a3b8; border:1px solid #334155;
                         border-radius:6px; font-size:0.8rem; cursor:pointer;
                         touch-action:manipulation; -webkit-tap-highlight-color:transparent; }
.diff-btn.active,
.diff-btn:hover        { background:#334155; color:white; border-color:#7c3aed; }

Use the accent color of the game for border-color on the active/hover state (e.g. Breakout uses #ea580c instead of #7c3aed).

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

Live Demo ๐Ÿ

Snake

Arrow keys or WASD to move

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

๐Ÿ’€

Game Over

Score
4280
Best
8160
Level
3

Pattern: Classic

๐Ÿ†

You Win!

Score
9840
Best
9840
Level
8
#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

1 ยท Score / stats row
2 ยท Selector row (difficulty / pattern)
3 ยท Game board / canvas overlays go here (absolute)
4 ยท D-pad (touch controls)
5 ยท Controls hint text
6 ยท Secondary buttons (New Game)

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 */