# Ultimate Tic-Tac-Toe A two-player, hot-seat tic-tac-toe game for the browser where **each player brings their own flavor**. Every player picks **any** avatar from a combined roster β€” sparkle *or* shred β€” and that choice sets their personal color, move sound, and particle effects. One player can rock πŸ¦„ unicorns and rainbows while the other brings πŸ’€ skulls and metal. A separate **stage** selector sets the global vibe. ## Camps & stages **Camps** (per player, set by the chosen avatar): - **✨ Sparkle** β€” β­πŸ¦„πŸŒΈπŸŽ€πŸ¦‹πŸŒˆπŸŽ¨πŸ­ Β· pink glow, bright sine chimes, sparkle particles. - **πŸ’€ Shred** β€” πŸ’€β˜ οΈπŸŽΈπŸ€˜πŸ”₯βš‘πŸ΄β€β˜ οΈπŸ‰ Β· red glow, sawtooth power-chords, fire/lightning particles, board shake. **Stages** (global, pick on the setup screen): - **✨ Sparkle** β€” pastel gradient, glassmorphism cards, twinkling stars. - **πŸ’€ Shred** β€” black & red, rising embers, lightning flashes, metal lettering. - **βš”οΈ Mixed** β€” neutral dark arena where both camps' colors pop; both backdrops play. Great when the two players don't agree. ## Features - **Free-for-all avatars** β€” each player's grid shows all 16; a guard stops both choosing the *same* one. - **Per-player identity** carries through the whole game: side panels, scoreboard, and each player's pieces on the board glow in their camp color, with camp-specific move sounds and particle bursts. - **Three stages** driven entirely by CSS custom properties (`[data-stage]`); the winner screen adopts the winner's camp. - **Web Audio** SFX (no asset files); mute toggle, top-right. - **Persistent** names, avatars, stage, scores, and mute via `localStorage`. - **Accessible** β€” keyboard-navigable board (arrow keys + Enter/Space), ARIA roles and live status, visible focus rings, `prefers-reduced-motion`. - **Responsive & scroll-safe** β€” no horizontal overflow or clipped content from 320 px to desktop (the old nested-scroll / clipped-top bug is fixed: the layout now centers with `margin:auto` instead of fragile flex centering + `max-height`). ## Run it Static site, no build step. **Local web server** (so `localStorage` + audio behave like production) ```bash python3 -m http.server 8000 # β†’ http://localhost:8000 ``` **Docker / OrbStack** (nginx, production-style headers) ```bash docker compose up -d --build # β†’ http://localhost:8080 docker compose down ``` ## Test End-to-end tests drive real Chromium against the running app via `playwright-core` and print a plain-text PASS/FAIL report (avatar roster, mixed-camp play, stage switching, win/draw flows, persistence, responsive overflow at five widths, keyboard a11y, console cleanliness). Screenshots land in `screenshots/`. ```bash # with a server (or the container) running on :8080 PW_CORE="$(node -e "console.log(require.resolve('playwright-core'))")" \ node test/e2e.mjs http://localhost:8080 # optional: PW_CHROMIUM=/path/to/Chromium to pin a browser build ``` ## Project structure ``` index.html markup; stage selector + three screens (setup / game / winner) style.css stages ([data-stage]) + per-player camps (.camp-sparkle/.camp-shred) script.js engine: avatar roster, camps, stages, audio, FX, persistence, a11y Dockerfile nginx:alpine static image nginx.conf gzip, cache + security headers docker-compose.yml one-command run on :8080 via OrbStack test/e2e.mjs Playwright end-to-end suite ```