Dead Pi-Rat reroll rework

This commit is contained in:
2026-06-12 11:32:15 -07:00
parent df7a0cc83f
commit f61201144f
18 changed files with 908 additions and 460 deletions

View File

@@ -3,6 +3,7 @@
import { apiRequest } from '../lib/api';
import { getSuggestion, getTechniqueSuggestion, getTechniqueSuggestions } from '../lib/suggestions';
import { displayName } from '../lib/cards';
import FaceCardAssigner from './FaceCardAssigner.svelte';
export let state;
@@ -352,130 +353,7 @@
<div class="alert alert-danger">{faceError}</div>
{/if}
<div class="available-techniques-container">
<h4>Available Swapped Techniques</h4>
<p class="instruction-help">Drag a technique onto a card slot below to assign it.</p>
<div class="techniques-drag-pool">
{#each swappedTechniques as tech}
<div
draggable={tech !== jackTech && tech !== queenTech && tech !== kingTech}
on:dragstart={(e) => e.dataTransfer.setData('text/plain', tech)}
class="draggable-tech-chip {tech === jackTech || tech === queenTech || tech === kingTech ? 'assigned-hidden' : ''}"
>
<span class="drag-icon">⋮⋮</span> {tech}
</div>
{/each}
</div>
</div>
<!-- Visual Card Slots Grid -->
<div class="face-cards-slots-grid">
<!-- JACK SLOT -->
<div class="face-card-slot-wrapper">
<div class="face-card-slot {jackTech ? 'has-assignment' : ''}"
on:dragover|preventDefault
on:drop={(e) => {
const tech = e.dataTransfer.getData('text/plain');
if (tech) {
if (queenTech === tech) queenTech = '';
if (kingTech === tech) kingTech = '';
jackTech = tech;
}
}}>
<div class="card-bg-letter">J</div>
<div class="card-slot-header">
<span class="rank">J</span>
</div>
<div class="card-slot-body">
<div class="card-title">Jack</div>
<div class="drop-zone-placeholder">Drop Technique Here</div>
<div class="assigned-tech-content">
{#if jackTech}
<div class="assigned-tech-chip">
{jackTech}
<span class="remove-tech-btn" on:click={() => jackTech = ''}>×</span>
</div>
{/if}
</div>
</div>
<div class="card-slot-footer">
<span></span>
<span class="rank">J</span>
</div>
</div>
</div>
<!-- QUEEN SLOT -->
<div class="face-card-slot-wrapper">
<div class="face-card-slot {queenTech ? 'has-assignment' : ''}"
on:dragover|preventDefault
on:drop={(e) => {
const tech = e.dataTransfer.getData('text/plain');
if (tech) {
if (jackTech === tech) jackTech = '';
if (kingTech === tech) kingTech = '';
queenTech = tech;
}
}}>
<div class="card-bg-letter">Q</div>
<div class="card-slot-header">
<span class="rank">Q</span>
</div>
<div class="card-slot-body">
<div class="card-title">Queen</div>
<div class="drop-zone-placeholder">Drop Technique Here</div>
<div class="assigned-tech-content">
{#if queenTech}
<div class="assigned-tech-chip">
{queenTech}
<span class="remove-tech-btn" on:click={() => queenTech = ''}>×</span>
</div>
{/if}
</div>
</div>
<div class="card-slot-footer">
<span></span>
<span class="rank">Q</span>
</div>
</div>
</div>
<!-- KING SLOT -->
<div class="face-card-slot-wrapper">
<div class="face-card-slot {kingTech ? 'has-assignment' : ''}"
on:dragover|preventDefault
on:drop={(e) => {
const tech = e.dataTransfer.getData('text/plain');
if (tech) {
if (jackTech === tech) jackTech = '';
if (queenTech === tech) queenTech = '';
kingTech = tech;
}
}}>
<div class="card-bg-letter">K</div>
<div class="card-slot-header">
<span class="rank">K</span>
</div>
<div class="card-slot-body">
<div class="card-title">King</div>
<div class="drop-zone-placeholder">Drop Technique Here</div>
<div class="assigned-tech-content">
{#if kingTech}
<div class="assigned-tech-chip">
{kingTech}
<span class="remove-tech-btn" on:click={() => kingTech = ''}>×</span>
</div>
{/if}
</div>
</div>
<div class="card-slot-footer">
<span></span>
<span class="rank">K</span>
</div>
</div>
</div>
</div>
<FaceCardAssigner techniques={swappedTechniques} bind:jack={jackTech} bind:queen={queenTech} bind:king={kingTech} />
<div class="mt-6">
<button

View File

@@ -0,0 +1,79 @@
<script>
// Drag-and-drop assignment of 3 techniques onto the J/Q/K face cards.
// Used during initial character creation and between-scenes recruit creation.
export let techniques = [];
export let jack = '';
export let queen = '';
export let king = '';
const slots = [
{ key: 'jack', letter: 'J', title: 'Jack' },
{ key: 'queen', letter: 'Q', title: 'Queen' },
{ key: 'king', letter: 'K', title: 'King' },
];
$: values = { jack, queen, king };
$: assigned = [jack, queen, king];
function set(key, val) {
if (key === 'jack') jack = val;
else if (key === 'queen') queen = val;
else king = val;
}
function handleDrop(e, key) {
const tech = e.dataTransfer.getData('text/plain');
if (!tech) return;
for (const s of slots) {
if (s.key !== key && values[s.key] === tech) set(s.key, '');
}
set(key, tech);
}
</script>
<div class="available-techniques-container">
<h4>Available Techniques</h4>
<p class="instruction-help">Drag a technique onto a card slot below to assign it.</p>
<div class="techniques-drag-pool">
{#each techniques as tech}
<div
draggable={!assigned.includes(tech)}
on:dragstart={(e) => e.dataTransfer.setData('text/plain', tech)}
class="draggable-tech-chip {assigned.includes(tech) ? 'assigned-hidden' : ''}"
>
<span class="drag-icon">⋮⋮</span> {tech}
</div>
{/each}
</div>
</div>
<div class="face-cards-slots-grid">
{#each slots as s}
<div class="face-card-slot-wrapper">
<div class="face-card-slot {values[s.key] ? 'has-assignment' : ''}"
on:dragover|preventDefault
on:drop={(e) => handleDrop(e, s.key)}>
<div class="card-bg-letter">{s.letter}</div>
<div class="card-slot-header">
<span class="rank">{s.letter}</span>
</div>
<div class="card-slot-body">
<div class="card-title">{s.title}</div>
<div class="drop-zone-placeholder">Drop Technique Here</div>
<div class="assigned-tech-content">
{#if values[s.key]}
<div class="assigned-tech-chip">
{values[s.key]}
<span class="remove-tech-btn" on:click={() => set(s.key, '')}>×</span>
</div>
{/if}
</div>
</div>
<div class="card-slot-footer">
<span></span>
<span class="rank">{s.letter}</span>
</div>
</div>
</div>
{/each}
</div>

View File

@@ -0,0 +1,309 @@
<script>
import { apiRequest } from '../lib/api';
import { getSuggestion, getTechniqueSuggestion } from '../lib/suggestions';
import { displayName } from '../lib/cards';
import FaceCardAssigner from './FaceCardAssigner.svelte';
export let state;
function getPlayerName(id) {
if (!id) return 'None';
const p = state.players.find(x => x.id === id);
return p ? displayName(p) : 'Unknown';
}
function incomingTechs(p) {
return p.incoming_techniques ? JSON.parse(p.incoming_techniques) : [];
}
$: me = state.player;
$: recruits = state.players.filter(p => p.needs_reroll);
$: iAmRecruit = me.needs_reroll;
// --- My recruit sheet (only if I'm a pending recruit) ---
let basicDetails = {
avatar_look: state.player.avatar_look || '',
avatar_smell: state.player.avatar_smell || '',
first_words: state.player.first_words || '',
good_at_math: state.player.good_at_math || ''
};
let savingBasic = false;
async function saveBasicDetails() {
savingBasic = true;
try {
await apiRequest(`/game/${state.game.id}/player/${me.id}/save-basic`, 'POST', basicDetails);
} catch(e) {
console.error(e);
} finally {
savingBasic = false;
}
}
$: basicComplete = !!(me.avatar_look && me.avatar_smell && me.first_words && me.good_at_math);
$: likeDone = !me.other_like_from_player_id || !!me.other_like;
$: hateDone = !me.other_hate_from_player_id || !!me.other_hate;
$: myTechs = incomingTechs(me);
$: myTechTexts = myTechs.map(s => s.text);
$: techsDone = myTechTexts.length === 3 && myTechTexts.every(t => t);
// J/Q/K assignment + finalize
let jackTech = '', queenTech = '', kingTech = '';
let finalizing = false;
let finalizeError = '';
async function finalizeRecruit() {
finalizing = true;
finalizeError = '';
try {
await apiRequest(`/game/${state.game.id}/player/${me.id}/finalize-recruit`, 'POST', {
jack: jackTech, queen: queenTech, king: kingTech
});
} catch(e) {
finalizeError = e.message;
} finally {
finalizing = false;
}
}
// --- Helping other recruits ---
// Like/Hate answers (same endpoint as character creation)
let inboxAnswers = {};
async function submitDelegatedAnswer(type, targetId) {
const answer = (inboxAnswers[`${targetId}:${type}`] || '').trim();
if (!answer) return;
try {
await apiRequest(`/game/${state.game.id}/player/${me.id}/submit-delegate/${targetId}/${type}`, 'POST', { answer });
delete inboxAnswers[`${targetId}:${type}`];
inboxAnswers = inboxAnswers;
} catch(e) {
console.error(e);
}
}
// Technique contributions: one draft per (recruit, slot)
let techDrafts = {};
let techErrors = {};
async function contributeTechnique(recruitId, slot) {
const text = (techDrafts[`${recruitId}:${slot}`] || '').trim();
if (!text) return;
try {
await apiRequest(`/game/${state.game.id}/player/${me.id}/contribute-technique`, 'POST', {
recruit_id: recruitId, slot, text
});
delete techDrafts[`${recruitId}:${slot}`];
techDrafts = techDrafts;
delete techErrors[`${recruitId}:${slot}`];
techErrors = techErrors;
} catch(e) {
techErrors[`${recruitId}:${slot}`] = e.message;
techErrors = techErrors;
}
}
async function suggestTechnique(recruitId, slot) {
techDrafts[`${recruitId}:${slot}`] = await getTechniqueSuggestion(Object.values(techDrafts));
techDrafts = techDrafts;
}
// My outstanding tasks for other recruits
$: myTasks = recruits.filter(p => p.id !== me.id).map(p => ({
recruit: p,
like: p.other_like_from_player_id === me.id && !p.other_like,
hate: p.other_hate_from_player_id === me.id && !p.other_hate,
techSlots: incomingTechs(p).map((s, i) => ({ ...s, slot: i })).filter(s => s.from_id === me.id)
})).filter(t => t.like || t.hate || t.techSlots.length > 0);
function recruitProgress(p) {
const techs = incomingTechs(p);
const parts = [];
parts.push(p.avatar_look && p.avatar_smell && p.first_words && p.good_at_math ? '✅ Records' : '❌ Records');
const likeOk = !p.other_like_from_player_id || p.other_like;
const hateOk = !p.other_hate_from_player_id || p.other_hate;
parts.push(likeOk && hateOk ? '✅ Like/Hate' : '❌ Like/Hate');
parts.push(`${techs.filter(s => s.text).length}/3 Techniques`);
return parts.join(' · ');
}
</script>
<div class="recruit-phase-view">
<div class="view-header text-center">
<h2>🐀 New Recruits</h2>
<p>Fresh rats are washing aboard! Recruits fill in their Rat Records while the crew answers their Like/Hate questions and writes their Secret Techniques.</p>
</div>
<div class="creation-grid max-w-5xl mx-auto">
{#if iAmRecruit}
<!-- MY NEW RECRUIT SHEET -->
<div class="card glass-panel section-basic">
<h3>I. Your New Rat Records {basicComplete ? '✅' : '❌'}</h3>
{#if basicComplete}
<div class="read-only-sheet">
<div class="record-line"><strong>Look:</strong> {me.avatar_look}</div>
<div class="record-line"><strong>Smell:</strong> {me.avatar_smell}</div>
<div class="record-line"><strong>First Words:</strong> "{me.first_words}"</div>
<div class="record-line"><strong>Good at Math?</strong> {me.good_at_math}</div>
</div>
{:else}
<form on:submit|preventDefault={saveBasicDetails} class="creation-form">
<div class="form-group">
<label for="avatar_look">What does your Pi-Rat look like?</label>
<div class="input-row">
<input type="text" id="avatar_look" placeholder="e.g. A blue bandana, scarred snout" bind:value={basicDetails.avatar_look} required class="input-field">
<button type="button" class="btn btn-secondary btn-small" on:click={() => basicDetails.avatar_look = getSuggestion('look')}>Suggest</button>
</div>
</div>
<div class="form-group">
<label for="avatar_smell">What does your Pi-Rat smell like? (Determines name)</label>
<div class="input-row">
<input type="text" id="avatar_smell" placeholder="e.g. Damp gunpowder, salty cheese" bind:value={basicDetails.avatar_smell} required class="input-field">
<button type="button" class="btn btn-secondary btn-small" on:click={() => basicDetails.avatar_smell = getSuggestion('smell')}>Suggest</button>
</div>
</div>
<div class="form-group">
<label for="first_words">What were your first words after transforming?</label>
<div class="input-row">
<input type="text" id="first_words" placeholder="e.g. Where is my gat?!" bind:value={basicDetails.first_words} required class="input-field">
<button type="button" class="btn btn-secondary btn-small" on:click={() => basicDetails.first_words = getSuggestion('first_words')}>Suggest</button>
</div>
</div>
<div class="form-group">
<label for="good_at_math">Is your Pi-Rat good at Math?</label>
<div class="input-row">
<input type="text" id="good_at_math" placeholder="e.g. Thinks Pi is a dessert" bind:value={basicDetails.good_at_math} required class="input-field">
<button type="button" class="btn btn-secondary btn-small" on:click={() => basicDetails.good_at_math = getSuggestion('good_at_math')}>Suggest</button>
</div>
</div>
<button type="submit" class="btn btn-primary btn-full" disabled={savingBasic}>Save Records</button>
</form>
{/if}
</div>
<div class="card glass-panel section-delegations">
<h3>II. Crew Delegations {likeDone && hateDone ? '✅' : '❌'}</h3>
<p class="section-desc">Crewmates decide what their rats like and hate about your new recruit.</p>
<div class="delegation-list">
<div class="delegation-box glass-panel">
<h4>What does another rat LIKE about you?</h4>
<div class="answer-box">
{me.other_like ? `"${me.other_like}"` : '(Waiting for answer...)'}
</div>
<div class="author-label"> {getPlayerName(me.other_like_from_player_id)}</div>
</div>
<div class="delegation-box glass-panel">
<h4>What does another rat HATE about you?</h4>
<div class="answer-box">
{me.other_hate ? `"${me.other_hate}"` : '(Waiting for answer...)'}
</div>
<div class="author-label"> {getPlayerName(me.other_hate_from_player_id)}</div>
</div>
</div>
</div>
<div class="card glass-panel section-techniques" style="grid-column: span 2;">
<h3>III. Your New Secret Techniques {techsDone ? '✅' : '❌'}</h3>
{#if !techsDone}
<p class="section-desc">Your crewmates are writing 3 fresh techniques for you:</p>
<div class="submitted-techniques text-center mt-4">
{#each myTechs as slot, i}
<div class="tech-chip">#{i + 1}: {slot.text ? slot.text : `Waiting for ${getPlayerName(slot.from_id)}...`}</div>
{/each}
</div>
{:else}
<div class="max-w-4xl mx-auto space-y-6">
<p>Drag and drop your new techniques onto your Face Cards:</p>
{#if finalizeError}
<div class="alert alert-danger">{finalizeError}</div>
{/if}
<FaceCardAssigner techniques={myTechTexts} bind:jack={jackTech} bind:queen={queenTech} bind:king={kingTech} />
<div class="mt-6">
<button
class="btn btn-primary btn-large w-full glow-effect"
disabled={finalizing || !basicComplete || !likeDone || !hateDone || !jackTech || !queenTech || !kingTech}
on:click={finalizeRecruit}
>
{finalizing ? 'Joining the Crew...' : '🐀 Join the Crew!'}
</button>
{#if !basicComplete || !likeDone || !hateDone}
<p class="info-text text-center" style="margin-top: 0.5rem;">Finish your Rat Records and wait for your Like/Hate answers before joining.</p>
{/if}
</div>
</div>
{/if}
</div>
{/if}
<!-- TASKS FOR OTHER RECRUITS -->
<div class="card glass-panel section-inbox" style="grid-column: span 2;">
<h3>{iAmRecruit ? 'IV.' : 'I.'} Your Inbox (Help the Recruits)</h3>
<p class="section-desc">Answer questions and write Secret Techniques for the fresh recruits. Be creative and have fun with it!</p>
<div class="inbox-list">
{#each myTasks as task (task.recruit.id)}
{#if task.like}
<div class="inbox-item">
<label>What do you LIKE about {displayName(task.recruit)}?</label>
<div class="input-row">
<input type="text" class="input-field" bind:value={inboxAnswers[`${task.recruit.id}:like`]} placeholder="e.g. They always share their cheese"/>
<button type="button" class="btn btn-secondary btn-small" on:click={() => inboxAnswers[`${task.recruit.id}:like`] = getSuggestion('like')}>Suggest</button>
<button class="btn btn-primary" disabled={!(inboxAnswers[`${task.recruit.id}:like`] || '').trim()} on:click={() => submitDelegatedAnswer('like', task.recruit.id)}>Submit</button>
</div>
</div>
{/if}
{#if task.hate}
<div class="inbox-item">
<label>What do you HATE about {displayName(task.recruit)}?</label>
<div class="input-row">
<input type="text" class="input-field" bind:value={inboxAnswers[`${task.recruit.id}:hate`]} placeholder="e.g. They snore too loudly"/>
<button type="button" class="btn btn-secondary btn-small" on:click={() => inboxAnswers[`${task.recruit.id}:hate`] = getSuggestion('hate')}>Suggest</button>
<button class="btn btn-primary" disabled={!(inboxAnswers[`${task.recruit.id}:hate`] || '').trim()} on:click={() => submitDelegatedAnswer('hate', task.recruit.id)}>Submit</button>
</div>
</div>
{/if}
{#each task.techSlots as slot}
<div class="inbox-item">
<label>
Write a Secret Technique for {displayName(task.recruit)}
{#if slot.text}<span class="text-success">(submitted: "{slot.text}" — you can revise it)</span>{/if}
</label>
{#if techErrors[`${task.recruit.id}:${slot.slot}`]}
<div class="alert alert-danger">{techErrors[`${task.recruit.id}:${slot.slot}`]}</div>
{/if}
<div class="input-row">
<input type="text" class="input-field" bind:value={techDrafts[`${task.recruit.id}:${slot.slot}`]} placeholder="e.g. Parabolic boarding bounce"/>
<button type="button" class="btn btn-secondary btn-small" on:click={() => suggestTechnique(task.recruit.id, slot.slot)}>Suggest</button>
<button class="btn btn-primary" disabled={!(techDrafts[`${task.recruit.id}:${slot.slot}`] || '').trim()} on:click={() => contributeTechnique(task.recruit.id, slot.slot)}>Submit</button>
</div>
</div>
{/each}
{/each}
{#if myTasks.length === 0}
<p class="inbox-empty-message text-muted italic">
{#if iAmRecruit}
No tasks your crewmates are hard at work on your new rat.
{:else}
Nothing left to write. Waiting for the recruits to finish their sheets...
{/if}
</p>
{/if}
</div>
</div>
<!-- RECRUIT PROGRESS -->
<div class="card glass-panel section-crew-status" style="grid-column: span 2;">
<h3>{iAmRecruit ? 'V.' : 'II.'} Recruit Progress</h3>
<div class="crew-status-list">
<ul class="space-y-2 mt-4">
{#each recruits as p}
<li class="status-row">
<span>{displayName(p)}</span>
<span class="text-sm text-muted">{recruitProgress(p)}</span>
</li>
{/each}
{#if recruits.length === 0}
<li class="status-row"><span class="text-success">All recruits are aboard! Heading to the next scene...</span></li>
{/if}
</ul>
</div>
</div>
</div>
</div>

View File

@@ -41,7 +41,7 @@
}
}
$: allRolesAssigned = state.players.every(p => p.role !== null);
$: allRolesAssigned = state.players.every(p => p.role !== null || p.needs_reroll);
$: deepPlayer = state.players.find(p => p.role === 'deep');
$: piratPlayer = state.players.find(p => p.role === 'pirat');
$: canStart = allRolesAssigned && deepPlayer && piratPlayer && (state.player.is_creator || state.player.role === 'deep');
@@ -67,7 +67,13 @@
<!-- Role Selection Card -->
<div class="card glass-panel role-selection-card">
<h3>Select Your Role</h3>
{#if state.player.needs_reroll}
<div class="notice-banner accent">
<p class="info-text" style="margin: 0;">🐀 You don't have a Pi-Rat yet! You'll spectate this scene and create your recruit with the crew between scenes.</p>
</div>
{/if}
<div class="role-buttons">
<button on:click={() => setRole('pirat')}
disabled={settingRole || !allowedRoles.includes('pirat')}
@@ -83,14 +89,14 @@
</div>
<div class="role-restrictions mt-4">
{#if !allowedRoles.includes('pirat')}
{#if !allowedRoles.includes('pirat') && !state.player.needs_reroll}
{#if state.game.current_scene_number === 1}
<p class="text-danger text-sm italic">You start at Rank 3, so you must play The Deep for the first scene!</p>
{:else}
<p class="text-danger text-sm italic">You are the only eligible player for The Deep this scene!</p>
{/if}
{/if}
{#if !allowedRoles.includes('deep')}
{#if !allowedRoles.includes('deep') && !state.player.needs_reroll}
{#if state.game.current_scene_number === 1}
<p class="text-danger text-sm italic">You start at Rank 1, so you must play your Pi-Rat for the first scene!</p>
{:else}

View File

@@ -1,6 +1,5 @@
<script>
import { apiRequest } from '../lib/api';
import { getSuggestion } from '../lib/suggestions';
import { displayName } from '../lib/cards';
import Card from './Card.svelte';
@@ -20,16 +19,7 @@
let discardCards = [];
let dragNDropInitialized = false;
// Ghost & Re-rolling fate states
let isRolling = false;
let reRollDetails = {
avatar_look: '',
avatar_smell: '',
first_words: '',
good_at_math: ''
};
let rollingError = '';
// Ghost & Re-rolling fate choice
async function becomeGhost() {
try {
await apiRequest(`/game/${state.game.id}/player/${state.player.id}/become-ghost`, 'POST');
@@ -38,6 +28,14 @@
}
}
async function chooseReRoll() {
try {
await apiRequest(`/game/${state.game.id}/player/${state.player.id}/choose-reroll`, 'POST');
} catch(e) {
console.error(e);
}
}
async function finishGame() {
if (!confirm('End the story for everyone? Best saved for when every player has played the Deep, each Pi-Rat has completed a Personal Objective, and all 3 Crew Objectives are done.')) return;
try {
@@ -47,30 +45,6 @@
}
}
async function submitReRoll() {
if (!reRollDetails.avatar_look.trim() ||
!reRollDetails.avatar_smell.trim() ||
!reRollDetails.first_words.trim() ||
!reRollDetails.good_at_math.trim()) {
rollingError = 'Please fill out all character details.';
return;
}
rollingError = '';
try {
await apiRequest(`/game/${state.game.id}/player/${state.player.id}/roll-new-character`, 'POST', reRollDetails);
isRolling = false;
// Clear inputs
reRollDetails = {
avatar_look: '',
avatar_smell: '',
first_words: '',
good_at_math: ''
};
} catch(e) {
rollingError = e.message;
}
}
$: {
if (state && state.game.phase === 'deep_upkeep') {
if (!dragNDropInitialized) {
@@ -182,74 +156,31 @@
<h2>Between Scenes (Scene #{state.game.current_scene_number} Concluded)</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>
{#if state.player.is_dead && !state.player.is_ghost}
{#if state.player.is_dead && !state.player.is_ghost && !state.player.needs_reroll}
<!-- Death Fate Panel -->
<div class="glass-panel death-fate-card">
<h2>💀 Your Pi-Rat Has Died!</h2>
<p class="description">
Your story arc is complete! You went out in a blaze of glory (or retired honorably). You must now choose how your journey continues:
</p>
{#if !isRolling}
<div class="fate-choices">
<button class="btn btn-secondary btn-large glow-effect" on:click={becomeGhost}>
👻 Remain as a Ghost
</button>
<button class="btn btn-primary btn-large glow-effect" on:click={() => isRolling = true}>
🐀 Roll a New Character
</button>
</div>
<p class="info-text italic" style="margin-top: 1.5rem;">
Ghosts look back into the living world, watch over friends, and help their crew. New recruits start fresh at Rank 1 with surprise Secret Techniques and a name based on their smell.
</p>
{:else}
<div class="creation-form-wrapper">
<h3>Create Your New Recruit</h3>
{#if rollingError}
<div class="alert alert-danger" style="margin-bottom: 1.5rem;">{rollingError}</div>
{/if}
<div class="form-group">
<label>What does your Pi-Rat look like?</label>
<div class="input-row">
<input type="text" placeholder="e.g. Scruffy snout, wearing a tattered vest" bind:value={reRollDetails.avatar_look} required class="input-field">
<button type="button" class="btn btn-secondary btn-small" on:click={() => reRollDetails.avatar_look = getSuggestion('look')}>Suggest</button>
</div>
</div>
<div class="form-group">
<label>What does your Pi-Rat smell like? (Determines name)</label>
<div class="input-row">
<input type="text" placeholder="e.g. Spiced rum, salty cheese, gunpowder" bind:value={reRollDetails.avatar_smell} required class="input-field">
<button type="button" class="btn btn-secondary btn-small" on:click={() => reRollDetails.avatar_smell = getSuggestion('smell')}>Suggest</button>
</div>
</div>
<div class="form-group">
<label>What were your first words after transforming?</label>
<div class="input-row">
<input type="text" placeholder="e.g. Shiver my decimals!" bind:value={reRollDetails.first_words} required class="input-field">
<button type="button" class="btn btn-secondary btn-small" on:click={() => reRollDetails.first_words = getSuggestion('first_words')}>Suggest</button>
</div>
</div>
<div class="form-group">
<label>Is your Pi-Rat good at Math?</label>
<div class="input-row">
<input type="text" placeholder="e.g. Thinks Pi is a dessert" bind:value={reRollDetails.good_at_math} required class="input-field">
<button type="button" class="btn btn-secondary btn-small" on:click={() => reRollDetails.good_at_math = getSuggestion('good_at_math')}>Suggest</button>
</div>
</div>
<div class="form-actions">
<button class="btn btn-secondary" on:click={() => isRolling = false}>Cancel</button>
<button class="btn btn-primary" on:click={submitReRoll}>Submit New Character</button>
</div>
</div>
{/if}
<div class="fate-choices">
<button class="btn btn-secondary btn-large glow-effect" on:click={becomeGhost}>
👻 Remain as a Ghost
</button>
<button class="btn btn-primary btn-large glow-effect" on:click={chooseReRoll}>
🐀 Roll a New Character
</button>
</div>
<p class="info-text italic" style="margin-top: 1.5rem;">
Ghosts look back into the living world, watch over friends, and help their crew. New recruits start fresh at Rank 1 — after upkeep, the whole crew will help create them.
</p>
</div>
{:else}
{#if state.player.needs_reroll}
<div class="notice-banner" style="margin-bottom: 1rem;">
<p style="margin: 0;">🐀 A fresh recruit is on the way! Once upkeep wraps up, you and the crew will create your new Pi-Rat together.</p>
</div>
{/if}
<div class="between-grid">
<!-- Ranks and Voting Card -->
<div class="card glass-panel voting-card">

View File

@@ -54,6 +54,13 @@
<div class="alert alert-danger">{error}</div>
{/if}
{#if state.player.needs_reroll}
<div class="prompt-box accent text-center">
<h4>🐀 Recruit Incoming!</h4>
<p class="info-text" style="margin-bottom: 0; font-size: 0.9rem;">You don't have a Pi-Rat in this scene. Sit back, enjoy the chaos, and you'll create your recruit with the crew between scenes.</p>
</div>
{/if}
{#if state.player.is_dead}
<div class="prompt-box danger text-center">
<h4>💀 You have Died / Retired!</h4>
@@ -102,7 +109,7 @@
</div>
<!-- Duel another Pi-Rat -->
{#if !state.player.is_dead && duelTargets.length > 0}
{#if !state.player.is_dead && !state.player.needs_reroll && duelTargets.length > 0}
<div class="sheet-group margin-top">
<h4>⚔️ Duel a Pi-Rat</h4>
<p class="info-text" style="font-size: 0.85rem;">Challenge a crewmate (e.g. for the Captaincy)! Your card becomes a temporary Obstacle they must beat. Both cards are discarded afterwards.</p>