Press-and-hold peeks a segment — the tooltip, for hands without a mouse

The same 450ms hold that peeks a square now works on walls, doors, and
fire walls: the segment's full description (state, lock, hitpoints, the
fire's burn warning) rises in the peek scrim. Shimmering illusions keep
their richer tap modal.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0138A8CjeQRpvzKxuMfz1Bqc
This commit is contained in:
Eric Wagoner
2026-08-18 10:40:55 -04:00
co-authored by Claude Fable 5
parent 497c2245a3
commit 0f91d30a3e
4 changed files with 50 additions and 9 deletions
+9 -3
View File
@@ -938,6 +938,9 @@
clearSelection();
}
// Press-and-hold on a wall/door/fire segment (touch has no hover).
let peekEdge = $state<string | null>(null);
// A shimmering illusion wall, tapped or bumped: offer the belief test.
let illusionPrompt = $state<{ cell: { x: number; y: number }; side: Side } | null>(null);
function illusionEdgeParts(key: string): { cell: { x: number; y: number }; side: Side } {
@@ -1130,11 +1133,13 @@
</div>
{/if}
{#if peekCard || discardPeek || peekTreasure}
{#if peekCard || discardPeek || peekTreasure || peekEdge}
<div class="big-peek-scrim" role="button" tabindex="-1"
onclick={() => { peekCard = null; peekCreatureId = null; peekToken = null; discardPeek = null; peekTreasure = null; }} onkeydown={() => {}}>
onclick={() => { peekCard = null; peekCreatureId = null; peekToken = null; discardPeek = null; peekTreasure = null; peekEdge = null; }} onkeydown={() => {}}>
<div class="big-peek">
{#if peekTreasure}
{#if peekEdge}
<div class="peek-note big-peek-note">{peekEdge}</div>
{:else if peekTreasure}
<img class="big-peek-token" src={peekTreasure.src} alt="" />
<div class="peek-note big-peek-note">{peekTreasure.note}</div>
{:else}
@@ -1559,6 +1564,7 @@
onGhostClick={clickGhostSlot}
onCellPeek={(c) => peekAt(c)}
onIllusionClick={(cell, side) => (illusionPrompt = { cell, side })}
onEdgePeek={(tip) => (peekEdge = tip)}
{litCells}
{sightTrace}
/>
+6 -2
View File
@@ -26,6 +26,7 @@
onWarpClick,
onCellPeek,
onIllusionClick,
onEdgePeek,
markedCell = null,
markedCells = null,
litCells = null,
@@ -47,6 +48,8 @@
onCellPeek?: (cell: { x: number; y: number }) => void;
/** Tap on a shimmering (untested) illusion wall: offer the belief test. */
onIllusionClick?: (cell: { x: number; y: number }, side: Side) => void;
/** Press-and-hold a wall/door/fire segment: its tooltip, for touch. */
onEdgePeek?: (tip: string) => void;
/** First square of a two-square spell: marked so the click reads as taken. */
markedCell?: { x: number; y: number } | null;
/** A multi-square placement in progress (boobytrap tokens), in click
@@ -387,7 +390,7 @@
<!-- walls & doors & firewalls -->
{#each edges as e (`${e.kind}:${e.x},${e.y}`)}
{#if e.state === "firewall"}
<FirewallEdge x={e.x} y={e.y} kind={e.kind === "V" ? "V" : "H"} />
<FirewallEdge x={e.x} y={e.y} kind={e.kind === "V" ? "V" : "H"} onpeek={onEdgePeek} />
{:else}
{@const lockTitle = e.lock === "removed" ? "lock removed — swings free"
: e.lock === "jammed" ? "lock jammed — sealed for good"
@@ -396,7 +399,8 @@
<EdgeGlyph x={e.x} y={e.y} kind={e.kind === "V" ? "V" : "H"}
state={e.state === "door" ? "door" : "wall"}
lock={(e.lock ?? null) as "removed" | "jammed" | "held" | "ajar" | null}
title={lockTitle} damage={view.wallDamage[`${e.kind}:${e.x},${e.y}`] ?? 0} />
title={lockTitle} damage={view.wallDamage[`${e.kind}:${e.x},${e.y}`] ?? 0}
onpeek={onEdgePeek} />
{/if}
{/each}
+16 -1
View File
@@ -2,13 +2,15 @@
// A wall or door on an edge, exactly as the board draws it. Wall-segment
// effects (locks picked, jammed, removed; doors held) are color states of
// this one glyph — the game has never used tokens for them.
let { x, y, kind, state, lock = null, title = null, damage = 0 }: {
let { x, y, kind, state, lock = null, title = null, damage = 0, onpeek }: {
x: number; y: number; kind: "V" | "H";
state: "wall" | "door";
lock?: "removed" | "jammed" | "held" | "ajar" | null;
title?: string | null;
/** Battle damage taken so far (walls fall at 20, doors at 15). */
damage?: number;
/** Press-and-hold (touch has no hover): report the tooltip's text. */
onpeek?: (tip: string) => void;
} = $props();
const CELL = 48;
const WALL = 7;
@@ -19,6 +21,17 @@
? `${Math.max(0, needed - damage)} of ${needed} hitpoints left`
: `${needed} hitpoints`);
const tip = $derived(`${title ?? (state === "door" ? "a locked door" : "a wall")} — ${hp}`);
// Press-and-hold peeks the segment, mirroring the board's cell gesture.
let timer: ReturnType<typeof setTimeout> | null = null;
function press() {
if (!onpeek) return;
timer = setTimeout(() => onpeek?.(tip), 450);
}
function release() {
if (timer) clearTimeout(timer);
timer = null;
}
</script>
{#if kind === "V"}
@@ -26,12 +39,14 @@
x={(x + 1) * CELL - WALL / 2} y={y * CELL - WALL / 2}
width={WALL} height={CELL + WALL}
class={cls}
role="img" onpointerdown={press} onpointerup={release} onpointerleave={release}
><title>{tip}</title></rect>
{:else}
<rect
x={x * CELL - WALL / 2} y={(y + 1) * CELL - WALL / 2}
width={CELL + WALL} height={WALL}
class={cls}
role="img" onpointerdown={press} onpointerup={release} onpointerleave={release}
><title>{tip}</title></rect>
{/if}
+19 -3
View File
@@ -2,7 +2,21 @@
// A standing WALL OF FIRE: board furniture, not a flourish — it burns for
// as long as the spell holds, so the animation is pure CSS on a handful
// of shapes (no turbulence filters; those are budgeted for one-shot fx).
let { x, y, kind }: { x: number; y: number; kind: "V" | "H" } = $props();
let { x, y, kind, onpeek }: {
x: number; y: number; kind: "V" | "H";
/** Press-and-hold (touch has no hover): report what this fire is. */
onpeek?: (tip: string) => void;
} = $props();
const TIP = "a wall of fire — passing through burns for 4";
let peekTimer: ReturnType<typeof setTimeout> | null = null;
function press() {
if (!onpeek) return;
peekTimer = setTimeout(() => onpeek?.(TIP), 450);
}
function release() {
if (peekTimer) clearTimeout(peekTimer);
peekTimer = null;
}
const uid = $props.id();
const CELL = 48;
const WALL = 7;
@@ -42,10 +56,12 @@
<!-- the burning bar itself, seated on the wall line -->
{#if kind === "V"}
<rect x={-WALL / 2} y={-CELL / 2 - 2} width={WALL} height={CELL + 4} rx="3"
fill={`url(#fw-bar-${uid})`} class="fw-bar" />
fill={`url(#fw-bar-${uid})`} class="fw-bar"
role="img" onpointerdown={press} onpointerup={release} onpointerleave={release} />
{:else}
<rect x={-CELL / 2 - 2} y={-WALL / 2} width={CELL + 4} height={WALL} rx="3"
fill={`url(#fw-bar-${uid})`} class="fw-bar" />
fill={`url(#fw-bar-${uid})`} class="fw-bar"
role="img" onpointerdown={press} onpointerup={release} onpointerleave={release} />
{/if}
<!-- tongues of flame, each licking on its own beat -->
{#each TONGUES as f (f.t)}