Implement createModPlayer with a TDD-first ModPlayer contract
(idle/loading/playing/paused/unavailable), wired into App and
TrackerPanel. Uses chiptune3@0.8.7, loaded lazily via a deep
import (the package ships no main/exports field). Adds two Vite
fixes needed for the nested worklet asset to actually load: copy
libopenmpt.worklet.js next to the built worklet chunk, and
exclude chiptune3 from dep pre-bundling so dev mode resolves the
same sibling file.
Ships public/music/ride.mod: "11th-hour" by TDK, CC BY 4.0, from
The Mod Archive (modarchive.org, module ID 67104). Credited in
the HUD scroller text.
Implement Task 10: HUD components for LE BUS demoscene page.
- Scroller.tsx: canvas-2D sine-wave text animation at bottom
- TrackerPanel.tsx: play/mute control panel (top-right)
- modPlayer.ts: interface stubs (ModState, ModPlayer)
- CSS styling for both components appended to style.css
Context: the ride passed 4 stops (boot, identities, electron
fou, greetings) but rendered no content at any of them.
Approach: shared StopPanel component places a pole, a yellow
sign with the stop title, and a drei Html transform panel
anchored beside the road via stopAnchor(); each stop composes
it with its own content module.
Changes:
- StopPanel.tsx: pole + sign + Html panel, panel--active class
driven by useRide selector on the active stop id
- StopBoot.tsx: avatar art, name, tagline, bio lines
- StopIdentities.tsx: identity links list
- StopElectronFou.tsx: music blurb + pulsing tube amp with VU
needles animated via useFrame
- StopGreetings.tsx: greetings list + ride-again reset button
- Stops.tsx: mounts the four stop components
- style.css: .panel styling, active glow, sign buttons/links
- App.tsx: mount <Stops /> in the Canvas, drop the placeholder
Impact: the ride now has readable, interactive content at every
stop; build (tsc -b + vite build) and full vitest suite (12/12)
pass.
Implement Bus component (low-poly orange van following the path with
engine judder) and Passengers component (glowing I2C frames with
labels that board the bus at stops). Mount both in App.tsx. All
tests pass (12).
Add busPath curve helpers (pointAt/tangentAt), CameraRig as the
sole consumer of useRide.frame(), and App.tsx wiring the R3F
Canvas at low dpr for chunky pixels. Replace the boot.ts stub
with the real React root mount and append fixed-canvas CSS.
Implement ride progression logic with pure functions
(clamp01, ease, activeStop), Zustand store for state
management, and React input hook for wheel/keyboard/touch.
Add a Vite plugin that renders bio, links and greetings from the
Task 2 content modules into the raw served index.html at build
time, so non-JS clients (Mastodon's rel=me verifier included) see
real content and the mastodon.saillant.cc/@clement rel=me anchor.
Hide the fallback for sighted users once the 3D demo boots, but
keep it in the accessibility tree. Add @types/node so tsc can
type-check the node:fs import in the new test.
Implement content modules (links, bio, greetings, music, stops) as a
single source of truth for the UI, HUD, and 3D scene. All content is
defined in TypeScript with interfaces for type safety.
The Bandcamp URL for L'Électron Fou is not available yet, so the music
module defaults to the Mastodon profile URL as a fallback (per spec).
Update this URL when the Bandcamp page is created.
Establish initial project structure for LE BUS (clement.saillant.cc),
a demoscene-style personal identity hub built with Vite, React, and
react-three-fiber.
Changes:
- Add package.json with dependencies (React 19, Three.js,
react-three/fiber, zustand, playwright for e2e)
- Add tsconfig.json with ES2022 target and react-jsx support
- Add vite.config.ts with React plugin
- Add minimal index.html, src/main.tsx, and src/style.css
- Add README.md with usage commands
- Add .gitignore (node_modules, dist, test artifacts, .DS_Store)
- Generate package-lock.json via npm install
Note: chiptune3 removed from dependencies (not on npm registry;
will be isolated behind src/audio/modPlayer.ts in Task 11 with mocks).
Build and test harness verified:
- npm run build succeeds → dist/index.html created
- npx vitest run --passWithNoTests exits 0
Demoscene-style personal identity hub: bus on a giant PCB,
4 content stops, MOD tracker audio, semantic DOM fallback
with rel=me Mastodon verification. Static build, deployed
on Tower behind Traefik + cloudflared tunnel.