More detailed card tooltips
Card tooltips now show what a card represents *as* an Obstacle (the rulebook obstacle table entry) in addition to its suit theme, which is relevant when challenging another Pi-Rat. The obstacle tables are served from the backend via GET /api/obstacles (single source of truth in cards.py) and cached client-side in cards.js, with a retry so a transient first-load failure doesn't silently disable the tooltips. Enriched everywhere cards appear in the challenge flow: hand/board cards (Card.svelte), PvP defend buttons and the temporary-obstacle line (ChallengePanel), and the duel card selector, which also shows an inline description of what the chosen card becomes as an Obstacle. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
<script>
|
||||
import { apiRequest } from '../../lib/api';
|
||||
import { getCardDisplay, isJoker, displayName, playerName as lookupName } from '../../lib/cards';
|
||||
import { getCardDisplay, isJoker, displayName, playerName as lookupName, cardTooltip, obstacleTable } from '../../lib/cards';
|
||||
|
||||
export let state;
|
||||
|
||||
@@ -83,7 +83,7 @@
|
||||
{/if}
|
||||
{#if ch.challenge_type === 'pvp'}
|
||||
<p class="info-text" style="margin: 0.5rem 0 0 0;">
|
||||
Temporary Obstacle: <strong>{getCardDisplay(ch.temp_card)}</strong> — {playerName(ch.acting_player_id)} must answer it!
|
||||
Temporary Obstacle: <strong title={cardTooltip(ch.temp_card, $obstacleTable)}>{getCardDisplay(ch.temp_card)}</strong> — {playerName(ch.acting_player_id)} must answer it!
|
||||
</p>
|
||||
{:else}
|
||||
<p class="info-text" style="margin: 0.5rem 0 0 0;">
|
||||
@@ -134,7 +134,7 @@
|
||||
<p style="margin: 0 0 0.5rem 0;"><strong>Defend yourself!</strong> Pick a card:</p>
|
||||
<div class="challenge-actions">
|
||||
{#each hand.filter(c => !isJoker(c)) as card}
|
||||
<button class="btn btn-secondary" on:click={() => pvpDefend(ch.id, card)}>{getCardDisplay(card)}</button>
|
||||
<button class="btn btn-secondary" title={cardTooltip(card, $obstacleTable)} on:click={() => pvpDefend(ch.id, card)}>{getCardDisplay(card)}</button>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script>
|
||||
import { apiRequest } from '../../lib/api';
|
||||
import { getCardDisplay, isJoker, displayName } from '../../lib/cards';
|
||||
import { getCardDisplay, isJoker, displayName, cardObstacleInfo, obstacleTable } from '../../lib/cards';
|
||||
|
||||
export let state;
|
||||
|
||||
@@ -12,6 +12,8 @@
|
||||
$: hand = state.player.hand_cards ? JSON.parse(state.player.hand_cards) : [];
|
||||
$: scenePirats = state.players.filter(p => p.role === 'pirat');
|
||||
$: duelTargets = scenePirats.filter(p => p.id !== state.player.id && !p.is_dead);
|
||||
// What the chosen duel card becomes as a temporary Obstacle for the defender
|
||||
$: pvpCardObstacle = cardObstacleInfo(pvpCard, $obstacleTable);
|
||||
|
||||
async function submitNewName() {
|
||||
if (!newNameInput.trim()) return;
|
||||
@@ -122,9 +124,14 @@
|
||||
<select class="select-field" bind:value={pvpCard} style="width: 100%; margin-bottom: 0.5rem;">
|
||||
<option value="">Throw which card?</option>
|
||||
{#each hand.filter(c => !isJoker(c)) as card}
|
||||
<option value={card}>{getCardDisplay(card)}</option>
|
||||
<option value={card} title={cardObstacleInfo(card, $obstacleTable) ? `${cardObstacleInfo(card, $obstacleTable).title} — ${cardObstacleInfo(card, $obstacleTable).description}` : ''}>{getCardDisplay(card)}</option>
|
||||
{/each}
|
||||
</select>
|
||||
{#if pvpCardObstacle}
|
||||
<p class="info-text" style="font-size: 0.8rem; margin: -0.25rem 0 0.5rem 0;">
|
||||
As an Obstacle, {getCardDisplay(pvpCard)} is <strong>{pvpCardObstacle.title}</strong>: {pvpCardObstacle.description}
|
||||
</p>
|
||||
{/if}
|
||||
<button class="btn btn-secondary btn-full" on:click={createPvp} disabled={!pvpOpponentId || !pvpCard}>Throw Down!</button>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
Reference in New Issue
Block a user