Design system
How the app looks the way it looks: tokens, block colours, terrain themes,
entity art and skins. The authority for styling lives in two places:
styles/theme.css (the shared palette, breakpoints, typography, radii,
shadows and the v-* height variants) and src/style.css (Tailwind entry,
app-specific tokens, entity art, [data-*] selectors, browser quirks).
styles/global.css holds the base element styles; everything lives inside
this app, there is no separate design package. The .agent/ folder's
color-sweep.md keeps the same information for contributors.
The token system
Tailwind v4 @theme declares semantic tokens; components use semantic
classes, never raw palette colours:
| Group | Tokens |
|---|---|
| Accent | primary, primary-hover/-active, primary-light, primary-dark |
| Surfaces | surface, surface-raised (+ -hover/-active) |
| Content | content, content-raised, content-masked |
| Borders | border, border-masked |
| Status | success, warning, error, info |
App-specific additions in src/style.css:
- Blocks:
block-sky,block-emerald,block-yellow,block-green,block-red,block-orange,block-gray(the locked/default scheme), each with-hover,-active,-border, plus-washedvariants for container bodies and-washed-hoverfor the drag-over state. - Terrain themes (paired ground/wall):
default(ground/orange wall),amber,forest(-terrain),frost,icy-blue(blue-ice),legend,boss,stone, plusice. - Entities:
player(equalsprimary),target(cheese yellow),stop(the Stop button),success-strong(completed-level dot, because the sharedsuccessis too pale on the picker). - Code export:
syntax-keyword,syntax-call,syntax-numberand the brand badgeslogo-c,logo-python(+accent),logo-javascript.
Block chrome
BlockShell (see Blocks) is the single source of block
chrome: the clipart body, the top notch and bottom nub, the variant map and
the drag-over colour swap. Block colours are solid 80% or washed 60% opacity
with a 4 px backdrop blur; 70/50 was tried first and looked muddy when
containers nested, so the values are what they are on purpose. --block-height
is the shared height so every block, including the Start head, aligns.
Terrain themes
A level names a theme per block kind through looks; the theme is an
identifier, never a class. src/terrain.css maps each data-terrain +
data-kind pair to a colour token and a procedural texture, with the rule
that ground and wall of a theme are different images (different
frequency, seed, scale or offset), never the same image. The art direction:
default- large smooth rock/sand grain.amber- coarse sand.forest- grass ground with directional needles; the wall is a dense, overlapping, upright evergreen canopy of varied size and tone that covers the whole tile (no ground shows between trees).frost- soft snow drifts.icy-blue- ice with large stripes.legend(deep ice) - large smooth depth patches, no lines.boss(purple) - hazy sharp polygons.stone- dry gravel (ground) and darker wet gravel (wall).ice- icy surface with stripes; the block kindicealways uses it.
Textures are tileable (stitchTiles noise or <pattern> shapes), large
(72-160 px), and every cell paints from the same origin, so tiles stay
perfectly square and aligned. The --tex-block grain is shared by the block
bodies and the cheese so entities read as part of the same world.
Entity art rules
Flat art only: filled shapes, no strokes, outlines or gradients. Friendly and non-violent by design (a game for all ages). The mouse, cheese, lava and hole are described in detail on The playground.
Skins
data-player-skin='blue' | 'pink' overrides --color-player, so it works
on <html> (the live game, set by lib/skin.ts) and on any element (the
picker options render a preview mouse in each skin). The choice persists in
localStorage under bunplate:player-skin.
Responsive scale
Sizing scales with the viewport instead of breakpoint jumps: clamp() for
block padding and text, the v-* custom variants (v-xs through v-3xl)
for spacing, a fixed clamp() height for the palette (it must not grow or
shrink between levels), and explicit per-breakpoint widths for the level's
left column so the palette keeps its width even while its blocks are hidden
during a delete drag.
Next: Persistence.