diff --git a/README.md b/README.md index 0d079886..08e27801 100644 --- a/README.md +++ b/README.md @@ -69,6 +69,10 @@ Use **Inspect forces** under any player to enlarge their counters and read publi reserves, Tanks and deployed groups, including Homeworld reserve breakdowns. See [force inspection](docs/FORCE_INSPECTION.md). +Use **Inspect faction** under any player for public powers and alliance guidance. +The sheet starts in the table’s rules mode and offers an Advanced preview without +changing the game. Browse all twelve in the [faction-sheet gallery](docs/FACTION_SHEETS.md). + Above the board, **Treachery draw pile** and **Spice draw pile** show live card counts separately from hands, auction cards and discards. See [draw-pile counts](docs/DRAW_PILES.md). diff --git a/app/globals.css b/app/globals.css index bc28abfe..8fecaac3 100644 --- a/app/globals.css +++ b/app/globals.css @@ -2006,3 +2006,15 @@ footer { .force-reference { padding: 1rem; border: 1px solid #807352; border-radius: .75rem; font-size: 1rem; } .force-reference label { display: grid; gap: .5rem; } .force-reference select { min-height: 44px; width: 100%; font-size: 1rem; } +.faction-sheet { min-width: 0; margin-top: 1rem; overflow: hidden; border: 1px solid #807352; border-top-width: 4px; border-radius: .75rem; background: #20271f; color: #f2e8d3; } +.faction-sheet header, .faction-sheet section, .faction-sheet footer { padding: 1rem; } +.faction-sheet section + section, .faction-sheet footer { border-top: 1px solid #807352; } +.faction-sheet .faction-sheet-label { font-size: .875rem; color: #edcc8c; } +.faction-sheet p, .faction-sheet li { font-size: 1rem; line-height: 1.65; overflow-wrap: anywhere; } +.faction-sheet h3 { font: 1.8rem/1.3 Georgia, serif; } +.faction-sheet h4 { margin: 0 0 .75rem; font-size: 1.2rem; } +.faction-sheet ol { display: grid; gap: .9rem; margin: 0; padding-left: 1.4rem; } +.faction-sheet-art { display: block; width: 100%; max-height: 10rem; object-fit: cover; } +.faction-sheet footer a, .faction-sheet-help { display: block; padding-block: .65rem; font-size: 1rem; line-height: 1.5; text-decoration: underline; } +.faction-sheet-reader .faction-sheet-toggle { display: flex; align-items: center; gap: .75rem; min-height: 44px; margin-block: .75rem; font-size: 1rem; } +.faction-sheet-toggle input { width: 1.25rem; height: 1.25rem; flex-shrink: 0; } diff --git a/components/faction-inspector.tsx b/components/faction-inspector.tsx new file mode 100644 index 00000000..7242bdbd --- /dev/null +++ b/components/faction-inspector.tsx @@ -0,0 +1,80 @@ +'use client'; + +import { useState } from 'react'; +import Image from 'next/image'; +import Link from 'next/link'; +import { Dialog as DialogPrimitive } from '@base-ui/react/dialog'; +import { Eye, X } from 'lucide-react'; +import { FACTIONS, faction, type FactionId } from '@/game/catalog'; +import { FACTION_RULES } from '@/game/faction-reference'; +import { HOMEWORLD_CARDS } from '@/game/homeworld-cards'; +import { Button } from './ui/button'; +import { Dialog, DialogClose, DialogDescription, DialogOverlay, DialogPortal, DialogTitle, DialogTrigger } from './ui/dialog'; + +/** Static public reference only: no player, holdings, hidden choices or action callback. */ +export function FactionSheet({ factionId, advanced }: { factionId: FactionId; advanced: boolean }) { + const house = faction(factionId); + const rules = FACTION_RULES[factionId]; + const world = HOMEWORLD_CARDS.find(card => card.faction === factionId)!; + return
+
+

Faction sheet · {advanced ? 'Advanced' : 'Basic'}

+

{house.name}

+

{house.title}

+
+ +
+

Basic powers and alliances

+
    {rules.basic.map(text =>
  1. {text}
  2. )}
+
+ {advanced &&
+

Advanced powers

+

Use these with the Basic powers above and the Advanced game rules.

+
    {rules.advanced.map(text =>
  1. {text}
  2. )}
+
} + +
; +} + +function FactionSheetReader({ factionId, tableAdvanced }: { factionId: FactionId; tableAdvanced?: boolean }) { + const [advanced, setAdvanced] = useState(tableAdvanced ?? false); + return
+ {tableAdvanced !== undefined &&

This table uses {tableAdvanced ? 'Advanced' : 'Basic'} rules.{advanced !== tableAdvanced && ' You are previewing the other rules mode; the table has not changed.'}

} + + +
; +} + +export function FactionInspector({ factionId, advanced }: { factionId: FactionId; advanced: boolean }) { + const house = faction(factionId); + return + } aria-label={`Inspect faction: ${house.name}`}> + + + + +
Public faction sheet}>
+ {/* oxlint-disable-next-line jsx-a11y/no-noninteractive-tabindex -- The long faction sheet scrolls independently and needs keyboard access. */} +
+ {house.name} faction sheet + Read public faction powers and alliance guidance without changing the game. + + Faction sheet help +
+
+
+
; +} + +export function FactionSheetGallery() { + const [selected, setSelected] = useState('atreides'); + return
+ + +
; +} diff --git a/components/game-table.tsx b/components/game-table.tsx index 3f022723..69e4e5d4 100644 --- a/components/game-table.tsx +++ b/components/game-table.tsx @@ -3,6 +3,7 @@ import { EcazSetup } from './ecaz-setup'; import { StormCardInspector, StormCardLogInspector } from './storm-cards'; import { KwisatzInspector } from './kwisatz-inspector'; import { ForceInspector } from './force-inspector'; +import { FactionInspector } from './faction-inspector'; import { DrawPiles } from './draw-piles'; import { HandBrowser } from './hand-browser'; import { LobbyBotControls } from './lobby-bot-controls'; @@ -1111,6 +1112,7 @@ export function GameTable({ )} + {p.revealedTraitors.length > 0 && (

Revealed traitors:{' '} diff --git a/components/rules-reference.tsx b/components/rules-reference.tsx index cd5378ee..a32771f2 100644 --- a/components/rules-reference.tsx +++ b/components/rules-reference.tsx @@ -6,6 +6,7 @@ import { NexusCardGallery } from './nexus-cards'; import { StormCardGallery } from './storm-cards'; import { KwisatzReferenceCard } from './kwisatz-inspector'; import { ForceReferenceGallery } from './force-inspector'; +import { FactionSheetGallery } from './faction-inspector'; import { createStrongholdCards } from '@/game/stronghold-cards'; import { useSearchParams } from 'next/navigation'; import { Suspense, useState, useEffect, useRef } from 'react'; @@ -202,6 +203,7 @@ function RulesReferenceContent() { {topic.id === 'storm-cards' && } {topic.id === 'kwisatz-haderach' && } {topic.id === 'force-counters' && } + {topic.id === 'faction-sheets' && } {topic.id === 'richese-cards' && (