From 683c60fff9c8e12fb80e2b56d461e35dcb3045bb Mon Sep 17 00:00:00 2001 From: Tim McCarthy Date: Fri, 12 Jun 2026 01:35:04 -0700 Subject: [PATCH] Serve the rulebook as a static HTML page from FastAPI Replace the SPA Rules route with a self-contained src/pirats/rules.html served at GET /rules (registered before the SPA mount, included in package data for the Nix build). Plain anchor links replace the scrollIntoView workaround since there's no hash router to fight. The corner Rules link and splash-page link now point at /rules, and the Vite dev server proxies /rules to the backend like /api. Co-Authored-By: Claude Fable 5 --- frontend/src/App.svelte | 25 +- frontend/src/app.css | 1 - frontend/src/assets/css/rules.css | 242 ------------------ frontend/src/pages/Home.svelte | 2 +- frontend/vite.config.js | 4 + pyproject.toml | 2 +- src/pirats/main.py | 6 + .../Rules.svelte => src/pirats/rules.html | 229 +++++++++++++++-- 8 files changed, 237 insertions(+), 274 deletions(-) delete mode 100644 frontend/src/assets/css/rules.css rename frontend/src/pages/Rules.svelte => src/pirats/rules.html (94%) diff --git a/frontend/src/App.svelte b/frontend/src/App.svelte index 2fbba1f..65e5268 100644 --- a/frontend/src/App.svelte +++ b/frontend/src/App.svelte @@ -4,18 +4,37 @@ import Join from './pages/Join.svelte'; import Dashboard from './pages/Dashboard.svelte'; import Admin from './pages/Admin.svelte'; - import Rules from './pages/Rules.svelte'; const routes = { '/': Home, '/game/:id/join': Join, '/game/:id/player/:pid': Dashboard, '/game/:id/admin': Admin, - '/rules': Rules, };
- 📖 Rules + 📖 Rules
+ + diff --git a/frontend/src/app.css b/frontend/src/app.css index 622e989..1e0e604 100644 --- a/frontend/src/app.css +++ b/frontend/src/app.css @@ -9,4 +9,3 @@ @import './assets/css/card.css'; @import './assets/css/upkeep.css'; @import './assets/css/admin.css'; -@import './assets/css/rules.css'; diff --git a/frontend/src/assets/css/rules.css b/frontend/src/assets/css/rules.css deleted file mode 100644 index e66b2a7..0000000 --- a/frontend/src/assets/css/rules.css +++ /dev/null @@ -1,242 +0,0 @@ -/* --- Rules Page --- */ -.rules-page { - max-width: 860px; - margin: 0 auto; - padding: 2rem 1rem 4rem; - display: flex; - flex-direction: column; - gap: 1.5rem; -} - -.rules-page .glass-panel { - padding: 1.5rem 2rem; -} - -.rules-header { - text-align: center; -} - -.rules-header h1 { - font-family: var(--font-heading); - color: var(--gold); - font-size: 2.4rem; - margin-bottom: 0.25rem; -} - -.rules-subtitle { - color: var(--text-primary); - font-size: 1.15rem; - margin-bottom: 0.75rem; -} - -.rules-credits { - color: var(--text-muted); - font-size: 0.85rem; - margin-bottom: 0.5rem; -} - -.rules-credits a { - color: var(--neon-cyan); -} - -.playtest-note { - margin-top: 0.75rem; - padding: 0.5rem 1rem; - border: 1px dashed var(--glass-border); - border-radius: 8px; - color: var(--gold); - font-size: 0.9rem; -} - -.rules-toc h2 { - font-family: var(--font-heading); - color: var(--gold); - margin-bottom: 0.75rem; -} - -.rules-toc ul { - list-style: none; - padding-left: 0; -} - -.rules-toc ul ul { - padding-left: 1.5rem; -} - -.rules-toc li { - margin: 0.2rem 0; -} - -.rules-toc button { - background: none; - border: none; - color: var(--neon-cyan); - cursor: pointer; - font-family: var(--font-body); - font-size: 1rem; - padding: 0; - text-align: left; -} - -.rules-toc button:hover { - text-decoration: underline; -} - -.rules-body { - display: flex; - flex-direction: column; - gap: 1.5rem; -} - -.rules-body section { - scroll-margin-top: 1rem; -} - -.rules-body h2 { - font-family: var(--font-heading); - color: var(--gold); - font-size: 1.8rem; - margin-bottom: 1rem; - border-bottom: 1px solid var(--glass-border); - padding-bottom: 0.5rem; -} - -.rules-body h3 { - font-family: var(--font-heading); - color: var(--text-primary); - font-size: 1.35rem; - margin: 1.5rem 0 0.75rem; - scroll-margin-top: 1rem; -} - -.rules-body h4 { - color: var(--gold); - font-size: 1.1rem; - margin: 1.25rem 0 0.5rem; -} - -.rules-body h5 { - color: var(--text-primary); - font-size: 1rem; - margin: 1rem 0 0.5rem; -} - -.objective-note { - color: var(--text-muted); - font-weight: normal; - font-style: italic; - font-size: 0.9em; -} - -.rules-body p { - margin-bottom: 0.75rem; -} - -.rules-body ul, -.rules-body ol { - margin: 0 0 0.75rem 1.5rem; -} - -.rules-body li { - margin-bottom: 0.35rem; -} - -.rules-body .aside { - color: var(--text-muted); - font-style: italic; - font-size: 0.95em; -} - -.technique-examples { - list-style: none; - margin-left: 0.5rem; -} - -.rules-body table { - width: 100%; - border-collapse: collapse; - margin: 0.75rem 0 1rem; - background: rgba(7, 11, 18, 0.5); - border: 1px solid var(--glass-border); - border-radius: 8px; -} - -.rules-body th, -.rules-body td { - padding: 0.5rem 0.75rem; - border-bottom: 1px solid rgba(212, 175, 55, 0.1); - text-align: left; - vertical-align: top; -} - -.rules-body thead th { - color: var(--gold); - font-family: var(--font-heading); -} - -.obstacle-chart th { - width: 2.5rem; - text-align: center; - color: var(--gold); - font-family: var(--font-heading); -} - -.suit-ways th { - width: 7.5rem; - white-space: nowrap; -} - -.suit-red { - color: var(--red-suit); -} - -.suit-black { - color: var(--black-suit); -} - -.rules-body details { - border: 1px solid var(--glass-border); - border-radius: 8px; - padding: 0.75rem 1rem; -} - -.rules-body details summary { - cursor: pointer; - color: var(--neon-cyan); - font-weight: 600; - padding: 0.25rem 0; -} - -.rules-body details[open] summary { - margin-bottom: 0.75rem; -} - -.signature { - color: var(--gold); - font-style: italic; -} - -.back-to-top { - text-align: center; -} - -/* --- Always-available rules corner link (rendered by App.svelte) --- */ -.rules-corner-link { - position: fixed; - top: 12px; - left: 16px; - z-index: 1001; - padding: 6px 14px; - border-radius: 16px; - background: rgba(10, 15, 25, 0.9); - border: 1px solid var(--glass-border-focus); - color: var(--neon-cyan); - font-size: 0.85rem; - font-weight: 700; - text-decoration: none; - font-family: var(--font-heading); -} - -.rules-corner-link:hover { - background: rgba(0, 242, 254, 0.1); -} diff --git a/frontend/src/pages/Home.svelte b/frontend/src/pages/Home.svelte index fd20c11..a261024 100644 --- a/frontend/src/pages/Home.svelte +++ b/frontend/src/pages/Home.svelte @@ -56,6 +56,6 @@ diff --git a/frontend/vite.config.js b/frontend/vite.config.js index e677226..50f8725 100644 --- a/frontend/vite.config.js +++ b/frontend/vite.config.js @@ -9,6 +9,10 @@ export default defineConfig({ '/api': { target: 'http://127.0.0.1:8000', changeOrigin: true, + }, + '/rules': { + target: 'http://127.0.0.1:8000', + changeOrigin: true, } } } diff --git a/pyproject.toml b/pyproject.toml index 611fc71..883fbdd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -27,7 +27,7 @@ pirats = "pirats.main:main" where = ["src"] [tool.setuptools.package-data] -pirats = ["static/**/*"] +pirats = ["static/**/*", "rules.html"] [tool.pytest.ini_options] pythonpath = ["src"] diff --git a/src/pirats/main.py b/src/pirats/main.py index ae71cbb..ea5ab44 100644 --- a/src/pirats/main.py +++ b/src/pirats/main.py @@ -1,6 +1,7 @@ from contextlib import asynccontextmanager from typing import Optional from fastapi import FastAPI, Form, Depends, HTTPException, APIRouter +from fastapi.responses import FileResponse from fastapi.staticfiles import StaticFiles from sqlmodel import Session import uvicorn @@ -105,6 +106,11 @@ api.include_router(admin_router) # Mount API at /api app.include_router(api, prefix="/api") +# Static rulebook page (registered before the SPA mount so it takes precedence) +@app.get("/rules", include_in_schema=False) +def rules_page(): + return FileResponse(BASE_DIR / "rules.html", media_type="text/html") + # Mount SPA static_dir = BASE_DIR / "static" if os.path.exists(static_dir): diff --git a/frontend/src/pages/Rules.svelte b/src/pirats/rules.html similarity index 94% rename from frontend/src/pages/Rules.svelte rename to src/pirats/rules.html index f15db2a..b531717 100644 --- a/frontend/src/pages/Rules.svelte +++ b/src/pirats/rules.html @@ -1,11 +1,186 @@ - + + + + + +Rats with Gats — Rulebook + + +

Rats with Gats

@@ -25,30 +200,30 @@ @@ -1122,6 +1297,8 @@

— Nick

-

+

⬆ Back to top

+ +