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