'use client'; import Link from 'next/link'; import { StrongholdCardGallery } from './stronghold-cards'; import { createStrongholdCards } from '@/game/stronghold-cards'; import { useSearchParams } from 'next/navigation'; import { Suspense, useState, useEffect, useRef } from 'react'; import { Search, ArrowUpRight, BookOpen } from 'lucide-react'; import { Input } from '@/components/ui/input'; import { RULE_TOPICS } from '@/game/reference'; import { BattleWheel } from './battle-wheel'; import { RICHESE_CARD_DEFINITIONS } from '@/game/richese-cards'; import { CardInspector, CardRules } from './card-inspector'; export function RulesReference() { return ( Loading the rules reference…

} >
); } function RulesReferenceContent() { const searchParams = useSearchParams(); const topicQuery = searchParams.get('topic'); const [query, setQuery] = useState(''); const [practiceDial, setPracticeDial] = useState(3); const [category, setCategory] = useState('All topics'); const [opened, setOpened] = useState>({}); const pendingScroll = useRef(null); useEffect(() => { const reveal = () => { const id = topicQuery || window.location.hash.slice(1); if (!RULE_TOPICS.some((t) => t.id === id)) return; pendingScroll.current = id; setQuery(''); setCategory('All topics'); setOpened((previous) => ({ ...previous, [id]: true })); }; reveal(); window.addEventListener('hashchange', reveal); return () => window.removeEventListener('hashchange', reveal); }, [topicQuery]); // Scroll only after React has rendered the destination with cleared filters. useEffect(() => { const id = pendingScroll.current; if (!id) return; const article = document.getElementById(id); if (!article) return; pendingScroll.current = null; article.scrollIntoView({ block: 'start' }); }); const openTopic = (id: string) => { pendingScroll.current = id; setQuery(''); setCategory('All topics'); setOpened((previous) => ({ ...previous, [id]: true })); }; const categories = [ 'All topics', ...new Set(RULE_TOPICS.map((t) => t.category)), ]; const terms = query.toLowerCase().trim().split(/\s+/).filter(Boolean); const topics = RULE_TOPICS.filter( (t) => (category === 'All topics' || t.category === category) && terms.every((term) => [ t.title, t.summary, ...t.steps, t.example ?? '', ...(t.checklist ?? []).flatMap((item) => [ item.area, item.status, item.detail, ...(item.evidence ?? []), ]), ] .join(' ') .toLowerCase() .includes(term), ), ); return (
← Return to Arrakis THE MENTAT’S REFERENCE

Know the rules.
Choose your moment.

Find a phase, card or faction. Follow the action guides, inspect examples and move directly between related rules.

This reference is growing with the game. Advanced play and expansion rules are unfinished.{' '} View the implementation ledger {' · '} Open feature checklists

{topics.length} {topics.length === 1 ? 'topic' : 'topics'} {query ? ` matching “${query}”` : ''} {topics.length === 0 && (

No matching topics. Try a card name or choose All topics.

)} {topics.map((topic) => (
0 || !!opened[topic.id]} onToggle={(event) => { if (terms.length) return; const isOpen = event.currentTarget.open; setOpened((previous) => previous[topic.id] === isOpen ? previous : { ...previous, [topic.id]: isOpen }, ); }} > {topic.category}

{topic.title}

{topic.coverage}

{topic.summary}

    {topic.steps.map((step) => (
  1. {step}
  2. ))}
{topic.id === 'stronghold-cards' && ( )} {topic.id === 'richese-cards' && (
{RICHESE_CARD_DEFINITIONS.map(({ card, printedType }) => (

{card.name}

{printedType} · 1 card

))}
)} {topic.checklist && (

Feature checklist

{topic.checklist.map((item) => ( ))}
{topic.title}: implementation, controls, AI, documentation and verification
Area Status Scope and evidence
{item.area} {item.status}

{item.detail}

{item.evidence && (
    {item.evidence.map((file) => (
  • {file}
  • ))}
)}
)} {topic.id === 'battle' && ( )} {topic.example && ( )} {topic.related && ( )} openTopic(topic.id)} > Link to this topic
))}
); }