Compare commits
3
Commits
40a52dfe37
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ee7b1d68ec | ||
|
|
c6366853cd | ||
|
|
647e8cb2bf |
No files matched your search
@@ -294,6 +294,10 @@ and read each new page against this list before it ships.
|
|||||||
deliberate triplet as its tagline when the energy is the point, as
|
deliberate triplet as its tagline when the energy is the point, as
|
||||||
Wiz-War's "Steal treasure. Blast through walls. Outwit your friends."
|
Wiz-War's "Steal treasure. Blast through walls. Outwit your friends."
|
||||||
does. Once is a choice, and a pattern is a tell.
|
does. Once is a choice, and a pattern is a tell.
|
||||||
|
- **No one sentence shape for a whole list.** Thirty chronicle lines that
|
||||||
|
each run image, colon, consequence read as a template however good the
|
||||||
|
images are. Let the fact come first in some, in its own sentence in others,
|
||||||
|
and joined by "and" or "which" in the rest.
|
||||||
- **No closing aphorisms.** A paragraph ends on its last fact, not on a line
|
- **No closing aphorisms.** A paragraph ends on its last fact, not on a line
|
||||||
meant to sound wise ("This is the whole art", "Nothing is unlocked by it
|
meant to sound wise ("This is the whole art", "Nothing is unlocked by it
|
||||||
and nothing is withheld without it").
|
and nothing is withheld without it").
|
||||||
@@ -351,6 +355,11 @@ opened its mouth.") as long as the fact survives.
|
|||||||
- `npm install --omit=optional` on the droplet breaks Vite; the server
|
- `npm install --omit=optional` on the droplet breaks Vite; the server
|
||||||
carries its own small `package.json` and installs only that.
|
carries its own small `package.json` and installs only that.
|
||||||
- A dynamic `/join/[code]` route must set `prerender = false`.
|
- A dynamic `/join/[code]` route must set `prerender = false`.
|
||||||
|
- The hall, the rules and the guide set `ssr = true` in their own `+page.ts`,
|
||||||
|
so view-source, a crawler and a reader without scripts get the text; the
|
||||||
|
rest stays an empty shell. The adapter's fallback is `shell.html`, not
|
||||||
|
`index.html`, because a fallback named `index.html` overwrites the
|
||||||
|
prerendered hall, and Caddy's `try_files` ends in `/shell.html`.
|
||||||
- Two tabs on one origin share a seat; test a second player from
|
- Two tabs on one origin share a seat; test a second player from
|
||||||
`127.0.0.1` against `localhost` (Vite needs `--host`).
|
`127.0.0.1` against `localhost` (Vite needs `--host`).
|
||||||
- Svelte proxies do not `structuredClone`: hand the engine `$state.snapshot`.
|
- Svelte proxies do not `structuredClone`: hand the engine `$state.snapshot`.
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ SLUG="${2:-$(basename "$(cd "$GAME" && pwd)")}"
|
|||||||
KIT="$(cd "$(dirname "$0")" && pwd)"
|
KIT="$(cd "$(dirname "$0")" && pwd)"
|
||||||
MAP="$KIT/drift-map/$SLUG.txt"
|
MAP="$KIT/drift-map/$SLUG.txt"
|
||||||
SHARED="server/src/index.ts server/src/rooms.ts server/src/store.ts server/src/reports.ts server/src/ratelimit.ts server/src/tally.ts
|
SHARED="server/src/index.ts server/src/rooms.ts server/src/store.ts server/src/reports.ts server/src/ratelimit.ts server/src/tally.ts
|
||||||
|
src/routes/+layout.ts src/routes/+page.ts src/routes/rules/+page.ts src/routes/guide/+page.ts vite.config.ts
|
||||||
src/lib/game/spec.ts src/lib/net/client.ts src/lib/net/talk.ts src/lib/net/view.ts src/lib/net/room.svelte.ts
|
src/lib/game/spec.ts src/lib/net/client.ts src/lib/net/talk.ts src/lib/net/view.ts src/lib/net/room.svelte.ts
|
||||||
src/lib/components/TableTalk.svelte src/lib/components/ReportSlip.svelte src/lib/components/TabbedDialog.svelte src/lib/components/Tabs.svelte src/lib/components/Announcement.svelte
|
src/lib/components/TableTalk.svelte src/lib/components/ReportSlip.svelte src/lib/components/TabbedDialog.svelte src/lib/components/Tabs.svelte src/lib/components/Announcement.svelte
|
||||||
deploy/deploy.sh deploy/setup-droplet.sh deploy/setup-server.sh deploy/Caddyfile.tmpl deploy/verify-ledgers.sh
|
deploy/deploy.sh deploy/setup-droplet.sh deploy/setup-server.sh deploy/Caddyfile.tmpl deploy/verify-ledgers.sh
|
||||||
|
|||||||
+1
-1
@@ -31,7 +31,7 @@
|
|||||||
"name": "Dip",
|
"name": "Dip",
|
||||||
"url": "https://dip.kestrelsnest.social",
|
"url": "https://dip.kestrelsnest.social",
|
||||||
"pitch": "Seven powers share one map, and none of them has to keep its word.",
|
"pitch": "Seven powers share one map, and none of them has to keep its word.",
|
||||||
"players": "2 to 7",
|
"players": "2 to 10, depending on the map",
|
||||||
"origin": "Allan B. Calhamer's Diplomacy, 1959; an unofficial rebuild"
|
"origin": "Allan B. Calhamer's Diplomacy, 1959; an unofficial rebuild"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
+19
-41
@@ -4,7 +4,7 @@
|
|||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<title>Kestrel's Hall</title>
|
<title>Kestrel's Hall</title>
|
||||||
<meta name="description" content="Free board games in the browser: Wiz-War, Waving Hands, Hnefatafl, and Dip. Play with friends by link or alone against the house. No accounts, no adverts, nothing sold.">
|
<meta name="description" content="Free board games in the browser: Wiz-War, Waving Hands, Hnefatafl, Dip and Wyrm. Play with friends by link or alone against the house. No accounts, no adverts, nothing sold.">
|
||||||
<link rel="canonical" href="https://gamehall.kestrelsnest.social/">
|
<link rel="canonical" href="https://gamehall.kestrelsnest.social/">
|
||||||
<meta property="og:type" content="website">
|
<meta property="og:type" content="website">
|
||||||
<meta property="og:title" content="Kestrel's Hall">
|
<meta property="og:title" content="Kestrel's Hall">
|
||||||
@@ -58,18 +58,22 @@
|
|||||||
header.mast p.newcomer { margin: 10px auto 0; max-width: 38em; font-size: 0.95rem; color: var(--bone-dim); }
|
header.mast p.newcomer { margin: 10px auto 0; max-width: 38em; font-size: 0.95rem; color: var(--bone-dim); }
|
||||||
|
|
||||||
.games { display: grid; gap: 22px; grid-template-columns: repeat(2, 1fr); margin-top: 22px; }
|
.games { display: grid; gap: 22px; grid-template-columns: repeat(2, 1fr); margin-top: 22px; }
|
||||||
@media (max-width: 860px) { .games { grid-template-columns: 1fr; max-width: 520px; margin-left: auto; margin-right: auto; } }
|
|
||||||
.game {
|
.game {
|
||||||
display: flex; flex-direction: column;
|
display: flex; flex-direction: column;
|
||||||
background: var(--panel);
|
background: var(--panel);
|
||||||
border: 1px solid var(--line);
|
border: 1px solid var(--line);
|
||||||
border-radius: 12px;
|
border-radius: 6px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
color: inherit;
|
color: inherit;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
transition: transform .15s ease, border-color .15s ease;
|
|
||||||
}
|
}
|
||||||
.game:hover { transform: translateY(-3px); border-color: var(--gold); text-decoration: none; }
|
.game:hover { border-color: var(--gold); text-decoration: none; }
|
||||||
|
.game:hover h2 { color: var(--gold); }
|
||||||
|
/* The first game is the one newcomers are pointed at, so it has the whole width: art beside the words. */
|
||||||
|
.game.first { grid-column: 1 / -1; flex-direction: row; }
|
||||||
|
.game.first .art { flex: 0 0 55%; aspect-ratio: 2 / 1; }
|
||||||
|
.game.first .body { justify-content: center; padding: 22px 26px; }
|
||||||
|
.game.first h2 { font-size: 1.9rem; }
|
||||||
.game .art { position: relative; aspect-ratio: 5 / 3; background: #222; overflow: hidden; }
|
.game .art { position: relative; aspect-ratio: 5 / 3; background: #222; overflow: hidden; }
|
||||||
.game .art img.fill { width: 100%; height: 100%; object-fit: cover; display: block; }
|
.game .art img.fill { width: 100%; height: 100%; object-fit: cover; display: block; }
|
||||||
.game .art img.tafl { object-position: 50% 50%; }
|
.game .art img.tafl { object-position: 50% 50%; }
|
||||||
@@ -77,13 +81,15 @@
|
|||||||
.game .body { padding: 16px 18px 18px; display: flex; flex-direction: column; gap: 6px; flex: 1; }
|
.game .body { padding: 16px 18px 18px; display: flex; flex-direction: column; gap: 6px; flex: 1; }
|
||||||
.game h2 { margin: 0; font-size: 1.45rem; font-weight: 500; }
|
.game h2 { margin: 0; font-size: 1.45rem; font-weight: 500; }
|
||||||
.game .pitch { margin: 0; color: var(--bone); }
|
.game .pitch { margin: 0; color: var(--bone); }
|
||||||
.game .players, .game .length { margin: 0; color: var(--bone-dim); font-size: 0.92rem; }
|
.game .players { margin: 0; color: var(--bone-dim); font-size: 0.92rem; }
|
||||||
.game .origin { margin: 0; color: var(--bone-dim); font-size: 0.85rem; }
|
.game .origin { margin: 0; color: var(--bone-dim); font-size: 0.85rem; }
|
||||||
.game .length:empty { display: none; }
|
@media (max-width: 860px) {
|
||||||
.game .go { margin-top: auto; padding-top: 10px; display: flex; flex-direction: column; gap: 2px; }
|
.games { grid-template-columns: 1fr; max-width: 520px; margin-left: auto; margin-right: auto; }
|
||||||
.game .play { color: var(--gold); font-size: 1rem; }
|
.game.first { flex-direction: column; }
|
||||||
.game .site { color: var(--bone-dim); font-size: 0.85rem; }
|
.game.first .art { flex: none; aspect-ratio: 5 / 3; }
|
||||||
.game:hover .play { text-decoration: underline; }
|
.game.first .body { padding: 16px 18px 18px; }
|
||||||
|
.game.first h2 { font-size: 1.45rem; }
|
||||||
|
}
|
||||||
.nowrap { white-space: nowrap; }
|
.nowrap { white-space: nowrap; }
|
||||||
|
|
||||||
/* The maze card: a stone floor in CSS, mortar-coloured walls, and the hall's own token drawings. */
|
/* The maze card: a stone floor in CSS, mortar-coloured walls, and the hall's own token drawings. */
|
||||||
@@ -128,14 +134,11 @@
|
|||||||
<header class="mast">
|
<header class="mast">
|
||||||
<h1>Kestrel's Hall</h1>
|
<h1>Kestrel's Hall</h1>
|
||||||
<p class="lede"><b>Free board games in the browser.</b> Play alone against the house right away, or start a table and send friends the link. No accounts, no adverts, no timers, and nothing sold.</p>
|
<p class="lede"><b>Free board games in the browser.</b> Play alone against the house right away, or start a table and send friends the link. No accounts, no adverts, no timers, and nothing sold.</p>
|
||||||
<!-- A word for newcomers, one line to tune or remove. -->
|
|
||||||
<p class="newcomer">New here? Wiz-War is the friendliest place to start, and a game can be over in minutes. Dip is the long game.</p>
|
<p class="newcomer">New here? Wiz-War is the friendliest place to start, and a game can be over in minutes. Dip is the long game.</p>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<div class="games">
|
<div class="games">
|
||||||
<!-- Each card's empty "length" line is for a typical game's length, once real play has shown it; it is hidden until filled. -->
|
<a class="game first" href="https://wizwar.kestrelsnest.social">
|
||||||
|
|
||||||
<a class="game" href="https://wizwar.kestrelsnest.social">
|
|
||||||
<div class="art">
|
<div class="art">
|
||||||
<div class="maze">
|
<div class="maze">
|
||||||
<div class="w h" style="left:12.5%;top:19%;width:25%"></div>
|
<div class="w h" style="left:12.5%;top:19%;width:25%"></div>
|
||||||
@@ -159,11 +162,6 @@
|
|||||||
<p class="pitch">Steal treasure. Blast through walls. Outwit your friends.</p>
|
<p class="pitch">Steal treasure. Blast through walls. Outwit your friends.</p>
|
||||||
<p class="players">2 to 6 players, or solo against the automaton</p>
|
<p class="players">2 to 6 players, or solo against the automaton</p>
|
||||||
<p class="origin">An unofficial rebuild of Tom Jolly's 1993 board game</p>
|
<p class="origin">An unofficial rebuild of Tom Jolly's 1993 board game</p>
|
||||||
<p class="length"></p>
|
|
||||||
<div class="go">
|
|
||||||
<span class="play">Play Wiz-War →</span>
|
|
||||||
<span class="site">wizwar.kestrelsnest.social</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
@@ -174,11 +172,6 @@
|
|||||||
<p class="pitch">Wizards duel with their hands, and the right run of gestures is a spell.</p>
|
<p class="pitch">Wizards duel with their hands, and the right run of gestures is a spell.</p>
|
||||||
<p class="players">2 to 12 players, or solo against a rival wizard</p>
|
<p class="players">2 to 12 players, or solo against a rival wizard</p>
|
||||||
<p class="origin">Richard Bartle's pencil-and-paper duel, 1977</p>
|
<p class="origin">Richard Bartle's pencil-and-paper duel, 1977</p>
|
||||||
<p class="length"></p>
|
|
||||||
<div class="go">
|
|
||||||
<span class="play">Play Waving Hands →</span>
|
|
||||||
<span class="site">hands.kestrelsnest.social</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
@@ -189,11 +182,6 @@
|
|||||||
<p class="pitch">The Viking board game of a king breaking for safety and a ring of attackers closing in.</p>
|
<p class="pitch">The Viking board game of a king breaking for safety and a ring of attackers closing in.</p>
|
||||||
<p class="players">2 players, or solo against the housecarl</p>
|
<p class="players">2 players, or solo against the housecarl</p>
|
||||||
<p class="origin">The Norse tafl game, by the Copenhagen rules or Linnaeus's 1732 Tablut</p>
|
<p class="origin">The Norse tafl game, by the Copenhagen rules or Linnaeus's 1732 Tablut</p>
|
||||||
<p class="length"></p>
|
|
||||||
<div class="go">
|
|
||||||
<span class="play">Play Hnefatafl →</span>
|
|
||||||
<span class="site">tafl.kestrelsnest.social</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
@@ -204,11 +192,6 @@
|
|||||||
<p class="pitch">Seven powers share one map, and none of them has to keep its word. Other maps seat five or ten.</p>
|
<p class="pitch">Seven powers share one map, and none of them has to keep its word. Other maps seat five or ten.</p>
|
||||||
<p class="players">2 to 10 players, depending on the map, or solo against the house's envoys</p>
|
<p class="players">2 to 10 players, depending on the map, or solo against the house's envoys</p>
|
||||||
<p class="origin">An unofficial rebuild of Allan B. Calhamer's 1959 Diplomacy</p>
|
<p class="origin">An unofficial rebuild of Allan B. Calhamer's 1959 Diplomacy</p>
|
||||||
<p class="length"></p>
|
|
||||||
<div class="go">
|
|
||||||
<span class="play">Play Dip →</span>
|
|
||||||
<span class="site">dip.kestrelsnest.social</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
@@ -219,11 +202,6 @@
|
|||||||
<p class="pitch">Chess on three boards, with a Dragon in the air above your King and a Basilisk in the earth below.</p>
|
<p class="pitch">Chess on three boards, with a Dragon in the air above your King and a Basilisk in the earth below.</p>
|
||||||
<p class="players">2 players, or solo against the drake</p>
|
<p class="players">2 players, or solo against the drake</p>
|
||||||
<p class="origin">An unofficial rebuild of Gary Gygax's 1985 Dragonchess, from Dragon Magazine No. 100</p>
|
<p class="origin">An unofficial rebuild of Gary Gygax's 1985 Dragonchess, from Dragon Magazine No. 100</p>
|
||||||
<p class="length"></p>
|
|
||||||
<div class="go">
|
|
||||||
<span class="play">Play Wyrm →</span>
|
|
||||||
<span class="site">wyrm.kestrelsnest.social</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
@@ -250,7 +228,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<h2 style="margin-top:28px">The hosting</h2>
|
<h2 style="margin-top:28px">The hosting</h2>
|
||||||
<p>The games are free and will stay free. They run on small servers that cost a few dollars a month, and anyone who wants to chip in toward the hosting can do so at <a class="nowrap" href="https://ko-fi.com/kestrelsnest">ko-fi.com/kestrelsnest</a>. Chipping in unlocks nothing, and nothing is held back without it.</p>
|
<p>The games are free and will stay free. They run on small servers that cost a few dollars a month, and anyone who wants to chip in toward the hosting can do so at <a class="nowrap" href="https://ko-fi.com/kestrelsnest">ko-fi.com/kestrelsnest</a>. Chipping in unlocks nothing, and the games are the same for everyone.</p>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
|||||||
@@ -43,7 +43,10 @@ __HOST__ {
|
|||||||
header @immutable Cache-Control "public, max-age=31536000, immutable"
|
header @immutable Cache-Control "public, max-age=31536000, immutable"
|
||||||
@mutable not path /_app/immutable/*
|
@mutable not path /_app/immutable/*
|
||||||
header @mutable Cache-Control "no-cache"
|
header @mutable Cache-Control "no-cache"
|
||||||
try_files {path} {path}.html /index.html
|
# try_files passes the root over as a directory and would fall to the shell; the hall's
|
||||||
|
# own page is index.html.
|
||||||
|
rewrite / /index.html
|
||||||
|
try_files {path} {path}.html /shell.html
|
||||||
file_server
|
file_server
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -25,7 +25,7 @@ rsync -az --delete --filter='P _app/immutable/*' \
|
|||||||
# it needs only its code and the engine. Ledgers live outside this tree.
|
# it needs only its code and the engine. Ledgers live outside this tree.
|
||||||
rsync -az --delete --exclude='/server/node_modules' \
|
rsync -az --delete --exclude='/server/node_modules' \
|
||||||
--include='/server/***' --include='/src/' --include='/src/lib/' --include='/src/lib/game/***' --include='/src/lib/net/' --include='/src/lib/net/view.ts' \
|
--include='/server/***' --include='/src/' --include='/src/lib/' --include='/src/lib/game/***' --include='/src/lib/net/' --include='/src/lib/net/view.ts' \
|
||||||
--include='/deploy/' --include='/deploy/__SLUG__.service' --include='/deploy/__SLUG__.cron' --include='/deploy/*.sh' --include='/deploy/traffic.py' \
|
--include='/deploy/' --include='/deploy/__SLUG__.service' --include='/deploy/__SLUG__.cron' --include='/deploy/*.sh' --include='/deploy/Caddyfile.tmpl' --include='/deploy/traffic.py' \
|
||||||
--exclude='*' \
|
--exclude='*' \
|
||||||
./ "root@$HOST:/opt/__SLUG__/app/"
|
./ "root@$HOST:/opt/__SLUG__/app/"
|
||||||
|
|
||||||
@@ -60,6 +60,12 @@ ssh "root@$HOST" '
|
|||||||
systemctl daemon-reload
|
systemctl daemon-reload
|
||||||
systemctl enable --now __SLUG__
|
systemctl enable --now __SLUG__
|
||||||
systemctl restart __SLUG__
|
systemctl restart __SLUG__
|
||||||
|
# The site file is rendered from the template on every deploy, with the address line the box was set up with.
|
||||||
|
ADDR=$(head -1 /etc/caddy/sites/__SLUG__.caddy | sed "s/ {\$//")
|
||||||
|
sed "s|__HOST__|$ADDR|" /opt/__SLUG__/app/deploy/Caddyfile.tmpl > /etc/caddy/sites/__SLUG__.caddy
|
||||||
|
caddy validate --config /etc/caddy/Caddyfile >/dev/null
|
||||||
|
# Validation as root touches the access logs; Caddy runs as caddy and must own them.
|
||||||
|
chown -R caddy:caddy /var/lib/caddy
|
||||||
systemctl reload caddy
|
systemctl reload caddy
|
||||||
# "active" means only that the process started: wait for it to answer HTTP,
|
# "active" means only that the process started: wait for it to answer HTTP,
|
||||||
# so a server that dies loading its code fails the deploy instead of looping.
|
# so a server that dies loading its code fails the deploy instead of looping.
|
||||||
|
|||||||
@@ -12,11 +12,13 @@ from collections import Counter, defaultdict
|
|||||||
BOT = re.compile(r"bot|crawl|spider|slurp|facebookexternalhit|slack|discord|twitter|whatsapp|telegram|preview|fetch|curl|python|go-http|wget|headless|scan|monitor|uptime", re.I)
|
BOT = re.compile(r"bot|crawl|spider|slurp|facebookexternalhit|slack|discord|twitter|whatsapp|telegram|preview|fetch|curl|python|go-http|wget|headless|scan|monitor|uptime", re.I)
|
||||||
# Link previews: the app that fetched the page to show it in a conversation. The people
|
# Link previews: the app that fetched the page to show it in a conversation. The people
|
||||||
# who click such a link arrive with no referrer, so this is the best sign of where it went.
|
# who click such a link arrive with no referrer, so this is the best sign of where it went.
|
||||||
|
# Meta's crawlers (meta-externalagent, meta-webindexer) name facebook.com too but share nothing.
|
||||||
UNFURL = [
|
UNFURL = [
|
||||||
("Slack", re.compile(r"slack", re.I)),
|
("Slack", re.compile(r"slack", re.I)),
|
||||||
("Discord", re.compile(r"discord", re.I)),
|
("Discord", re.compile(r"discord", re.I)),
|
||||||
|
("Google Messages", re.compile(r"googlemessages", re.I)),
|
||||||
("iMessage", re.compile(r"facebookexternalhit.*twitterbot|applebot.*preview", re.I)),
|
("iMessage", re.compile(r"facebookexternalhit.*twitterbot|applebot.*preview", re.I)),
|
||||||
("Facebook", re.compile(r"facebookexternalhit|facebot|meta-externalagent", re.I)),
|
("Facebook", re.compile(r"facebookexternalhit|facebot", re.I)),
|
||||||
("WhatsApp", re.compile(r"whatsapp", re.I)),
|
("WhatsApp", re.compile(r"whatsapp", re.I)),
|
||||||
("Telegram", re.compile(r"telegram", re.I)),
|
("Telegram", re.compile(r"telegram", re.I)),
|
||||||
("Reddit", re.compile(r"redditbot", re.I)),
|
("Reddit", re.compile(r"redditbot", re.I)),
|
||||||
@@ -144,8 +146,10 @@ def scan(day, logs=LOGS):
|
|||||||
t["bot"] += 1
|
t["bot"] += 1
|
||||||
t["botAddresses"].add(ip)
|
t["botAddresses"].add(ip)
|
||||||
service = unfurled_by(ua)
|
service = unfurled_by(ua)
|
||||||
# One share per app and page: a preview is often fetched twice.
|
# One share per app and page: a preview is often fetched twice. Only a
|
||||||
if service and (service, path) not in seen_share:
|
# page counts: an app that already holds a preview fetches its card
|
||||||
|
# image again to show it, and robots.txt and scripts are not shared.
|
||||||
|
if service and (path in PAGES or path.startswith("/join/")) and (service, path) not in seen_share:
|
||||||
seen_share.add((service, path))
|
seen_share.add((service, path))
|
||||||
t["shared"][service] += 1
|
t["shared"][service] += 1
|
||||||
continue
|
continue
|
||||||
|
|||||||
@@ -324,7 +324,7 @@
|
|||||||
{/snippet}
|
{/snippet}
|
||||||
{#snippet aboutTally()}
|
{#snippet aboutTally()}
|
||||||
{#if told}
|
{#if told}
|
||||||
<TallyBoard title="How much love the table is getting" headline={told.headline} details={told.details} note={told.note} />
|
<TallyBoard title="What the ledgers add up to" headline={told.headline} details={told.details} note={told.note} />
|
||||||
{:else}
|
{:else}
|
||||||
<p class="muted">Counting…</p>
|
<p class="muted">Counting…</p>
|
||||||
{/if}
|
{/if}
|
||||||
@@ -340,7 +340,7 @@
|
|||||||
{/if}
|
{/if}
|
||||||
<p class="hosting">
|
<p class="hosting">
|
||||||
The games of Kestrel's Hall are free and will stay free. They run on small servers that cost a few dollars a month, and anyone who wants to chip in toward the hosting can do so at
|
The games of Kestrel's Hall are free and will stay free. They run on small servers that cost a few dollars a month, and anyone who wants to chip in toward the hosting can do so at
|
||||||
<a href="https://ko-fi.com/kestrelsnest" target="_blank" rel="noreferrer">ko-fi.com/kestrelsnest</a>. Chipping in unlocks nothing, and nothing is held back without it.
|
<a href="https://ko-fi.com/kestrelsnest" target="_blank" rel="noreferrer">ko-fi.com/kestrelsnest</a>. Chipping in unlocks nothing, and the games are the same for everyone.
|
||||||
</p>
|
</p>
|
||||||
<p class="made">__NAME__ is its designer's.</p>
|
<p class="made">__NAME__ is its designer's.</p>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
// Everything happens in the browser against the game server, so nothing is rendered on the
|
// Everything happens in the browser against the game server, so by default a route is
|
||||||
// server. Each route is prerendered as an empty shell that the client fills in.
|
// prerendered as an empty shell that the client fills in. The hall, the rules and the guide
|
||||||
|
// turn rendering on in their own +page.ts, since their text is the same for everyone.
|
||||||
export const ssr = false;
|
export const ssr = false;
|
||||||
export const prerender = true;
|
export const prerender = true;
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
// The page is text that reads the same for everyone, so it is rendered at build time and served whole;
|
||||||
|
// the browser-only state it shows (held seats, the saved name) fills in once the script runs.
|
||||||
|
export const ssr = true;
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
// The page is text that reads the same for everyone, so it is rendered at build time and served whole;
|
||||||
|
// the browser-only state it shows (held seats, the saved name) fills in once the script runs.
|
||||||
|
export const ssr = true;
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
// The page is text that reads the same for everyone, so it is rendered at build time and served whole;
|
||||||
|
// the browser-only state it shows (held seats, the saved name) fills in once the script runs.
|
||||||
|
export const ssr = true;
|
||||||
@@ -10,7 +10,9 @@ export default defineConfig({
|
|||||||
runes: ({ filename }) => (filename.split(/[/\\]/).includes('node_modules') ? undefined : true)
|
runes: ({ filename }) => (filename.split(/[/\\]/).includes('node_modules') ? undefined : true)
|
||||||
},
|
},
|
||||||
// A static build: Caddy serves files and proxies /api and /ws to the game server.
|
// A static build: Caddy serves files and proxies /api and /ws to the game server.
|
||||||
adapter: adapter({ fallback: 'index.html' })
|
// The fallback is the empty shell Caddy serves for a room address; it is not index.html, so the
|
||||||
|
// hall's own prerendered page is not overwritten by it.
|
||||||
|
adapter: adapter({ fallback: 'shell.html' })
|
||||||
})
|
})
|
||||||
],
|
],
|
||||||
server: {
|
server: {
|
||||||
|
|||||||
Reference in new issue
Block a user