Add gat-description modal when a Pi-Rat earns a Gat
Mirrors the name modal: earning the Gat objective sets Player.needs_gat_description, and GatModal.svelte (rendered by Dashboard across all phases) prompts for a one-of-a-kind Gat description, stored in Player.gat_description and shown on the character sheet. Like a stolen Name, the Gat's description travels with it through Gat Tax transfers. - Player.gat_description / needs_gat_description (+ migration) - toggle_objective personal_1 sets/clears the prompt - crud_challenge gat-tax paths move the description with the Gat - set-gat-description route; CharacterSheet displays it Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
88
frontend/src/components/GatModal.svelte
Normal file
88
frontend/src/components/GatModal.svelte
Normal file
@@ -0,0 +1,88 @@
|
||||
<script>
|
||||
import { apiRequest } from '../lib/api';
|
||||
|
||||
export let state;
|
||||
|
||||
let descInput = '';
|
||||
let error = '';
|
||||
let submitting = false;
|
||||
|
||||
async function submitDescription() {
|
||||
if (!descInput.trim() || submitting) return;
|
||||
error = '';
|
||||
submitting = true;
|
||||
try {
|
||||
await apiRequest(`/game/${state.game.id}/player/${state.player.id}/set-gat-description`, 'POST', {
|
||||
description: descInput.trim()
|
||||
});
|
||||
descInput = '';
|
||||
} catch (e) {
|
||||
error = e.message;
|
||||
} finally {
|
||||
submitting = false;
|
||||
}
|
||||
}
|
||||
|
||||
function onKeydown(e) {
|
||||
if (e.key === 'Enter') submitDescription();
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if state.player.needs_gat_description}
|
||||
<div class="modal-backdrop">
|
||||
<div class="modal-box glass-panel">
|
||||
<h3>🔫 You Got a Gat!</h3>
|
||||
<p class="info-text">
|
||||
Every Pi-Rat's Gat is one of a kind. What does yours look like?
|
||||
</p>
|
||||
{#if error}
|
||||
<div class="alert alert-danger">{error}</div>
|
||||
{/if}
|
||||
<input
|
||||
type="text"
|
||||
bind:value={descInput}
|
||||
class="input-field"
|
||||
placeholder="e.g. A pearl-handled flintlock that smells of cheese"
|
||||
on:keydown={onKeydown}
|
||||
autofocus
|
||||
>
|
||||
<button
|
||||
class="btn btn-gold btn-full"
|
||||
on:click={submitDescription}
|
||||
disabled={!descInput.trim() || submitting}
|
||||
>
|
||||
Claim Gat
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<style>
|
||||
.modal-backdrop {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: 2000;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 20px;
|
||||
background: color-mix(in srgb, var(--bg-deep) 70%, transparent);
|
||||
backdrop-filter: blur(6px);
|
||||
}
|
||||
.modal-box {
|
||||
max-width: 440px;
|
||||
width: 100%;
|
||||
padding: 1.75rem;
|
||||
border: 1px solid var(--edge);
|
||||
border-radius: var(--radius-md);
|
||||
box-shadow: var(--shadow-deep);
|
||||
text-align: center;
|
||||
}
|
||||
.modal-box h3 {
|
||||
margin-top: 0;
|
||||
}
|
||||
.modal-box .input-field {
|
||||
width: 100%;
|
||||
margin: 0.75rem 0;
|
||||
}
|
||||
</style>
|
||||
@@ -80,6 +80,9 @@
|
||||
<p><strong>Look:</strong> {state.player.avatar_look}</p>
|
||||
<p><strong>Smell:</strong> {state.player.avatar_smell}</p>
|
||||
<p><strong>First Words:</strong> "{state.player.first_words}"</p>
|
||||
{#if state.player.gat_description}
|
||||
<p><strong>🔫 Gat:</strong> {state.player.gat_description}</p>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
{#if state.player.other_like || state.player.other_hate}
|
||||
|
||||
Reference in New Issue
Block a user