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
{
  "parameters": [
    {
      "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"]
    }
  ]
}
Auto-
gen
Generated UI
operator Setup → params
type: "range"
Speed 72
type: "range"
Symmetry Arms 8
type: "select"
Color Mode
Auto
Rainbow
Brand
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.

Current (v1) 6 lines
{
  "name": "default",
  "accessCode": {
    "duration": 28800
  }
}
Proposed (v2) Reference example · Acme Corp
{
  // Identity
  "event": "Wellness Day 2026",
  "client": "Acme Corporation",
  "venue": "HQ Atrium — Building 3",
  "date": "2026-05-20",

  // Branding (logo → auto-extracts palette)
  "branding": {
    "logo": "assets/acme-logo.svg",
    "primary": "#1A3A8F",
    "accent": "#E8402A",
    "footerText": "Acme Corp Wellness Day 2026"
  },

  // Sketch whitelist (omit = show all 15)
  "sketches": [
    "kaleidoscope", "warp-stars",
    "aurora-borealis", "circuit-board"
  ],

  // Feature flags
  "features": {
    "leaderboard": true,
    "requireName": true,
    "prize": false,
    "qr": true,
    "qrUrl": "events.bpe.io/acme-2026"
  },

  // Access + data
  "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.