The front page and the about page point to the other games of the Kestrel's Nest
The list is the kit's family.json, shipped as /family.json and refreshed at deploy when the kit is beside this checkout. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Jm2auWk6RP71CjaAb4FMoG
This commit is contained in:
co-authored by
Claude Fable 5.1
parent
13a6fc2603
commit
9d087ccaa1
@@ -3,6 +3,10 @@
|
|||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
HOST="${1:?usage: deploy.sh <droplet-ip-or-host>}"
|
HOST="${1:?usage: deploy.sh <droplet-ip-or-host>}"
|
||||||
|
|
||||||
|
# The family list is the kit's; the site deploys with the kit's latest copy when the kit is beside this checkout.
|
||||||
|
KIT_FAMILY="$(dirname "$0")/../../game-kit/family.json"
|
||||||
|
[ -f "$KIT_FAMILY" ] && cp "$KIT_FAMILY" packages/web/public/family.json
|
||||||
|
|
||||||
npm run build --workspace=@wizwar/web
|
npm run build --workspace=@wizwar/web
|
||||||
# Old hashed chunks stay a week: a page loaded before this deploy can still
|
# Old hashed chunks stay a week: a page loaded before this deploy can still
|
||||||
# fetch the module it was built against instead of failing mid-game.
|
# fetch the module it was built against instead of failing mid-game.
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"family": "the Kestrel's Nest",
|
||||||
|
"games": [
|
||||||
|
{
|
||||||
|
"slug": "wizwar",
|
||||||
|
"name": "Wiz-War",
|
||||||
|
"url": "https://wizwar.kestrelsnest.social",
|
||||||
|
"pitch": "Steal treasure. Blast through walls. Outwit your friends.",
|
||||||
|
"players": "2 to 6",
|
||||||
|
"origin": "Tom Jolly's board game, sixth edition, 1993"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"slug": "waving-hands",
|
||||||
|
"name": "Waving Hands",
|
||||||
|
"url": "https://hands.kestrelsnest.social",
|
||||||
|
"pitch": "Duelling wizards. Two hands each. The right run of gestures is a spell.",
|
||||||
|
"players": "2 to 12",
|
||||||
|
"origin": "Richard Bartle's pencil-and-paper duel, 1977"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -10,6 +10,7 @@
|
|||||||
import Card from "./Card.svelte";
|
import Card from "./Card.svelte";
|
||||||
import Help from "./Help.svelte";
|
import Help from "./Help.svelte";
|
||||||
import { HELP_TABS, type HelpTab } from "./help-state";
|
import { HELP_TABS, type HelpTab } from "./help-state";
|
||||||
|
import { otherGames, type FamilyGame } from "./family";
|
||||||
import { summarizeMissed } from "./catchup";
|
import { summarizeMissed } from "./catchup";
|
||||||
import Replay from "./Replay.svelte";
|
import Replay from "./Replay.svelte";
|
||||||
import FxGallery from "./FxGallery.svelte";
|
import FxGallery from "./FxGallery.svelte";
|
||||||
@@ -26,6 +27,11 @@
|
|||||||
net.startGamePolling();
|
net.startGamePolling();
|
||||||
|
|
||||||
let name = $state(prefs.wizardName);
|
let name = $state(prefs.wizardName);
|
||||||
|
/** The other games of the Kestrel's Nest, for the front page. */
|
||||||
|
let family = $state<FamilyGame[]>([]);
|
||||||
|
$effect(() => {
|
||||||
|
void otherGames("wizwar").then((g) => (family = g));
|
||||||
|
});
|
||||||
let joinCode = $state("");
|
let joinCode = $state("");
|
||||||
let claimPhrase = $state("");
|
let claimPhrase = $state("");
|
||||||
/** A /join/<CODE> recruiting link. The visitor lands looking at the
|
/** A /join/<CODE> recruiting link. The visitor lands looking at the
|
||||||
@@ -2561,6 +2567,10 @@
|
|||||||
<img src="/hero.jpg" alt="Through a wizard's eyes: an ambush watches its corridor through a wormhole" />
|
<img src="/hero.jpg" alt="Through a wizard's eyes: an ambush watches its corridor through a wormhole" />
|
||||||
</a>
|
</a>
|
||||||
<a class="about-link clips-link" href="/clips">watch clips from the labyrinth</a>
|
<a class="about-link clips-link" href="/clips">watch clips from the labyrinth</a>
|
||||||
|
{#if family.length}
|
||||||
|
<p class="family-line">Also at the Kestrel's Nest:
|
||||||
|
{#each family as g, i (g.slug)}{i > 0 ? ", " : " "}<a href={g.url} title={g.pitch}>{g.name}</a>{/each}</p>
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<details class="more-ways" open={local.hasSave()}>
|
<details class="more-ways" open={local.hasSave()}>
|
||||||
@@ -4108,6 +4118,8 @@
|
|||||||
margin-bottom: 1.2rem;
|
margin-bottom: 1.2rem;
|
||||||
}
|
}
|
||||||
.about-link:hover { color: #43331f; }
|
.about-link:hover { color: #43331f; }
|
||||||
|
.family-line { margin: 0.5rem 0 0; font-size: 0.85rem; color: #7b6a51; }
|
||||||
|
.family-line a { color: #5a4630; }
|
||||||
.clips-link { display: inline-block; margin: 0 0 0.9rem; }
|
.clips-link { display: inline-block; margin: 0 0 0.9rem; }
|
||||||
.field { display: block; text-align: left; margin-bottom: 1rem; }
|
.field { display: block; text-align: left; margin-bottom: 1rem; }
|
||||||
.field span {
|
.field span {
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
// body, the card library with proper entries, the house rulings, the
|
// body, the card library with proper entries, the house rulings, the
|
||||||
// story of the table, and the tally. Every section and card has a link
|
// story of the table, and the tally. Every section and card has a link
|
||||||
// a player can send; the desk remembers where the reader was.
|
// a player can send; the desk remembers where the reader was.
|
||||||
|
import { otherGames, type FamilyGame } from "./family";
|
||||||
import { cardDef } from "@wizwar/engine";
|
import { cardDef } from "@wizwar/engine";
|
||||||
import { tick } from "svelte";
|
import { tick } from "svelte";
|
||||||
import Card from "./Card.svelte";
|
import Card from "./Card.svelte";
|
||||||
@@ -127,6 +128,11 @@
|
|||||||
if (initialAnchor) void jumpTo(initialAnchor);
|
if (initialAnchor) void jumpTo(initialAnchor);
|
||||||
else if (bodyEl) bodyEl.scrollTop = helpState.scroll[tab] ?? 0;
|
else if (bodyEl) bodyEl.scrollTop = helpState.scroll[tab] ?? 0;
|
||||||
});
|
});
|
||||||
|
/** The other games of the Kestrel's Nest, for the about page. */
|
||||||
|
let family = $state<FamilyGame[]>([]);
|
||||||
|
$effect(() => {
|
||||||
|
void otherGames("wizwar").then((g) => (family = g));
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#snippet section(s: RefSection, withSource: boolean)}
|
{#snippet section(s: RefSection, withSource: boolean)}
|
||||||
@@ -311,6 +317,14 @@
|
|||||||
The keeper of this table roosts at
|
The keeper of this table roosts at
|
||||||
<a href="https://kestrelsnest.social" target="_blank" rel="noreferrer">kestrelsnest.social</a>.
|
<a href="https://kestrelsnest.social" target="_blank" rel="noreferrer">kestrelsnest.social</a>.
|
||||||
</p>
|
</p>
|
||||||
|
{#if family.length}
|
||||||
|
<h3>More games at the Kestrel's Nest</h3>
|
||||||
|
<ul class="family">
|
||||||
|
{#each family as g (g.slug)}
|
||||||
|
<li><a href={g.url}>{g.name}</a>: {g.pitch} <span class="family-note">{g.players} players; {g.origin}.</span></li>
|
||||||
|
{/each}
|
||||||
|
</ul>
|
||||||
|
{/if}
|
||||||
<p class="colophon">
|
<p class="colophon">
|
||||||
Wiz-War © Tom Jolly. Sixth edition published by Chessex, 1993.
|
Wiz-War © Tom Jolly. Sixth edition published by Chessex, 1993.
|
||||||
Built by Eric and a very enthusiastic AI, 2026.
|
Built by Eric and a very enthusiastic AI, 2026.
|
||||||
@@ -691,4 +705,7 @@
|
|||||||
.entry-card :global(.card:hover) { transform: none; }
|
.entry-card :global(.card:hover) { transform: none; }
|
||||||
.contents { columns: 1; }
|
.contents { columns: 1; }
|
||||||
}
|
}
|
||||||
|
.family { margin: 0.4rem 0 0.8rem 1.2rem; padding: 0; }
|
||||||
|
.family li + li { margin-top: 0.3rem; }
|
||||||
|
.family-note { color: #7b6a51; }
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
// The other games of the Kestrel's Nest, from the family list the kit keeps
|
||||||
|
// (family.json, one copy shipped with every game), so a player who liked one
|
||||||
|
// table can find the next. This game is left out of its own list.
|
||||||
|
|
||||||
|
export interface FamilyGame {
|
||||||
|
slug: string;
|
||||||
|
name: string;
|
||||||
|
url: string;
|
||||||
|
pitch: string;
|
||||||
|
players: string;
|
||||||
|
origin: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function otherGames(self: string): Promise<FamilyGame[]> {
|
||||||
|
try {
|
||||||
|
const res = await fetch('/family.json', { cache: 'no-cache' });
|
||||||
|
if (!res.ok) return [];
|
||||||
|
const data = (await res.json()) as { games?: FamilyGame[] };
|
||||||
|
return (data.games ?? []).filter((g) => g.slug !== self);
|
||||||
|
} catch {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user