The board's furniture and the reel's fists, painted as cels

The last of the inked look. Walls carry stone joints and highlights,
doors their grain, hinges, and a brass lock plate whose keyhole turns
to a cross when jammed; battle scars fracture and branch with the
damage; illusion walls drift as tapered wisps with breathing glints;
the wall of fire burns in flat three-tone flames on a stepped flicker;
warp mouths and destinations are painted curls. Through a wizard's
eyes the punch is a three-tone hand with an ink contour and a
hand-cut POW, the same on every platform where the emoji glyph was
not, and the incoming fist no longer reverses late in its travel. The
inked stand-ins for missing conjuration files match. Goldens
re-blessed: every board frame, and the web's third ring.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Jm2auWk6RP71CjaAb4FMoG
This commit is contained in:
Eric Wagoner
2026-09-15 12:42:02 -04:00
co-authored by Claude Fable 5.1
parent 8dfd0e0c29
commit 34b621b5cb
22 changed files with 305 additions and 189 deletions
+26 -22
View File
@@ -6,6 +6,7 @@
import type { BoardFx } from "./fx";
import { colorIndexOf as sharedColorIndex, wizardColor } from "./colors";
import { CREATURE_ART, objectArt, TERRAIN_ART, tokenArt } from "./art";
import { edgeScar, edgeWisps, webThreads, warpCurls } from "./boardArt";
import EdgeGlyph, { type LockState } from "./EdgeGlyph.svelte";
import FirewallEdge from "./FirewallEdge.svelte";
import IllusionShimmer from "./IllusionShimmer.svelte";
@@ -464,11 +465,13 @@
{@const wy = Number(key.split(":")[1]?.split(",")[1])}
{@const frac = Math.min(1, dmg / 20)}
{#if kind === "V"}
<line x1={(wx + 1) * CELL} y1={wy * CELL + 3} x2={(wx + 1) * CELL} y2={(wy + 1) * CELL - 3}
class="crack" style:opacity={0.35 + frac * 0.65}><title>battle-scarred — {dmg} damage taken</title></line>
<path d={edgeScar((wx+1)*CELL, wy*CELL+3, (wx+1)*CELL, (wy+1)*CELL-3, frac)} class="crack-shadow" style:opacity={0.35 + frac * 0.65} />
<path d={edgeScar((wx+1)*CELL, wy*CELL+3, (wx+1)*CELL, (wy+1)*CELL-3, frac)}
class="crack" style:opacity={0.35 + frac * 0.65}><title>battle-scarred — {dmg} damage taken</title></path>
{:else}
<line x1={wx * CELL + 3} y1={(wy + 1) * CELL} x2={(wx + 1) * CELL - 3} y2={(wy + 1) * CELL}
class="crack" style:opacity={0.35 + frac * 0.65}><title>battle-scarred — {dmg} damage taken</title></line>
<path d={edgeScar(wx*CELL+3, (wy+1)*CELL, (wx+1)*CELL-3, (wy+1)*CELL, frac)} class="crack-shadow" style:opacity={0.35 + frac * 0.65} />
<path d={edgeScar(wx*CELL+3, (wy+1)*CELL, (wx+1)*CELL-3, (wy+1)*CELL, frac)}
class="crack" style:opacity={0.35 + frac * 0.65}><title>battle-scarred — {dmg} damage taken</title></path>
{/if}
{/each}
@@ -478,9 +481,9 @@
{@const ix = Number(key.split(":")[1]?.split(",")[0])}
{@const iy = Number(key.split(":")[1]?.split(",")[1])}
{#if kind === "V"}
<line x1={(ix + 1) * CELL} y1={iy * CELL} x2={(ix + 1) * CELL} y2={(iy + 1) * CELL} class="illusion"><title>an illusion — your eyes see through it</title></line>
<path d={edgeWisps((ix+1)*CELL, iy*CELL, (ix+1)*CELL, (iy+1)*CELL)} class="illusion"><title>an illusion — your eyes see through it</title></path>
{:else}
<line x1={ix * CELL} y1={(iy + 1) * CELL} x2={(ix + 1) * CELL} y2={(iy + 1) * CELL} class="illusion"><title>an illusion — your eyes see through it</title></line>
<path d={edgeWisps(ix*CELL, (iy+1)*CELL, (ix+1)*CELL, (iy+1)*CELL)} class="illusion"><title>an illusion — your eyes see through it</title></path>
{/if}
{/each}
@@ -523,10 +526,7 @@
</g>
{#if standing}
<!-- show where this opening leads -->
<circle
cx={w.to.cell.x * CELL + CELL / 2} cy={w.to.cell.y * CELL + CELL / 2}
r={CELL * 0.42} class="warp-dest"
/>
<path d={warpCurls(w.to.cell.x*CELL+CELL/2, w.to.cell.y*CELL+CELL/2, CELL*.42)} class="warp-dest" />
{/if}
{/each}
@@ -592,9 +592,7 @@
/>
{#if hasSpell(p.id, "sticky-web")}
<g class="webbing">
<line x1={-half} y1={-half * 0.4} x2={half} y2={half * 0.5} />
<line x1={-half * 0.6} y1={half} x2={half * 0.7} y2={-half} />
<line x1={-half} y1={half * 0.7} x2={half} y2={-half * 0.2} />
<path d={webThreads(half)} />
</g>
{/if}
{:else}
@@ -796,7 +794,7 @@
}
.creature-ring.selected { stroke-width: 4; stroke-dasharray: 5 3; }
.ground-object { fill: #a6812e; stroke: #4a3a10; stroke-width: 1; }
.illusion { stroke: #7a6f9a; stroke-width: 4; stroke-dasharray: 6 5; opacity: 0.7; }
.illusion { fill: #7a6f9a; stroke: none; opacity: 0.7; pointer-events: none; }
.creature { cursor: pointer; }
.creature-body { fill: #3b3428; stroke-width: 2.5; }
.creature-body.selected { fill: #6b5a34; }
@@ -914,7 +912,7 @@
fill: rgba(90, 44, 110, 0.05);
stroke: #7a4a8a;
stroke-width: 2;
stroke-dasharray: 8 6;
stroke-dasharray: 15 5 3 5;
pointer-events: none;
animation: fear-breathe 2.4s ease-in-out infinite;
}
@@ -929,16 +927,19 @@
:global(.token-art.ghosted) { opacity: 0.4; animation: ghost-breathe 2.6s ease-in-out infinite; }
@keyframes -global-ghost-breathe { 50% { opacity: 0.22; } }
:global(.token-art.stone-gazed) { filter: grayscale(0.9) brightness(0.8); }
.webbing line {
stroke: rgba(220, 218, 205, 0.85);
stroke-width: 1.6;
.webbing { pointer-events: none; }
.webbing path {
fill: none;
stroke: rgba(240, 231, 209, 0.9);
stroke-width: 1.25;
stroke-linecap: round;
pointer-events: none;
}
.warp-dest {
fill: none;
stroke: #2e7d32;
stroke-width: 2.5;
stroke-dasharray: 7 4;
stroke-linecap: round;
pointer-events: none;
animation: warp-pulse 1.6s ease-in-out infinite;
}
@@ -947,7 +948,8 @@
50% { opacity: 0.35; }
}
@media (prefers-reduced-motion: reduce) {
.marked-cell, .marked-sector, .ghost-slot, .warp-dest, .fear-aura { animation: none; }
.marked-cell, .marked-sector, .ghost-slot, .warp-dest, .fear-aura,
.spot-cell, .sight-corner, .sight-pierced { animation: none; }
.fx-layer { display: none; }
:global(.token-art.ghosted) { animation: none; }
.mover { transition: none; }
@@ -963,10 +965,12 @@
filter: drop-shadow(0.5px 1px 1px rgba(0, 0, 0, 0.55));
pointer-events: none;
}
.crack-shadow { fill: none; stroke: #6e5a3d; stroke-width: 2.8; stroke-linejoin: bevel; stroke-linecap: round; pointer-events: none; }
.crack {
fill: none;
stroke: #efe8d4;
stroke-width: 2;
stroke-dasharray: 3 5;
stroke-width: 1.4;
stroke-linejoin: bevel;
stroke-linecap: round;
pointer-events: none;
}