From 3abc325ea27feee5c4cc59f02296ff218f342a81 Mon Sep 17 00:00:00 2001 From: Thomas Hallock Date: Sun, 2 Nov 2025 06:22:59 -0600 Subject: [PATCH] refactor(rithmomachia): extract reusable components from SetupPhase MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Extract three reusable components from SetupPhase.tsx to eliminate code duplication and improve maintainability. **Components Created:** - GameRuleCard.tsx (94 lines): Reusable card component for game settings - Replaces 4 nearly identical card implementations - Supports enabled/disabled states with visual feedback - Accepts optional children for custom content (e.g., threshold input) - SetupHeader.tsx (155 lines): Medieval-style ornamental header - Title, subtitle, decorative corners, and guide button - StartButton.tsx (73 lines): Dramatic start button with animations **Impact:** - SetupPhase.tsx: 759 → 308 lines (59% reduction, -451 lines) - Eliminated ~480 lines of duplicate JSX across 4 game rule cards - Improved component reusability and consistency - Cleaner, more maintainable codebase **Testing:** - Pre-commit checks passed (format, lint) - TypeScript: No new errors in changed files - Ready for manual testing 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .../components/phases/GameRuleCard.tsx | 93 ++++ .../components/phases/SetupHeader.tsx | 153 ++++++ .../components/phases/SetupPhase.tsx | 509 +----------------- .../components/phases/StartButton.tsx | 71 +++ 4 files changed, 346 insertions(+), 480 deletions(-) create mode 100644 apps/web/src/arcade-games/rithmomachia/components/phases/GameRuleCard.tsx create mode 100644 apps/web/src/arcade-games/rithmomachia/components/phases/SetupHeader.tsx create mode 100644 apps/web/src/arcade-games/rithmomachia/components/phases/StartButton.tsx diff --git a/apps/web/src/arcade-games/rithmomachia/components/phases/GameRuleCard.tsx b/apps/web/src/arcade-games/rithmomachia/components/phases/GameRuleCard.tsx new file mode 100644 index 00000000..6603b3dc --- /dev/null +++ b/apps/web/src/arcade-games/rithmomachia/components/phases/GameRuleCard.tsx @@ -0,0 +1,93 @@ +import type { ReactNode } from 'react' +import { css } from '../../../../../styled-system/css' + +export interface GameRuleCardProps { + title: string + description: string + enabled: boolean + onClick: () => void + dataAttribute?: string + children?: ReactNode +} + +export function GameRuleCard({ + title, + description, + enabled, + onClick, + dataAttribute, + children, +}: GameRuleCardProps) { + return ( +
+
+
+
+ {enabled && '✓ '} + {title} +
+
{description}
+
+ {enabled && ( +
+ )} +
+ + {children} +
+ ) +} diff --git a/apps/web/src/arcade-games/rithmomachia/components/phases/SetupHeader.tsx b/apps/web/src/arcade-games/rithmomachia/components/phases/SetupHeader.tsx new file mode 100644 index 00000000..a70c7bc5 --- /dev/null +++ b/apps/web/src/arcade-games/rithmomachia/components/phases/SetupHeader.tsx @@ -0,0 +1,153 @@ +import { css } from '../../../../../styled-system/css' + +export interface SetupHeaderProps { + onOpenGuide: () => void +} + +export function SetupHeader({ onOpenGuide }: SetupHeaderProps) { + return ( +
+ {/* Ornamental corners */} +
+
+
+
+ +

+ ⚔️ RITHMOMACHIA ⚔️ +

+
+

+ The Philosopher's Game +

+

+ Win by forming mathematical progressions in enemy territory +

+ +
+ ) +} diff --git a/apps/web/src/arcade-games/rithmomachia/components/phases/SetupPhase.tsx b/apps/web/src/arcade-games/rithmomachia/components/phases/SetupPhase.tsx index ed08cc1f..d7b9b8fb 100644 --- a/apps/web/src/arcade-games/rithmomachia/components/phases/SetupPhase.tsx +++ b/apps/web/src/arcade-games/rithmomachia/components/phases/SetupPhase.tsx @@ -5,6 +5,9 @@ import { useGameMode } from '@/contexts/GameModeContext' import { css } from '../../../../../styled-system/css' import { useRithmomachia } from '../../Provider' import type { RithmomachiaConfig } from '../../types' +import { GameRuleCard } from './GameRuleCard' +import { SetupHeader } from './SetupHeader' +import { StartButton } from './StartButton' export interface SetupPhaseProps { onOpenGuide: () => void @@ -164,152 +167,7 @@ export function SetupPhase({ onOpenGuide }: SetupPhaseProps) { {/* Only show setup config when we have enough players */} {rosterStatus.status !== 'tooFew' && ( <> - {/* Title Section - Compact medieval manuscript style */} -
- {/* Ornamental corners - smaller */} -
-
-
-
- -

- ⚔️ RITHMOMACHIA ⚔️ -

-
-

- The Philosopher's Game -

-

- Win by forming mathematical progressions in enemy territory -

- -
+ {/* Game Settings - Compact with flex: 1 to take remaining space */}
- {/* Point Victory */} -
toggleSetting('pointWinEnabled')} - className={css({ - display: 'flex', - flexDirection: 'column', - gap: '1vh', - p: '1.5vh', - bg: state.pointWinEnabled - ? 'rgba(251, 191, 36, 0.25)' - : 'rgba(139, 92, 246, 0.1)', - borderRadius: '1vh', - border: '0.3vh solid', - borderColor: state.pointWinEnabled - ? 'rgba(251, 191, 36, 0.8)' - : 'rgba(139, 92, 246, 0.3)', - transition: 'all 0.3s ease', - cursor: 'pointer', - position: 'relative', - overflow: 'hidden', - boxShadow: state.pointWinEnabled - ? '0 0.5vh 2vh rgba(251, 191, 36, 0.4)' - : '0 0.2vh 0.5vh rgba(0,0,0,0.1)', - _hover: { - bg: state.pointWinEnabled - ? 'rgba(251, 191, 36, 0.35)' - : 'rgba(139, 92, 246, 0.2)', - borderColor: state.pointWinEnabled - ? 'rgba(251, 191, 36, 1)' - : 'rgba(139, 92, 246, 0.5)', - transform: 'translateY(-0.2vh)', - }, - _active: { - transform: 'scale(0.98)', - }, - })} + dataAttribute="point-victory" > -
-
-
- {state.pointWinEnabled && '✓ '}Point Victory -
-
- Win at {state.pointWinThreshold}pts -
-
- {state.pointWinEnabled && ( -
- )} -
- - {/* Threshold input - only visible when enabled */} {state.pointWinEnabled && (
)} -
+ - {/* Threefold Repetition */} -
toggleSetting('repetitionRule')} - className={css({ - display: 'flex', - justifyContent: 'space-between', - alignItems: 'center', - p: '1.5vh', - bg: state.repetitionRule - ? 'rgba(251, 191, 36, 0.25)' - : 'rgba(139, 92, 246, 0.1)', - borderRadius: '1vh', - border: '0.3vh solid', - borderColor: state.repetitionRule - ? 'rgba(251, 191, 36, 0.8)' - : 'rgba(139, 92, 246, 0.3)', - transition: 'all 0.3s ease', - cursor: 'pointer', - position: 'relative', - overflow: 'hidden', - boxShadow: state.repetitionRule - ? '0 0.5vh 2vh rgba(251, 191, 36, 0.4)' - : '0 0.2vh 0.5vh rgba(0,0,0,0.1)', - _hover: { - bg: state.repetitionRule - ? 'rgba(251, 191, 36, 0.35)' - : 'rgba(139, 92, 246, 0.2)', - borderColor: state.repetitionRule - ? 'rgba(251, 191, 36, 1)' - : 'rgba(139, 92, 246, 0.5)', - transform: 'translateY(-0.2vh)', - }, - _active: { - transform: 'scale(0.98)', - }, - })} - > -
-
- {state.repetitionRule && '✓ '}Threefold Draw -
-
- Same position 3x -
-
- {state.repetitionRule && ( -
- )} -
+ dataAttribute="threefold-repetition" + /> - {/* Fifty Move Rule */} -
toggleSetting('fiftyMoveRule')} - className={css({ - display: 'flex', - justifyContent: 'space-between', - alignItems: 'center', - p: '1.5vh', - bg: state.fiftyMoveRule - ? 'rgba(251, 191, 36, 0.25)' - : 'rgba(139, 92, 246, 0.1)', - borderRadius: '1vh', - border: '0.3vh solid', - borderColor: state.fiftyMoveRule - ? 'rgba(251, 191, 36, 0.8)' - : 'rgba(139, 92, 246, 0.3)', - transition: 'all 0.3s ease', - cursor: 'pointer', - position: 'relative', - overflow: 'hidden', - boxShadow: state.fiftyMoveRule - ? '0 0.5vh 2vh rgba(251, 191, 36, 0.4)' - : '0 0.2vh 0.5vh rgba(0,0,0,0.1)', - _hover: { - bg: state.fiftyMoveRule - ? 'rgba(251, 191, 36, 0.35)' - : 'rgba(139, 92, 246, 0.2)', - borderColor: state.fiftyMoveRule - ? 'rgba(251, 191, 36, 1)' - : 'rgba(139, 92, 246, 0.5)', - transform: 'translateY(-0.2vh)', - }, - _active: { - transform: 'scale(0.98)', - }, - })} - > -
-
- {state.fiftyMoveRule && '✓ '}Fifty-Move Draw -
-
- 50 moves no event -
-
- {state.fiftyMoveRule && ( -
- )} -
+ dataAttribute="fifty-move-rule" + /> - {/* Harmony Persistence */} -
toggleSetting('allowAnySetOnRecheck')} - className={css({ - display: 'flex', - justifyContent: 'space-between', - alignItems: 'center', - p: '1.5vh', - bg: state.allowAnySetOnRecheck - ? 'rgba(251, 191, 36, 0.25)' - : 'rgba(139, 92, 246, 0.1)', - borderRadius: '1vh', - border: '0.3vh solid', - borderColor: state.allowAnySetOnRecheck - ? 'rgba(251, 191, 36, 0.8)' - : 'rgba(139, 92, 246, 0.3)', - transition: 'all 0.3s ease', - cursor: 'pointer', - position: 'relative', - overflow: 'hidden', - boxShadow: state.allowAnySetOnRecheck - ? '0 0.5vh 2vh rgba(251, 191, 36, 0.4)' - : '0 0.2vh 0.5vh rgba(0,0,0,0.1)', - _hover: { - bg: state.allowAnySetOnRecheck - ? 'rgba(251, 191, 36, 0.35)' - : 'rgba(139, 92, 246, 0.2)', - borderColor: state.allowAnySetOnRecheck - ? 'rgba(251, 191, 36, 1)' - : 'rgba(139, 92, 246, 0.5)', - transform: 'translateY(-0.2vh)', - }, - _active: { - transform: 'scale(0.98)', - }, - })} - > -
-
- {state.allowAnySetOnRecheck && '✓ '}Flexible Harmony -
-
- Any valid set -
-
- {state.allowAnySetOnRecheck && ( -
- )} -
+ dataAttribute="flexible-harmony" + />
- {/* Start Button - Compact but dramatic */} - + )}
diff --git a/apps/web/src/arcade-games/rithmomachia/components/phases/StartButton.tsx b/apps/web/src/arcade-games/rithmomachia/components/phases/StartButton.tsx new file mode 100644 index 00000000..86059e25 --- /dev/null +++ b/apps/web/src/arcade-games/rithmomachia/components/phases/StartButton.tsx @@ -0,0 +1,71 @@ +import { css } from '../../../../../styled-system/css' + +export interface StartButtonProps { + onClick: () => void + disabled: boolean +} + +export function StartButton({ onClick, disabled }: StartButtonProps) { + return ( + + ) +}