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

@@ -2,9 +2,9 @@
- [x] **Event log items**. Completing a crew objective should go in the event log
- [x] **Personal objectives for pi-rats** Mousing over a personal objective as a pi-rat should not make the cursor change, as it's a non-interactive UI element for them
- [ ] **PvP challenge tooltips** Update the PvP challenge card selection dropdown tooltips so that they describe the general class of obstacles for the suit rather than the specific challenge for that card. This also goes for the temporary obstacles created by PvP challenges.
- [ ] **Secret technique tooltips** The tooltip for a played secret technique should include the full secret technique description, since it gets truncated in the main display.
- [ ] **Character sheet layout** Personal objectives should be on a second column to the right, on screen layouts wide enough to accommodate it
- [x] **PvP challenge tooltips** Update the PvP challenge card selection dropdown tooltips so that they describe the general class of obstacles for the suit rather than the specific challenge for that card. This also goes for the temporary obstacles created by PvP challenges.
- [x] **Secret technique tooltips** The tooltip for a played secret technique should include the full secret technique description, since it gets truncated in the main display.
- [x] **Character sheet layout** Personal objectives should be on a second column to the right, on screen layouts wide enough to accommodate it
## DevOps/Maintenance

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,8 +184,10 @@
{/if}
</div>
{/if}
</div> <!-- end sheet-main-column -->
<div class="sheet-group margin-top">
<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>
@@ -204,6 +207,7 @@
</label>
</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',