Simple tasks: Tooltips and UI Layout

Completed tasks: 'PvP challenge tooltips', 'Secret technique tooltips', and 'Character sheet layout' from TODO.md
This commit is contained in:
2026-06-15 11:01:16 -07:00
parent e1779292e5
commit b18bf683a9
7 changed files with 82 additions and 31 deletions

View File

@@ -123,6 +123,24 @@
/* --- Character sheet layout --- */
.character-sheet-details {
text-align: left;
display: flex;
flex-direction: column;
}
@media (min-width: 768px) {
.character-sheet-details {
flex-direction: row;
align-items: flex-start;
gap: 1.5rem;
}
.sheet-main-column {
flex: 2;
min-width: 0;
}
.sheet-side-column {
flex: 1;
min-width: 0;
}
}
.techniques-list-sheet {

View File

@@ -48,7 +48,7 @@
</div>
{#if techName}
<div class="card-tech-overlay">
<div class="tech-tag" use:tooltip={"Automatic success when played"}>{cardValue(card)}: "{techName}"</div>
<div class="tech-tag" use:tooltip={`Automatic success when played\n\n"${techName}"`}>{cardValue(card)}: "{techName}"</div>
</div>
{/if}
<div class="card-corner bottom-right">

View File

@@ -123,7 +123,7 @@
{/if}
{#if ch.challenge_type === 'pvp'}
<p class="info-text" style="margin: 0.5rem 0 0 0;">
Temporary Obstacle: <strong use:tooltip={{ html: cardTooltipHtml(ch.temp_card, $obstacleTable) }}>{getCardDisplay(ch.temp_card)}</strong>{playerName(ch.acting_player_id)} must answer it!
Temporary Obstacle: <strong use:tooltip={{ html: cardTooltipHtml(ch.temp_card, $obstacleTable, true) }}>{getCardDisplay(ch.temp_card)}</strong>{playerName(ch.acting_player_id)} must answer it!
</p>
{:else}
<div class="challenge-obstacles">
@@ -173,7 +173,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" use:tooltip={{ html: cardTooltipHtml(card, $obstacleTable) }} on:click={() => pvpDefend(ch.id, card)}>{getCardDisplay(card)}</button>
<button class="btn btn-secondary" use:tooltip={{ html: cardTooltipHtml(card, $obstacleTable, true) }} on:click={() => pvpDefend(ch.id, card)}>{getCardDisplay(card)}</button>
{/each}
</div>
</div>

View File

@@ -1,7 +1,7 @@
<script>
import { createEventDispatcher } from 'svelte';
import { apiRequest } from '../../lib/api';
import { getCardDisplay, isJoker, displayName, crewLabel, captainName, cardObstacleInfo, obstacleTable } from '../../lib/cards';
import { getCardDisplay, isJoker, displayName, crewLabel, captainName, cardObstacleInfo, pvpObstacleInfo, obstacleTable } from '../../lib/cards';
export let state;
export let target; // the player whose sheet is being viewed
@@ -26,7 +26,7 @@
&& viewer.role === 'pirat' && !viewer.is_dead && !viewer.needs_reroll
&& target.role === 'pirat' && !target.is_dead;
// What the chosen duel card becomes as a temporary Obstacle for the defender
$: pvpCardObstacle = cardObstacleInfo(pvpCard, $obstacleTable);
$: pvpCardObstacle = pvpObstacleInfo(pvpCard);
function getPlayerName(id) {
if (!id) return 'a crewmate';
@@ -86,6 +86,7 @@
{/if}
<div class="character-sheet-details">
<div class="sheet-main-column">
{#if error}
<div class="alert alert-danger">{error}</div>
{/if}
@@ -159,7 +160,7 @@
<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} title={cardObstacleInfo(card, $obstacleTable) ? `${cardObstacleInfo(card, $obstacleTable).title} ${cardObstacleInfo(card, $obstacleTable).description}` : ''}>{getCardDisplay(card)}</option>
<option value={card} title={pvpObstacleInfo(card) ? `${pvpObstacleInfo(card).title} ${pvpObstacleInfo(card).description}` : ''}>{getCardDisplay(card)}</option>
{/each}
</select>
{#if pvpCardObstacle}
@@ -183,27 +184,30 @@
{/if}
</div>
{/if}
</div> <!-- end sheet-main-column -->
<div class="sheet-group margin-top">
<h4>Personal Objectives</h4>
{#if canManageObjectives}
<p class="info-text" style="font-size: 0.8rem;">As the Deep, tick these off in sequence (1 → 2 → 3).</p>
{/if}
<div class="objectives-checklist">
<label class="checkbox-label">
<input type="checkbox" checked={target.completed_personal_1} disabled={!canManageObjectives} on:change={() => toggleObjective('personal_1')}>
<span>1. Gat</span>
</label>
<label class="checkbox-label">
<input type="checkbox" checked={target.completed_personal_2} disabled={!canManageObjectives} on:change={() => toggleObjective('personal_2')}>
<span>2. Name</span>
</label>
<label class="checkbox-label">
<input type="checkbox" checked={target.completed_personal_3} disabled={!canManageObjectives} on:change={() => toggleObjective('personal_3')}>
<span>3. Die/Retire</span>
</label>
<div class="sheet-side-column">
<div class="sheet-group">
<h4>Personal Objectives</h4>
{#if canManageObjectives}
<p class="info-text" style="font-size: 0.8rem;">As the Deep, tick these off in sequence (1 → 2 → 3).</p>
{/if}
<div class="objectives-checklist">
<label class="checkbox-label">
<input type="checkbox" checked={target.completed_personal_1} disabled={!canManageObjectives} on:change={() => toggleObjective('personal_1')}>
<span>1. Gat</span>
</label>
<label class="checkbox-label">
<input type="checkbox" checked={target.completed_personal_2} disabled={!canManageObjectives} on:change={() => toggleObjective('personal_2')}>
<span>2. Name</span>
</label>
<label class="checkbox-label">
<input type="checkbox" checked={target.completed_personal_3} disabled={!canManageObjectives} on:change={() => toggleObjective('personal_3')}>
<span>3. Die/Retire</span>
</label>
</div>
</div>
</div>
</div> <!-- end sheet-side-column -->
</div>
</div>
</div>

View File

@@ -69,7 +69,7 @@ function esc(s) {
// it means when it surfaces as an Obstacle. Built only from static rulebook
// data (SUIT_THEMES + the obstacle table), so the HTML is trusted. Feed the
// result to the `tooltip` action as `{ html: cardTooltipHtml(...) }`.
export function cardTooltipHtml(code, table = OBSTACLE_TABLE) {
export function cardTooltipHtml(code, table = OBSTACLE_TABLE, isPvp = false) {
if (!code) return '';
if (isJoker(code)) {
return '<div class="tt-head"><span class="tt-card tt-joker">🃏 Joker</span></div>'
@@ -83,7 +83,14 @@ export function cardTooltipHtml(code, table = OBSTACLE_TABLE) {
let html = `<div class="tt-head"><span class="tt-card ${colorClass}">${esc(val)}${SUIT_EMOJI[suit] || esc(suit)}</span>`
+ `<span class="tt-color tt-color-${isRed ? 'red' : 'black'}">${isRed ? 'Red' : 'Black'}</span></div>`;
html += `<div class="tt-theme">${esc(SUIT_THEMES[suit] || '')}</div>`;
if (FACE_VALUES.includes(val)) {
if (isPvp) {
const themeStr = SUIT_THEMES[suit] || '';
const splitIdx = themeStr.indexOf(': ');
const title = splitIdx !== -1 ? themeStr.substring(0, splitIdx) : themeStr;
const desc = splitIdx !== -1 ? themeStr.substring(splitIdx + 2) : '';
html += `<div class="tt-row"><span class="tt-label">PvP Obstacle</span><b>${esc(title)}</b> — ${esc(desc)}</div>`;
} else if (FACE_VALUES.includes(val)) {
html += '<div class="tt-row"><span class="tt-label">Played by a Pi-Rat</span>Triggers a Secret Technique — automatic success.</div>';
html += "<div class=\"tt-row\"><span class=\"tt-label\">As an Obstacle</span>Its value equals the challenged Pi-Rat's Rank.</div>";
} else {
@@ -95,6 +102,19 @@ export function cardTooltipHtml(code, table = OBSTACLE_TABLE) {
return html;
}
export function pvpObstacleInfo(code) {
if (!code || isJoker(code)) return null;
const suit = cardSuit(code);
const themeStr = SUIT_THEMES[suit];
if (!themeStr) return null;
const splitIdx = themeStr.indexOf(': ');
if (splitIdx === -1) return { title: themeStr, description: '' };
return {
title: themeStr.substring(0, splitIdx),
description: themeStr.substring(splitIdx + 2)
};
}
export function isJoker(code) {
return !!code && code.startsWith('Joker');
}

View File

@@ -6,10 +6,19 @@
// - 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 = 5;
export const VERSION = 6;
// Newest first. Each entry: { version, date: 'YYYY-MM-DD', changes: [string, ...] }.
export const CHANGELOG = [
{
version: 6,
date: '2026-06-15',
changes: [
'PvP challenge cards now display the general suit theme instead of specific obstacles in tooltips.',
'Secret Technique tooltips now show the full technique description when played.',
'Personal Objectives on the Character Sheet are now positioned in a side column on wider screens.'
],
},
{
version: 5,
date: '2026-06-15',