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
+
+
+
+
+