Welcome to the Stim Webtoys Library, hosted at no.toil.fyi. This is a collection of interactive web-based toys designed to provide some fun sensory stimulation. They’re built with Three.js, WebGL, and live audio interaction for anyone who enjoys engaging, responsive visuals. These are great for casual play, or as a form of sensory exploration, especially for neurodiverse folks.
For setup, testing, and contribution guidelines, see CONTRIBUTING.md. If you're building or updating toys, the developer docs in docs/ cover common workflows and patterns. If you’re using the Model Context Protocol server in scripts/mcp-server.ts, see the dedicated guide in docs/MCP_SERVER.md.
Looking for release notes? Check out the CHANGELOG to see what’s new, what changed, and what’s coming next.
Use these entry points to find the right docs quickly:
docs/README.md: overview of all project docs plus onboarding highlights.docs/DEVELOPMENT.md: day-to-day setup, tooling, scripts, and workflow expectations.docs/TOY_DEVELOPMENT.md,docs/TOY_SCRIPT_INDEX.md, anddocs/toys.md: how to build or modify toys, toy script index, and per-toy notes.docs/QA_PLAN.md: QA coverage for high-impact flows and how to run the automation that protects them.docs/PAGE_SPECIFICATIONS.md: page-level specs for the library landing and toy shell, covering data, layout, and accessibility expectations.docs/DEPLOYMENT.md: production build/preview steps, Cloudflare Pages/Workers notes, and static hosting tips.docs/ARCHITECTURE.md: runtime composition, loaders, and diagrams for the core systems.docs/MCP_SERVER.md: Model Context Protocol stdio server usage and available tools.
If you add new scripts, toys, or deployment options, update the relevant doc above so the workflows stay discoverable.
| Change type | Docs to touch |
|---|---|
| New toy or renamed slug | docs/TOY_DEVELOPMENT.md, docs/TOY_SCRIPT_INDEX.md, docs/toys.md |
| New script or workflow change | docs/DEVELOPMENT.md |
| Hosting or CI change | docs/DEPLOYMENT.md, docs/ARCHITECTURE.md (if it alters runtime/data flow) |
| MCP server updates | docs/MCP_SERVER.md |
- Clone the repo and
cdinto it. - Choose your runtime (the repo records
bun@1.2.14inpackage.jsonviapackageManager):- Bun 1.2+: install from bun.sh for the fastest install/test cycle and the supported workflow.
- Install dependencies with
bun install. The repository tracksbun.lockfor reproducible installs—usebun install --frozen-lockfileto respect it. - Start the dev server with
bun run dev, then openhttp://localhost:5173in your browser. The dev server already binds to all interfaces for easy forwarding and mobile checks;bun run dev:hostremains available as an explicit alternative.
See the Deployment Guide for build, preview, static hosting, and Cloudflare Worker instructions. That playbook also covers PR preview validation and multi-entry-point checks before merging.
- A Modern Web Browser: Anything that supports WebGL should work (think Chrome, Firefox, Edge).
- Microphone Access: A lot of these toys respond to sound, so you’ll want to enable that.
- Touch Devices: Some toys are enhanced by touch, but that’s optional.
Head to no.toil.fyi and jump right in. The toys respond to sound, touch, and other inputs to create a chill or stimulating experience. If you’d rather play locally, follow the steps in Local Setup to run the dev server and open the toys at http://localhost:5173.
This project is organized so you can find the visuals, core utilities, and shared assets quickly:
assets/js/toys/: Individual toy implementations such ascube-wave.ts,spiral-burst.ts, and other sound-reactive scenes.assets/js/core/: Rendering and input helpers used by multiple toys (for example, renderer initialization and audio analyzers).assets/js/utils/: Small utility modules that support the core helpers and toys.assets/css/: Shared styling for the various HTML entry points.assets/data/: Static data files consumed by the toys.tests/: Bun specs that validate core behaviors.toy.html,brand.html,seary.html, and other HTML files: Entry points that load specific toys or collections of toys.
If you add a new toy, place the implementation in assets/js/toys/, register it in assets/js/toys-data.js, and make sure there’s an entry point (often toy.html?toy=<slug>) that can load it.
| Toy | Description |
|---|---|
| 3D Toy | A twisting 3D tunnel that responds to sound. |
| Aurora Painter | Paint flowing aurora ribbons that react to your microphone in layered waves. |
| Star Guitar Visualizer | Visuals inspired by an iconic music video, synced to your music. |
| Pottery Wheel Sculptor | Spin and shape a 3D clay vessel with smoothing, carving, and pinching tools. |
| Defrag Visualizer | A nostalgic, sound-reactive visualizer evoking old defragmentation screens. |
| Evolutionary Weirdcore | Watch surreal landscapes evolve with fractals and glitches that react to music. |
| Geometry Visualizer | Push shifting geometric forms directly from live mic input with responsive controls. |
| Halo Visualizer | Layered halos, particles, and shapes that respond to your music. |
| Multi-Capability Visualizer | Shapes and lights move with both sound and device motion. (Requires WebGPU.) |
| Synesthetic Visualizer | Blend audio and visuals into linked patterns. |
| Pattern Recognition Visualizer | See patterns form dynamically in response to sound. |
| Terminal Word Grid | A retro green text grid that pulses to audio and surfaces fresh words as you play. |
| SVG + Three.js Visualizer | A hybrid visualizer blending 2D and 3D elements, reacting in real time. |
| Spectrograph | A spectrograph that moves gently with your audio. |
| Interactive Word Cloud | Speak and watch the word cloud react and shift with your voice. |
| Grid Visualizer | Swap between cube waves and bouncing spheres without stopping the music. |
| Bubble Harmonics | Translucent, audio-inflated bubbles that split into harmonics on high frequencies. |
| Cosmic Particles | Jump between orbiting swirls and nebula fly-throughs with a single toggle. |
| Audio Light Show | Swap shader styles and color palettes while lights ripple with your microphone input. |
| Spiral Burst | Colorful spirals rotate and expand with every beat. |
| Rainbow Tunnel | Fly through colorful rings that spin to your music. |
| Star Field | A field of shimmering stars reacts to the beat. |
| Fractal Kite Garden | Grow branching kite fractals that sway with mids and shimmer with crisp highs. |
| Tactile Sand Table | Heightfield sand ripples that respond to bass, mids, and device tilt. |
| Bioluminescent Tidepools | Sketch glowing currents that bloom with high-frequency sparkle from your music. |
- Issue: Some users with older or unsupported browsers/devices run into WebGL or WebGPU gating with unclear recovery steps.
- Fix: Add a richer compatibility preflight with clear next steps, plus a “why this won’t run here” state that links to supported browsers and fallback toys. Include a no-mic path in the primary CTA flow so first-time users always see a successful start option.
- Issue: Heavier toys can stutter on mid-tier devices, which breaks the intended sensory flow.
- Fix: Add a quality control panel (pixel ratio cap, particle density presets, and a “low motion” mode) that persists per device and defaults to a safe mid-tier profile. Provide a short “performance profile” explanation so users understand what each preset changes.
If you want to reduce GPU load on high-DPI screens without degrading visuals too much, pass a maxPixelRatio option to
initRenderer (defaults to 2). This caps the renderer to Math.min(window.devicePixelRatio, maxPixelRatio), so setting
maxPixelRatio to 1.5 or 1 can significantly cut per-frame work while retaining clarity.
- Issue: Users who deny mic access often get stuck or miss the demo audio escape hatch.
- Fix: Expand the status copy and inline guidance so the demo audio path is always visible, clearly recommended when mic access is blocked, and explains how to re-enable permissions.
- Issue: Touch interactions feel inconsistent across devices, especially for multi-touch toys.
- Fix: Improve touch handling, add clearer gesture hints, and confirm touch targets meet the minimum size for reliable taps. Add explicit “tap to start” feedback within 100ms so touch users see immediate response.
To play with the toys locally you’ll need to run them from a local web server. Opening the HTML files directly won’t work because the TypeScript modules and JSON fetches can’t load over file://. Here’s the quick setup:
-
Clone the repository:
git clone https://github.com/zz-plant/stims.git cd stims -
Use Bun 1.2+ (recorded in
package.json). -
Install dependencies (Bun is required and the only locked flow):
bun install
The repository tracks
bun.lock; pin installs withbun install --frozen-lockfile.Bun does not automatically run
preparescripts, so the repo includes apostinstallhook that installs Husky whennpm_config_user_agentstarts withbun. If that step fails for any reason, fall back tobun x husky install. -
Start the development server:
bun run dev
Open http://localhost:5173 in your browser.
To serve a static build instead of the dev server, run:
bun run build
bun run preview
bun run serve:dist
# or use Python as a fallback
python3 -m http.server distThe preview server hosts the contents of dist/ on port 4173 using Vite's --host flag, so you can load the build from other devices on your LAN if needed.
All JavaScript dependencies are installed via Bun and bundled locally with Vite, so everything works offline without hitting a CDN.
- If the browser denied microphone access, re-allow it via the address bar/site settings and click the start button again. Hard-refreshing the page will re-trigger the prompt on most browsers.
- Switch to demo audio if you keep seeing timeouts or “blocked” errors—the UI exposes a fallback button for a pre-mixed track so you can keep exploring without mic input.
- Use the YouTube audio option on
toy.htmlto paste a video link and capture tab audio when you want a shared playback source instead of live mic input. - For deeper debugging (including timeouts and permission-state checks), see the microphone flow helper in
assets/js/core/microphone-flow.ts.
- A WebGPU fallback warning means the browser lacked a compatible adapter or device at startup; toys will fall back to WebGL when possible.
- To force a retry (for example, after toggling a browser flag or switching GPUs), refresh the page—WebGPU detection resets and will attempt the adapter/device handshake again.
- WebGPU-only toys (like
multi) won’t run without WebGPU; expect them to stay idle or prompt you to pick another toy until the capability probe succeeds. The renderer capability probe and fallback reasons live inassets/js/core/renderer-capabilities.tsif you need to trace the gating logic.
- Use
bun run dev:hostto bind the dev server to your LAN interface for mobile/device testing; the script mirrors the defaultdevcommand but with explicit hosting. - Check the served port in the terminal output (Vite defaults to
5173; preview uses4173). Connect from other devices viahttp://<your-ip>:<port>. - Avoid loading toys over
file://; the modules and JSON fetches require an HTTP server, so always use the dev server, preview server, or another static host.
bun run dev: Start the Vite development server for local exploration.bun run dev:host: Start the Vite dev server bound to your LAN interface for quick mobile/device testing.bun run build: Produce a production build indist/.bun run preview: Serve the production build locally (Vite preview with--hostfor LAN testing) to validate the output before deploying.bun run test: Run the Bun-native test suite with the required--preload=./tests/setup.tsand--importmap=./tests/importmap.jsonflags applied. These load happy-dom globals and a Three.js stub so specs run headlessly.bun run test:watch: Keep the Bun test runner active while you iterate on specs.bun run lint: Check code quality with Biome.bun run lint:fix: Apply Biome auto-fixes and formatting.bun run format: Format files with Biome.bun run format:check: Validate formatting with Biome.bun run typecheck: Run TypeScript’s type checker without emitting files.bun run typecheck:watch: Keep TypeScript checking in watch mode without emitting files.bun run check: Run lint, typecheck, and tests in one go (handy before opening a PR).bun run check:quick: Run lint and typecheck only (fast guardrail during iteration).bun run scripts/scaffold-toy.ts: Interactive (or flag-driven) scaffolder that prompts for a slug/title/type, creates a starter module fromdocs/TOY_DEVELOPMENT.md, appends metadata toassets/js/toys-data.js, updatesdocs/TOY_SCRIPT_INDEX.md, and can optionally drop a minimal Bun spec. Pass flags such as--slug ripple-orb --title "Ripple Orb" --type module --with-testfor non-interactive runs.bun run serve:dist: Serve thedist/build with Bun (preferred for local production previews).
Please review our Code of Conduct before participating in the project. By contributing, you agree to uphold these community standards. A full contributing guide will live alongside the Code of Conduct so expectations are always clear.
This project uses the Bun test runner for its suite. To install dependencies and run the tests:
-
Install dependencies:
bun install
-
Run the tests (via the script so required flags are applied):
bun run testThis script pins
--preload=./tests/setup.tsand--importmap=./tests/importmap.jsonto load happy-dom globals and a Three.js stub for headless execution.
For quick iteration, use the watch mode:
bun run test:watchBefore committing, run bun run lint to check code style and bun run format to automatically format your files with Biome. This keeps the project consistent.
Cloudflare Pages can build this project with Bun using the wrangler.toml in the repo root. Key settings:
- Project name:
stims(top-levelnameinwrangler.toml) - Build output directory:
dist/(set viapages_build_output_dirinwrangler.toml) - Build command:
bun run build(set in the Pages UI under Settings → Builds & deployments → Build command; Pages rejects a[build]table inwrangler.toml) - Cloudflare Pages automatically detects the Bun version from the repo’s
.bun-versionfile. As a fallback, you can set theBUN_VERSIONenvironment variable (for example,BUN_VERSION=1.2.14) in your Pages project to ensure the hosted runtime matches local installs. - Enable Pages’ Bun runtime so the build runs under Bun instead of Node.
- The
compatibility_dateinwrangler.tomlkeeps Pages aligned with the Cloudflare Workers API version. - Do not add a
[pages]table inwrangler.toml; Cloudflare Pages expects project linkage to be configured in the dashboard. - If you prefer to omit the build command in the Pages UI, keep
CF_PAGES=1in the environment;scripts/postinstall.mjswill runbun run buildduring install to populatedist/automatically. The script still only installs Husky when the installer is Bun, matching local behavior.
To verify the preview locally, run bun run build and inspect the generated dist/ folder; it matches the assets Pages will serve when the Bun runtime is enabled and the build output directory is dist/.
If you run into a problem or want to propose an improvement, please use the GitHub issue templates so we get the details we need:
- Bug reports: include clear reproduction steps, your environment, and what you expected to happen.
- Feature requests: describe the problem you’re trying to solve, the behavior you’d like, and any alternatives you considered.
When opening a pull request, fill out the PR template with a summary of the change and the tests you ran. Check the lint and test boxes only if you executed those commands.
This project is released under the Unlicense, so you’re free to copy, modify, sell, and distribute it however you like. Do whatever you want with it—there are no restrictions.
Feel free to add more toys, tweak the visuals, or contribute in any way.