Credibility pass: one conflict check, one hostile list, one set of chrome
Duplicated logic and styling that had drifted apart is unified: the gesture-sharing check, the hostile spell list, the enchanted test, the plan limits, and the shared button, link, field and disabled styles. Dead plumbing is gone: the always-true implemented flag, the unread cast length, an unreachable guard, an impossible time-stop condition, the unused sequence formatter and utility class, the template placeholder. The counter-spell tests now put a counter-spell in front of a spell, the test helpers live in one file, and the resolver's sections are numbered in order. The deploy script's cache headers now do what its comment says, and the README describes the screen rather than listing features in the order they arrived. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5.1
parent
527820ee95
commit
5de352db43
@@ -1,8 +1,8 @@
|
||||
<script lang="ts">
|
||||
import { castKey, tokensText, type Duel, type SetName } from '$lib/game/duel.svelte';
|
||||
import GestureIcon from './GestureIcon.svelte';
|
||||
import { GESTURE_SHORT, glyph } from '$lib/game/glyphs';
|
||||
import { GESTURES, type Hand, type SpellId } from '$lib/game/spells';
|
||||
import GestureIcon from './GestureIcon.svelte';
|
||||
|
||||
let { duel, hand, set = 'main' }: { duel: Duel; hand: Hand; set?: SetName } = $props();
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
duel.you.constraints.amnesia
|
||||
? 'Amnesia: you must repeat last turn.'
|
||||
: duel.you.constraints.paralysedHand === hand
|
||||
? 'Paralysed in last turn’s position.'
|
||||
? "Paralysed in last turn's position."
|
||||
: ''
|
||||
);
|
||||
</script>
|
||||
@@ -34,7 +34,7 @@
|
||||
{#if forced}
|
||||
<p class="locked"><span class="letter big">{glyph(forced, duel.gestureFor(set, other))}</span> {whyForced}</p>
|
||||
{:else if charmed}
|
||||
<p class="locked"><span class="letter big">?</span> Charmed: {duel.foe.name} chooses this hand’s gesture.</p>
|
||||
<p class="locked"><span class="letter big">?</span> Charmed: {duel.foe.name} chooses this hand's gesture.</p>
|
||||
{:else}
|
||||
<div class="gestures" role="group" aria-label="{hand} hand gesture{set === 'haste' ? ', second pair' : ''}">
|
||||
{#each GESTURES as g (g)}
|
||||
@@ -55,31 +55,33 @@
|
||||
{#if duel.you.constraints.fear && !duel.timeStopped}
|
||||
<p class="hint">Fear: no clap, digit, fingers or snap this turn.</p>
|
||||
{/if}
|
||||
{#if set === 'main' && duel.showPlans && duel.plans[hand].length === 0}
|
||||
<p class="hint">Nothing under way. <button type="button" class="link" onclick={() => duel.openSheet()}>Pick a spell from the sheet</button> to plan with this hand.</p>
|
||||
{:else if set === 'main' && duel.showPlans && duel.plans[hand].length > 0}
|
||||
<ul class="plans" aria-label="{hand} hand, spells under way">
|
||||
{#each plans as plan (plan.spell.id)}
|
||||
<li class:pinned={plan.pinned}>
|
||||
<button type="button" class="plan" title="Make the next gesture toward {plan.spell.name}" onclick={() => duel.follow(hand, plan)}>
|
||||
<span class="seq">{#if plan.done.length}<span class="done">{tokensText(plan.done)}</span><span class="arrow">→</span>{/if}<span class="next">{tokensText(plan.remaining)}</span></span>
|
||||
<span class="pname">{plan.spell.name}</span>
|
||||
</button>
|
||||
<button type="button" class="pin" aria-pressed={plan.pinned} title={plan.pinned ? 'Unpin' : 'Pin this spell'} onclick={() => duel.pin(hand, plan.spell.id)}>{plan.pinned ? '\u25c6' : '\u25c7'}</button>
|
||||
</li>
|
||||
{/each}
|
||||
{#if hiddenPlans > 0 || morePlans}
|
||||
<li class="more">
|
||||
<button type="button" class="plan" onclick={() => (morePlans = !morePlans)}>{morePlans ? 'Fewer spells' : `More spells (${hiddenPlans})`}</button>
|
||||
</li>
|
||||
{/if}
|
||||
</ul>
|
||||
{#if set === 'main' && duel.showPlans}
|
||||
{#if duel.plans[hand].length === 0}
|
||||
<p class="hint">Nothing under way. <button type="button" class="link" onclick={() => duel.openSheet()}>Pick a spell from the sheet</button> to plan with this hand.</p>
|
||||
{:else}
|
||||
<ul class="plans" aria-label="{hand} hand, spells under way">
|
||||
{#each plans as plan (plan.spell.id)}
|
||||
<li class:pinned={plan.pinned}>
|
||||
<button type="button" class="plan" title="Make the next gesture toward {plan.spell.name}" onclick={() => duel.follow(hand, plan)}>
|
||||
<span class="seq">{#if plan.done.length}<span class="done">{tokensText(plan.done)}</span><span class="arrow">→</span>{/if}<span class="next">{tokensText(plan.remaining)}</span></span>
|
||||
<span class="pname">{plan.spell.name}</span>
|
||||
</button>
|
||||
<button type="button" class="pin" aria-pressed={plan.pinned} title={plan.pinned ? 'Unpin' : 'Pin this spell'} onclick={() => duel.pin(hand, plan.spell.id)}>{plan.pinned ? '\u25c6' : '\u25c7'}</button>
|
||||
</li>
|
||||
{/each}
|
||||
{#if hiddenPlans > 0 || morePlans}
|
||||
<li class="more">
|
||||
<button type="button" class="plan" onclick={() => (morePlans = !morePlans)}>{morePlans ? 'Fewer spells' : `More spells (${hiddenPlans})`}</button>
|
||||
</li>
|
||||
{/if}
|
||||
</ul>
|
||||
{/if}
|
||||
{/if}
|
||||
{/if}
|
||||
|
||||
{#if options.length > 0}
|
||||
<div class="cast">
|
||||
<label>
|
||||
<label class="field">
|
||||
<span>Finishes</span>
|
||||
<select value={draft.spellId} onchange={(e) => duel.setSpell(set, hand, e.currentTarget.value as SpellId | '')}>
|
||||
{#each options as c (c.spell.id + c.seqIndex)}
|
||||
@@ -89,7 +91,7 @@
|
||||
</select>
|
||||
</label>
|
||||
{#if spell}
|
||||
<label>
|
||||
<label class="field">
|
||||
<span>at</span>
|
||||
<select bind:value={draft.target}>
|
||||
{#each duel.targetsFor(spell) as t (t.id)}
|
||||
@@ -98,7 +100,7 @@
|
||||
</select>
|
||||
</label>
|
||||
{#if spell.id === 'summon_elemental'}
|
||||
<label>
|
||||
<label class="field">
|
||||
<span>of</span>
|
||||
<select bind:value={draft.elemental}>
|
||||
<option value="fire">fire</option>
|
||||
@@ -107,7 +109,7 @@
|
||||
</label>
|
||||
{/if}
|
||||
{#if spell.id === 'paralysis' || spell.id === 'charm_person'}
|
||||
<label>
|
||||
<label class="field">
|
||||
<span>their</span>
|
||||
<select bind:value={draft.chosenHand}>
|
||||
<option value="left">left hand</option>
|
||||
@@ -172,11 +174,6 @@
|
||||
color: var(--slate-deep);
|
||||
}
|
||||
|
||||
.gesture:disabled {
|
||||
opacity: 0.3;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.gesture .letter {
|
||||
font-size: 1.25rem;
|
||||
margin-top: 0.05rem;
|
||||
@@ -211,17 +208,6 @@
|
||||
align-items: baseline;
|
||||
}
|
||||
|
||||
.cast label {
|
||||
display: inline-flex;
|
||||
gap: 0.4rem;
|
||||
align-items: baseline;
|
||||
}
|
||||
|
||||
.cast label span {
|
||||
color: var(--bone-dim);
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.plans {
|
||||
list-style: none;
|
||||
margin: 0.4rem 0 0;
|
||||
@@ -308,15 +294,6 @@
|
||||
margin-top: 0.3rem;
|
||||
}
|
||||
|
||||
.link {
|
||||
background: none;
|
||||
border: 0;
|
||||
padding: 0;
|
||||
color: var(--frost);
|
||||
text-decoration: underline;
|
||||
font-size: inherit;
|
||||
}
|
||||
|
||||
.summary strong {
|
||||
display: block;
|
||||
font-weight: 500;
|
||||
|
||||
@@ -104,13 +104,10 @@
|
||||
{#each ['left', 'right'] as const as hand (hand)}
|
||||
<td class="cell you">
|
||||
{#each duel.activeSets as set (set)}
|
||||
{@const chosen = duel.forcedFor(set, hand) ?? duel.sets[set][hand].gesture}
|
||||
{@const charmed = duel.charmedHand === hand}
|
||||
{@const undecided = duel.chosenGesture(set, hand) === null || duel.charmedHand === hand}
|
||||
{@const cast = duel.chosen[set][hand]}
|
||||
<span class="entry" class:cast={!!cast} class:haste={set === 'haste'}>
|
||||
<span class="letter" class:placeholder={chosen === null || charmed}>
|
||||
{charmed ? '?' : chosen === null ? '·' : glyph(chosen, duel.gestureFor(set, hand === 'left' ? 'right' : 'left'))}
|
||||
</span>
|
||||
<span class="letter" class:placeholder={undecided}>{duel.drawn(set, hand)}</span>
|
||||
{#if cast && duel.sets[set][hand].spellId}<span class="note">{cast.spell.name}</span>{/if}
|
||||
</span>
|
||||
{/each}
|
||||
@@ -251,11 +248,4 @@
|
||||
border-bottom: 2px solid var(--ember);
|
||||
}
|
||||
|
||||
.visually-hidden {
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
overflow: hidden;
|
||||
clip: rect(0 0 0 0);
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,12 +1,9 @@
|
||||
<script lang="ts">
|
||||
import { FOE, YOU, type Duel } from '$lib/game/duel.svelte';
|
||||
import { glyph } from '$lib/game/glyphs';
|
||||
import type { Duel } from '$lib/game/duel.svelte';
|
||||
import { HANDS } from '$lib/game/spells';
|
||||
import { MAX_HP } from '$lib/game/state';
|
||||
|
||||
let { duel }: { duel: Duel } = $props();
|
||||
|
||||
const left = $derived(duel.forcedFor('main', 'left') ?? duel.sets.main.left.gesture);
|
||||
const right = $derived(duel.forcedFor('main', 'right') ?? duel.sets.main.right.gesture);
|
||||
</script>
|
||||
|
||||
{#if !duel.state.over}
|
||||
@@ -16,8 +13,9 @@
|
||||
<span><em>{duel.foe.name}</em> {duel.foe.hp}/{MAX_HP}</span>
|
||||
</div>
|
||||
<div class="gestures" aria-label="Chosen gestures">
|
||||
<span class="letter" class:empty={left === null}>{duel.charmedHand === 'left' ? '?' : left === null ? '·' : glyph(left, right ?? '-')}</span>
|
||||
<span class="letter" class:empty={right === null}>{duel.charmedHand === 'right' ? '?' : right === null ? '·' : glyph(right, left ?? '-')}</span>
|
||||
{#each HANDS as hand (hand)}
|
||||
<span class="letter" class:empty={duel.chosenGesture('main', hand) === null}>{duel.drawn('main', hand)}</span>
|
||||
{/each}
|
||||
</div>
|
||||
<button type="button" class="reveal" disabled={!duel.ready} onclick={() => duel.reveal()}>
|
||||
{duel.surrendering ? 'Surrender' : duel.timeStopped ? 'Act' : 'Reveal'}
|
||||
@@ -30,6 +28,7 @@
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Must match COMPACT_QUERY in duel.svelte.ts. */
|
||||
@media (max-width: 860px) {
|
||||
.bar {
|
||||
position: fixed;
|
||||
@@ -77,15 +76,7 @@
|
||||
}
|
||||
|
||||
.reveal {
|
||||
background: var(--bone);
|
||||
color: var(--slate-deep);
|
||||
border: 0;
|
||||
border-radius: 4px;
|
||||
padding: 0.5rem 1rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.reveal:disabled {
|
||||
opacity: 0.35;
|
||||
font-size: 1rem;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -12,7 +12,6 @@
|
||||
{ category: 'damage', title: 'Damage' },
|
||||
{ category: 'enchantment', title: 'Enchantments' }
|
||||
];
|
||||
|
||||
</script>
|
||||
|
||||
<details class="sheet" id="spell-sheet" bind:open={duel.sheetOpen}>
|
||||
@@ -26,8 +25,8 @@
|
||||
{@const done = duel.completedNow(spell.id)}
|
||||
<li class:done class:open={open === spell.id}>
|
||||
<button type="button" class="row" aria-expanded={open === spell.id} onclick={() => (open = open === spell.id ? null : spell.id)}>
|
||||
<span class="seq">{#each spell.sequences as seq, si (seq)}{#if si > 0}<span class="or"> or </span>{/if}{#each seq.split('-') as part, i (i)}{#if i > 0}<span class="dash">-</span>{/if}<span class="tok" class:lit={done || i < k}>{part}</span>{/each}{/each}</span>
|
||||
<span class="name">{spell.name}</span>
|
||||
<span class="seq">{#each spell.sequences as seq, si (seq)}{#if si > 0}<span class="or"> or </span>{/if}{#each seq.split('-') as part, i (i)}{#if i > 0}<span class="dash">-</span>{/if}<span class="tok" class:lit={done || i < k}>{part}</span>{/each}{/each}</span>
|
||||
<span class="name">{spell.name}</span>
|
||||
</button>
|
||||
{#if open === spell.id}
|
||||
<p class="about">{spell.summary}</p>
|
||||
@@ -137,11 +136,6 @@
|
||||
border-color: var(--frost);
|
||||
}
|
||||
|
||||
.planbtn:disabled {
|
||||
opacity: 0.4;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.which {
|
||||
color: var(--frost);
|
||||
}
|
||||
|
||||
@@ -31,14 +31,14 @@
|
||||
</div>
|
||||
{/each}
|
||||
</dl>
|
||||
{#if summary.events?.length}
|
||||
{#if summary.events.length}
|
||||
<ul class="events">
|
||||
{#each summary.events as e, i (i)}
|
||||
<li>{e}</li>
|
||||
{/each}
|
||||
</ul>
|
||||
{/if}
|
||||
{#if duel.showLessons && summary.lessons?.length}
|
||||
{#if duel.showLessons && summary.lessons.length}
|
||||
<ul class="lessons">
|
||||
{#each summary.lessons as l, i (i)}
|
||||
<li>{l}</li>
|
||||
@@ -58,7 +58,7 @@
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
/* On a phone, leave the last two ledger rows in view above the result. */
|
||||
/* On a phone, leave the last two ledger rows in view above the result. Must match COMPACT_QUERY in duel.svelte.ts. */
|
||||
@media (max-width: 860px) {
|
||||
.result {
|
||||
scroll-margin-top: 9.5rem;
|
||||
|
||||
Reference in New Issue
Block a user