English · 简体中文
My playground. Small toys you can just open and play with — no install or build step.
| Toy | What it does | |
|---|---|---|
| 🖨️ | Bambu Lab A1 Print Desk | A detailed Three.js A1 printing sculptural samples layer by layer |
| 🐉 | Mythical Beast Field Guide | Turn a page and meet a beast no ancient book remembered |
| 🖌️ | Gravity Calligraphy | Ink refuses to stay on the page. Release a stroke and let gravity and wind take over |
| 🌊 | flow field | A few thousand particles drift along a noise field, trails piling up into streamlines. Your cursor pushes them around |
| 🔉 | chladni | Nodal lines of a standing wave equation. Twelve thousand grains of sand find the spots that don't vibrate and settle there |
| 🩸 | pixel sort | Pixels sorted by luminance into smeared glitch streaks. Drop in your own image, download the result |
This is a personal playground. I add things as they catch my interest — no attempt at coverage, no promises about maintenance pace.
That said, issues with ideas are very welcome. Seen a visual effect, an algorithm, a physical phenomenon that would make a fun little toy? Tell me about it. Want to build it yourself? See Adding a toy.
pnpm devOpen http://localhost:4173 .
No install or build step — the browser runs native ES modules directly, with the occasional library vendored alongside the toys. pnpm dev runs scripts/serve.mjs, a ~50-line static server on Node's built-ins (ES modules can't load from file://, so a server is required). It sends no-store, which matters more than it sounds: servers that answer 304 will happily hand the browser a stale ES module or registry.json, and you end up debugging a page that's running half your edits.
pnpm new wave-clockThat generates toys/wave-clock/ with a working skeleton (a dot going in circles). Write your idea in main.js, fill in meta.json, then:
pnpm syncsync scans every toys/*/meta.json and rolls them up into toys/registry.json, which the gallery homepage reads. Run it after adding a toy — CI checks that the file is current and fails if it isn't.
meta.json keeps the language-independent fields at the top level and the copy in a block per language. Both languages are required — the gallery has a toggle, and a missing one would render as undefined, so sync rejects it instead:
{
"accent": "#ffb347",
"added": "2026-08-06",
"zh": {
"title": "wave clock",
"description": "一句话说清这是什么",
"tags": ["互动"]
},
"en": {
"title": "wave clock",
"description": "One line on what this is",
"tags": ["interactive"]
}
}Three conventions, that's it: one directory per toy, a meta.json is required, and keep a link back to the gallery. Everything else is up to you — use a different stack if you want, as long as toys/<slug>/index.html opens.
index.html gallery homepage
src/gallery/ homepage styles and rendering
src/shared/ shared across every toy
stage.js canvas boilerplate: DPR scaling, resize, animation loop, pointer tracking
noise.js 2D gradient noise + fBm
i18n.js language state (localStorage) + the toggle button
vibe.css dark background, mono type, control styles
toys/<slug>/
index.html the page (top bar + controls + stage)
main.js the logic
meta.json colors, date, and copy per language
toys/registry.json generated by pnpm sync — don't edit by hand
scripts/serve.mjs the dev server behind pnpm dev
scripts/banner.mjs regenerates the banner above using this repo's own noise.js
stage.js and noise.js are written from scratch with no packages behind them, so lifting them out for your own use is fine.
Pushes to main publish to GitHub Pages automatically (see deploy.yml).
There's no build output — the whole repo is served as a static site. Every path is relative, so it works both under a subpath (/vibe/) and at a domain root.
MIT. Take whatever you like.