BACK TO HOME

CASE STUDY

Built in
the open

Two weeks in October 2026. A link list with three experiments became a mobile first playground with nine of them, a shared design system, twelve looks and tests that refuse a bad push. This is how it went, including the parts that didn't work.

Role
Design, build, art
Timeline
11 to 24 Oct 2026
Stack
HTML, CSS, JS, no build step
Hosting
GitHub Pages

Where it started

On 11 October the site was a name, a short bio and a list of links. The Playground had three experiments: Particle Architecture, Shader Lab and an early ASCII World. The contact page still said "Start a Project", which made a personal site read like a services page.

I wanted somewhere to put things I make for fun and to show how I work as a designer who also ships the code. The brief I wrote for myself was short: phone first, fast, honest about being a playground, and easy to keep adding to without the whole thing rotting.

Discovery

Before designing anything new I audited what was there. The findings were not glamorous, but they set the whole plan.

  • style.css was 42 KB of overrides stacked on overrides. Nobody could say which rule was winning.
  • Particle Architecture was one 60 KB HTML file with 25 !important rules and 15 media queries.
  • Only Shader Lab had tests. Every other page could break silently.
  • The copy pointed the wrong way. "Start a Project" invited client work I wasn't looking for.
  • Analytics was on a paid trial plus a session recorder, which was heavier than a small site needs.

From that I set five working rules: mobile first at 360 to 430 px, colours and sizes only from tokens, no build step, preview every new feature before it reaches main, and turn every rule into a test so it can't drift.

Foundations first

The shared layer is three files loaded in a fixed order: tokens, layout, components. Every tool loads them, even the ones with their own look, and overrides them with selectors scoped to its own body class instead of dropping them.

Small rules carry a lot. Internal links get a straight arrow, external links a diagonal one, and a link never shows two arrows. Every static button gets the same class, so focus states and sizes stay consistent across ten very different pages.

Then the cleanup. I rewrote style.css from 40.5 KB to 18.2 KB and checked it with 37 before and after screenshots across five pages, three viewports and every hover and error state. All of them were pixel identical, which was the only way I was willing to delete that much CSS.

What's in the Playground

Nine experiments: two games, one growing collection of creatures and six tools. Every one runs in a phone browser with nothing to install, and files you add stay on your device.

Beyond the Playground

The token thinking on this site comes from a bigger piece of work: a pipeline I built for a product team that takes a brand from Figma to running code without anyone retyping a value. It isn't on this site, so the names are left out, but the approach is the same.

  1. Step 1Token builderA Figma plugin that writes the brand's tokens as Figma variables: about 743 of them in four collections. Primitives hold the raw values, and semantic names like surface, text or border point at primitives instead of repeating them.tokens.json
  2. Step 2Component builderA second plugin that builds 61 components and patterns on those variables, with full variant matrices (a button alone is 36 combinations of appearance, size and state) and a report that flags any value not bound to a token.components.json
  3. Step 3Code generatorA command line tool that reads both files and generates the front-end components with their styles, tests and Storybook stories.components + stories

Two decisions made it work. Components may only use tokens, so a brand change is a token change and nothing else. And every generated file carries an ownership header: delete it and the file becomes hand-written, so the next sync leaves it alone. That let designers keep regenerating while developers kept polishing.

LearnedA component in Figma should carry a contract (names, variants, tokens), not try to be a pixel clone of the code. The latest report came back with no clipped text, no missing token paths and no failed components.

What got hard

Each of these started as a problem in front of me, not a plan. Most of them include something I built, tried and threw away.

Phones are the real test

Ashen Vigil, a 1-bit horde survival game, ran fine on my machine and stuttered on a Galaxy A71. I moved it from the 2D canvas to WebGL with one texture atlas, about one draw call a frame, and kept 2D as a fallback. The bar is now 60 fps on a phone viewport with the CPU throttled six times. Then some phone GPUs drew a black field because they rounded texture coordinates by a few texels, so the white texel every fill uses is now pinned to the atlas origin.

Tried and droppedA darkness layer with dithered light pools. It looked moody and made the game too dark and too slow on phones.

Looking like someone else

The first version of Ashen Vigil was too close to another studio's 1-bit isometric look. I kept the mechanics and the 1-bit style and changed what made it recognisable: no isometric grid, a winding road over cracked earth, ash piles where enemies fall, and one colour, ember orange, reserved only for fire. The run now ends at dawn, with the horizon catching and the horde crumbling to ash.

LearnedCheck references before the first public version, not after.

Ink drawings to pixels

Bestiary turns my ink drawings into animated 1-bit sprites with a small Python pipeline. The first rigs moved rectangles of the image. It worked, and it looked like a head cut at the mouth moving up and down. The newer warp rig bends the full resolution drawing with a smooth displacement field and only then downsamples it, so jaws open along the real line of the mouth and fingers curl without seams.

Tried and droppedA trace mode with crisp 1 px lines (it lost the ink), and a negative dark view (too harsh).

One interface, many looks

The VIEW menu switches between Normal, Pixel and ASCII, and four colour themes sit on top. All twelve combinations come from the same tokens, so a new page gets them for free. One rule kept it sane: modes change the interface, never the art. Playground covers stay exactly as they are in every mode.

A browser test opens every theme in every mode on four pages and fails if body text drops below 7:1 contrast or anything scrolls sideways.

Tried and droppedRe-rendering the covers in pixel and ASCII. And a full Ink mode, paper and pen-drawn rules, which I liked and switched off because it felt unrelated to the rest of the site. The code is still there.

Measuring without being creepy

When the analytics trial ended I removed it and the session recorder, and moved to Umami: cookieless, EU hosted, free at this size. One small loader holds the ID and only reports from the live domain, so local servers and previews stay out of the numbers. Events are named after what a person did (picked a theme, opened a card, finished a run), never what they typed.

At one point a tracking tag got pasted straight into a page and every visit counted twice. The smoke test now fails on that, and adding ?notrack to any URL keeps my own visits out.

Readable at a glance

A playtester said the enemies blended into the party. Three ink tones fixed it: the hero in bright ink, companions a step paler, the hollow in ash grey.

Tried and droppedDark silhouettes for enemies. Clearer on paper, too dark in play.

How it ships

  1. New features go on a work branch.
  2. I review a private preview on my phone.
  3. Only then does it merge to main and go live.
  4. Small fixes to live features can go straight to main.

Two test suites run on every push. A source check covers routes, metadata, gallery order, share images, the sitemap and analytics. A Playwright run at phone size covers view modes, themes, contrast, the 404 page and lazy loaded covers. Every change gets a line in the changelog.

Where it landed

  • 9experiments, two of them games
  • 12looks from one token set
  • −55%CSS, zero visual change
  • 60 fpswith the CPU throttled 6x

Traffic numbers aren't here yet. Umami has been live for a few days, and I'd rather wait for a real week of data than quote a guess.

Next

Review the first week of data and drop the events nobody needs. Set up a funnel from Home to Playground to a game. Move the two older Bestiary creatures to the warp rig. And keep adding to the Playground, now that adding something new is a checklist rather than a risk.