Use browser navigation for reliable production page links
This commit is contained in:
1 parent
b14593fa40
commit
c5661ec255
15 files changed
+40
-42
No files matched your search
@@ -35,6 +35,7 @@
|
|||||||
],
|
],
|
||||||
"rules": {
|
"rules": {
|
||||||
"no-array-constructor": "error",
|
"no-array-constructor": "error",
|
||||||
|
"nextjs/no-html-link-for-pages": "off",
|
||||||
"no-var": "error",
|
"no-var": "error",
|
||||||
"prefer-const": "error",
|
"prefer-const": "error",
|
||||||
"prefer-rest-params": "error",
|
"prefer-rest-params": "error",
|
||||||
|
|||||||
+3
-4
@@ -1,5 +1,4 @@
|
|||||||
'use client';
|
'use client';
|
||||||
import Link from 'next/link';
|
|
||||||
import { useState, useEffect, useCallback, useRef } from 'react';
|
import { useState, useEffect, useCallback, useRef } from 'react';
|
||||||
import { TableTalk } from '@/components/table-talk';
|
import { TableTalk } from '@/components/table-talk';
|
||||||
import { GameTable } from '@/components/game-table';
|
import { GameTable } from '@/components/game-table';
|
||||||
@@ -914,11 +913,11 @@ export default function Home() {
|
|||||||
</div>
|
</div>
|
||||||
<div className="masthead-right">
|
<div className="masthead-right">
|
||||||
<span className="status-dot" /> Multiplayer table{' '}
|
<span className="status-dot" /> Multiplayer table{' '}
|
||||||
<Link href="/learn">Learn to play</Link>
|
<a href="/learn">Learn to play</a>
|
||||||
<Link href="/rules">
|
<a href="/rules">
|
||||||
<BookOpen size={17} />
|
<BookOpen size={17} />
|
||||||
Rulebook
|
Rulebook
|
||||||
</Link>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
<div className="lobby-grid">
|
<div className="lobby-grid">
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
import Link from 'next/link';
|
|
||||||
export default function Rules() {
|
export default function Rules() {
|
||||||
return (
|
return (
|
||||||
<main className="rule-page">
|
<main className="rule-page">
|
||||||
<Link href="/">← Return to the table</Link>
|
<a href="/">← Return to the table</a>
|
||||||
<h1>Rules coverage ledger</h1>
|
<h1>Rules coverage ledger</h1>
|
||||||
<p>
|
<p>
|
||||||
This project targets classic Gale Force Nine Dune, the advanced game,
|
This project targets classic Gale Force Nine Dune, the advanced game,
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
'use client';
|
'use client';
|
||||||
import { useId, useState } from 'react';
|
import { useId, useState } from 'react';
|
||||||
import Link from 'next/link';
|
|
||||||
import type { Action, GameView } from '@/game/engine';
|
import type { Action, GameView } from '@/game/engine';
|
||||||
import { bribeAction } from '@/game/bribe-options';
|
import { bribeAction } from '@/game/bribe-options';
|
||||||
import { faction } from '@/game/catalog';
|
import { faction } from '@/game/catalog';
|
||||||
@@ -47,7 +46,7 @@ export function Bribes({ game: g, act, busy }: {
|
|||||||
<p>{recipient ? `You may pay up to ${recipient.maximum} spice to this recipient.` : `Uncommitted spice: ${options.available}. Choose a recipient to see the payment limit.`}</p>
|
<p>{recipient ? `You may pay up to ${recipient.maximum} spice to this recipient.` : `Uncommitted spice: ${options.available}. Choose a recipient to see the payment limit.`}</p>
|
||||||
{recipient?.limitation ? <p>{recipient.limitation}</p> : null}
|
{recipient?.limitation ? <p>{recipient.limitation}</p> : null}
|
||||||
{recipient && !options.blocked && !action ? <output className="bribe-status">{recipient.maximum < 1 ? 'No spice is available for this payment.' : `Enter a whole number from 1 to ${recipient.maximum}.`}</output> : null}
|
{recipient && !options.blocked && !action ? <output className="bribe-status">{recipient.maximum < 1 ? 'No spice is available for this payment.' : `Enter a whole number from 1 to ${recipient.maximum}.`}</output> : null}
|
||||||
<p>Eligible Bureaucrats may divert two spice to the Bank. AI opponents can receive spice, but do not negotiate deals yet. <Link href="/rules?topic=bribes#bribes">Bribe rules</Link></p>
|
<p>Eligible Bureaucrats may divert two spice to the Bank. AI opponents can receive spice, but do not negotiate deals yet. <a href="/rules?topic=bribes#bribes">Bribe rules</a></p>
|
||||||
</details>
|
</details>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
import Link from 'next/link';
|
|
||||||
|
|
||||||
/** Counts only: this component never receives deck contents or private cards. */
|
/** Counts only: this component never receives deck contents or private cards. */
|
||||||
export function DrawPiles({ piles }: {
|
export function DrawPiles({ piles }: {
|
||||||
@@ -19,7 +18,7 @@ export function DrawPiles({ piles }: {
|
|||||||
</dl>
|
</dl>
|
||||||
<p>Draw piles exclude hands, auction cards and discards.
|
<p>Draw piles exclude hands, auction cards and discards.
|
||||||
{(piles.treachery === 0 || piles.spice === 0) && ' An empty pile may be replenished from eligible discards.'}
|
{(piles.treachery === 0 || piles.spice === 0) && ' An empty pile may be replenished from eligible discards.'}
|
||||||
{' '}<Link href="/rules?topic=draw-piles#draw-piles">About these piles</Link>
|
{' '}<a href="/rules?topic=draw-piles#draw-piles">About these piles</a>
|
||||||
</p>
|
</p>
|
||||||
</section>
|
</section>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import Link from 'next/link';
|
|
||||||
import { leaders } from '@/game/cards';
|
import { leaders } from '@/game/cards';
|
||||||
import type { GameView } from '@/game/engine';
|
import type { GameView } from '@/game/engine';
|
||||||
import { LeaderInspector } from './leader-inspector';
|
import { LeaderInspector } from './leader-inspector';
|
||||||
@@ -16,7 +15,7 @@ export function EcazLoyaltyCard({ loyalty }: { loyalty: GameView['ecazLoyalty']
|
|||||||
<p className="text-base! leading-7!">This Ecaz Traitor Card was randomly set aside face up before dealing. It stays outside the deck for this game and cannot be drawn as a Traitor or Face Dancer. Its leader remains in play under the normal leader rules.</p>
|
<p className="text-base! leading-7!">This Ecaz Traitor Card was randomly set aside face up before dealing. It stays outside the deck for this game and cannot be drawn as a Traitor or Face Dancer. Its leader remains in play under the normal leader rules.</p>
|
||||||
<div className="flex flex-wrap items-center gap-3">
|
<div className="flex flex-wrap items-center gap-3">
|
||||||
<LeaderInspector kind="loyalty" identity={{ id: leader.id, name: leader.name, factionName: 'Ecaz', strength: leader.strength }} />
|
<LeaderInspector kind="loyalty" identity={{ id: leader.id, name: leader.name, factionName: 'Ecaz', strength: leader.strength }} />
|
||||||
<Link className="py-3 text-base underline" href="/rules?topic=ecaz-loyalty#ecaz-loyalty">Loyalty rules</Link>
|
<a className="py-3 text-base underline" href="/rules?topic=ecaz-loyalty#ecaz-loyalty">Loyalty rules</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>;
|
</section>;
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import Image from 'next/image';
|
import Image from 'next/image';
|
||||||
import Link from 'next/link';
|
|
||||||
import { Dialog as DialogPrimitive } from '@base-ui/react/dialog';
|
import { Dialog as DialogPrimitive } from '@base-ui/react/dialog';
|
||||||
import { Eye, X } from 'lucide-react';
|
import { Eye, X } from 'lucide-react';
|
||||||
import { FACTIONS, faction, type FactionId } from '@/game/catalog';
|
import { FACTIONS, faction, type FactionId } from '@/game/catalog';
|
||||||
@@ -34,8 +33,8 @@ export function FactionSheet({ factionId, advanced }: { factionId: FactionId; ad
|
|||||||
</section>}
|
</section>}
|
||||||
<footer>
|
<footer>
|
||||||
<p>The rules and game are still being completed. This sheet describes faction powers; table controls show the choices currently available. Optional modules can add or change powers.</p>
|
<p>The rules and game are still being completed. This sheet describes faction powers; table controls show the choices currently available. Optional modules can add or change powers.</p>
|
||||||
<Link href={`/rules?topic=faction-${factionId}#faction-${factionId}`}>Full {house.name} reference</Link>
|
<a href={`/rules?topic=faction-${factionId}#faction-${factionId}`}>Full {house.name} reference</a>
|
||||||
{advanced && <Link href={`/rules?topic=advanced-${factionId}#advanced-${factionId}`}>Advanced {house.name} reference</Link>}
|
{advanced && <a href={`/rules?topic=advanced-${factionId}#advanced-${factionId}`}>Advanced {house.name} reference</a>}
|
||||||
</footer>
|
</footer>
|
||||||
</article>;
|
</article>;
|
||||||
}
|
}
|
||||||
@@ -64,7 +63,7 @@ export function FactionInspector({ factionId, advanced }: { factionId: FactionId
|
|||||||
<DialogTitle>{house.name} faction sheet</DialogTitle>
|
<DialogTitle>{house.name} faction sheet</DialogTitle>
|
||||||
<DialogDescription>Read public faction powers and alliance guidance without changing the game.</DialogDescription>
|
<DialogDescription>Read public faction powers and alliance guidance without changing the game.</DialogDescription>
|
||||||
<FactionSheetReader key={`${factionId}:${advanced}`} factionId={factionId} tableAdvanced={advanced} />
|
<FactionSheetReader key={`${factionId}:${advanced}`} factionId={factionId} tableAdvanced={advanced} />
|
||||||
<Link className="faction-sheet-help" href="/rules?topic=faction-sheets#faction-sheets">Faction sheet help</Link>
|
<a className="faction-sheet-help" href="/rules?topic=faction-sheets#faction-sheets">Faction sheet help</a>
|
||||||
</section>
|
</section>
|
||||||
</DialogPrimitive.Popup>
|
</DialogPrimitive.Popup>
|
||||||
</DialogPortal>
|
</DialogPortal>
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { useState, type CSSProperties } from 'react';
|
import { useState, type CSSProperties } from 'react';
|
||||||
import Link from 'next/link';
|
|
||||||
import { Dialog as DialogPrimitive } from '@base-ui/react/dialog';
|
import { Dialog as DialogPrimitive } from '@base-ui/react/dialog';
|
||||||
import { Eye, Star, X } from 'lucide-react';
|
import { Eye, Star, X } from 'lucide-react';
|
||||||
import { FACTIONS, faction, type FactionId } from '@/game/catalog';
|
import { FACTIONS, faction, type FactionId } from '@/game/catalog';
|
||||||
@@ -64,7 +63,7 @@ export function ForceInventoryDetails({ player, worlds = null }: { player: Publi
|
|||||||
<div><h3>Concealed No-Field</h3><p>{inventory.marker.name}</p><p>Counts as one force for board presence until revealed. It is a separate marker, not an extra physical force counter. Its hidden force value is not shown here. Revelation draws from the reserves available then.</p></div>
|
<div><h3>Concealed No-Field</h3><p>{inventory.marker.name}</p><p>Counts as one force for board presence until revealed. It is a separate marker, not an extra physical force counter. Its hidden force value is not shown here. Revelation draws from the reserves available then.</p></div>
|
||||||
</section>}
|
</section>}
|
||||||
<p>Inspecting counters does not move them or commit a Battle Plan. Spice support, temporary powers and leaders may change battle strength without adding counters.</p>
|
<p>Inspecting counters does not move them or commit a Battle Plan. Spice support, temporary powers and leaders may change battle strength without adding counters.</p>
|
||||||
<Link href="/rules?topic=force-counters#force-counters">Force counter guide</Link>
|
<a href="/rules?topic=force-counters#force-counters">Force counter guide</a>
|
||||||
</div>;
|
</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -137,7 +137,6 @@ import {
|
|||||||
import { validBattleSlotPair } from '@/game/battle-card-slots';
|
import { validBattleSlotPair } from '@/game/battle-card-slots';
|
||||||
import { stoneBurnerPlanBlock } from '@/game/stone-burner';
|
import { stoneBurnerPlanBlock } from '@/game/stone-burner';
|
||||||
import { TECH_TOKENS, ownedTech } from '@/game/tech-tokens';
|
import { TECH_TOKENS, ownedTech } from '@/game/tech-tokens';
|
||||||
import Link from 'next/link';
|
|
||||||
import {
|
import {
|
||||||
canUseAsKaramaRole,
|
canUseAsKaramaRole,
|
||||||
canUseAsTruthtranceRole,
|
canUseAsTruthtranceRole,
|
||||||
@@ -2627,9 +2626,9 @@ export function GameTable({
|
|||||||
comparison. Other weapon effects and traitor precedence
|
comparison. Other weapon effects and traitor precedence
|
||||||
still apply.
|
still apply.
|
||||||
</p>
|
</p>
|
||||||
<Link href="/rules?topic=stone-burner">
|
<a href="/rules?topic=stone-burner">
|
||||||
Read Stone Burner rules
|
Read Stone Burner rules
|
||||||
</Link>
|
</a>
|
||||||
{actionButton('Kill both leaders', {
|
{actionButton('Kill both leaders', {
|
||||||
type: 'decision',
|
type: 'decision',
|
||||||
event: g.decision.event,
|
event: g.decision.event,
|
||||||
@@ -3286,13 +3285,13 @@ export function GameTable({
|
|||||||
<details className="phase-guidance">
|
<details className="phase-guidance">
|
||||||
<summary>What happens in this phase?</summary>
|
<summary>What happens in this phase?</summary>
|
||||||
<p>{PHASE_HELP[g.phase]}</p>
|
<p>{PHASE_HELP[g.phase]}</p>
|
||||||
<Link
|
<a
|
||||||
href={`/rules#${phaseRuleId(g.phase)}`}
|
href={`/rules#${phaseRuleId(g.phase)}`}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noreferrer"
|
rel="noreferrer"
|
||||||
>
|
>
|
||||||
Open phase guide ↗
|
Open phase guide ↗
|
||||||
</Link>
|
</a>
|
||||||
</details>
|
</details>
|
||||||
<ChoamBaliset game={g} act={act} busy={busy} />
|
<ChoamBaliset game={g} act={act} busy={busy} />
|
||||||
<ChoamGamont game={g} act={act} busy={busy} />
|
<ChoamGamont game={g} act={act} busy={busy} />
|
||||||
@@ -5529,9 +5528,9 @@ export function GameTable({
|
|||||||
several special cards, optional faction reactions, ally support,
|
several special cards, optional faction reactions, ally support,
|
||||||
auction deadlines, and a printed-board connectivity audit.
|
auction deadlines, and a printed-board connectivity audit.
|
||||||
</p>
|
</p>
|
||||||
<Link href="/rules">
|
<a href="/rules">
|
||||||
Open the rules coverage ledger <ArrowRight size={14} />
|
Open the rules coverage ledger <ArrowRight size={14} />
|
||||||
</Link>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
'use client';
|
'use client';
|
||||||
import { Fragment, useId, useState, type ReactNode } from 'react';
|
import { Fragment, useId, useState, type ReactNode } from 'react';
|
||||||
import Link from 'next/link';
|
|
||||||
import type { Card } from '@/game/cards';
|
import type { Card } from '@/game/cards';
|
||||||
import { browseHand, type HandCategory, type HandSort } from '@/game/hand-browsing';
|
import { browseHand, type HandCategory, type HandSort } from '@/game/hand-browsing';
|
||||||
import { Button } from './ui/button';
|
import { Button } from './ui/button';
|
||||||
@@ -48,7 +47,7 @@ export function HandBrowser({ cards, empty, children }: {
|
|||||||
<div className="hand-browse-results">
|
<div className="hand-browse-results">
|
||||||
<output aria-live="polite">{shown.length} of {cards.length} {cards.length === 1 ? 'card' : 'cards'} shown</output>
|
<output aria-live="polite">{shown.length} of {cards.length} {cards.length === 1 ? 'card' : 'cards'} shown</output>
|
||||||
{filtered && <Button type="button" variant="outline" onClick={() => { setQuery(''); setCategory('all'); }}>Show all cards</Button>}
|
{filtered && <Button type="button" variant="outline" onClick={() => { setQuery(''); setCategory('all'); }}>Show all cards</Button>}
|
||||||
<Link href="/rules?topic=hand-browsing#hand-browsing">Hand browsing help</Link>
|
<a href="/rules?topic=hand-browsing#hand-browsing">Hand browsing help</a>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</search>
|
</search>
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import Link from 'next/link';
|
|
||||||
import { useEffect, useRef, useState, useSyncExternalStore } from 'react';
|
import { useEffect, useRef, useState, useSyncExternalStore } from 'react';
|
||||||
import { BattleWheel } from './battle-wheel';
|
import { BattleWheel } from './battle-wheel';
|
||||||
import { CardInspector, CardRules } from './card-inspector';
|
import { CardInspector, CardRules } from './card-inspector';
|
||||||
@@ -25,7 +24,7 @@ export function Introduction() {
|
|||||||
const hydrated = useSyncExternalStore(subscribeHydration, clientSnapshot, serverSnapshot);
|
const hydrated = useSyncExternalStore(subscribeHydration, clientSnapshot, serverSnapshot);
|
||||||
return <main className={styles.page}>
|
return <main className={styles.page}>
|
||||||
<header className={styles.header}>
|
<header className={styles.header}>
|
||||||
<Link href="/">← Return to Arrakis</Link>
|
<a href="/">← Return to Arrakis</a>
|
||||||
<p className="eyebrow">LEARN TO PLAY · BASIC GAME</p>
|
<p className="eyebrow">LEARN TO PLAY · BASIC GAME</p>
|
||||||
<h1>Your first steps on Arrakis</h1>
|
<h1>Your first steps on Arrakis</h1>
|
||||||
<p>Practice the Basic phases, from the storm and spice blow to alliances, resources, bidding, movement, battle and collection, then take a seat at a real table. These are separate examples; they do not change any saved game.</p>
|
<p>Practice the Basic phases, from the storm and spice blow to alliances, resources, bidding, movement, battle and collection, then take a seat at a real table. These are separate examples; they do not change any saved game.</p>
|
||||||
@@ -164,11 +163,11 @@ function IntroductionLessons() {
|
|||||||
<li>Use <strong>Protect your saved seat</strong> and keep its recovery kit private. An invitation identifies the room; the kit restores your seat after cookie loss.</li>
|
<li>Use <strong>Protect your saved seat</strong> and keep its recovery kit private. An invitation identifies the room; the kit restores your seat after cookie loss.</li>
|
||||||
</ul>
|
</ul>
|
||||||
<p>Refresh restores a live game through its saved seat. If a request has an uncertain result, use the offered retry rather than repeating the game action.</p>
|
<p>Refresh restores a live game through its saved seat. If a request has an uncertain result, use the offered retry rather than repeating the game action.</p>
|
||||||
<div className={styles.links}><Link href="/">Create or join a game →</Link><Link href="/rules">Explore the full reference →</Link></div>
|
<div className={styles.links}><a href="/">Create or join a game →</a><a href="/rules">Explore the full reference →</a></div>
|
||||||
</>}
|
</>}
|
||||||
<footer className={styles.footer}>
|
<footer className={styles.footer}>
|
||||||
<button disabled={state.step === 0} onClick={() => go(state.step - 1)}>Previous lesson</button>
|
<button disabled={state.step === 0} onClick={() => go(state.step - 1)}>Previous lesson</button>
|
||||||
<Link href={`/rules?topic=${lesson.topic}#${lesson.topic}`}>Read the related rules</Link>
|
<a href={`/rules?topic=${lesson.topic}#${lesson.topic}`}>Read the related rules</a>
|
||||||
{state.step < INTRODUCTION_STEPS.length - 1 && <button onClick={() => go(state.step + 1)}>Next lesson</button>}
|
{state.step < INTRODUCTION_STEPS.length - 1 && <button onClick={() => go(state.step + 1)}>Next lesson</button>}
|
||||||
</footer>
|
</footer>
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import type { GameView } from '@/game/engine';
|
import type { GameView } from '@/game/engine';
|
||||||
import Link from 'next/link';
|
|
||||||
import { faction } from '@/game/catalog';
|
import { faction } from '@/game/catalog';
|
||||||
import { leaderStrengthLabel } from '@/game/cards';
|
import { leaderStrengthLabel } from '@/game/cards';
|
||||||
import { isStoneBurner } from '@/game/battle-cards';
|
import { isStoneBurner } from '@/game/battle-cards';
|
||||||
@@ -37,7 +36,7 @@ export function RevealedBattle({ game }: { game: GameView }) {
|
|||||||
<section className="revealed-plans" id="revealed-battle-plans" tabIndex={-1} aria-label="Revealed battle plans">
|
<section className="revealed-plans" id="revealed-battle-plans" tabIndex={-1} aria-label="Revealed battle plans">
|
||||||
<h3>Revealed battle plans</h3>
|
<h3>Revealed battle plans</h3>
|
||||||
<a className="battle-display-link" href="#table-decisions">Return to battle decisions</a>
|
<a className="battle-display-link" href="#table-decisions">Return to battle decisions</a>
|
||||||
<p className="revealed-plans-help">Both plans are public. Inspect any played component; use the decision controls to resolve the battle. <Link href="/rules?topic=revealed-battle-components#revealed-battle-components">Component guide</Link> · <Link href="/rules?topic=battle#battle">Battle rules</Link></p>
|
<p className="revealed-plans-help">Both plans are public. Inspect any played component; use the decision controls to resolve the battle. <a href="/rules?topic=revealed-battle-components#revealed-battle-components">Component guide</a> · <a href="/rules?topic=battle#battle">Battle rules</a></p>
|
||||||
<div className="revealed-plan-pair">
|
<div className="revealed-plan-pair">
|
||||||
{[battle.attacker, battle.defender].map((id) => {
|
{[battle.attacker, battle.defender].map((id) => {
|
||||||
const plan = battle.plans[id];
|
const plan = battle.plans[id];
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
'use client';
|
'use client';
|
||||||
import Link from 'next/link';
|
|
||||||
import { StrongholdCardGallery } from './stronghold-cards';
|
import { StrongholdCardGallery } from './stronghold-cards';
|
||||||
import { HomeworldCardGallery } from './homeworld-cards';
|
import { HomeworldCardGallery } from './homeworld-cards';
|
||||||
import { NexusCardGallery } from './nexus-cards';
|
import { NexusCardGallery } from './nexus-cards';
|
||||||
@@ -94,7 +93,7 @@ function RulesReferenceContent() {
|
|||||||
return (
|
return (
|
||||||
<main className="reference-page">
|
<main className="reference-page">
|
||||||
<header className="reference-header">
|
<header className="reference-header">
|
||||||
<Link href="/">← Return to Arrakis</Link>
|
<a href="/">← Return to Arrakis</a>
|
||||||
<span className="eyebrow">
|
<span className="eyebrow">
|
||||||
<BookOpen size={14} /> THE MENTAT’S REFERENCE
|
<BookOpen size={14} /> THE MENTAT’S REFERENCE
|
||||||
</span>
|
</span>
|
||||||
@@ -107,17 +106,17 @@ function RulesReferenceContent() {
|
|||||||
Find a phase, card or faction. Follow the action guides, inspect
|
Find a phase, card or faction. Follow the action guides, inspect
|
||||||
examples and move directly between related rules.
|
examples and move directly between related rules.
|
||||||
</p>
|
</p>
|
||||||
<p><Link href="/learn">New to Dune? Try the interactive introduction →</Link></p>
|
<p><a href="/learn">New to Dune? Try the interactive introduction →</a></p>
|
||||||
<p className="reference-status">
|
<p className="reference-status">
|
||||||
This reference is growing with the game. Advanced play and expansion
|
This reference is growing with the game. Advanced play and expansion
|
||||||
rules are unfinished.{' '}
|
rules are unfinished.{' '}
|
||||||
<Link href="/rules/coverage">
|
<a href="/rules/coverage">
|
||||||
View the implementation ledger <ArrowUpRight size={13} />
|
View the implementation ledger <ArrowUpRight size={13} />
|
||||||
</Link>
|
</a>
|
||||||
{' · '}
|
{' · '}
|
||||||
<Link href="/rules?topic=implementation-checklist#implementation-checklist">
|
<a href="/rules?topic=implementation-checklist#implementation-checklist">
|
||||||
Open feature checklists <ArrowUpRight size={13} />
|
Open feature checklists <ArrowUpRight size={13} />
|
||||||
</Link>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
</header>
|
</header>
|
||||||
<div className="reference-layout">
|
<div className="reference-layout">
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import { ForceCountFields, forceCountInputError } from './truthtrance-force-coun
|
|||||||
import { isKnowledgeFact, knowledgeFactInputError } from '@/game/truthtrance-knowledge';
|
import { isKnowledgeFact, knowledgeFactInputError } from '@/game/truthtrance-knowledge';
|
||||||
import type { PlanClaim } from '@/game/battle-promises';
|
import type { PlanClaim } from '@/game/battle-promises';
|
||||||
import { useId, useState } from 'react';
|
import { useId, useState } from 'react';
|
||||||
import Link from 'next/link';
|
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { Input } from '@/components/ui/input';
|
import { Input } from '@/components/ui/input';
|
||||||
import { CHEAP_HERO_TRAITOR } from '@/game/traitors';
|
import { CHEAP_HERO_TRAITOR } from '@/game/traitors';
|
||||||
@@ -130,7 +129,7 @@ export function Truthtrance({
|
|||||||
<section className="truthtrance-panel" aria-label="Truthtrance question">
|
<section className="truthtrance-panel" aria-label="Truthtrance question">
|
||||||
<h2>Truthtrance</h2>
|
<h2>Truthtrance</h2>
|
||||||
<p className="fine">
|
<p className="fine">
|
||||||
<Link href="/rules#card-truthtrance">Question rules</Link> · The table
|
<a href="/rules#card-truthtrance">Question rules</a> · The table
|
||||||
resumes its previous decision after these questions.
|
resumes its previous decision after these questions.
|
||||||
</p>
|
</p>
|
||||||
{w.stage === 'priority' ? (
|
{w.stage === 'priority' ? (
|
||||||
|
|||||||
@@ -52,6 +52,16 @@ public hosting remains separately gated. HTTP does not encrypt cookies or seat
|
|||||||
recovery kits. Browser operation IDs now use cryptographic `getRandomValues`,
|
recovery kits. Browser operation IDs now use cryptographic `getRandomValues`,
|
||||||
which also works at LAN HTTP origins where `randomUUID` is unavailable.
|
which also works at LAN HTTP origins where `randomUUID` is unavailable.
|
||||||
|
|
||||||
|
## Production navigation
|
||||||
|
|
||||||
|
Use native anchors for app page links. The pinned Vinext/Vite production build
|
||||||
|
renames navigation exports while its Link runtime dynamically looks up their
|
||||||
|
original names, breaking clicks and prefetch despite successful HTTP responses.
|
||||||
|
Native navigation keeps query strings, rule anchors, browser history and saved
|
||||||
|
seat cookies. The Next.js lint preference for client-side Link is disabled for
|
||||||
|
this intentional choice. Verify page links in the production browser when
|
||||||
|
changing framework versions; development navigation alone does not cover it.
|
||||||
|
|
||||||
## Update and preserve games
|
## Update and preserve games
|
||||||
|
|
||||||
### Existing NGINX HTTPS termination
|
### Existing NGINX HTTPS termination
|
||||||
|
|||||||
Reference in new issue
Block a user