Files
wizwar6e/packages/web/src/TokenGallery.svelte
T
Eric WagonerandClaude Fable 5 7a32370ccb Credibility pass: the reel's scars sanded, the veil finally hung
Three blind reviews over the first-person arc, every finding verified
against the source. History-narrating comments made timeless or cut;
the stacked BUDDY comment collapsed to one voice. Dead code out: the
orphaned FACE copy, the DIR_ANGLE duplicate, the dead loop-counter
poke, the impossible-state sentinel. The never-produced "warp" hit
kind resolved the right way — warp mouths now hang a translucent veil
of the painted warp texture, so art that never rendered finally does.
Types tightened (SlabStrike named once, ShareData rides CatchUpStep,
botTier loses its casts), the gallery reads MATERIALS instead of a
hand-copied list, the chronicle resets through one helper, a
superseded share mint rejects instead of stranding, and the conjured
safe gains the growth key its siblings had. Tests lose a triple
assignment, a tautology, and two self-swallowing regex alternatives.
The sprite spec — which still told the artist to paint for additive
compositing the renderer no longer uses — now describes the renderer
that exists.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-23 21:41:11 -04:00

239 lines
8.4 KiB
Svelte

<script lang="ts">
// The token workshop: every token in both arts, side by side — the
// photographed physical set and the hand-drawn SVG contingency. Open
// with /?tokens under `npm run dev`; edits to public/tokens-svg/*.svg
// show on the next refresh. Board size and close-up for each.
import TokenArt from "./TokenArt.svelte";
import EdgeGlyph, { type LockState } from "./EdgeGlyph.svelte";
import FirewallEdge from "./FirewallEdge.svelte";
import IllusionShimmer from "./IllusionShimmer.svelte";
import { FX_ART } from "./fpv/fx3d";
import { MATERIALS } from "./fpv/textures";
import { TERRAIN3D } from "./fpv/terrain3d";
const GROUPS: { title: string; files: string[] }[] = [
{ title: "Wizards", files: ["wizard-0", "wizard-1", "wizard-2", "wizard-3", "wizard-4", "wizard-5"] },
{ title: "Treasures", files: ["treasure-0", "treasure-1", "treasure-2", "treasure-3", "treasure-4", "treasure-5"] },
{
title: "Monsters",
files: ["troll", "skeleton", "wraith", "fire-imp", "democratic-monster", "shadow", "alter-ego"],
},
{
title: "Terrain",
files: [
"solid-stone", "thorn-bush", "rosebush", "killer-ooze", "slime", "dustcloud",
"tacks", "pit", "safe",
],
},
{
title: "Objects & markers",
files: [
"dagger", "rock", "magic-stone", "magic-wand", "master-key", "boobytrap",
"dimensional-warp",
],
},
];
// Edge treatments, not tokens — see EdgeGlyph. Shown exactly as the
// board mounts them.
const EDGE_PIECES: {
caption: string;
state?: "wall" | "door";
lock?: LockState | null;
special?: "firewall" | "shimmer" | "known-illusion";
}[] = [
{ caption: "wall", state: "wall" },
{ caption: "door (locked)", state: "door" },
{ caption: "door, picked open (ajar)", state: "door", lock: "ajar" },
{ caption: "door, held open", state: "door", lock: "held" },
{ caption: "door, lock removed", state: "door", lock: "removed" },
{ caption: "door, lock jammed", state: "door", lock: "jammed" },
{ caption: "wall of fire", special: "firewall" },
{ caption: "illusion wall (untested)", special: "shimmer" },
{ caption: "illusion wall (you know it's fake)", special: "known-illusion" },
];
</script>
<div class="gallery">
<h1>The Token Workshop</h1>
<p class="sub">
Every token, twice: the photographed physical set beside the hand-drawn
contingency. Edit <code>public/tokens-svg/</code> and refresh. Small is
board size; large is the close-up the peek modal shows.
<a href="/?fx">→ the flourish workshop</a>
</p>
{#each GROUPS as group (group.title)}
<h2>{group.title}</h2>
<div class="grid">
{#each group.files as f (f)}
<figure>
<div class="pair">
<div class="col">
<img class="big" src={`/tokens/${f}.png`} alt={`${f} (photo)`} />
<img class="small" src={`/tokens/${f}.png`} alt="" />
<span class="tag">photo</span>
</div>
<div class="col">
<svg viewBox="0 0 96 96" class="big">
<TokenArt href={`/tokens-svg/${f}.svg`} x={0} y={0} width={96} height={96} />
</svg>
<svg viewBox="0 0 28 28" class="small">
<TokenArt href={`/tokens-svg/${f}.svg`} x={0} y={0} width={28} height={28} />
</svg>
<span class="tag">drawn</span>
</div>
</div>
<figcaption>{f}</figcaption>
</figure>
{/each}
</div>
{/each}
<h2>The masonry — first-person wall textures</h2>
<p class="sub">
The materials the raycaster (<code>/?fpv</code>) wraps its walls in.
Each lives at <code>public/textures/&lt;name&gt;.png</code> — repaint the
file (any size) and refresh; the built-in procedural bake stands in
wherever a file is missing.
</p>
<div class="grid">
{#each MATERIALS as name (name)}
<figure>
<img class="masonry" src={`/textures/${name}.png`} alt={`${name} texture`} />
<figcaption>{name}</figcaption>
</figure>
{/each}
</div>
<h2>The conjurations — first-person spell sprites</h2>
<p class="sub">
The moments the raycaster throws through the air: projectiles in
flight, bursts on landing, shimmers and shields. Each lives at
<code>public/fx3d/&lt;name&gt;.png</code> — square, transparent ground,
drawn glowing mid-air at any size; a procedural glow stands in
wherever a file is missing.
</p>
<div class="grid">
{#each FX_ART as name (name)}
<figure>
<img class="masonry conjuration" src={`/fx3d/${name}.png`} alt={`${name} sprite`} />
<figcaption>{name}</figcaption>
</figure>
{/each}
</div>
<h2>The standing terrain — first-person volumes</h2>
<p class="sub">
Terrain that stands in the room: hedges filling a square to half
height, the killer ooze as a translucent cube, dust billowing, the
dimensional warp's pillar of light. Each lives at
<code>public/terrain3d/&lt;name&gt;.png</code> — transparent PNG, any
size; hedges render twice as wide as tall. Rehearse them in place with
<code>/?fpv&amp;terrain=thornbush@3,7;jello@4,7</code>.
</p>
<div class="grid">
{#each TERRAIN3D as name (name)}
<figure>
<img class="masonry conjuration" src={`/terrain3d/${name}.png`} alt={`${name} sprite`} />
<figcaption>{name}</figcaption>
</figure>
{/each}
</div>
<h2>Walls &amp; doors — as the maze draws them</h2>
<p class="sub">
Wall-segment effects have never been tokens: locks picked, jammed, and
removed, held doors, standing fires, and illusions are edge treatments,
shown here with the very components the board mounts.
</p>
<div class="grid">
{#each EDGE_PIECES as piece (piece.caption)}
<figure>
<svg viewBox="20 20 104 104" class="edge-demo">
{#each [0, 1, 2] as cy (cy)}{#each [0, 1, 2] as cx (cx)}
<rect x={cx * 48} y={cy * 48} width="48" height="48" class="cell" />
{/each}{/each}
{#if piece.special === "firewall"}
<FirewallEdge x={1} y={0} kind="H" />
{:else if piece.special === "shimmer"}
<EdgeGlyph x={1} y={0} kind="H" state="wall" />
<IllusionShimmer x={1} y={0} kind="H" />
{:else if piece.special === "known-illusion"}
<line x1={48} y1={48} x2={96} y2={48} class="known-illusion" />
{:else}
<EdgeGlyph x={1} y={0} kind="H" state={piece.state!} lock={piece.lock ?? null} />
{/if}
</svg>
<figcaption>{piece.caption}</figcaption>
</figure>
{/each}
</div>
</div>
<style>
.gallery {
min-height: 100vh;
background: #171a20;
color: #d8d2c0;
font-family: "Archivo Narrow", system-ui, sans-serif;
padding: 1.5rem;
}
h1, h2 {
font-family: "Oswald", sans-serif;
text-transform: uppercase;
letter-spacing: 0.14em;
color: #e9e1cb;
margin: 0 0 0.2rem;
}
h1 { font-size: 1.2rem; }
h2 { font-size: 0.95rem; margin-top: 1.4rem; }
.sub { color: #8d8672; margin: 0 0 1rem; }
.sub code { color: #c9a72a; }
.sub a { color: #c9a72a; }
.grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(190px, 1fr));
gap: 0.9rem;
}
figure {
margin: 0;
background: #efe8d4;
border-radius: 4px;
padding: 0.5rem;
}
.pair { display: flex; gap: 0.6rem; justify-content: center; }
.col { display: flex; flex-direction: column; align-items: center; gap: 0.25rem; }
.big { width: 72px; height: 72px; display: block; }
.small { width: 28px; height: 28px; display: block; }
img.big, img.small { object-fit: cover; border-radius: 3px; }
.tag {
font-family: "Courier Prime", monospace;
font-size: 0.6rem;
color: #8a7a5e;
}
.edge-demo { width: 100%; display: block; }
.cell {
fill: #efe8d4;
stroke: rgba(95, 74, 51, 0.25);
stroke-width: 1;
}
/* Mirrors Board's known-illusion ghost: dashed, violet, see-through. */
.known-illusion { stroke: #7a6f9a; stroke-width: 4; stroke-dasharray: 6 5; opacity: 0.7; }
figcaption {
font-family: "Courier Prime", monospace;
font-size: 0.72rem;
color: #43331f;
text-align: center;
padding-top: 0.35rem;
}
.masonry {
width: 128px;
height: 128px;
image-rendering: pixelated;
border: 1px solid #3a3428;
background: #101318;
}
/* Sprites fly against darkness; show them on it. */
.conjuration { background: #14101c; }
</style>