Add standalone TL;DR rulebook reference

This commit is contained in:
2026-09-04 19:51:15 -07:00
parent 5c761b079d
commit 1dac2141e6
7 changed files with 205 additions and 3 deletions
+2
View File
@@ -36,6 +36,8 @@ When you run into a repeatable problem during testing (e.g. port assignment coll
- Multi-socket TestClient tests must use `with TestClient(app) as client` so sockets share one event loop; separate portals can hang on cross-socket broadcasts. Close sockets explicitly before asserting disconnect messages. Override lifespan when using an isolated test database. - Multi-socket TestClient tests must use `with TestClient(app) as client` so sockets share one event loop; separate portals can hang on cross-socket broadcasts. Close sockets explicitly before asserting disconnect messages. Override lifespan when using an isolated test database.
- For browser QA, use a temporary database (for example, `DATABASE_URL=sqlite:////tmp/pirats-qa.db`) and `PIRATS_PURGE_ENABLED=false`. Normal app startup migrates the selected database and automatically purges stale games.
## Versioning & changelog ## Versioning & changelog
The app shows its version number and a player-facing changelog in the ☰ menu → About. Both come from `frontend/src/lib/changelog.js` (rendered by `frontend/src/components/AboutModal.svelte`). The app shows its version number and a player-facing changelog in the ☰ menu → About. Both come from `frontend/src/lib/changelog.js` (rendered by `frontend/src/components/AboutModal.svelte`).
+1 -1
View File
@@ -37,5 +37,5 @@
## Words Words Words ## Words Words Words
- [ ] **TLDR rules**. A one page summary sheet of the rulebook, for the impatient - [x] **TLDR rules**. A one page summary sheet of the rulebook, for the impatient
- [ ] **Suggestions.** Take a pass through all of the suggestions in suggestions.js, rewrite ones that are stiff, awkward, or don't fit the theme/setting as described in the rulebook. Move the suggestion pool from suggestions.js into a backend API endpoint. - [ ] **Suggestions.** Take a pass through all of the suggestions in suggestions.js, rewrite ones that are stiff, awkward, or don't fit the theme/setting as described in the rulebook. Move the suggestion pool from suggestions.js into a backend API endpoint.
+2 -1
View File
@@ -6,10 +6,11 @@
// - Add a CHANGELOG entry only when a commit changes something players can // - Add a CHANGELOG entry only when a commit changes something players can
// see. Skip refactors, tests, and tooling. Keep wording player-facing. // see. Skip refactors, tests, and tooling. Keep wording player-facing.
export const VERSION = 45; export const VERSION = 46;
// Newest first. Each entry: { version, date: 'YYYY-MM-DD', changes: [string, ...] }. // Newest first. Each entry: { version, date: 'YYYY-MM-DD', changes: [string, ...] }.
export const CHANGELOG = [ export const CHANGELOG = [
{ version: 46, date: '2026-09-04', changes: ['Added a printable TL;DR rules page, linked at the top of the full rulebook.'] },
{ version: 44, date: '2026-09-04', changes: ['Crew Objectives opens as one connected panel, with the toggle and checklist sharing a border and background.'] }, { version: 44, date: '2026-09-04', changes: ['Crew Objectives opens as one connected panel, with the toggle and checklist sharing a border and background.'] },
{ version: 43, date: '2026-09-04', changes: ['The crew roster keeps players in the same order when roles or ranks change, including when someone becomes the Deep.'] }, { version: 43, date: '2026-09-04', changes: ['The crew roster keeps players in the same order when roles or ranks change, including when someone becomes the Deep.'] },
{ version: 42, date: '2026-09-04', changes: ['Gats require a description, including older Gats that were left blank. Read descriptions on character sheets or hover over Gat icons in the crew roster.', 'Gat descriptions and prompts follow Gat Taxes correctly and clear when you create a new recruit.'] }, { version: 42, date: '2026-09-04', changes: ['Gats require a description, including older Gats that were left blank. Read descriptions on character sheets or hover over Gat icons in the crew roster.', 'Gat descriptions and prompts follow Gat Taxes correctly and clear when you create a new recruit.'] },
+1 -1
View File
@@ -29,7 +29,7 @@ pirats = "pirats.main:main"
where = ["src"] where = ["src"]
[tool.setuptools.package-data] [tool.setuptools.package-data]
pirats = ["static/**/*", "rules.html", "migrations/**/*"] pirats = ["static/**/*", "rules.html", "rules-tldr.html", "migrations/**/*"]
[tool.pytest.ini_options] [tool.pytest.ini_options]
pythonpath = ["src"] pythonpath = ["src"]
+4
View File
@@ -358,6 +358,10 @@ app.include_router(api, prefix="/api")
def rules_page(): def rules_page():
return FileResponse(BASE_DIR / "rules.html", media_type="text/html") return FileResponse(BASE_DIR / "rules.html", media_type="text/html")
@app.get("/rules/tldr", include_in_schema=False)
def rules_tldr_page():
return FileResponse(BASE_DIR / "rules-tldr.html", media_type="text/html")
# Mount SPA # Mount SPA
static_dir = BASE_DIR / "static" static_dir = BASE_DIR / "static"
if os.path.exists(static_dir): if os.path.exists(static_dir):
+194
View File
@@ -0,0 +1,194 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Rats with Gats — TL;DR Rules</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Alegreya+SC:wght@500;700&family=Alegreya+Sans:wght@400;500;700&family=Pirata+One&display=swap" rel="stylesheet">
<script>
// Apply the saved theme before first paint (same localStorage key as the SPA).
document.documentElement.dataset.theme =
localStorage.getItem('pirats-theme') === 'dark' ? 'dark' : 'light';
</script>
<style>
:root {
--bg-dark: #9fc9e2;
--bg-ocean: #cde6f5;
--gold: #a8770f;
--neon-cyan: #0c8d96;
--red-suit: #c92a35;
--black-suit: #2c3245;
--glass-bg: rgba(248, 241, 221, 0.92);
--glass-border: rgba(168, 119, 15, 0.35);
--text-primary: #33291a;
--text-muted: #7d6e54;
--well-bg: rgba(51, 41, 26, 0.06);
--row-border: rgba(168, 119, 15, 0.2);
--solid-bg: rgba(255, 250, 236, 0.95);
--shadow-panel: 0 8px 32px rgba(36, 62, 87, 0.2);
--font-display: 'Pirata One', 'Alegreya SC', serif;
--font-heading: 'Alegreya SC', serif;
--font-body: 'Alegreya Sans', sans-serif;
}
:root[data-theme="dark"] {
--bg-dark: #0a0d13;
--bg-ocean: #131822;
--gold: #d9a23c;
--neon-cyan: #41c9bd;
--red-suit: #e8606e;
--black-suit: #dfe6ef;
--glass-bg: rgba(27, 33, 46, 0.85);
--glass-border: rgba(217, 162, 60, 0.25);
--text-primary: #efe7d3;
--text-muted: #a89e8a;
--well-bg: rgba(10, 13, 19, 0.5);
--row-border: rgba(217, 162, 60, 0.12);
--solid-bg: rgba(10, 13, 19, 0.9);
--shadow-panel: 0 8px 32px rgba(0, 0, 0, 0.4);
}
* { margin: 0; padding: 0; box-sizing: border-box; }
html { scroll-behavior: smooth; }
body {
background: radial-gradient(circle at 50% 50%, var(--bg-ocean) 0%, var(--bg-dark) 100%);
color: var(--text-primary);
font-family: var(--font-body);
font-size: 16px;
line-height: 1.6;
min-height: 100vh;
}
a { color: var(--neon-cyan); }
.glass-panel {
background: var(--glass-bg);
border: 1px solid var(--glass-border);
border-radius: 12px;
box-shadow: var(--shadow-panel);
}
.sheet { max-width: 1080px; margin: 0 auto; padding: 2rem 1.25rem 5rem; }
header { margin-bottom: 1.25rem; }
h1 { font: 400 2.8rem var(--font-display); color: var(--gold); }
.tagline { margin: .4rem 0 .8rem; }
nav { display: flex; flex-wrap: wrap; gap: .5rem 1.5rem; align-items: center; }
button { font: inherit; color: var(--neon-cyan); background: var(--glass-bg); border: 1px solid var(--glass-border); padding: .35rem .7rem; border-radius: 6px; cursor: pointer; }
.grid { display: grid; grid-template-columns: 1fr 1fr; gap: 1rem; }
section { padding: 1rem 1.25rem; }
h2 { font: 700 1.3rem var(--font-heading); color: var(--gold); margin-bottom: .45rem; }
p + p, ul, ol { margin-top: .45rem; }
ul, ol { padding-left: 1.2rem; }
li + li { margin-top: .25rem; }
footer { margin-top: 1rem; font-size: .85rem; }
@media (max-width: 650px) {
.sheet { padding: 1rem .8rem 2rem; }
.grid { grid-template-columns: 1fr; }
h1 { font-size: 2.3rem; }
}
@page { size: auto; margin: 10mm; }
@media print {
:root, :root[data-theme="dark"] { --text-primary: #111; --gold: #111; --neon-cyan: #111; }
body { background: white; font-size: 9pt; line-height: 1.2; min-height: auto; }
.sheet { max-width: none; padding: 0; }
header { margin-bottom: 8pt; }
h1 { font-size: 24pt; }
nav, footer { font-size: 8pt; }
button { display: none; }
.grid { grid-template-columns: 1fr 1fr; gap: 7pt; }
.glass-panel { background: white; box-shadow: none; border: 1px solid #bbb; border-radius: 0; }
section { padding: 7pt; break-inside: avoid; }
h2 { font-size: 12pt; }
a { text-decoration: none; }
}
</style>
</head>
<body>
<main class="sheet">
<header>
<h1>Rats with Gats — TL;DR</h1>
<p class="tagline">Ordinary rats. Math Magic. Extremely questionable piracy. A one-page rules reference.</p>
<nav aria-label="Rulebook navigation">
<a href="/rules">← Full rulebook</a>
<a href="/rules#example">Example of play</a>
<button id="print" type="button">Print summary</button>
<button id="theme-toggle" type="button"></button>
</nav>
</header>
<div class="grid">
<section class="glass-panel">
<h2>1. Muster the crew</h2>
<p><strong>36 players; one deck with both Jokers.</strong> Everyone makes a Pi-Rat: looks, smell, first words, math ability, and what others like/hate about them. Names come later.</p>
<p>Write 3 Secret Pirate Techniques, swap them with other players, then assign your new techniques to J, Q and K.</p>
<p>Random starting ranks: one Rank 3 (first Deep), one Rank 1 (first Pi-Rat), everyone else Rank 2 (choose either role).</p>
<p><strong>Hand limits:</strong> highest rank 4, middle 3, lowest 2; compare the whole crew. Captain gets +1. Draw your starting hand to its limit. Draw immediately when your hand limit increases.</p>
</section>
<section class="glass-panel">
<h2>2. Set a scene</h2>
<p>At least one player is a <strong>Pi-Rat</strong> (take action); at least one is the <strong>Deep</strong> (world, NPCs and trouble). Last scenes Deep must play Pi-Rats next.</p>
<p>Shuffle discards into the deck. Keep unfinished Obstacles and draw until the list has at least <strong>Deep players + 1</strong> cards, plus any accumulated Joker increases.</p>
<p>The Deep frames an urgent problem using the <a href="/rules#obstacles">Obstacle tables</a>. First scene: steal the ship youre on!</p>
<p><strong>Suit themes:</strong> ♣ shootin &amp; stabbin; ♠ sneakin &amp; schemin; ♦ swimmin &amp; sailin; ♥ shoutin &amp; singin.</p>
</section>
<section class="glass-panel">
<h2>3. Face a Challenge</h2>
<ol>
<li>The Deep states success/failure stakes and applies one or more Obstacles.</li>
<li>Play up to <strong>one card per Obstacle</strong>. Beat its current value to succeed. Equal, lower or no card = failure.</li>
<li>One success passes the Challenge; every failed Obstacle adds a complication. Narrate success using your cards suit (or Technique).</li>
<li>Stack played cards beneath each Obstacle. The <strong>last card sets its next value</strong>; the original card keeps its suit/color.</li>
<li>After resolution, draw a replacement for each of your cards matching the <strong>original Obstacles color</strong>, even on failure.</li>
</ol>
<p>Discard an Obstacle and its column after <strong>one success per player</strong> in the whole group.</p>
</section>
<section class="glass-panel">
<h2>4. Know your cards</h2>
<p><strong>A = 1; 210 = printed value.</strong> A Gat adds +1 to black cards; a Name adds +1 to red cards.</p>
<p><strong>J / Q / K:</strong> your assigned Secret Pirate Technique automatically succeeds. As an Obstacle or the last card beneath one, its value is the challenged Pi-Rats rank.</p>
<p><strong>Joker in hand:</strong> before or after a Challenge, discard and replace one Obstacle (and its column); discard the Joker. <strong>Drawn as an Obstacle:</strong> discard it; permanently increase the list requirement by 1 starting next scene.</p>
<p><strong>Helping:</strong> only with multiple applied Obstacles. Each helper plays one card and shares the outcome; helpers dont draw replacements.</p>
<p><strong>Duels:</strong> attacker plays a temporary Obstacle; defender responds normally. Discard the duel cards afterward. Only the defender gets color-match replacements.</p>
</section>
<section class="glass-panel">
<h2>5. Chase glory</h2>
<p><strong>Personal, in order:</strong> get a Gat → earn a Name → die like a pirate (or retire). The group decides when youve earned each. First two: +1 rank each; third: give another Pi-Rat +1 rank.</p>
<p><strong>Crew, in order:</strong> steal a ship → choose a Captain → commit piracy.</p>
<p><strong>Captain:</strong> highest rank after taking a ship; can be challenged for command. +1 hand limit, final say in crew disputes and vote ties; 1 rank whenever they personally fail a Challenge.</p>
<p><strong>Gat / Name Taxes:</strong> ask an eligible crewmate to take your Challenge for a random card. Refusal puts their Gat or Name at stake. <a href="/rules#objectives">Read the full tax rules before using them.</a></p>
</section>
<section class="glass-panel">
<h2>6. Wrap up &amp; go again</h2>
<p>The Deep may end the scene once every participating Pi-Rat has faced a Challenge and had an Objective opportunity, or no Obstacles remain, or all Pi-Rats have fled/been incapacitated.</p>
<p><strong>Flee</strong> before or after a Challenge; youre out of the scene and dont count as progressing or completing Objectives that scene.</p>
<p><strong>Between scenes:</strong> everyone votes for another Pi-Rat who wasnt Deep; most votes earns +1 rank. Resolve ties together (Captain breaks ties). Apply Objective rank rewards.</p>
<p>Only the previous Deep may discard any cards and refill to their hand limit. Rotate roles and set the next scene.</p>
<p><strong>End the story</strong> when the group is ready—ideally after everyone tries the Deep, each rat earns an Objective, and the crew completes all three. Celebrate!</p>
</section>
</div>
<footer>Quick reference to the playtest rulebook by Nick Smith. Rats with Gats © 2025 Nick Smith and J. Richmond · Yeld LLC. <a href="/rules">Full rules, exceptions &amp; credits</a>.</footer>
</main>
<script>
// Theme toggle: shares the "pirats-theme" localStorage key with the SPA.
const themeToggle = document.getElementById('theme-toggle');
function refreshThemeToggle() {
const dark = document.documentElement.dataset.theme === 'dark';
themeToggle.textContent = dark ? '☀️ Light Mode' : '🌙 Dark Mode';
}
themeToggle.addEventListener('click', () => {
const next = document.documentElement.dataset.theme === 'dark' ? 'light' : 'dark';
document.documentElement.dataset.theme = next;
localStorage.setItem('pirats-theme', next);
refreshThemeToggle();
});
refreshThemeToggle();
document.getElementById('print').addEventListener('click', () => window.print());
</script>
</body>
</html>
+1
View File
@@ -336,6 +336,7 @@ a { color: var(--neon-cyan); }
<header class="rules-header glass-panel"> <header class="rules-header glass-panel">
<h1>Rats with Gats</h1> <h1>Rats with Gats</h1>
<p class="rules-subtitle">A Story Game about the Pi-Rats of Yeld!</p> <p class="rules-subtitle">A Story Game about the Pi-Rats of Yeld!</p>
<p class="rules-subtitle"><a href="/rules/tldr">Short on time? Read the TL;DR rules →</a></p>
<p class="rules-credits"> <p class="rules-credits">
Written &amp; Designed by Nick Smith · Art by J. Richmond · Produced by Sally Hsu · Written &amp; Designed by Nick Smith · Art by J. Richmond · Produced by Sally Hsu ·
Support: Maia and EmmaVoid Support: Maia and EmmaVoid