Bike Playground · Design Pass v2 · May 2026
Decisions & Specs
Why the product is built this way. Read first. These are the closed-loop decisions
from the design audit + the technical specs they imply.
Section 1 · Closed Decisions
Decisions
Nine forks identified in the Round 1 design audit. All are now closed.
This pass — and every implementation step — builds from these decisions only. No new scope.
01
Product Model
Session-based + Admin in v1
Rider name → START RIDE → 20s sprint → END RIDE → leaderboard.
Admin portal (sessions table, whitelist, feature flags) is v1 scope — not deferred.
Rider-phone live tracking stays offline (event WiFi only, no cloud relay).
02
Visual Identity
Signal — Dual Surface
Barlow Condensed 900 + IBM Plex Mono throughout.
Display/projector: dark (#04040e, #00e676).
Operator/Admin/Studio: dark mode for v1 (light mode is a deferred follow-up).
Dark sidebar nav on all desktop surfaces.
03
Cloud Posture
Cloud-hosted on Cloudflare
[Adapted from the original "offline-first" decision to match deployment reality.]
Live website at bpe-bike-playground.pages.dev. Real-time fanout via Cloudflare
Durable Object. Sessions persist to D1. Works anywhere with internet.
Offline event mode (local Node server) remains as a fallback path.
04
Ride Mechanic
20-second Sprint
Fixed 20-second timed sprint. Score = avg watts × 20 (watt-seconds).
Equal-duration rides make the leaderboard fair across fitness levels.
Countdown is the display hero — color shifts green → amber → red.
End card shows total score + QR.
05
Operator UI Device
Desktop Browser
The laptop powering the projector is already on-site. Desktop-first removes
the phone-specific technical complexity (PWA, responsive breakpoints, touch targets).
Setup, Admin, and Studio all run at /setup, /admin, /studio in a browser tab.
06
Sketch Studio Scope
Playground + Deploy Tool
Offline-capable. CodeMirror + RPM simulator + save-to-folder at /studio.
Also hosts the Client Config tool (deployment.json GUI, branding, whitelist).
No build step for vanilla surfaces.
07
Frontend Stack
Vanilla HTML/JS
Every operator surface (Setup, Library, Admin, Studio, Display) is a
self-contained HTML file with inline styles + scripts. No React, no Babel,
no build step beyond static asset copy. Cloud functions (Pages Functions)
use Hono for the REST API.
08
Two-Bike Support
Out for v1
bike.rpm is singular throughout the codebase. A two-bike contract requires
bikes[] everywhere + split-screen display logic + multi-serial-port management.
Defer until the second hardware unit is physically tested.
09
Sketch Cuts
15 Keepers Accepted in Principle — All 25 Kept
[Adapted from the original cut decision.]
Sketch Audit recommends 4 flagship + 4 strong + 7 situational, archiving 10.
All 25 remain in the repo per owner direction; cut sketches stay reachable via
the existing /api/sketches/:slug/restore mechanism. Deployment whitelist
filters per-event.
10
Lead Capture Data Model
5th Event Type — lead_captured
Events are marketing spend for the client; a ride becomes a captured lead.
A lead is naturally event-shaped, so it joins the append-only log as a fifth
type rather than a second table — preserving the single-store / everything-derived
philosophy. Name lives in rider_name, email + consent in the
metadata JSON bag, so the append/INSERT SQL stays byte-identical
across server/events.js and src/api/events.js (only the
CHECK constraint widened, via migrations/0002_lead_captured.sql).
PII: email is stored ONLY with an explicit consent opt-in; no opt-in → name only.
Gated by features.captureLeads (default off); leads age out with the
log per retention. The leads CSV is PII — operator-auth-gated and
local-only; the public/cloud recap shows a non-PII count.
Spec A · UX Spec
Parameter Binding
config.json → Live Controls. The data model already exists.
This spec defines the UI binding. No new JSON schema — only a rendering layer
over the existing parameters array.
Source
sketches/kaleidoscope/config.json
{
: [
{
"name": "speed",
"label": "Speed",
"type": "range",
"default": 72,
"min": 0,
"max": 100
},
{
"name": "arms",
"label": "Symmetry Arms",
"type": "range",
"default": 8,
"min": 3,
"max": 12
},
{
"name": "colorMode",
"label": "Color Mode",
"type": "select",
"default": "rainbow",
"options": ["auto","rainbow","brand"]
}
]
}
Generated UI
operator Setup → params
type: "range"
Speed
72
type: "range"
Symmetry Arms
8
type: "select"
Color Mode
○
No parameters declared in config.json —
this sketch has no tunable controls.
Live
Param changes POST to /api/sketch/params and stream via
WebSocket to display. No page reload.
Schema
No new JSON shape. Existing kaleidoscope/config.json already
has type, default, min, max, options.
Fallback
Sketch with zero parameters shows the empty state. All existing sketches
without params.json still load.
Spec B · UX Spec
Sketch Deployment Pipeline
Studio → Library → Deployment. How a sketch goes from Studio to the live
installation. Filesystem is the database — folders are sketches.
✎
Studio "Save"
User clicks Save in Playground. Studio writes sketch.js + config.json
to sketches/{slug}/.
Single button. No CLI.
→
⊞
Folder Artifact
sketches/{slug}/ is the unit of deployment. One folder = one experience.
Server auto-discovers on startup.
slug auto-generated from title
→
↻
Server Discovery
Express scans sketches/ on boot (fs.readdir). New folders appear in
Library instantly on /api/sketches.
No registry. No DB.
→
☑
Whitelist
deployment.json lists allowed slugs. Operator sees only whitelisted
sketches in Setup + Library.
deployments/{id}/deployment.json
→
▶
Live on Display
Operator taps sketch in Library → display swaps with crossfade.
Params stream in real-time.
Zero downtime swap.
Folder Contract — sketches/{slug}/
sketch.js
REQUIRED — p5.js sketch. Must read bike.rpm and params.*.
config.json
REQUIRED — metadata: name, category, tier, parameters[].
thumbnail.png
OPTIONAL — 400×270 static preview. Auto-generated if absent.
README.md
OPTIONAL — dev notes, parameter docs.
Deployment Whitelist
{
"sketches": [
"kaleidoscope",
"warp-stars",
"aurora-borealis",
"circuit-board"
]
}
Absent key = all discovered sketches shown.
No DB
Filesystem is the database. Sketches are folders. No migrations,
no schema changes.
Undo
git rm sketches/{slug} removes a sketch. Or remove from
whitelist to hide without deleting.
Thumbnail
Server can auto-generate thumbnail.png by running sketch headlessly
for 2s and screenshotting.
Spec C · Schema Spec
deployment.json: Before → After
Current v1 schema has 2 fields. v2 adds identity, branding, whitelist,
feature flags, and retention. Reference example: Acme Corp Wellness Day 2026.
{
"name": "default",
"accessCode": {
"duration": 28800
}
}
→
{
"event": "Wellness Day 2026",
"client": "Acme Corporation",
"venue": "HQ Atrium — Building 3",
"date": "2026-05-20",
"branding": {
"logo": "assets/acme-logo.svg",
"primary": "#1A3A8F",
"accent": "#E8402A",
"footerText": "Acme Corp Wellness Day 2026"
},
"sketches": [
"kaleidoscope", "warp-stars",
"aurora-borealis", "circuit-board"
],
"features": {
"leaderboard": true,
"requireName": true,
"prize": false,
"qr": true,
"qrUrl": "events.bpe.io/acme-2026"
},
"accessCode": { "duration": 28800 },
"retention": "24h"
}
New Fields
event / client / venue / date
Identity fields. Shown in admin header and end-of-event export.
branding.logo
Path to SVG or PNG. Server auto-extracts primary/accent colors on load if not specified.
sketches[]
Ordered whitelist of sketch slugs. Absent = all discovered sketches. Order determines library display order.
features.requireName
Operator must enter a rider name before START RIDE is enabled.
features.qrUrl
URL printed on end-of-ride QR code. Points at the public leaderboard for the cloud deployment.
retention
Session data TTL. "24h" wipes at midnight. "event" wipes on next boot. "forever" never wipes.