feat(web): add Rules/Reference page with collapsible battle reference sections

Embeds Battle reference.md content as collapsible sections with markdown
rendering, expand/collapse all controls, and styled tables and blockquotes.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Bas van Rossem
2026-02-19 16:29:07 +01:00
parent b3a55d9fac
commit 510820b77a
3 changed files with 411 additions and 1 deletions

View File

@@ -476,3 +476,106 @@ button:disabled {
min-height: 80px;
resize: vertical;
}
/* Rules Page */
.rules-actions {
display: flex;
gap: var(--spacing-sm);
margin-bottom: var(--spacing-md);
}
.rules-sections {
display: flex;
flex-direction: column;
gap: var(--spacing-sm);
}
.rules-section {
background: var(--color-surface);
border: 1px solid var(--color-border);
border-radius: var(--radius);
overflow: hidden;
}
.rules-summary {
padding: var(--spacing-md);
cursor: pointer;
font-weight: 600;
font-size: 0.95rem;
list-style: none;
display: flex;
align-items: center;
gap: var(--spacing-sm);
}
.rules-summary::before {
content: '▸';
transition: transform 0.15s;
font-size: 0.8rem;
}
details[open] > .rules-summary::before {
transform: rotate(90deg);
}
.rules-summary::-webkit-details-marker {
display: none;
}
.rules-content {
padding: 0 var(--spacing-md) var(--spacing-md);
font-size: 0.9rem;
line-height: 1.6;
}
.rules-content h3 {
font-size: 0.9rem;
margin-top: var(--spacing-md);
margin-bottom: var(--spacing-xs);
color: var(--color-primary);
}
.rules-content ul,
.rules-content ol {
padding-left: var(--spacing-lg);
margin: var(--spacing-xs) 0;
}
.rules-content li {
margin-bottom: 2px;
}
.rules-content table {
width: 100%;
border-collapse: collapse;
margin: var(--spacing-sm) 0;
font-size: 0.85rem;
}
.rules-content th,
.rules-content td {
border: 1px solid var(--color-border);
padding: var(--spacing-xs) var(--spacing-sm);
text-align: left;
}
.rules-content th {
background: var(--color-accent);
font-weight: 600;
}
.rules-content blockquote {
border-left: 3px solid var(--color-primary);
padding-left: var(--spacing-md);
margin: var(--spacing-sm) 0;
color: var(--color-text-muted);
font-style: italic;
}
.rules-content strong {
color: var(--color-text);
}
.rules-content p {
margin: var(--spacing-xs) 0;
}

View File

@@ -1,12 +1,57 @@
import { useState } from 'react';
import Markdown from 'react-markdown';
import TopBar from '../components/layout/TopBar';
import PageContainer from '../components/layout/PageContainer';
import { battleReferenceSections } from '../rules/battle-reference';
export default function RulesPage() {
const [openSections, setOpenSections] = useState<Set<number>>(new Set());
const toggle = (index: number) => {
setOpenSections((prev) => {
const next = new Set(prev);
if (next.has(index)) {
next.delete(index);
} else {
next.add(index);
}
return next;
});
};
const expandAll = () => {
setOpenSections(new Set(battleReferenceSections.map((_, i) => i)));
};
const collapseAll = () => {
setOpenSections(new Set());
};
return (
<>
<TopBar title="Battle Reference" />
<PageContainer>
<p style={{ color: 'var(--color-text-muted)' }}>Rules reference coming soon...</p>
<div className="rules-actions">
<button className="btn-secondary btn-sm" onClick={expandAll}>
Expand All
</button>
<button className="btn-secondary btn-sm" onClick={collapseAll}>
Collapse All
</button>
</div>
<div className="rules-sections">
{battleReferenceSections.map((section, i) => (
<details key={i} className="rules-section" open={openSections.has(i)}>
<summary className="rules-summary" onClick={(e) => { e.preventDefault(); toggle(i); }}>
{section.title}
</summary>
<div className="rules-content">
<Markdown>{section.content}</Markdown>
</div>
</details>
))}
</div>
</PageContainer>
</>
);

View File

@@ -0,0 +1,262 @@
// Battle reference content from Battle reference.md
// Sections are split by ## headings for collapsible rendering
export interface RulesSection {
title: string;
content: string;
}
const rawMarkdown = `## 1. Ship Health (Armor vs Hull)
* Ships have **Armor Points** and **Hull Points**.
* **Armor is lost first**, then Hull takes damage.
* **Hull is hard to repair in combat** (usually requires shipyard-level repair).
* **Armor can be recovered quickly** through actions.
## 2. Combat Round Structure
Ship combat happens in **two phases**:
### Phase 1 — Movement Phase
1. DM secretly plans enemy ship movement
2. Ally/revealed ships move openly
3. **Helmsman moves the ship as an action**
4. DM reveals remaining enemy movement
Movement ends → action phase begins.
### Phase 2 — Action Phase
All characters act in initiative order (group initiative is common).
Each PC turn includes:
* Move around the ship
* Bonus "card action" (if using decks)
* One standard action
Special ship actions include:
* **Fire a weapon**
* **Reload a weapon**
* **Recover armor**
* **Use subsystem**
* **Help another crew member**
* **Tend complications**
* **Insight check** (reveal enemy movement next round)
## 3. Ship Maneuverability (Movement Rules)
Ships have a **Maneuverability Class** defining turning + speed.
| Class | Turn Radius | Segment Size | Segment Count | Acceleration |
| ----- | ----------- | ------------ | ------------- | ------------ |
| S | Any | 1 | Varies | Instant |
| A | 120° | 1 | 8 | Instant |
| B | 60° | 2 | 3 | Half |
| C | 60° | 3 | 2 | Half |
| D | 60° | 4 | 1 | Half |
| F | 60° | 3 | 1 | Half (Poor) |
Acceleration:
* **Instant (S/A):** move any distance up to full speed
* **Half (BF):** must step between stopped → half → full speed
Turning:
* 60° = one hex face rotation
* Ships must travel straight per segment before turning.
### Complex Maneuverability Option
Helmsman may reduce maneuverability by 1 step to change segment pattern (useful for obstacles).
## 4. Ship Weapons (Core Rules)
Firing ship weapons works like ranged attacks:
### Attack Roll
* Mundane weapons use **Dexterity**
* Cannons may allow **Dex or Intelligence**
* Spellcannons use **spell attack bonus**
* If you have **3+ gunner cards**, add proficiency bonus
### Damage
* Ship weapon damage is **Big damage**
* Damage is fixed — no ability modifier added
### Reload
* All ship weapons require an **action to reload**
* Cannot be bypassed by feats like Crossbow Expert
## 5. Firing Arcs & Hardpoints
Weapons can only fire within their arc.
Common arcs:
* **Port Broadside:** points 11 → 1
* **Starboard Broadside:** points 7 → 5
* **Chase Gun:** points 9 → 8
* **Fore Gun:** points 2 → 3
* **Forward Swivel:** points 12 → 6
* **Rear Swivel:** points 6 → 12
Edge of arc:
* Target gains **+2 cover bonus to AC**
## 6. Targeted Shots (Subsystem Attacks)
To target a subsystem:
* Must have **no other disadvantage**
* Must hit **Ship AC + subsystem modifier**
### Open Deck Targeting
On hit:
* All crew on deck make Dex save
* DC = 8 + weapon attack bonus
* Fail → full damage
* Success → no damage
### Subsystems (Weapons, Rudder, etc.)
* Damage goes to subsystem HP, not armor/hull
* At 0 HP → subsystem disabled
* If hit again while disabled:
* Ship makes Con save vs damage
* Fail → subsystem destroyed permanently
Helm:
* Nearly indestructible
* If exposed, can be targeted (default +8 AC modifier)
## 7. Armor Repair (In Combat)
A PC may spend an action to recover:
* Armor = proficiency bonus
* Boatswain-heavy crews recover more
Enemy ships may also have limited armor recovery actions.
## 8. Grappling & Boarding
Ships can grapple when adjacent and either:
* Facing same direction
* OR both stopped
To grapple:
* Captain plays a grapple card → auto success
* Otherwise discard cards equal to 3× target maneuver class
Example:
* F = 3 cards
* D = 6
* C = 9
* B = 12
* A = 15
* S = 18
While grappled:
* Ships move together
* Opposed spellcasting checks to resist movement
## 9. Saving Throws (Ship Effects)
* Strength save = helmsman spellcasting ability
* Dexterity save = helmsman Dex save
* +5 bonus if ship is A/S class
* 5 penalty if ship is D class
* Constitution save = ship's listed Con bonus
* Ship immune to Int/Wis/Cha saves
Size differences may shift DC by ±2 per category.
## 10. Complications (Fire, Maintenance, Rift…)
Complications are tracked as a **DC total**.
### Tending a complication (Action)
* Roll check → reduce DC by that amount
* If DC reaches 0 → complication ends
### Natural Growth (end of round)
DC increases by:
> (DC ÷ 10) + 1
Example:
* DC 53 → grows to 59
Thresholds trigger bad effects when DC gets too high.
## 11. Enemy Initiative Options
DM can choose enemy ship behavior:
* **Full:** move + fire freely (hardest)
* **Split:** helm + guns on separate initiative
* **Skilled:** move first, then fire
* **Unskilled:** fire first, then move (easiest)
## Battle Checklist (Quick Reference)
Each round:
1. **Movement Phase**
* Helmsman moves ship (respect segments + turning)
2. **Action Phase**
* Gunners fire/reload
* Boatswain repairs armor
* Crew handles complications
* Captain coordinates
3. **End of Round**
* Complications grow
* Disabled subsystems may worsen
* Prepare next movement`;
export function parseSections(markdown: string): RulesSection[] {
const sections: RulesSection[] = [];
const lines = markdown.split('\n');
let currentTitle = '';
let currentContent: string[] = [];
for (const line of lines) {
if (line.startsWith('## ')) {
if (currentTitle) {
sections.push({ title: currentTitle, content: currentContent.join('\n').trim() });
}
currentTitle = line.replace('## ', '');
currentContent = [];
} else {
currentContent.push(line);
}
}
if (currentTitle) {
sections.push({ title: currentTitle, content: currentContent.join('\n').trim() });
}
return sections;
}
export const battleReferenceSections = parseSections(rawMarkdown);