Refine crew column: names, Captain label, slide-out objectives

Follow-up tweaks to the scene dashboard:
- Bubbles now show the human player name alongside the Pi-Rat name.
- The Captain is named "Captain <name>" (the "Recruit" honorific swapped,
  or "Captain" prepended once Named) instead of a 🛞 icon — applied on
  bubbles and the character-sheet/duel headings via new crewLabel/
  captainName helpers in lib/cards.js.
- "Objectives" → "Crew Objectives" moved to the end of the roster as a
  left-aligned toggle with a right-aligned X/3 completed counter; it now
  slides the detailed list open in place (svelte/transition slide)
  instead of opening a modal.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-14 20:12:44 -07:00
parent 2ea91c066a
commit 045857a8fd
4 changed files with 88 additions and 45 deletions

View File

@@ -1,4 +1,6 @@
<script>
import { slide } from 'svelte/transition';
import { crewLabel } from '../../lib/cards';
import CharacterSheet from './CharacterSheet.svelte';
export let state;
@@ -8,7 +10,9 @@
// Pi-Rats first, the Deep last; otherwise keep join order.
$: crew = [...state.players].sort((a, b) => (a.role === 'deep' ? 1 : 0) - (b.role === 'deep' ? 1 : 0));
$: captainId = state.game.captain_player_id;
$: openTarget = openTargetId ? state.players.find(p => p.id === openTargetId) : null;
$: crewDone = [state.game.completed_crew_1, state.game.completed_crew_2, state.game.completed_crew_3].filter(Boolean).length;
function handSize(p) {
try { return JSON.parse(p.hand_cards || '[]').length; } catch { return 0; }
@@ -19,18 +23,9 @@
if (p.is_dead) return '💀';
return '🐀';
}
function onKeydown(e) {
// CharacterSheet handles its own Escape; this closes the objectives popup.
if (e.key === 'Escape') showObjectives = false;
}
</script>
<svelte:window on:keydown={onKeydown} />
<div class="crew-column">
<button class="crew-objectives-btn" on:click={() => showObjectives = true}>🎯 Objectives</button>
<div class="crew-bubbles">
{#each crew as p (p.id)}
<button
@@ -43,26 +38,24 @@
on:click={() => openTargetId = p.id}
>
<span class="bubble-icon">{roleIcon(p)}</span>
<span class="bubble-name">{p.name}{#if p.id === state.game.captain_player_id}&nbsp;🛞{/if}</span>
<span class="bubble-name">{crewLabel(p, captainId)}</span>
<span class="bubble-meta">
{#if p.role === 'deep'}Deep{:else}Rank {p.rank}{/if} · 🃏 {handSize(p)}
</span>
</button>
{/each}
</div>
</div>
{#if openTarget}
<CharacterSheet {state} target={openTarget} on:close={() => openTargetId = null} />
{/if}
{#if showObjectives}
<div class="modal-backdrop" on:click|self={() => showObjectives = false}>
<div class="modal-box glass-panel" style="text-align: left;">
<button class="modal-close" on:click={() => showObjectives = false} aria-label="Close">×</button>
<h3>🎯 Crew Objectives</h3>
<p class="info-text" style="font-size: 0.85rem;">The whole crew works toward these. The Deep marks them off as you accomplish them.</p>
<div class="objectives-checklist">
<!-- Crew Objectives sits at the end of the roster and slides open in place -->
<button
class="crew-objectives-toggle"
aria-expanded={showObjectives}
on:click={() => showObjectives = !showObjectives}
>
<span class="co-label">{showObjectives ? '▾' : '▸'} Crew Objectives</span>
<span class="co-count">{crewDone}/3</span>
</button>
{#if showObjectives}
<div class="crew-objectives-detail" transition:slide>
<label class="checkbox-label">
<input type="checkbox" checked={state.game.completed_crew_1} disabled>
<span>Steal a Ship</span>
@@ -76,6 +69,10 @@
<span>Commit Piracy</span>
</label>
</div>
</div>
{/if}
</div>
</div>
{#if openTarget}
<CharacterSheet {state} target={openTarget} on:close={() => openTargetId = null} />
{/if}