A simple "spin the wheel" web app for picking winners from a pool of names.
  • Vue 59.2%
  • TypeScript 32.7%
  • CSS 7.9%
  • JavaScript 0.2%
Find a file
2026-05-19 13:11:44 +02:00
.zed feat: some design system components 2026-05-18 16:11:04 +02:00
app feat: tick sound when spinning 2026-05-19 12:35:12 +02:00
docs docs: add screenshots 2026-05-19 13:11:44 +02:00
i18n/locales feat: tick sound when spinning 2026-05-19 12:35:12 +02:00
public feat: browser test 2026-05-19 11:09:15 +02:00
test feat: browser test 2026-05-19 11:09:15 +02:00
.gitignore initial commit 2026-05-18 15:47:30 +02:00
.nuxtrc initial commit 2026-05-18 15:47:30 +02:00
.prettierignore style: run formatter 2026-05-19 10:29:02 +02:00
.prettierrc feat: some design system components 2026-05-18 16:11:04 +02:00
eslint.config.mjs style: run formatter 2026-05-19 10:29:02 +02:00
LICENSE chore: remove unused deps + add license 2026-05-19 11:22:08 +02:00
nuxt.config.ts feat: tick sound when spinning 2026-05-19 12:35:12 +02:00
package.json feat: tick sound when spinning 2026-05-19 12:35:12 +02:00
pnpm-lock.yaml feat: tick sound when spinning 2026-05-19 12:35:12 +02:00
README.md docs: add screenshots 2026-05-19 13:11:44 +02:00
tsconfig.json initial commit 2026-05-18 15:47:30 +02:00
vitest.config.ts feat: browser test 2026-05-19 11:09:15 +02:00

Orbis

A simple "spin the wheel" web app for picking winners from a pool of names.

Features

  • Admin + presentation views — open / to manage the pool and /present in a separate window for a chrome-free, projector-friendly wheel.
  • Persistent state — entries, winners, and settings survive a reload via localStorage.
  • Bulk paste — paste a newline- or comma-separated list to populate the pool.
  • Optional dedupe — show each name once on the wheel even if entered multiple times (case-insensitive).
  • Optional auto-remove — pull the winner out of the pool after each spin.

Screenshots

Admin Presentation
Admin view — manage the pool, settings, and winners Presentation view — the wheel in full screen

Setup

pnpm install

Development

pnpm dev

Opens at http://localhost:3000.

  1. Add names to the pool (one at a time, or paste a list).
  2. Click Open presentation to spawn /present in a second window — put it on a projector or external display.
  3. Hit Spin. Both views animate together; the winner pops up on the presentation with confetti.

Scripts

Command What it does
pnpm dev Run the dev server
pnpm build Build for production
pnpm preview Preview the production build
pnpm lint ESLint with --fix
pnpm format Prettier write
pnpm test Run all test projects
pnpm test:unit Pure logic — store actions, spin math (property test)
pnpm test:nuxt Component integration via mountSuspended + happy-dom
pnpm test:browser Real Chromium via @vitest/browser + Playwright (CSS transitions)

The browser tests need Playwright's Chromium binary, installed once with:

pnpm exec playwright install chromium

Architecture notes

  • Store-driven: the wheel is a pure view over useWheelStore. The spin's target angle is computed up front in startSpin and persisted on the store, so admin and presentation animate to the same name even if one tab reloads mid-spin.
  • Cross-tab sync (app/plugins/wheel-cross-tab.client.ts): a BroadcastChannel broadcasts the store state on every mutation, with a value-equality check to avoid ping-pong loops.
  • Pointer at 90°: the right-side pointer is the source of truth for "who won." startSpin's rotation math is verified by a property test that decodes the segment under the pointer from targetAngle and checks it matches winnerIndex.
  • Test-only spin duration: store.spinDurationMs defaults to 10s but tests set it to 200ms so real CSS transitions complete in ~hundreds of ms.

License

MIT License