diff --git a/web/src/index.css b/web/src/index.css index 903a9e6..0a4b9e6 100644 --- a/web/src/index.css +++ b/web/src/index.css @@ -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; +} diff --git a/web/src/pages/RulesPage.tsx b/web/src/pages/RulesPage.tsx index 1963ccd..86ec44d 100644 --- a/web/src/pages/RulesPage.tsx +++ b/web/src/pages/RulesPage.tsx @@ -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>(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 ( <> -

Rules reference coming soon...

+
+ + +
+ +
+ {battleReferenceSections.map((section, i) => ( +
+ { e.preventDefault(); toggle(i); }}> + {section.title} + +
+ {section.content} +
+
+ ))} +
); diff --git a/web/src/rules/battle-reference.ts b/web/src/rules/battle-reference.ts new file mode 100644 index 0000000..b3c976c --- /dev/null +++ b/web/src/rules/battle-reference.ts @@ -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 (B–F):** 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);