Multiplayer Fighting pinball game running on a two-screen physical setup. Each screen is an independent React/Three.js app sharing a common design system, type definitions, and utilities through a pnpm monorepo.
┌─────────────────────────────────────────────────────────┐
│ Physical Setup │
│ │
│ ┌──────────────────┐ ┌──────────────────────┐ │
│ │ front-screen │ │ back-screen │ │
│ │ :3000 │ │ :3001 │ │
│ │ │ │ │ │
│ │ Player-facing │ │ Scoreboard / │ │
│ │ 3D playfield │ │ secondary view │ │
│ └──────────────────┘ └──────────────────────┘ │
└─────────────────────────────────────────────────────────┘
│ │
└──────────┬───────────────┘
│ consumes
┌──────────▼───────────────┐
│ packages/ │
│ types · utils · ui │
│ eslint-config │
│ tailwind-config │
│ tsconfig │
└──────────────────────────┘
Stack: React · TypeScript · Vite · React Three Fiber · Rapier.js · TailwindCSS v4 · Zustand
Tooling: pnpm workspaces · ESLint · Prettier · Vitest · Playwright · Husky + lint-staged
Prerequisites: Node >= 20, pnpm >= 9
# 1. Install dependencies, link workspace packages, and set up git hooks (Husky)
pnpm install
# 2. Only needed if you plan to run E2E tests (~300 MB browser download)
pnpm exec playwright install --with-deps chromium
# 3. Start both dev servers
pnpm dev
pnpm installautomatically runs thepreparescript which initialises Husky — no separate git hook setup required.
| App | URL | Description |
|---|---|---|
front-screen |
http://localhost:3000 | Player-facing 3D view |
back-screen |
http://localhost:3001 | Scoreboard / secondary view |
We used Athena for init the structure of the docker-compose.yml file. For use it, you need to install it first. After that, you can run:
athena validate multi-react-apps.athThis will validate the configuration. After this you need to run:
athena build multi-react-apps.athThis will generate the docker-compose.yml file.
To run the app locally using Docker, follow these steps:
docker compose up --buildThen go to:
- Back Screen: http://localhost:3000
- DMD Screen: http://localhost:3001
- Front Screen: http://localhost:3002
And if you want to stop the app:
docker compose downAll commands run from the repo root unless noted.
pnpm dev # both apps in parallel
pnpm dev:front # front-screen only
pnpm dev:back # back-screen onlypnpm build # build all apps
pnpm typecheck # typecheck all packages and apps
# Per-app
pnpm --filter @frontend/front-screen build
pnpm --filter @frontend/front-screen typecheckpnpm lint # ESLint on the whole repo
pnpm lint:fix # ESLint with auto-fix
pnpm format # Prettier on everything
pnpm format:check # Prettier check (no write)# Unit tests (Vitest) — all packages + apps
pnpm test
pnpm test:watch
# Unit tests — single app or package
pnpm --filter @frontend/front-screen test
pnpm --filter @frontend/utils test
# E2E tests (Playwright) — all apps
pnpm test:e2e
# E2E tests — single app
pnpm --filter @frontend/front-screen test:e2epnpm clean # delete all node_modules and dist foldersfrontend/
├── apps/
│ ├── front-screen/ @frontend/front-screen (port 3000)
│ └── back-screen/ @frontend/back-screen (port 3001)
│
└── packages/
├── types/ @frontend/types
├── utils/ @frontend/utils
├── ui/ @frontend/ui
├── tailwind-config/ @frontend/tailwind-config
├── eslint-config/ @frontend/eslint-config
└── tsconfig/ @frontend/tsconfig
| Package | Purpose |
|---|---|
@frontend/types |
Game domain types — GameState, Player, WS message protocol |
@frontend/utils |
Pure utility functions — clamp, lerp, formatScore |
@frontend/ui |
Shared React components — ScoreDisplay, HUD elements |
@frontend/tailwind-config |
Tailwind v4 CSS theme (@theme tokens — neon palette, fonts) |
@frontend/eslint-config |
Shared ESLint flat config (strict TS + React rules) |
@frontend/tsconfig |
Shared TypeScript base configs (base, react, node) |
Both apps map @/ to their own src/:
import { MyComponent } from "@/components/MyComponent"Each app follows the same layout:
src/
components/ React UI components
hooks/ Custom React hooks
scenes/ React Three Fiber scene definitions
stores/ Zustand state stores
App.tsx
main.tsx
index.css Tailwind entry (@import "tailwindcss" + theme)
vite-env.d.ts Vite asset type declarations
tests/ Vitest unit tests (separate from src/)
setup.ts
e2e/ Playwright end-to-end tests
The visual theme is defined once in packages/tailwind-config/theme.css and imported by both apps. No tailwind.config.ts or PostCSS config — Tailwind v4 runs entirely through the @tailwindcss/vite plugin.
Color tokens:
| Token | Value | Use |
|---|---|---|
neon-pink |
#FF2D6B |
Accents, player 1 |
neon-cyan |
#00F0FF |
Highlights, UI glow |
neon-purple |
#B026FF |
Player 2, special effects |
neon-yellow |
#FFE156 |
Scores, warnings |
surface-dark |
#0A0A0F |
Page background |
surface-card |
#12121A |
Card / panel background |
surface-border |
#1E1E2E |
Borders, dividers |
Fonts: Orbitron (display/headings) · Inter (body)
Music
- Only a Dream — DANCE WITH THE DEAD — used with permission / for non-commercial purposes
Shaders
- Portal effect — adapted from Shadertoy
lcfyDjby MisterPrada (@Mister_Prada) — CC BY-NC-SA 3.0 (Shadertoy default license; non-commercial use with attribution)
Arnaud Fischer · Louis Dondey · Arthur Jenck · Alexis Gontier · Maxime Bidan
v1.0.0- 03/03/26