Files
wizwar6e/packages/web/src/SharePage.svelte
T
Eric WagonerandClaude Fable 5.1 4c2a253723 The tester's second pass: the rule as written, advice that follows the board, the fold, the story on the replay page, and one click to begin
- Help said "walk, then act"; the rules let moves and cards come in any
  order, and now the help does too.
- The council's advice is derived from the live view while its panel is
  open, so a Number just played for movement is no longer recommended.
  It rests when the turn is not yours; "ask again" had nothing to add.
- The landing puts the name and the ways in before the clip, and beside
  it on a desktop: at 1280x720 the play button and Create/Join are above
  the fold.
- The replay page opens with the same deed the share card promises, and
  "play free" stands beside the reel instead of after the history.
- In hotseat the "pass the device" scrim stood above the roll-off, so the
  first click on "begin" landed on the handoff card. The handoff waits
  for the dice.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Jm2auWk6RP71CjaAb4FMoG
2026-09-16 21:43:12 -04:00

156 lines
4.8 KiB
Svelte

<script lang="ts">
// The public share page (/watch/<id>): one turn of one game, watchable
// by anyone, spectator-redacted — the rest of the game stays private.
// The reel opens in first person through the turn-owner's eyes, framed
// by enough chrome to explain itself and point the way home.
import Replay from "./Replay.svelte";
import type { GameEvent, GameView } from "@wizwar/engine";
const id = location.pathname.split("/")[2] ?? "";
// Under the vite dev server the API lives on the game server's port.
const API = location.port === "5173" ? `http://${location.hostname}:8787` : "";
interface ShareSteps {
steps: { seq: number; actor: string; events: GameEvent[]; view: GameView }[];
actor: string;
round: number;
whole?: boolean;
/** The turn's deed in one line, as the share card says it. */
headline?: string | null;
}
let data = $state<ShareSteps | null>(null);
let failed = $state(false);
let reelKey = $state(0);
$effect(() => {
fetch(`${API}/api/share/${id}`)
.then((r) => (r.ok ? r.json() : Promise.reject(new Error("gone"))))
.then((d) => (data = d))
.catch(() => (failed = true));
});
</script>
<div class="share-page">
<header class="share-head">
<a class="share-brand" href="/">WIZ-WAR</a>
{#if data}
<div class="share-title">
{#if data.whole}
<span class="share-deed">The whole tale{data.actor ? ` — ${data.actor} triumphant` : ""}</span>
{:else if data.headline}
<span class="share-deed">{data.headline}</span>
<span class="share-sub">an instant replay of {data.actor}'s turn{data.round ? `, round ${data.round}` : ""}</span>
{:else}
<span class="share-deed">Instant replay — {data.actor}'s turn{data.round ? ` (round ${data.round})` : ""}</span>
{/if}
</div>
{/if}
</header>
{#if failed}
<div class="share-note">
<p>This replay has wandered off — the link may be old, or the game gone.</p>
<a class="share-cta" href="/">▶ deal yourself into a fresh game</a>
</div>
{:else if !data}
<div class="share-note"><p>Rebuilding the turn…</p></div>
{:else}
<div class="share-stage">
{#key reelKey}
<Replay steps={data.steps} moment pov={data.actor} endLabel="⟲ watch it again"
onclose={() => (reelKey += 1)} />
{/key}
</div>
<div class="share-play">
<a class="share-cta" href="/">▶ play free — two to six wizards enter the maze</a>
</div>
{/if}
<footer class="share-foot">
<p>
<strong>Wiz-War</strong> is Tom Jolly's 1983 game of magical combat in
a stone labyrinth. This digital table is an unofficial, non-commercial
fan re-creation of the sixth edition (Chessex, 1993) — every card
transcribed, every wall verified, replays rebuilt move by move from
the game's own ledger.
</p>
</footer>
</div>
<style>
.share-page {
min-height: 100vh;
background: #171a20;
color: #d8d2c0;
font-family: "Archivo Narrow", system-ui, sans-serif;
display: flex;
flex-direction: column;
align-items: center;
padding: 1rem;
}
.share-head {
display: flex;
align-items: baseline;
flex-wrap: wrap;
gap: 1rem;
width: min(46rem, 100%);
margin-bottom: 0.6rem;
}
.share-brand {
font-family: "Oswald", sans-serif;
letter-spacing: 0.3em;
font-size: 1.1rem;
color: #e0b34a;
text-decoration: none;
}
.share-title {
font-family: "Oswald", sans-serif;
text-transform: uppercase;
letter-spacing: 0.12em;
font-size: 0.85rem;
color: #e9e1cb;
}
.share-deed { display: block; }
.share-sub { display: block; text-transform: none; letter-spacing: 0.02em; color: #8d8672; font-size: 0.8rem; }
.share-play { width: min(46rem, 100%); margin-top: 0.8rem; text-align: center; }
/* The reel is a modal everywhere else; here it stands in the page. */
.share-stage { width: min(46rem, 100%); }
.share-stage :global(.replay-scrim) {
position: relative;
inset: auto;
background: none;
padding: 0;
display: block;
z-index: 1;
}
.share-stage :global(.replay) { width: 100%; max-height: none; }
.share-note {
background: #efe8d4;
color: #3a2f1f;
border-radius: 4px;
padding: 1.2rem 1.5rem;
margin: 2rem 0;
font-family: "Courier Prime", monospace;
text-align: center;
}
.share-foot {
width: min(46rem, 100%);
margin-top: 1rem;
color: #8d8672;
font-size: 0.9rem;
line-height: 1.5;
}
.share-foot strong { color: #c9a72a; }
.share-cta {
display: inline-block;
margin-top: 0.4rem;
background: #e9e1cb;
color: #43331f;
border: 1.5px solid #43331f;
border-radius: 3px;
padding: 0.45rem 0.9rem;
text-decoration: none;
font-family: "Oswald", sans-serif;
letter-spacing: 0.06em;
}
</style>