diff --git a/docs/superpowers/specs/2026-03-13-menu-rework-design.md b/docs/superpowers/specs/2026-03-13-menu-rework-design.md new file mode 100644 index 0000000..d353616 --- /dev/null +++ b/docs/superpowers/specs/2026-03-13-menu-rework-design.md @@ -0,0 +1,264 @@ +# Menu Rework — App-Style Navigation + +## Goal + +Replace the current ad-hoc tab navigation with a native-app-style bottom navigation bar, driven by TanStack Router nested routes. Clean up the host/player split so the host is simply a player with an extra "Host" tab. Add bingo card completion/redraw flow. + +## Scope + +- In-room views only (`/play/$roomCode`, `/host/$roomCode`) +- Display view (`/display/$roomCode`) unchanged — stays full-screen passive +- Landing page (`/`) unchanged + +--- + +## 1. Route Structure + +### Current + +``` +/play/$roomCode → flat component, all content inline +/host/$roomCode → flat component, all content inline, top tabs (Play | Host) +/display/$roomCode → flat component, passive view +``` + +### New + +``` +/play/$roomCode/ → layout route: WebSocket, store, header, bottom nav + /play/$roomCode/game → game content (predictions, jury, quiz) + /play/$roomCode/bingo → bingo card + /play/$roomCode/board → leaderboard + player list + +/host/$roomCode/ → layout route: WebSocket, store, header, bottom nav + /host/$roomCode/game → same game content as player + /host/$roomCode/bingo → same bingo card as player + /host/$roomCode/board → same leaderboard as player + /host/$roomCode/host → host-only controls + +/display/$roomCode → unchanged +``` + +- Layout routes handle WebSocket connection, Zustand store hydration, sticky header, and bottom nav rendering. +- Child routes consume room/game state from the Zustand store. +- Default redirect: `/play/$roomCode` → `/play/$roomCode/game` (same for host). +- Game, Bingo, and Leaderboard tab components are shared between player and host — identical behavior. The host is just a player with an extra tab. + +--- + +## 2. Sticky Header + +Replaces the current `RoomHeader` component. + +``` +┌─────────────────────────────────┐ +│ I❤️ESC ABCD 🟢 │ +└─────────────────────────────────┘ +``` + +- **Left:** "I❤️ESC" as the app title (text, not emoji — the heart is the Unicode character ❤️) +- **Right:** Room code (monospace, bold) + connection status dot (green/yellow/red) +- `position: sticky; top: 0; z-index: 50` +- `padding-top: env(safe-area-inset-top)` for iPhone notch/Dynamic Island +- Act badge removed — the game content makes the current phase obvious + +--- + +## 3. Bottom Navigation Bar + +### Layout + +``` +Player: +┌──────────┬──────────┬──────────────┐ +│ Game │ Bingo │ Leaderboard │ +└──────────┴──────────┴──────────────┘ + +Host: +┌────────┬────────┬─────────┬────────┐ +│ Game │ Bingo │ Board │ Host │ +└────────┴────────┴─────────┴────────┘ +``` + +### Behavior + +- `position: fixed; bottom: 0; left: 0; right: 0; z-index: 50` +- `padding-bottom: env(safe-area-inset-bottom)` for iPhone home indicator +- Each tab is a TanStack Router `` to the nested route +- Active tab: highlighted with primary color +- Inactive tab: muted foreground color +- Icons: iOS-style (SF Symbols aesthetic), rendered as SVG. No emoji. + - Game → gamepad/play icon + - Bingo → grid/squares icon + - Leaderboard/Board → trophy icon + - Host → wrench/settings icon +- Tab label text below each icon +- Main content area needs `padding-bottom` matching the nav bar height to avoid content hiding behind it + +### Bingo Tab Availability + +- Tab is always visible and always tappable in all acts +- Players can view and familiarize themselves with their bingo card before `live-event` +- Tapping squares to mark them is only enabled during `live-event` act +- Before `live-event`: card is visible but squares are non-interactive (visual only) + +--- + +## 4. Game Tab Content by Act + +Identical for player and host. No nested tabs. + +| Act | Content | +|-----|---------| +| **Lobby** | Predictions form (editable) | +| **Pre-Show** | Predictions form (editable until locked) | +| **Live Event** | Jury voting (when round is open) or "Waiting for host to open voting..." | +| **Scoring** | Locked predictions (with actual results comparison) + quiz buzzer (when question active) | +| **Ended** | Locked predictions + "Thanks for playing!" | + +The nested Jury/Bingo tabs that currently exist inside the Game content during `live-event` are removed — bingo has its own tab, and jury voting is the sole content of the Game tab during live-event. + +--- + +## 5. Bingo Tab Content + +### Active Card View + +Shows the player's current bingo card. Same `BingoCard` component as today, with one change: + +- Before `live-event`: squares are rendered but non-interactive (no `onTap` handler) +- During `live-event`: squares are interactive (tap to mark) +- After `live-event`: card is frozen (same as current behavior) + +### Completion Flow (New) + +When a player completes a bingo line: + +1. **Server detects completion** — the server already detects bingo lines and awards 10 bonus points. Extended to mark the card as `completed`. +2. **Card stored** — the completed card moves to a `completedBingoCards` array in game state (per player). Contains: `playerId`, `displayName`, `card` (the full card data), `completedAt` (timestamp). +3. **Player sees** — a celebration message ("Bingo!") + "Draw New Card" button. +4. **Redraw** — new WS message `request_new_bingo_card`. Server generates a fresh card with different tropes. The old card is already in `completedBingoCards`. +5. **New card** — `myBingoCard` in game state updates to the fresh card. Player can continue playing. + +### Data Changes + +Shared types additions: +``` +completedBingoCards: { playerId: string, displayName: string, card: BingoCard, completedAt: string }[] +``` + +New WS client message: +``` +{ type: "request_new_bingo_card" } +``` + +Game state changes: +- `myBingoCard` continues to represent the current active card +- `completedBingoCards` is a new array on `GameState` (visible to all — host needs it for verification) + +--- + +## 6. Leaderboard Tab Content + +Replaces both the current `Leaderboard` and `PlayerList` components as separate UI elements. + +- **Lobby** (no scores yet): shows player list with names only — a "who's here" view +- **All other acts**: full leaderboard table (rank, name, P/J/B/Q breakdown, total points) +- **Scoring explanation**: "How scoring works" box at the bottom (same as current) + +The `PlayerList` component is no longer rendered anywhere else. Every player appears in the leaderboard. The leaderboard tab is the single place to see who's in the room. + +--- + +## 7. Host Tab Content + +Vertical stack of cards, only visible to the host. This tab contains all host-exclusive controls. + +### Always Present + +1. **Act Controls** — advance/revert buttons with act-specific labels: + - Lobby → "Start Pre-Show" + - Pre-Show → "Start Live Event" + - Live Event → "Start Scoring" + - Scoring → "End Party" + - Ended → "Back to Scoring" + re-open option + - Revert button available for all acts except lobby + +2. **Display View** — explanation of what the display view is ("Project this on a TV for everyone to see") + the display URL (`/display/$roomCode`) + "Copy Link" button + +### Conditional (by act) + +3. **Jury Host** (live-event) — open/close voting per country (existing `JuryHost` component) +4. **Quiz Host** (scoring) — start question, judge answer, skip (existing `QuizHost` component) +5. **Actual Results Form** (scoring/ended) — enter final placings (existing `ActualResultsForm` component) +6. **Bingo Claims** (new, all acts after live-event starts) — list of completed bingo cards with player name and card preview, for host verification + +### Bottom + +7. **End Party** button — destructive (red), always available except when already ended + +--- + +## 8. Display View + +Unchanged. Full-screen passive view, no bottom nav. Continues to show: + +- Lobby: large room code with join instructions +- Pre-Show: prediction submission count +- Live Event: jury display + bingo announcements + leaderboard +- Scoring: quiz display + actual results + leaderboard +- Ended: final results + leaderboard + +--- + +## 9. Components Affected + +### New Components + +- `BottomNav` — the bottom navigation bar (renders tabs as ``s, highlights active) +- `RoomLayout` — sticky header + bottom nav + content outlet (used by both layout routes) +- `BingoClaims` — host-only component showing completed bingo cards for verification + +### Modified Components + +- `RoomHeader` → replaced by new sticky header in `RoomLayout` +- `BingoCard` → add read-only mode (disable tapping before live-event) +- `Leaderboard` → absorb player list display for lobby state +- `PlayerList` → removed from all views (absorbed into leaderboard tab) + +### Shared Between Player and Host + +- Game tab content (predictions, jury voting, quiz buzzer) +- Bingo tab content (bingo card) +- Leaderboard tab content + +### Host-Only + +- Host tab content (act controls, display link, jury host, quiz host, actual results, bingo claims) + +--- + +## 10. Server Changes + +### Bingo Completion + +- `GameManager.tapBingoSquare()` — after marking a square, check for completed lines. If bingo detected: + - Move current card to `completedBingoCards` array + - Award bonus points (already happens) + - Flag card as completed in response + +- New method: `GameManager.requestNewBingoCard(playerId)` — generates fresh card, assigns to player + +- New WS handler: `request_new_bingo_card` message → calls `requestNewBingoCard`, broadcasts updated state + +### Game State Changes + +- Add `completedBingoCards` to game state schema +- Include in `getGameStateForPlayer` and `getGameStateForDisplay` + +--- + +## 11. Migration / URL Compatibility + +- Old URLs (`/play/ABCD`, `/host/ABCD`) should redirect to `/play/ABCD/game` and `/host/ABCD/game` respectively +- TanStack Router can handle this via the layout route's default redirect