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:
@@ -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}
|
||||
|
||||
@@ -55,32 +55,32 @@
|
||||
{/if}
|
||||
|
||||
{#if state.player.is_dead}
|
||||
<div class="sheet-group prompt-box" style="background: rgba(255, 42, 95, 0.1); border: 1px dashed var(--red-suit); padding: 1rem; border-radius: 8px; margin-bottom: 1rem; text-align: center;">
|
||||
<h4 style="color: var(--red-suit); margin-top: 0; font-family: var(--font-heading); font-size: 1.1rem; font-weight: 700;">💀 You have Died / Retired!</h4>
|
||||
<div class="prompt-box danger text-center">
|
||||
<h4>💀 You have Died / Retired!</h4>
|
||||
<p class="info-text" style="margin-bottom: 0; font-size: 0.9rem;">Your story arc is complete. You will choose to become a Ghost or roll a new character during the upkeep phase between scenes.</p>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if state.player.is_ghost}
|
||||
<div class="sheet-group prompt-box" style="background: rgba(120, 150, 180, 0.1); border: 1px dashed #7890a8; padding: 1rem; border-radius: 8px; margin-bottom: 1rem; text-align: center;">
|
||||
<h4 style="color: #a0b0c0; margin-top: 0; font-family: var(--font-heading); font-size: 1.1rem; font-weight: 700;">👻 Playing as a Ghost</h4>
|
||||
<div class="prompt-box ghostly text-center">
|
||||
<h4>👻 Playing as a Ghost</h4>
|
||||
<p class="info-text" style="margin-bottom: 0; font-size: 0.9rem;">You are in the Ghost World (grayscale view). You cannot interact well with the living but you can still help them resolve challenges!</p>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if state.player.needs_name}
|
||||
<div class="sheet-group prompt-box" style="background: rgba(212, 175, 55, 0.1); border: 1px dashed var(--gold); padding: 1rem; border-radius: 8px; margin-bottom: 1rem;">
|
||||
<h4 style="color: var(--gold); margin-top: 0;">🏴☠️ You Earned a Name!</h4>
|
||||
<div class="prompt-box accent">
|
||||
<h4>🏴☠️ You Earned a Name!</h4>
|
||||
<p class="info-text" style="margin-bottom: 0.5rem;">You completed your second objective and ranked up! What is your new Pirate Name?</p>
|
||||
<div style="display: flex; gap: 0.5rem;">
|
||||
<input type="text" bind:value={newNameInput} class="form-control" placeholder="Enter new name...">
|
||||
<div class="input-row">
|
||||
<input type="text" bind:value={newNameInput} class="input-field" placeholder="Enter new name...">
|
||||
<button class="btn btn-gold" on:click={submitNewName} disabled={!newNameInput.trim()}>Claim Name</button>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if state.player.tax_banned}
|
||||
<div class="sheet-group prompt-box" style="background: rgba(255, 42, 95, 0.08); border: 1px dashed var(--red-suit); padding: 0.75rem; border-radius: 8px; margin-bottom: 1rem; text-align: center;">
|
||||
<div class="prompt-box danger text-center">
|
||||
<p class="info-text" style="margin: 0; font-size: 0.9rem;">🚫 You failed a refused Tax — no more Gat/Name Taxes for you this scene.</p>
|
||||
</div>
|
||||
{/if}
|
||||
@@ -104,7 +104,7 @@
|
||||
<!-- Duel another Pi-Rat -->
|
||||
{#if !state.player.is_dead && duelTargets.length > 0}
|
||||
<div class="sheet-group margin-top">
|
||||
<h4 style="color: var(--gold);">⚔️ Duel a Pi-Rat</h4>
|
||||
<h4>⚔️ Duel a Pi-Rat</h4>
|
||||
<p class="info-text" style="font-size: 0.85rem;">Challenge a crewmate (e.g. for the Captaincy)! Your card becomes a temporary Obstacle they must beat. Both cards are discarded afterwards.</p>
|
||||
<select class="select-field" bind:value={pvpOpponentId} style="width: 100%; margin-bottom: 0.5rem;">
|
||||
<option value="">Challenge whom?</option>
|
||||
@@ -123,7 +123,7 @@
|
||||
{/if}
|
||||
|
||||
<div class="sheet-group margin-top">
|
||||
<h4 style="color: var(--gold);">Crew Objectives</h4>
|
||||
<h4>Crew Objectives</h4>
|
||||
<div class="objectives-checklist">
|
||||
<label class="checkbox-label">
|
||||
<input type="checkbox" checked={state.game.completed_crew_1} disabled>
|
||||
@@ -141,7 +141,7 @@
|
||||
</div>
|
||||
|
||||
<div class="sheet-group margin-top">
|
||||
<h4 style="color: var(--gold);">Personal Objectives</h4>
|
||||
<h4>Personal Objectives</h4>
|
||||
<div class="objectives-checklist">
|
||||
<label class="checkbox-label">
|
||||
<input type="checkbox" checked={state.player.completed_personal_1} disabled>
|
||||
|
||||
@@ -62,7 +62,7 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="card glass-panel deep-panel-card" style="max-height: 80vh; overflow-y: auto;">
|
||||
<div class="card glass-panel deep-panel-card">
|
||||
<h3 class="deep-text text-center">🌊 Deep Control Panel</h3>
|
||||
|
||||
{#if error}
|
||||
@@ -71,7 +71,7 @@
|
||||
|
||||
<!-- Call a Challenge -->
|
||||
<div class="sheet-group margin-top">
|
||||
<h4 style="color: var(--gold);">⚔️ Call a Challenge</h4>
|
||||
<h4>⚔️ Call a Challenge</h4>
|
||||
<p class="info-text" style="font-size: 0.85rem;">Establish the stakes, pick a Pi-Rat, and apply one or more Obstacles. They play one card per Obstacle — beating one passes; each failure breeds a complication.</p>
|
||||
<select class="select-field" bind:value={challengeTargetId} style="width: 100%; margin-bottom: 0.5rem;">
|
||||
<option value="">Challenge which Pi-Rat?</option>
|
||||
@@ -88,7 +88,7 @@
|
||||
</label>
|
||||
{/each}
|
||||
</div>
|
||||
<input type="text" class="form-control" placeholder="Stakes (optional): what happens on success/failure?" bind:value={challengeStakes} style="width: 100%; margin-bottom: 0.5rem;">
|
||||
<input type="text" class="input-field" placeholder="Stakes (optional): what happens on success/failure?" bind:value={challengeStakes} style="width: 100%; margin-bottom: 0.5rem;">
|
||||
<button class="btn btn-primary btn-full" on:click={createChallenge}
|
||||
disabled={!challengeTargetId || !Object.values(selectedObstacles).some(v => v)}>
|
||||
Call Challenge
|
||||
@@ -97,7 +97,7 @@
|
||||
|
||||
<!-- Captain Assignment -->
|
||||
<div class="sheet-group margin-top">
|
||||
<h4 style="color: var(--gold);">🏴☠️ Captaincy</h4>
|
||||
<h4>🏴☠️ Captaincy</h4>
|
||||
<p class="info-text" style="font-size: 0.85rem;">The Captain holds power until they step down, lose a duel, die, or the ship is lost. Reassign after a leadership duel or resignation.</p>
|
||||
<div style="display: flex; gap: 0.5rem;">
|
||||
<select class="select-field" bind:value={captainSelectId} style="flex: 1;">
|
||||
@@ -111,7 +111,7 @@
|
||||
</div>
|
||||
|
||||
<div class="sheet-group margin-top">
|
||||
<h4 style="color: var(--gold);">Crew Objectives</h4>
|
||||
<h4>Crew Objectives</h4>
|
||||
<div class="objectives-checklist">
|
||||
<label class="checkbox-label">
|
||||
<input type="checkbox" checked={state.game.completed_crew_1} on:change={() => toggleObjective(state.player.id, 'crew_1')}>
|
||||
@@ -129,10 +129,10 @@
|
||||
</div>
|
||||
|
||||
<div class="sheet-group margin-top">
|
||||
<h4 style="color: var(--gold);">Pi-Rat Personal Objectives</h4>
|
||||
<h4>Pi-Rat Personal Objectives</h4>
|
||||
<p class="info-text">You control completion. Only mark in sequence (1 -> 2 -> 3).</p>
|
||||
{#each scenePirats as p}
|
||||
<div style="background: rgba(0,0,0,0.2); padding: 0.5rem; border-radius: 4px; margin-bottom: 0.5rem;">
|
||||
<div class="objective-player-block">
|
||||
<strong>{displayName(p)}</strong>
|
||||
<div class="objectives-checklist" style="margin-top: 0.25rem;">
|
||||
<label class="checkbox-label"><input type="checkbox" checked={p.completed_personal_1} on:change={() => toggleObjective(p.id, 'personal_1')}> <span>1. Gat</span></label>
|
||||
@@ -144,7 +144,7 @@
|
||||
</div>
|
||||
|
||||
<!-- End Scene Button -->
|
||||
<div class="scene-upkeep-controls text-center" style="margin-top: 1rem; border-top: 1px solid rgba(255,255,255,0.1); padding-top: 1rem;">
|
||||
<div class="scene-upkeep-controls text-center">
|
||||
<p class="info-text">End the scene once every Pi-Rat has faced a Challenge and had a chance at an Objective, or the Obstacle List is empty.</p>
|
||||
<button on:click={endScene} class="btn btn-danger btn-large btn-full glow-effect">
|
||||
End Scene & Proceed to Ranking
|
||||
|
||||
@@ -62,8 +62,7 @@
|
||||
{@const success_count = column_cards.filter(c => c.success).length}
|
||||
{@const is_completed = success_count >= state.players.length}
|
||||
{@const in_challenge = challengedObstacleIds.has(obs.id)}
|
||||
<div class="obstacle-item suit-{obs.suit.toLowerCase()} {is_completed ? 'completed' : ''}"
|
||||
style={in_challenge ? 'box-shadow: 0 0 0 2px var(--red-suit, #ff2a5f);' : ''}
|
||||
<div class="obstacle-item suit-{obs.suit.toLowerCase()} {is_completed ? 'completed' : ''} {in_challenge ? 'in-challenge' : ''}"
|
||||
data-obstacle-id={obs.id}
|
||||
on:dragover={(e) => { if (!is_completed) e.preventDefault(); }}
|
||||
on:dragenter={(e) => { if (!is_completed) e.currentTarget.classList.add("drag-over"); }}
|
||||
@@ -78,11 +77,11 @@
|
||||
else playCard(obs.id, cardCode);
|
||||
}
|
||||
}}>
|
||||
<div class="obstacle-card-wrapper" style="display: flex; justify-content: center; align-items: center;">
|
||||
<div class="obstacle-card-wrapper">
|
||||
<Card card={active_card_code} size="medium" title="Active Card: {getCardDisplay(active_card_code)}" />
|
||||
</div>
|
||||
<div class="obstacle-details">
|
||||
<h4>{obs.title} {#if in_challenge}<span style="color: var(--red-suit, #ff2a5f); font-size: 0.8rem;">⚔️ In Challenge</span>{/if}</h4>
|
||||
<h4>{obs.title} {#if in_challenge}<span class="in-challenge-tag">⚔️ In Challenge</span>{/if}</h4>
|
||||
<p class="desc">{obs.description}</p>
|
||||
</div>
|
||||
|
||||
@@ -102,7 +101,7 @@
|
||||
<!-- Successes Tracker -->
|
||||
<div class="obstacle-successes-display text-center">
|
||||
<span class="val-label">Successes</span>
|
||||
<span class="val-number" style="font-size: 1.5rem;">
|
||||
<span class="val-number">
|
||||
{success_count} / {state.players.length}
|
||||
</span>
|
||||
</div>
|
||||
@@ -121,8 +120,8 @@
|
||||
</div>
|
||||
|
||||
{#if is_completed && state.player.role === 'deep'}
|
||||
<div class="text-center" style="margin-top: 1rem;">
|
||||
<button class="btn btn-success" on:click={() => clearObstacle(obs.id)} style="width: 100%; border: 1px solid rgba(255,255,255,0.2);">Clear Obstacle</button>
|
||||
<div class="clear-obstacle-row text-center">
|
||||
<button class="btn btn-success btn-full" on:click={() => clearObstacle(obs.id)}>Clear Obstacle</button>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user