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:
@@ -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 {
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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');
|
||||
}
|
||||
|
||||
@@ -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',
|
||||
|
||||
Reference in New Issue
Block a user