add menu rework design spec

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-13 08:47:12 +01:00
parent d3a5d08d6b
commit 142455cdb8

View File

@@ -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 `<Link>` 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 `<Link>`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