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:
2026-06-14 09:46:02 -07:00
parent 7825ad2cca
commit 44bbb88836
9 changed files with 155 additions and 1 deletions

View 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>

View File

@@ -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}