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:
co-authored by
Claude Fable 5
parent
497c2245a3
commit
0f91d30a3e
@@ -938,6 +938,9 @@
|
|||||||
clearSelection();
|
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.
|
// A shimmering illusion wall, tapped or bumped: offer the belief test.
|
||||||
let illusionPrompt = $state<{ cell: { x: number; y: number }; side: Side } | null>(null);
|
let illusionPrompt = $state<{ cell: { x: number; y: number }; side: Side } | null>(null);
|
||||||
function illusionEdgeParts(key: string): { cell: { x: number; y: number }; side: Side } {
|
function illusionEdgeParts(key: string): { cell: { x: number; y: number }; side: Side } {
|
||||||
@@ -1130,11 +1133,13 @@
|
|||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if peekCard || discardPeek || peekTreasure}
|
{#if peekCard || discardPeek || peekTreasure || peekEdge}
|
||||||
<div class="big-peek-scrim" role="button" tabindex="-1"
|
<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">
|
<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="" />
|
<img class="big-peek-token" src={peekTreasure.src} alt="" />
|
||||||
<div class="peek-note big-peek-note">{peekTreasure.note}</div>
|
<div class="peek-note big-peek-note">{peekTreasure.note}</div>
|
||||||
{:else}
|
{:else}
|
||||||
@@ -1559,6 +1564,7 @@
|
|||||||
onGhostClick={clickGhostSlot}
|
onGhostClick={clickGhostSlot}
|
||||||
onCellPeek={(c) => peekAt(c)}
|
onCellPeek={(c) => peekAt(c)}
|
||||||
onIllusionClick={(cell, side) => (illusionPrompt = { cell, side })}
|
onIllusionClick={(cell, side) => (illusionPrompt = { cell, side })}
|
||||||
|
onEdgePeek={(tip) => (peekEdge = tip)}
|
||||||
{litCells}
|
{litCells}
|
||||||
{sightTrace}
|
{sightTrace}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -26,6 +26,7 @@
|
|||||||
onWarpClick,
|
onWarpClick,
|
||||||
onCellPeek,
|
onCellPeek,
|
||||||
onIllusionClick,
|
onIllusionClick,
|
||||||
|
onEdgePeek,
|
||||||
markedCell = null,
|
markedCell = null,
|
||||||
markedCells = null,
|
markedCells = null,
|
||||||
litCells = null,
|
litCells = null,
|
||||||
@@ -47,6 +48,8 @@
|
|||||||
onCellPeek?: (cell: { x: number; y: number }) => void;
|
onCellPeek?: (cell: { x: number; y: number }) => void;
|
||||||
/** Tap on a shimmering (untested) illusion wall: offer the belief test. */
|
/** Tap on a shimmering (untested) illusion wall: offer the belief test. */
|
||||||
onIllusionClick?: (cell: { x: number; y: number }, side: Side) => void;
|
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. */
|
/** First square of a two-square spell: marked so the click reads as taken. */
|
||||||
markedCell?: { x: number; y: number } | null;
|
markedCell?: { x: number; y: number } | null;
|
||||||
/** A multi-square placement in progress (boobytrap tokens), in click
|
/** A multi-square placement in progress (boobytrap tokens), in click
|
||||||
@@ -387,7 +390,7 @@
|
|||||||
<!-- walls & doors & firewalls -->
|
<!-- walls & doors & firewalls -->
|
||||||
{#each edges as e (`${e.kind}:${e.x},${e.y}`)}
|
{#each edges as e (`${e.kind}:${e.x},${e.y}`)}
|
||||||
{#if e.state === "firewall"}
|
{#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}
|
{:else}
|
||||||
{@const lockTitle = e.lock === "removed" ? "lock removed — swings free"
|
{@const lockTitle = e.lock === "removed" ? "lock removed — swings free"
|
||||||
: e.lock === "jammed" ? "lock jammed — sealed for good"
|
: 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"}
|
<EdgeGlyph x={e.x} y={e.y} kind={e.kind === "V" ? "V" : "H"}
|
||||||
state={e.state === "door" ? "door" : "wall"}
|
state={e.state === "door" ? "door" : "wall"}
|
||||||
lock={(e.lock ?? null) as "removed" | "jammed" | "held" | "ajar" | null}
|
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}
|
{/if}
|
||||||
{/each}
|
{/each}
|
||||||
|
|
||||||
|
|||||||
@@ -2,13 +2,15 @@
|
|||||||
// A wall or door on an edge, exactly as the board draws it. Wall-segment
|
// 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
|
// effects (locks picked, jammed, removed; doors held) are color states of
|
||||||
// this one glyph — the game has never used tokens for them.
|
// 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";
|
x: number; y: number; kind: "V" | "H";
|
||||||
state: "wall" | "door";
|
state: "wall" | "door";
|
||||||
lock?: "removed" | "jammed" | "held" | "ajar" | null;
|
lock?: "removed" | "jammed" | "held" | "ajar" | null;
|
||||||
title?: string | null;
|
title?: string | null;
|
||||||
/** Battle damage taken so far (walls fall at 20, doors at 15). */
|
/** Battle damage taken so far (walls fall at 20, doors at 15). */
|
||||||
damage?: number;
|
damage?: number;
|
||||||
|
/** Press-and-hold (touch has no hover): report the tooltip's text. */
|
||||||
|
onpeek?: (tip: string) => void;
|
||||||
} = $props();
|
} = $props();
|
||||||
const CELL = 48;
|
const CELL = 48;
|
||||||
const WALL = 7;
|
const WALL = 7;
|
||||||
@@ -19,6 +21,17 @@
|
|||||||
? `${Math.max(0, needed - damage)} of ${needed} hitpoints left`
|
? `${Math.max(0, needed - damage)} of ${needed} hitpoints left`
|
||||||
: `${needed} hitpoints`);
|
: `${needed} hitpoints`);
|
||||||
const tip = $derived(`${title ?? (state === "door" ? "a locked door" : "a wall")} — ${hp}`);
|
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>
|
</script>
|
||||||
|
|
||||||
{#if kind === "V"}
|
{#if kind === "V"}
|
||||||
@@ -26,12 +39,14 @@
|
|||||||
x={(x + 1) * CELL - WALL / 2} y={y * CELL - WALL / 2}
|
x={(x + 1) * CELL - WALL / 2} y={y * CELL - WALL / 2}
|
||||||
width={WALL} height={CELL + WALL}
|
width={WALL} height={CELL + WALL}
|
||||||
class={cls}
|
class={cls}
|
||||||
|
role="img" onpointerdown={press} onpointerup={release} onpointerleave={release}
|
||||||
><title>{tip}</title></rect>
|
><title>{tip}</title></rect>
|
||||||
{:else}
|
{:else}
|
||||||
<rect
|
<rect
|
||||||
x={x * CELL - WALL / 2} y={(y + 1) * CELL - WALL / 2}
|
x={x * CELL - WALL / 2} y={(y + 1) * CELL - WALL / 2}
|
||||||
width={CELL + WALL} height={WALL}
|
width={CELL + WALL} height={WALL}
|
||||||
class={cls}
|
class={cls}
|
||||||
|
role="img" onpointerdown={press} onpointerup={release} onpointerleave={release}
|
||||||
><title>{tip}</title></rect>
|
><title>{tip}</title></rect>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,21 @@
|
|||||||
// A standing WALL OF FIRE: board furniture, not a flourish — it burns for
|
// 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
|
// 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).
|
// 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 uid = $props.id();
|
||||||
const CELL = 48;
|
const CELL = 48;
|
||||||
const WALL = 7;
|
const WALL = 7;
|
||||||
@@ -42,10 +56,12 @@
|
|||||||
<!-- the burning bar itself, seated on the wall line -->
|
<!-- the burning bar itself, seated on the wall line -->
|
||||||
{#if kind === "V"}
|
{#if kind === "V"}
|
||||||
<rect x={-WALL / 2} y={-CELL / 2 - 2} width={WALL} height={CELL + 4} rx="3"
|
<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}
|
{:else}
|
||||||
<rect x={-CELL / 2 - 2} y={-WALL / 2} width={CELL + 4} height={WALL} rx="3"
|
<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}
|
{/if}
|
||||||
<!-- tongues of flame, each licking on its own beat -->
|
<!-- tongues of flame, each licking on its own beat -->
|
||||||
{#each TONGUES as f (f.t)}
|
{#each TONGUES as f (f.t)}
|
||||||
|
|||||||
Reference in New Issue
Block a user