A one-tap stacking game: a slab slides back and forth over the tower, alternating between the two horizontal axes, and one tap drops it. Whatever hangs over the edge is sliced off and falls away, so the tower gets narrower with every sloppy drop; a miss ends the run. A drop within a hair of the slab below is perfect: it snaps into place, the streak counter climbs, and from the third perfect in a row each one grows the slab back a little. The slab speeds up as the tower grows.
How it's built
- Rules as plain TypeScript.
core/stack.ts holds the whole game: slab motion with bounces at the ends of the track, the cut (kept part and sliced piece), perfect snapping, regrowth and scoring. It has no Three.js imports and is covered by Vitest tests, so the view only draws what the core returns. - Plain Three.js, no React Three Fiber. A game with its own loop does not need a React scene graph;
world.ts owns the renderer, an orthographic camera at a fixed isometric angle, one shared unit box geometry scaled per slab, and Lambert materials lit by an ambient and a directional light. The JavaScript for this page stays small. - A loop that sleeps. The render loop runs only while something moves: the sliding slab, falling pieces, the perfect ring, a shake or the camera catching up. On the menu it stops after the camera settles, and it pauses when the game scrolls off screen or the tab is hidden.
- Sky in CSS. The canvas is transparent; the gradient behind it is CSS driven by a registered
--sl-hue custom property, so the sky drifts with the tower colour through a CSS transition instead of being redrawn every frame. - Tall towers stay cheap. Only the top 60 slabs (28 on the light tier) are drawn; older ones are hidden. The light tier also turns off antialiasing and renders at device pixel ratio 1.
- Detail that reacts to play. Soft shadows from a sun that travels with the camera make every overhang read as a step (high tier only). Cut pieces leave a puff of dust in the slab colour; perfect drops send up sparks from the slab's edges, more with a longer streak, and the slab glows for a moment. All particles share one pool of 320 points updated on the CPU, each fading through its own vertex alpha. A dashed frame with an HTML "Best" tag marks the player's best height on the tower; the tag follows the frame through
Vector3.project. When a run ends the camera zooms out to frame the whole tower. Every 25 slabs the sky flashes and a chord plays; on phones, drops and misses give a short vibration. - Sky in layers. Under the gradient, CSS cloud banks near the ground fade out and a star field fades in as the tower climbs (
--sl-alt goes from 0 to 1 over 60 slabs). - Pause. P, Escape or the Pause button stop the run; leaving the tab pauses it too, so a run never continues unseen.
- Generated sound. Every sound is a short Web Audio oscillator envelope; perfect drops climb a pentatonic scale with the streak. Sound is off until the player turns it on.
- Everything readable is HTML. Score, best, panels and help are DOM; a polite live region announces the start, every tenth slab and the result. The canvas is
aria-hidden; the play area is a focusable application region that takes Space and Enter. - Reduced motion. No camera shake, rings, sparks, dust, sky flash, cloud drift or star twinkle; cut pieces fade in 0.15 s instead of tumbling, and the camera cuts to the new height instead of gliding. The sliding slab is the game itself and keeps moving.
- Lost WebGL context. The canvas stays mounted so the browser can restore it; the poster covers it meanwhile.