Refresh the scene dashboard around a crew bubble column

Replace the roster banner and the standalone character-sheet panel with
a left-hand column of crew bubbles. Each bubble shows the player's role
icon, name, rank, current hand size, and a 🛞 for the Captain; clicking
one pops out that player's character sheet as a modal.

- New CrewColumn.svelte: bubbles (Pi-Rats first, Deep last) + an
  Objectives button that opens a crew-objectives popup.
- CharacterSheet.svelte is now a target-driven modal: description,
  likes/hates, and personal objectives for anyone; secret J/Q/K only on
  your own sheet; the Duel UI (no "Challenge whom?" — target is fixed)
  only when a living Pi-Rat views another living Pi-Rat.
- ScenePhase.svelte → 3-column layout (crew | obstacles | hand/deep);
  crew column collapses to a horizontal strip on narrow screens.
- Shared .modal-backdrop/.modal-box/.modal-close moved into components.css;
  removed the now-dead roster-banner and captain-badge styles.

Backend unchanged — the state blob already carries hands, roles, ranks,
objectives, and the captain.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-14 20:03:41 -07:00
co-authored by Claude Opus 4.8
parent 8614a6c820
commit 2ea91c066a
6 changed files with 363 additions and 232 deletions
+6 -42
View File
@@ -1,24 +1,22 @@
<script>
import Card from './Card.svelte';
import { displayName } from '../lib/cards';
import ChallengePanel from './scene/ChallengePanel.svelte';
import ObstacleBoard from './scene/ObstacleBoard.svelte';
import DeepControlPanel from './scene/DeepControlPanel.svelte';
import CharacterSheet from './scene/CharacterSheet.svelte';
import CrewColumn from './scene/CrewColumn.svelte';
export let state;
$: hand = state.player.hand_cards ? JSON.parse(state.player.hand_cards) : [];
$: captain = state.players.find(p => p.id === state.game.captain_player_id) || null;
$: isCaptain = state.game.captain_player_id === state.player.id;
$: playerTechs = { J: state.player.tech_jack, Q: state.player.tech_queen, K: state.player.tech_king };
</script>
<div class="scene-view-layout" id="scene-layout-container" data-game-id={state.game.id} data-player-id={state.player.id}>
<!-- LEFT COLUMN: The Shared Table -->
<div class="table-column">
<!-- LEFT COLUMN: The Crew -->
<CrewColumn {state} />
<!-- Game Deck and Obstacles Roster -->
<!-- MIDDLE COLUMN: The Shared Table -->
<div class="table-column">
<div class="card glass-panel obstacle-list-card">
<div class="card-header">
<h3>🌊 The Obstacle List</h3>
@@ -26,45 +24,13 @@
</div>
<div class="obstacles-container" id="scene-obstacles-container">
<!-- TOP STATUS BAR: Scene Info and Captain -->
<div class="scene-status-banner">
<!-- Captain Badge -->
<div>
{#if captain}
<span class="captain-badge">
🏴‍☠️ Captain: {displayName(captain)} (Rank {captain.rank}){isCaptain ? ' — You!' : ''}
</span>
{:else}
<span class="captain-badge vacant">
🏴‍☠️ Captain: None{state.game.completed_crew_1 ? '' : ' (steal a ship first!)'}
</span>
{/if}
</div>
<!-- Active Scene Roster Mini-list -->
<div class="roster-chips">
<span class="roster-label">Active Roster:</span>
{#each state.players as p}
{#if p.role === 'deep'}
<span class="player-chip deep-chip">
🌊 {displayName(p)} (Deep)
</span>
{:else if p.role === 'pirat'}
<span class="player-chip pirat-chip {p.is_ghost ? 'is-ghost' : ''} {p.is_dead ? 'is-dead' : ''} {p.id === state.game.captain_player_id ? 'is-captain' : ''}">
{#if p.is_ghost}👻{:else if p.is_dead}💀{:else}🐀{/if} {displayName(p)} (Rank {p.rank}){p.id === state.game.captain_player_id ? ' ⭐' : ''}
</span>
{/if}
{/each}
</div>
</div>
<ChallengePanel {state} />
<ObstacleBoard {state} />
</div>
</div>
</div>
<!-- RIGHT COLUMN: Private Hand & Character Sheet -->
<!-- RIGHT COLUMN: Private Hand / Deep Controls -->
<div class="private-column">
{#if state.player.role === "deep"}
<DeepControlPanel {state} />
@@ -90,8 +56,6 @@
{/each}
</div>
</div>
<CharacterSheet {state} />
{/if}
</div>
</div>