Re-theme the app: token-based CSS and a full face lift

CSS refactor: every color, font, radius, and shadow is now defined once
in theme.css; all other styles derive translucent variants with
color-mix(), so re-theming means editing one file. Removed the
duplicate core.css/style.css import chains, dead styles, and the
Bootstrap/Tailwind orphan classes that were silently unstyled
(the main reason the splash page looked broken).

New "Lantern & Brine" look: brass and parchment on a lantern-lit
ink-navy night, Pirata One wordmark with Alegreya SC/Sans body fonts
(self-hosted via fontsource — they were previously referenced but
never loaded), parchment-faced playing cards, and a rebuilt splash
page. Inline color styles in components were replaced with semantic
classes (prompt-box, notice-banner, status chips, etc.), the rulebook's
embedded palette was updated to match, and the leftover Vite favicon
and "frontend" page title were replaced.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-06-12 06:50:00 -07:00
parent ba6bf35579
commit 0744893e7b
36 changed files with 1442 additions and 1185 deletions

View File

@@ -62,9 +62,9 @@
{#each openChallenges as ch}
{@const chPlays = JSON.parse(ch.plays || '[]')}
{@const chObstacleIds = JSON.parse(ch.obstacle_ids || '[]')}
<div class="glass-panel" style="margin-bottom: 1rem; padding: 1rem; border: 1px solid var(--red-suit, #ff2a5f); border-radius: 8px; background: rgba(255, 42, 95, 0.06);">
<div style="display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 0.5rem;">
<h4 style="margin: 0; font-family: var(--font-heading);">
<div class="challenge-item">
<div class="challenge-head">
<h4>
{#if ch.challenge_type === 'pvp'}
⚔️ Duel: {playerName(ch.challenger_player_id)} vs {playerName(ch.target_player_id)}
{:else}
@@ -72,7 +72,7 @@
{/if}
</h4>
{#if state.player.role === 'deep' && ch.challenge_type === 'deep'}
<div style="display: flex; gap: 0.5rem;">
<div class="challenge-actions">
<button class="btn btn-primary" on:click={() => resolveChallenge(ch.id)} disabled={ch.tax_state === 'requested'}>Resolve</button>
<button class="btn btn-secondary" on:click={() => cancelChallenge(ch.id)}>Withdraw</button>
</div>
@@ -101,10 +101,10 @@
<!-- Tax: pending request aimed at me -->
{#if ch.tax_state === 'requested'}
{#if myTaxRequest && myTaxRequest.id === ch.id}
<div style="margin-top: 0.75rem; padding: 0.75rem; border: 1px dashed var(--gold); border-radius: 6px;">
<p style="margin: 0 0 0.5rem 0;"><strong>{playerName(ch.target_player_id)}</strong> calls a <strong>{ch.tax_type === 'gat' ? 'Gat' : 'Name'} Tax</strong> on you: take this Challenge for them!</p>
<div class="tax-callout">
<p><strong>{playerName(ch.target_player_id)}</strong> calls a <strong>{ch.tax_type === 'gat' ? 'Gat' : 'Name'} Tax</strong> on you: take this Challenge for them!</p>
<p class="info-text" style="font-size: 0.85rem;">Accept: you get one random card from their hand and attempt the Challenge. Refuse: you hand over your {ch.tax_type === 'gat' ? 'Gat' : 'Name'} — they keep it if they succeed!</p>
<div style="display: flex; gap: 0.5rem;">
<div class="challenge-actions">
<button class="btn btn-primary" on:click={() => respondTax(ch.id, true)}>Accept</button>
<button class="btn btn-danger" on:click={() => respondTax(ch.id, false)}>Refuse</button>
</div>
@@ -116,8 +116,8 @@
<!-- Tax: option for the challenged player -->
{#if ch.challenge_type === 'deep' && ch.acting_player_id === state.player.id && !ch.tax_state && !state.player.tax_banned && taxType(state.player) && eligibleTaxTargets().length > 0}
<div style="margin-top: 0.75rem; display: flex; gap: 0.5rem; align-items: center; flex-wrap: wrap;">
<span class="info-text" style="font-size: 0.9rem;">No {taxType(state.player)}? Make it someone else's problem:</span>
<div class="challenge-actions" style="margin-top: 0.75rem;">
<span class="info-text" style="font-size: 0.9rem; margin: 0;">No {taxType(state.player)}? Make it someone else's problem:</span>
<select class="select-field" bind:value={taxTargetId} style="max-width: 200px;">
<option value="">Pick a crewmate...</option>
{#each eligibleTaxTargets() as p}
@@ -132,7 +132,7 @@
{#if myPvpDefense && myPvpDefense.id === ch.id}
<div style="margin-top: 0.75rem;">
<p style="margin: 0 0 0.5rem 0;"><strong>Defend yourself!</strong> Pick a card:</p>
<div style="display: flex; gap: 0.5rem; flex-wrap: wrap;">
<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>
{/each}