Advance after final rank vote and allow ballot changes

This commit is contained in:
2026-09-04 19:18:38 -07:00
parent 88f8781dce
commit 1837bf04e6
12 changed files with 132 additions and 68 deletions
+1 -1
View File
@@ -1,7 +1,7 @@
/* --- Between-scenes upkeep --- */
.between-grid {
display: grid;
grid-template-columns: 1fr 1fr;
grid-template-columns: minmax(0, 1fr);
gap: 2rem;
margin: 2rem 0;
}
+28 -39
View File
@@ -6,7 +6,7 @@
export let state;
let voting = false;
let readying = false;
let voteError = "";
let confirming = false;
let selectedVoteId = '';
@@ -134,28 +134,18 @@
async function submitVote() {
if (!selectedVoteId) return;
voting = true;
voteError = "";
try {
await apiRequest(`/game/${state.game.id}/player/${state.player.id}/submit-vote`, 'POST', {
nominated_id: selectedVoteId
});
} catch(e) {
console.error(e);
voteError = e.message;
} finally {
voting = false;
}
}
async function setReady() {
readying = true;
try {
await apiRequest(`/game/${state.game.id}/player/${state.player.id}/ready-next`, 'POST');
} catch(e) {
console.error(e);
} finally {
readying = false;
}
}
async function confirmRefresh() {
confirming = true;
try {
@@ -173,7 +163,9 @@
<div class="between-scenes-view text-center">
<h2>Between Scenes · Scene {state.game.current_scene_number}</h2>
<p class="description">Upkeep and tallying. Nominate a crewmate to Rank Up, redraw hand cards for resting Deep players, and ready up for the next scene.</p>
<p class="description">{state.game.phase === 'between_scenes'
? 'Vote for a crewmate to rank up. You can change your vote until everyone has voted.'
: 'Voting complete. The resting Deep refreshes their hand before the next scene.'}</p>
{#if state.player.is_dead && !state.player.is_ghost && !state.player.needs_reroll}
<!-- Death Fate Panel -->
@@ -204,7 +196,9 @@
<!-- Ranks and Voting Card -->
<div class="card glass-panel voting-card">
<h3>Rank Up</h3>
<p class="section-desc">Every player (including the Deep) nominates one crewmate who best exemplified pirate qualities in the previous scene. Previous Deep players are ineligible to receive votes.</p>
{#if state.game.phase === 'between_scenes'}
<p class="section-desc">Nominate another Pi-Rat who best showed their pirate spirit this scene. The Deep cannot receive votes.</p>
{/if}
{#if state.game.phase === 'deep_upkeep'}
<div class="voted-confirmation-box glass-panel">
@@ -214,26 +208,30 @@
<p class="success-text">✔️ Voting complete! No one ranked up this time.</p>
{/if}
</div>
{:else if hasVoted}
{:else}
{#if hasVoted}
<div class="voted-confirmation-box glass-panel">
<p class="success-text">✓ Your vote: <strong>{votedNominee ? displayName(votedNominee) : "Unavailable crewmate"}</strong></p>
</div>
{:else if !mustVote}
{/if}
{#if !mustVote}
<div class="voted-confirmation-box glass-panel">
<p class="info-text">No living crewmates left to nominate — you skip voting this round. Go ahead and ready up.</p>
<p class="info-text">No eligible crewmates — voting skipped.</p>
</div>
{:else}
<form on:submit|preventDefault={submitVote} class="vote-form inline-form">
<div class="form-group inline-group">
<select class="select-field" bind:value={selectedVoteId} required>
<select aria-label="Nominee" class="select-field" bind:value={selectedVoteId} required>
<option value="">Nominate crewmate...</option>
{#each myNominees as p}
<option value={p.id}>{displayName(p)} (Rank {p.rank})</option>
{/each}
</select>
<button type="submit" class="btn btn-primary" disabled={voting || !selectedVoteId}>Cast Nomination</button>
<button type="submit" class="btn btn-primary" disabled={voting || !selectedVoteId || selectedVoteId === myVote?.nominated_player_id}>{hasVoted ? "Change Vote" : "Vote"}</button>
</div>
</form>
{/if}
{#if voteError}<p class="alert alert-danger" role="alert">{voteError}</p>{/if}
{/if}
</div>
@@ -320,7 +318,7 @@
{/if}
</div>
<!-- Next Scene Ready Up -->
<!-- Upkeep progress and end-story action -->
<div class="action-box margin-top">
{#if state.game.phase === 'deep_upkeep'}
{#if state.player.role === 'deep' && !state.player.is_ready}
@@ -331,24 +329,6 @@
<div class="spinner-small"></div>
</div>
{/if}
{:else}
{#if mustVote && !hasVoted}
<p class="info-text gold-text" style="margin-bottom:0.5rem;">⚠️ You must nominate a crewmate above before you can ready up.</p>
<button class="btn btn-primary btn-large" disabled>Ready for Next Scene</button>
{:else}
{#if state.player.is_ready}
<div class="waiting-box">
<p>Ready! Waiting for other players to ready up...</p>
<div class="spinner-small"></div>
</div>
{:else}
<button on:click={setReady}
disabled={readying}
class="btn btn-primary btn-large glow-effect">
Ready for Next Scene
</button>
{/if}
{/if}
{/if}
{#if state.player.is_admin && state.game.phase === 'between_scenes'}
@@ -360,3 +340,12 @@
</div>
{/if}
</div>
<style>
.vote-form { margin-top: 1rem; }
.vote-form button { white-space: nowrap; flex-shrink: 0; }
.vote-form select { min-width: 0; }
@media (max-width: 600px) {
.vote-form .inline-group { flex-direction: column; align-items: stretch; }
}
</style>
+2 -1
View File
@@ -6,10 +6,11 @@
// - Add a CHANGELOG entry only when a commit changes something players can
// see. Skip refactors, tests, and tooling. Keep wording player-facing.
export const VERSION = 30;
export const VERSION = 31;
// Newest first. Each entry: { version, date: 'YYYY-MM-DD', changes: [string, ...] }.
export const CHANGELOG = [
{ version: 31, date: '2026-09-04', changes: ['Change your rank-up vote until everyone has voted. The final vote now reveals the result and advances automatically, without a Ready button.', 'The vote result stays visible through upkeep and next-scene setup.'] },
{ version: 30, date: '2026-09-04', changes: ['The crew roster shows voting progress with icons.', 'Your submitted vote now shows the crewmate you nominated.'] },
{ version: 29, date: '2026-09-04', changes: ['Removed the remaining deck count from the table.'] },
{ version: 28, date: '2026-09-04', changes: ['The Event Log toggle keeps the same size when opened or closed.', 'Dev Mode changes no longer clutter the Event Log.'] },
+9
View File
@@ -229,6 +229,15 @@
<div class="phase-view-layout" class:log-collapsed={!phaseLogOpen}>
<CrewSidebar {state} />
<main class="phase-main-column">
{#if ['scene_setup', 'recruit_creation'].includes(state.game.phase) && (state.game.last_rank_up_player_id || state.game.current_scene_number > 1)}
<div class="notice-banner" role="status">
{#if state.game.last_rank_up_player_id}
🏆 {displayName(state.players.find(p => p.id === state.game.last_rank_up_player_id))} won the rank-up vote!
{:else}
Voting complete. No one ranked up this time.
{/if}
</div>
{/if}
{#if state.game.phase === 'lobby'}
<LobbyPhase {state} />
{:else if state.game.phase === 'character_creation' || state.game.phase === 'swap_techniques' || state.game.phase === 'assign_techniques'}