Three advisory reviews with measured evidence: the confetti celebration, the wheel and theme design, and the API/operator experience. The API findings are untouched work - broken render:local CLI examples, 400/413/415 conditions surfacing as 500s, an unenforced timeout guarantee, and a production token default in compose.yml. The refinement plan covers the second pass and records what was deliberately excluded, including the bezel, the font-size cap, a night theme, and narrowing the request schema.
wheelofnames
Self-hosted API that renders wheel-of-names GIF animations with Remotion.
API
POST /api/gif
Content-Type: application/json
Accept: image/gif
Authorization: Bearer change-me
{
"options": ["alice", "bob", "carol"],
"winnerIndex": 1,
"durationMs": 6500,
"holdMs": 1200,
"fps": 15,
"size": 512,
"theme": "classic"
}
Response is image/gif with winner metadata headers:
X-Wheel-Winner-IndexX-Wheel-WinnerX-Render-Duration-Ms
X-Wheel-Winner is URL-encoded so non-ASCII labels are safe in HTTP headers.
The GIF ends on a winner celebration: the wheel settles, the winning slice is outlined, the winner's name appears over the hub, and a deterministic two-cannon confetti burst crosses the wheel. Each theme carries its own confetti palette, chosen for contrast against that theme's background rather than reusing the slice colors. The celebration starts a few frames before the wheel mathematically settles, so it lands on the perceived stop.
Labels stay radial but flip where needed so every name reads upright in the final frame, which is the frame most chat clients show as the GIF's poster image.
Local
Install dependencies and Chromium once:
pnpm install
pnpm browser:ensure
Start the local API:
pnpm dev
Generate GIF files locally
Generate the quick smoke fixture at the git-ignored path
fixtures/smoke.gif:
pnpm render:smoke
Generate the complete fixture set at fixtures/smoke.gif,
fixtures/vietnamese.gif, and fixtures/sixteen-options.gif:
pnpm render:fixtures
Render a custom GIF directly without starting the API server:
pnpm render:local -- `
--output wheel.gif `
--option "Chiều nay uống CraneTea" `
--option "Chiều nay uống CraneTea" `
--option "Cà phê" `
--winner 1
macOS, Linux, or Git Bash:
pnpm render:local -- \
--output wheel.gif \
--option "Chiều nay uống CraneTea" \
--option "Chiều nay uống CraneTea" \
--option "Cà phê" \
--winner 1
--winner is a zero-based index and is random when omitted. Run
pnpm render:local -- --help for duration, hold, FPS, size, theme, and timeout
options. The documented root wheel.gif and fixtures/*.gif outputs are
git-ignored and safe to delete; custom output paths may need their own ignore
rule.
Verify
Run the API smoke test and quality gates:
pnpm api:smoke
pnpm lint
pnpm typecheck
pnpm test
Deploy
Use a container runtime first. Static-only platforms cannot satisfy
POST /api/gif because Remotion server rendering needs Node, Chromium/runtime
dependencies, and FFmpeg/compositor support.
docker build -t wheelofnames .
docker run --rm -p 3000:3000 -e API_TOKEN=change-me wheelofnames
Recommended starting resources: 1-2 vCPU and 1-2 GB RAM, with
MAX_CONCURRENT_RENDERS=1. RENDER_TIMEOUT_MS defaults to 15000 and is
raised to Remotion's 7000ms browser timeout floor when configured lower.
API_TOKEN is required when NODE_ENV=production.