Removed non-functional wheel scrolling code and changed games carousel
to overflow: visible for a full-width effect that extends beyond the
container. Creates a more immersive, cinematic feel where game cards
bleed into the page edges.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Changed carousel wheel scrolling from discrete next/prev jumps to smooth
scrolling that follows the wheel delta. Now it feels like actually
scrolling instead of a rocket engine!
- Uses embla's internal scroll container directly
- Scales wheel delta by 0.5 for comfortable scroll speed
- Maintains horizontal scroll and shift+scroll detection
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Both games and player carousels now support horizontal scrolling with:
- Mouse wheel horizontal scroll (trackpad swipe gestures)
- Shift + vertical scroll for horizontal navigation
- Prevents page scroll when interacting with carousel
Implemented with custom wheel event handlers that detect horizontal
scroll intent and call embla's scrollNext/scrollPrev methods.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Fixed carousel looping issue by replacing generic 'transition: all' with
specific transition properties. Only transition opacity on carousel items
and transform/box-shadow/border-color on cards for hover effects.
This prevents CSS transitions from interfering with embla's carousel
transform animations at the loop wrap point.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Added a hero section at the top of the games page featuring a rotating
carousel of all available arcade games. Each card shows:
- Game icon and name
- Difficulty and player count
- Description
- Category chips (Multiplayer, Memory, Soroban, etc.)
- Game-specific gradient background
Uses embla-carousel for smooth dragging and navigation dots with game
icons for quick access. Cards link directly to each game's arcade page.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Removed the hero section subtitle since the page title is now in the
nav bar. The subtitle was awkwardly positioned where nobody would see it.
Added top padding to account for the fixed nav bar.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Fixed AppNavBar auto-detecting /games route as needing minimal mode.
The /games page should use the full navigation bar (like /create and
/guide), not the minimal arcade nav with only a hamburger menu.
Changed route detection to only apply minimal nav to /arcade/* routes,
not /games route.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
The static icon.svg has been replaced by dynamic day-of-month favicon
generation via src/app/icon/route.tsx which calls scripts/generateDayIcon.tsx
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Update favicon design based on user preference:
- Ones place: Gold (#fbbf24, stroke #f59e0b)
- Tens place: Purple (#a855f7, stroke #7e22ce)
- Remove background circle for clean, transparent appearance
The royal color scheme provides better contrast and a more
sophisticated look while maintaining excellent legibility at small sizes.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Use asymmetric padding to crop tighter to the active bead region:
- Top: 8px (some breathing room above)
- Bottom: 2px (minimal gap below last bead)
- Sides: 5px
This eliminates the visible gap of column post structure below the
last active bead while maintaining clean spacing above.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Previous approach used transforms to scale the entire SVG, which didn't
actually crop anything - just scaled everything uniformly.
New approach uses nested SVG with viewBox to truly crop the content:
- Outer SVG: 100x100 canvas with background circle
- Inner SVG: Uses viewBox to show only the cropped region
- viewBox dimensions vary per day based on active bead positions
Results:
- Day 1 (1 bead): viewBox height ~59px (narrow vertical crop)
- Day 15 (mixed): viewBox height ~88px (medium)
- Day 31 (many): viewBox height ~104px (tall vertical crop)
- Horizontal: Always ~110px (both columns with posts)
This actually maximizes bead size by showing only the relevant vertical region.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Clarify that horizontal and vertical cropping use different criteria:
- Horizontal (X): Always show full width of both columns (from structural rects)
- Vertical (Y): Crop dynamically to active bead positions + heights
This ensures consistent horizontal scale (0.8) across all days, with only
vertical positioning adjusting based on where active beads are located.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Refined the cropping algorithm to include structural elements (column posts)
in the bounding box calculation, not just active beads. This ensures the
complete abacus structure is visible.
Algorithm changes:
- X range: Full width from leftmost to rightmost post (all columns)
- Y range: From top active bead to bottom active bead (tight vertical crop)
Results:
- Consistent scale of 0.8 across all days (vs original 0.48)
- Shows complete column structure with posts
- Vertical position adjusts based on active bead positions
- Maintains visual context while maximizing bead visibility
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Implement bounding box calculation to crop the favicon SVG to show only
the active beads, maximizing their size within the 100x100 icon canvas.
Algorithm:
1. Parse rendered SVG to find all active bead positions (via regex)
2. Calculate bounding box with 15px padding
3. Compute optimal scale to fit within 96x96 (leaving border room)
4. Apply transform: translate + scale + translate to crop and center
Results:
- Day 1-9 (few beads): scale ~1.14 (2.4x larger than before)
- Day 31 (many beads): scale ~0.74 (1.5x larger than before)
- Original fixed scale: 0.48
This uses no external dependencies - just regex parsing of the rendered
SVG to extract active bead coordinates.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Metadata fixes:
- Update icon path from /icon.svg to /icon to match route handler
- Ensure proper MIME type (image/svg+xml) is set in both metadata and response
Favicon visibility improvements:
- Increase AbacusReact scaleFactor from 1.0 to 1.8 for larger beads
- Add hideInactiveBeads prop to hide inactive beads
- Add hide-inactive-mode class to wrapper for CSS to take effect
- Adjust outer scale to 0.48 to fit larger abacus in 100x100 viewBox
- Reposition abacus with translate(28, -2) for proper centering
CSS cleanup:
- Strip !important declarations from generated SVG (production code policy)
- Apply same fix to OG image generator
User needs to restart dev server to clear in-memory cache.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Create scripts/generateDayIcon.tsx for on-demand icon generation
- Route handler calls script via execSync (avoids Next.js react-dom/server restriction)
- Implement in-memory caching to minimize subprocess overhead
- Show current day (01-31) on 2-column abacus in US Central Time
- High-contrast design: blue/green beads, 2px strokes, gold border
- Document SSR pattern in .claude/CLAUDE.md for future reference
Fixes the Next.js limitation where route handlers cannot import react-dom/server
by using subprocess pattern - scripts CAN use react-dom/server, route handlers
call them via execSync and cache the results.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Move 'Soroban Arcade' title from page content to navigation bar
for consistency with other app pages (create, guide).
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Simplified hero section messaging:
- Changed subtitle from 'Level up your mental math powers...' to
'Classic strategy games and lightning-fast challenges'
- Removed outdated feature pills (xpBadge, streakBadge, features)
- More accurate description of current game offerings
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
This commit includes several related improvements to the games page:
1. Fix victories calculation - removed incorrect division by player count
2. Implement player profile carousel using embla-carousel-react
- Smooth infinite loop carousel with drag support
- Navigation dots with player emojis
- Simple opacity-based transitions (no 3D effects)
- Disabled text selection during drag
- Fixed gap spacing at carousel wrap point
3. Add conditional rendering - hide stats sections when user has no gameplay data
4. Update hero section - simplified design, removed excessive animations
5. Switch to PageWithNav for consistent navigation across the app
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Add embla-carousel-react dependency to enable smooth carousel
functionality for displaying player profiles on the games page.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Add timeZone: 'UTC' to server-side i18n config in src/i18n/request.ts
- Add timeZone="UTC" prop to NextIntlClientProvider in src/components/ClientProviders.tsx
- Fix TypeScript type error: handle undefined cookie value with nullish coalescing
- Eliminates IntlError: ENVIRONMENT_FALLBACK warnings in development server
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Generate icon.svg and og-image.svg from AbacusReact component via scripts/generateAbacusIcons.tsx
- OG image: huge 4-column abacus with place-value colors, dark theme with decorative diamond shapes and math operators at 0.4/0.35 opacity for visibility
- Favicon: single-column abacus showing value 5, dark brown beads, properly centered and scaled (0.7)
- opengraph-image.tsx: read pre-generated og-image.svg instead of using react-dom/server (avoids edge runtime restriction)
- All abacus visualizations now use AbacusReact component consistently
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Replaced manual HTML/CSS abacus representation in opengraph-image.tsx
with server-side rendered AbacusReact component, using the same SVG
extraction approach as icon.svg and og-image.svg.
Now all three image generation methods use the actual AbacusReact
component from @soroban/abacus-react instead of manual recreations.
Changes:
- Added renderToStaticMarkup and AbacusReact imports
- Added extractSvgContent() function to parse SVG from rendered markup
- Replaced 150+ lines of manual HTML/CSS with AbacusReact render
- Embedded extracted SVG in ImageResponse via dangerouslySetInnerHTML
Benefits:
- Consistent abacus rendering across all images
- Automatic updates when AbacusReact component changes
- Significantly less code to maintain
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Fixed generateAbacusIcons.tsx to properly extract SVG content from
AbacusReact server-side renders, removing invalid div wrappers that
prevented the abacus from displaying in icon.svg and og-image.svg.
Changes:
- Added extractSvgContent() function to parse rendered markup
- Regenerated icon.svg with proper SVG structure
- Regenerated og-image.svg showing abacus value 123
- Removed unused React import (modern TSX doesn't need it)
The Open Graph image now correctly displays the abacus visualization
alongside the site text, fixing the issue where only text was visible.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Update metadata in layout.tsx with full SEO, Open Graph, Twitter cards, PWA config
- Create sitemap.ts for dynamic sitemap generation
- Create robots.ts for search engine guidance
- Create icon.svg favicon generated from AbacusReact component
- Create opengraph-image.tsx for dynamic Open Graph image generation
- Create public/og-image.svg as fallback Open Graph image
- Make AbacusReact component SSR-compatible by:
- Detecting server environment and conditionally using animations
- Using regular SVG elements instead of animated ones when on server
- Making useSpring and useDrag hooks SSR-safe
- Add generateAbacusIcons.tsx script to generate icons from real AbacusReact component
All icons now use the actual AbacusReact component rendering, ensuring consistency
between static assets and the interactive version.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Eliminated duplication between Harmony and Victory tabs:
**Harmony Tab** (focus: what ARE harmonies and how to FORM them):
- Renamed title to "Mathematical Progressions" (clearer focus)
- Updated intro to focus on patterns, not winning
- Removed "Important Rules" section (moved to Victory)
- Kept: 3 harmony types, formation strategies, quick reference
**Victory Tab** (focus: how to WIN):
- Expanded harmony victory section with placement requirements:
- Must be in enemy territory
- Must be in straight line
- Must be adjacent/touching
- Must form progression
- Must survive one turn
- Added reference to Harmony tab for progression details
- Kept: Exhaustion victory, strategy tips
Benefits:
- Clear separation of concerns
- No duplication
- Better learning flow
- Matches SPEC.md simplified rules
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Fixed board rotation issues when guide is docked:
- Board now prioritizes HEIGHT when in portrait orientation
- Removed CSS constraints (maxWidth, maxHeight, aspectRatio) that were
interfering with explicit dimensions during rotation
- Board properly fills container whether guide is docked or not
- Smooth transition between portrait and landscape orientations
Technical changes:
- Set maxWidth/maxHeight to 'none' when svgDimensions are explicit
- Set aspectRatio to 'auto' when rotating to prevent override
- Cleaned up debug logging
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Fixed hover error tooltip to properly check pyramid pieces by testing
all 4 pyramid faces for valid capture relations. Previously the code
was using getEffectiveValue() which returns null for pyramids, causing
the tooltip to never show.
Now for pyramid pieces, the code iterates through all 4 faces and only
shows the "No valid relation" error if NONE of the faces can form a
valid relation with the target piece.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Added targeted console logging to help debug two issues:
1. Capture error tooltip on hover - logs mouse events, piece detection,
path validation, and relation checking
2. Guide dragging from docked state - logs drag events, undocking logic,
and position calculations
All logs use string concatenation and are prefixed with [HOVER_ERROR]
or [GUIDE_DRAG] for easy filtering. Removed unrelated debug logging.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Installed @types/react-textfit to resolve TypeScript compilation error
in PlayingGuideModal.tsx. This package provides type definitions for
the react-textfit library used in the Rithmomachia guide component.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Fixed the guide modal jumping to center when dragging from docked state.
**Root Cause:**
When undocking, the code was centering the modal on the cursor:
- Position set to: `{ x: e.clientX - width/2, y: e.clientY - 20 }`
- This caused a visual jump from the docked position to cursor-centered
**Solution:**
Changed undocking logic to maintain the modal's current visual position:
- Position set to: `{ x: rect.left, y: rect.top }` (current screen position)
- dragStart set to: `{ x: e.clientX - rect.left, y: e.clientY - rect.top }` (offset)
**Result:**
- Modal stays exactly where it is visually when undocking (no jump!)
- dragStart correctly reflects the cursor offset from modal position
- Subsequent mouse movements smoothly continue the drag operation
- Single continuous drag gesture from docked to floating state
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Fixed capture error tooltip not showing by adding path validation before
checking for invalid capture relations.
The hover error now correctly:
1. Validates the move path is legal (using validateMove)
2. Only shows if path is valid AND there's no valid capture relation
3. Prevents showing errors for moves that aren't even possible
Added import for validateMove from pathValidator utils.
This ensures the "no valid relation" tooltip only appears when you're
hovering over an enemy piece that your selected piece can legally move to,
but cannot capture due to missing valid numeric relations.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Changed pyramid face numbers to display when the piece is selected for movement,
instead of on hover, with subtle fade-in and scale animation:
**PieceRenderer changes:**
- Added react-spring import for animations
- Added pyramidNumbersSpring with fade-in (opacity 0→1) and scale (0.8→1) effect
- Wrapped pyramid face numbers in animated.g with spring-driven opacity and scale
- Changed from `hovered` prop back to `selected` prop
- Uses gentle spring config: tension 200, friction 20 for smooth, non-distracting animation
**SvgPiece changes:**
- Removed hover state management (useState, onMouseEnter/Leave)
- Reverted to passing `selected` prop to PieceRenderer
- Simplified component by removing hover tracking
Pyramid face numbers now appear when the piece is selected (ready to move),
with a subtle zoom-in effect that's noticeable but not distracting.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Changed "no valid relation" error to appear as a tooltip when hovering over
an enemy piece with a selected piece, instead of after clicking:
**CaptureErrorDialog changes:**
- Removed OK button (no manual dismiss needed)
- Made dialog non-interactive with pointerEvents: 'none'
- Simplified layout to center-aligned content
**BoardDisplay changes:**
- Added hoveredSquare state to track hovered enemy pieces
- Added handleSvgMouseMove to detect hovers over enemy pieces
- Added handleSvgMouseLeave to clear hover state
- Calculate showHoverError when hovering over invalid capture target
- Display inline hover error tooltip at hovered square position
- Auto-dismisses when mouse leaves (no manual dismiss needed)
This provides instant feedback about invalid captures before clicking,
improving the UX by showing errors as a preview rather than after attempting.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Changed pyramid face numbers to appear when hovering over any pyramid piece,
making them visible to all players as public information:
- Updated SvgPiece to manage hover state with useState
- Added onMouseEnter and onMouseLeave handlers
- Changed PieceRenderer prop from `selected` to `hovered`
- Updated condition to show numbers on hover for pyramid pieces
- Removed dependency on piece selection state
Pyramid face values are not secret information, so any player can now see
them by hovering over a pyramid piece, regardless of whose turn it is.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Made pyramid face numbers significantly larger and bolder:
- Increased font size from 0.26 to 0.35 (34.6% larger)
- Increased stroke width from 0.03 to 0.05 (66.7% thicker outline)
- Changed font to "Arial Black" for maximum boldness
- Font weight remains at 900 (maximum)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Enhanced visual treatment of pyramid face numbers when pieces are selected:
- Increased font size from 0.16 to 0.26 (62.5% larger)
- Repositioned to avoid edge clipping (0.12/0.88 instead of 0.05/0.95)
- Added dual-layer rendering: outline stroke + filled text with drop shadow
- Used vibrant amber colors (#fbbf24 for dark, #b45309 for light pieces)
- Added CSS transition for smooth animations
- Changed to bold Arial font (weight 900) for better readability
- High-contrast drop shadow (opacity 0.9) for visibility against yellow selection
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Pyramids now display all 4 face values around the piece when selected,
making it clear to players which numbers the pyramid can use for captures.
Implementation:
- Face numbers appear at top, right, bottom, left positions
- Only shown when pyramid piece is selected
- Clean board appearance when not selected
- Numbers scale appropriately with piece size
This permissive display helps players understand pyramid capabilities
without requiring explicit face selection (game auto-validates which
faces enable valid captures).
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
UI/UX Improvements:
- Bust-out button now closes the modal version when opening guide in new window
- Guide button is hidden when guide is already open to avoid confusion
- Replaced undock button with intuitive drag-to-undock gesture (drag title bar away from edge)
- Guide now supports dragging even when docked, with 50px threshold before undocking
- Improved cursor feedback (grab/grabbing) for docked guide
Persistence:
- Guide modal position and size are saved to localStorage
- Docked state (docked/floating) is saved to localStorage
- Dock side preference (left/right) is saved to localStorage
- Guide reopens in the same state and position as when it was last closed
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Change guide opening behavior:
- Created handleOpenGuide() function
- Sets guideDocked=true and guideDockSide='right' when opening
- Guide now opens docked to the right side by default
- No need to drag and dock manually on first open
- Provides better initial UX with guide immediately in workspace
User can still undock or drag to left side if preferred.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Replace ellipsized tab labels with react-textfit library:
- Install react-textfit package
- Use Textfit component for tab text labels
- Auto-scales font size (8px-14px) using binary search to fit width
- No more ellipsis (...) on narrow tabs
- Full tab labels always visible and readable
- Icon stays fixed size, only text scales
Tab labels now automatically shrink to fit available space while
remaining fully readable.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Panel Divider:
- Change from purple (rgba(139, 92, 246)) to neutral gray (#e5e7eb)
- Matches guide's light gray theme better
- Width: 2px (thinner), grows to 3px on hover
- Hover color: medium gray (#9ca3af)
- More elegant and fits the design aesthetic
Guide Tabs:
- Make tabs fully responsive with flex: '1 1 0'
- All tabs always visible and sized equally
- Remove horizontal scroll and fade indicators
- Tabs shrink/grow to fit available width
- Add text overflow handling for very narrow states
- Clean, simple design without scroll affordances
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Replace confusing fullscreen-like icon (⛶) with pop-out arrow (⤴️)
for the undock button. The new icon better conveys the action of
"pop out to floating mode" rather than looking like fullscreen/maximize.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Fix the guide in the preview panel to render as docked (not as another
floating modal):
- Set docked={true} when guide is in panel (both preview and committed dock)
- Guide now uses relative positioning, no shadow, fills panel properly
- Only show undock button when truly docked (not during preview)
- Preview guide now visually matches the final docked appearance
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Replace ghost panel overlay with actual docked layout preview:
- Add onDockPreview callback to communicate preview state to parent
- Parent renders real PanelGroup layout when dockPreviewSide is set
- Guide appears in docked position showing real final layout
- Board resizes automatically to show what docked state will look like
- Dragging modal shown at 0.8 opacity during preview
- Both guide (in panel) and dragging modal visible simultaneously
- Clear preview when drag ends or moves away from edges
This provides much better visual feedback - user sees exactly what
the final docked layout will look like before releasing the drag.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Add visual feedback when dragging the guide near dockable edges:
- Ghost panel preview appears when within 100px of left/right edge
- Semi-transparent purple overlay (35% width) shows where guide will dock
- Large arrow indicator (← or →) shows docking direction
- Dashed border for clear visual feedback
- Preview clears when drag ends or moves away from edges
- Preview only shows when guide is not already docked
This makes the docking feature discoverable and provides clear
visual feedback about where the guide will be positioned.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Add comprehensive console logging to debug guide docking feature:
- Log when PlayingGuideModal renders with props (isOpen, docked, handlers)
- Log when drag starts and ends
- Log mouse position and docking decision logic
- Log when handleDock/handleUndock are called
- Log state changes in RithmomachiaGame
This will help identify where the docking flow is breaking.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Implement ability to dock the Rithmomachia playing guide to left or right
side of the board screen. When docked, guide and board are in separate
panels with a resizable divider between them.
Features:
- Drag guide to left/right edge (within 100px) to dock it
- Resizable divider using react-resizable-panels library
- Guide defaults to 35% width, resizable between 20-50%
- Board takes remaining space (minimum 50%)
- Undock button (⛶) returns guide to floating modal mode
- No overlap between board and guide when docked
- Dragging disabled when docked
- Styling adjusted for docked mode (no shadows, no border-radius)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Create StrategySection.tsx component with comprehensive strategy content
- Add opening principles, mid-game tactics, victory paths, common mistakes, and advanced concepts
- Include interactive board diagrams for each strategic concept
- Integrate Strategy section into PlayingGuideModal navigation (between Capture and Harmony)
- Use existing translation keys from strategy section already present in all locale files
- Add brain emoji (🧠) icon for Strategy tab
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Add Old High German as a supported language across the entire app:
**Core i18n configuration:**
- Add 'goh' to supported locales in routing.ts
- Update Locale type in messages.ts to include 'goh'
- Add language selector label "Diutisc" with 🏰 flag emoji
**Translation files:**
- Create goh.json for all feature domains (home, games, guide, tutorial)
- Create goh.json for rithmomachia game
- Update all messages.ts files to import and export goh translations
- Currently using English text as placeholder (ready for translation)
**Bug fixes:**
- Add missing translation keys to ALL locales:
- rithmomachia.guide.overview.boardCaption
- rithmomachia.guide.pieces.pyramidMovement
- These keys were referenced in components but missing from translation files
Old High German will now appear in the language selector and can be selected.
All UI text currently displays in English until proper translations are added.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Fix tab navigation behavior on narrow widths:
- Keep tabs horizontal at all widths (remove vertical stacking at very narrow)
- Show scroll fade indicators at all narrow widths including very narrow
- Hide scrollbar for cleaner appearance while indicators show scroll availability
- Tabs are now icon-only at very narrow widths (<250px) with proper spacing
- Add whiteSpace: nowrap to prevent tab content wrapping
This makes the tabs more usable at ultra-narrow widths (down to 150px) while
making it clear that horizontal scrolling is available.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Add chess analogies and simplify piece display:
- Add chess piece analogies (bishop, rook, queen, king) for each piece type
- Simplify from multiple example values to single representative value
- Include Pyramid in main pieces array with chessAnalogy "like a king"
- Larger piece visualization (80px) with cleaner layout
- Display format: value, name, movement with analogy, count
Keeps detailed pyramid explanation section below the main pieces list.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Auto-format changes from Biome:
- Multi-line array formatting for COLUMN_LABELS
- Type import statement formatting
- Line wrapping improvements for readability
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
The guide modal is now usable at extremely narrow widths for better screen
real estate management while playing.
Responsive breakpoints:
- Very narrow (<250px): Vertical icon-only navigation, minimal header, tiny padding
- Narrow (250-400px): Horizontal icon + first word, compact layout, scroll indicators
- Medium (400-600px): Full labels, normal spacing
- Wide (>600px): Full layout with all features
Key changes:
- Minimum width reduced from 450px to 150px
- Minimum height reduced from 600px to 300px
- Navigation switches to vertical icon-only layout when very narrow
- Header collapses: hide title, smaller buttons, minimal padding
- Bust-out button hidden when very narrow
- Content padding and font sizes scale with width
- Added fade indicators on tab scroll edges
- All sizing now responds to modal width (or window width if standalone)
Layout modes:
- Very narrow: Icon-only vertical tabs, no header text, 8px padding
- Narrow: Abbreviated labels, 12px padding, scroll indicators
- Medium: Full labels, 24px padding, standard layout
This allows the guide to be positioned as a narrow sidebar while still
being fully functional.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Resizing was calculating deltas incrementally (delta-of-delta), which caused
the modal to slide away from the cursor. Now we save the initial dimensions
and position when resize starts, and always calculate the new size/position
from that initial state.
Changes:
- Added resizeStart state to track initial dimensions and position
- handleResizeStart now saves initial state
- Mouse move calculations use resizeStart instead of incrementally updating
- Removed the buggy dragStart reset that was causing drift
Resize now feels smooth and natural - handles follow the cursor exactly.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Guide modal now displays at 80% opacity on desktop to allow seeing the game
board behind it. Opacity increases to 100% when hovering over the modal.
Behavior:
- Desktop (≥768px): 80% opacity when not hovered, 100% when hovered
- Mobile (<768px): Always 100% opacity
- Standalone window: Always 100% opacity
- Smooth 0.2s transition between states
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
The board was incorrectly showing as 8x8 instead of the correct 16x8 dimensions
for Rithmomachia. This also fixes gap (5→2) and padding (20→10) values that
were incorrectly changed.
- Set BOARD_COLUMNS to 16 (was 8)
- Update COLUMN_LABELS to A-P (was A-H)
- Restore gap to 2 (was changed to 5)
- Restore padding to 10 (was changed to 20)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Eliminate prop drilling in all capture dialog components by using the new
CaptureContext:
- CaptureErrorDialog: 4 props → 0 props
- CaptureRelationOptions: 11 props → 1 prop (availableRelations)
- HelperSelectionOptions: 11 props → 1 prop (helpers)
- NumberBondVisualization: 15 props → 3 props (onConfirm, positions)
All components now:
- Use useCaptureContext() for shared capture state
- Use useAbacusSettings() directly instead of prop drilling
- Have cleaner, more focused interfaces
Added missing getSquarePosition import to components that need it.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Add centralized context provider for capture dialog subsystem. Provides:
- Layout configuration (targetPos, cellSize, gap, padding)
- Piece references (mover, target, helper)
- Selection state (selectedRelation, closing)
- Helper functions (findValidHelpers, selectRelation, selectHelper, dismissDialog)
This eliminates 8-15 props from each capture component and provides a
single source of truth for capture state.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Extract piece selection logic from BoardDisplay into a reusable custom hook.
Provides selectedSquare, handleSquareClick, and clearSelection.
This improves testability and reduces BoardDisplay complexity by ~100 lines.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Add custom hook to provide consistent board layout values (cellSize, gap,
padding, rows, columns) throughout the rithmomachia game components.
This eliminates magic numbers and provides a single source of truth for
board dimensions.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Add complete i18n support for the /guide page across all 6 supported
languages (en, de, ja, hi, es, la).
Created comprehensive translation files for guide content including:
- Page metadata (nav title, hero, tabs)
- Reading Numbers section (structure, single digits, multi-digit, practice, interactive)
- Arithmetic Operations section (addition, subtraction, multiplication/division, practice tips)
Updated components to use translations:
- guide/page.tsx: Main page layout with nav and tabs
- ReadingNumbersGuide.tsx: All 5 steps with nested content
- ArithmeticOperationsGuide.tsx: All operation sections with examples
All hardcoded strings replaced with translation calls using next-intl.
Message interpolation used for dynamic content. Arrays properly handled
with t.raw() for list items.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Fixed tutorial translations not displaying in user's language
- Updated homepage to use useMessages() instead of useTranslations().raw()
- Updated tutorialConverter to use translated actionDescription
- Updated ArithmeticOperationsGuide to pass tutorial translations
- Tutorial content now displays fully in selected language
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Add full translations for /games page (6 languages)
- Created translation files: en, de, ja, hi, es, la
- Internationalized all sections: hero, arcade, champions, dashboard, CTA
- Implemented message interpolation for dynamic content
- Internationalize tutorial step content
- Added step content translations to all tutorial locale files
- Updated tutorialConverter.ts to accept and use translations
- Modified homepage to pass tutorial translations to converter
- Tutorial now displays in user's selected language
All translations follow established i18n patterns with co-located messages
and server-side loading for FOUC prevention.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Use native abacus numbers setting from user preferences
- Conditionally render abacus based on nativeAbacusNumbers setting
- Apply setting to PressureGauge in Complement Race game
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Document proper process for creating database migrations with drizzle-kit
- Add warnings about NOT manually editing migration files
- Document Rithmomachia playing guide modal specifications
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Problem: Translation keys flashed for ~500ms before being replaced with
actual translations due to client-side async message loading.
Solution:
- Load messages server-side in layout.tsx before initial render
- Pass initialLocale and initialMessages as props to ClientProviders
- Update LocaleProvider to accept and use server-provided initial values
- Remove client-side useEffect that caused async loading delay
- Export getRequestLocale() function for server-side locale detection
Result: Zero FOUC - translations display instantly on page load while
maintaining instant language switching via changeLocale().
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Add professionally translated homepage content for:
- German (de): Formal German with proper terminology
- Japanese (ja): Native Japanese with appropriate kanji/hiragana mix
- Hindi (hi): Devanagari script with formal register
- Spanish (es): Latin American Spanish
- Latin (la): Classical Latin
All translations include complete homepage structure:
- Learn by Doing section
- Skills (Read Numbers, Friends, Multiply, Mental)
- The Arcade
- Your Journey
- Create Custom Flashcards
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Replace all hardcoded strings in homepage with t() translation calls
- Create English translation file with complete homepage content:
- Learn by Doing section
- What You'll Learn (4 skill cards)
- The Arcade section
- Your Journey section
- Create Custom Flashcards section (features and CTA)
- Add home messages to global message aggregator
- Use useTranslations('home') hook for type-safe translations
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Create reusable LanguageSelector component with two variants:
- dropdown-item: For hamburger menu (dark theme)
- inline: For full navbar
- Add language selector to hamburger menu (after Abacus Style)
- Add language selector to full navbar (after style dropdown)
- Remove language selector from PlayingGuideModal (now redundant)
- Display language with flag emoji and native name
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Create LocaleContext for global locale state management
- Implement changeLocale() function for instant language switching
- Update PlayingGuideModal to use changeLocale() instead of window.location.reload()
- Add language selector dropdown to Rithmomachia guide modal
- Language changes now happen instantly without page reload
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Adds /arcade/rithmomachia/guide route that displays the playing guide
modal in standalone mode. This allows the guide to be opened in a
separate window/tab.
Route: /arcade/rithmomachia/guide
Component: PlayingGuideModal with standalone=true
Fixes 404 error on production for this route.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Adds the nativeAbacusNumbers setting that was being used by Rithmomachia
but was never committed, causing CI build failures.
Changes:
- Add nativeAbacusNumbers boolean field to abacus_settings schema
- Add database migration for the new field (default: false)
- Add UI toggle in AbacusDisplayDropdown
- Update AbacusSettingsSync to exclude nativeAbacusNumbers from
abacus-react sync (it's app-specific, not abacus component config)
This setting allows displaying numbers as abaci throughout the app
where practical, used by arcade games like Rithmomachia.
Fixes TypeScript build errors in:
- src/arcade-games/rithmomachia/components/PlayingGuideModal.tsx
- src/arcade-games/rithmomachia/components/RithmomachiaGame.tsx
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Adds flat pyramid section translation keys to match the English structure:
- pyramidTitle, pyramidIntro
- blackPyramid/whitePyramid values
- pyramidHowItWorks with 4 rules
- pyramid example and visual example
- pyramid capture options (3 scenarios)
Fixes "returned an object instead of string" error for pyramid section
in Japanese guide.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Added i18next and react-i18next to package.json to fix Docker build failure.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Fixed nested object structures in harmony sections that were causing
"returned an object instead of string" errors.
Changes:
- Hindi (hi.json): Flattened arithmetic/geometric/harmonic objects to flat keys
- Japanese (ja.json): Flattened arithmetic/geometric/harmonic objects to flat keys
- Spanish (es.json): Flattened arithmetic/geometric/harmonic objects to flat keys
- Added missing translation keys for all languages
- Removed duplicate/old keys
- Fixed TypeScript error in PiecesSection.tsx (pyramid value type)
- Removed unused i18nInstance import from PlayingGuideModal.tsx
- Removed duplicate simpleTitle key from la.json
All harmony sections now use flat key structure matching English/German.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Fixed issue where modal would zoom away from cursor when resizing.
**Problem:** The resize logic was accumulating deltas by updating `dragStart`
with calculated offsets, causing exponential growth and the modal to zoom away.
**Solution:** Reset `dragStart` to current mouse position after each resize step.
This makes each delta calculation relative to the current position, not the
original click position.
**Changes:**
- Removed `actualDeltaX` and `actualDeltaY` tracking
- Reset `dragStart` to `{ x: e.clientX, y: e.clientY }` after each resize
- Simplified resize calculation logic
Now resizing works smoothly with cursor following the resize handle correctly.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Simplified language throughout the guide to make it super clear how to play:
**Pyramid section:**
- "Pyramids are special" instead of "multi-faced pieces"
- "Pick which value to use" instead of "declare which face value"
- Shorter, punchier rules
**Harmony section:**
- "Coolest way to win" instead of "elegant victory"
- "Equal spacing" instead of "differences are equal"
- "Multiply by same number" instead of technical descriptions
- "Tricky!" instead of "music-based, trickiest"
- Simpler rules: "Must be touching" vs "adjacent placement"
- Strategy tips use casual language: "They might not see it coming!"
**Helpers section:**
- "They don't move - they just add their number to the math"
- Much clearer explanation
**Victory section:**
- "Quick Tips" instead of "Quick Strategy Tips"
- Simpler, more direct language throughout
All changes applied to both English and German translations.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Fixed all guide section board visualizations to use correct props:
- Replaced non-existent `cropToSquares` prop with `cropArea` object
- Added `highlightSquares` prop for yellow highlighting of piece squares
- Created `squaresToCropArea()` helper function in each section
- Updated all examples in CaptureSection (6 examples)
- Updated all examples in HarmonySection (3 examples)
- Updated pyramid example in PiecesSection
- Updated victory example in VictorySection
Board examples now properly display:
✓ Cropped to relevant squares only
✓ Yellow highlighting on squares with pieces
✓ Proper coordinate labels
This matches the original guide design shown in the reference image.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Recreate the centered header design from the original guide:
- Centered large title "Rithmomachia Playing Guide"
- Centered subtitle "Rithmomachia – The Philosophers' Game"
- Language selector with dropdown (replaces button toggles)
- Close and bust-out buttons positioned in top-right corner
- Improved visual hierarchy with larger, more prominent title
- Better responsive sizing for mobile and desktop
This matches the original design shown in the reference image while maintaining
all modern functionality (dragging, resizing, bust-out, language switching).
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Add complete ratio capture example with visual board demonstration:
- Added ratioExample with 3 pieces showing White Triangle (20) capturing Black Circle (5) using helper Circle (4)
- Added ratio UI section with explanation and board visualization
- Added translation keys for ratio title, example, and caption in English and German
- Completes the full set of mathematical capture relations (equality, multiple, sum, difference, product, ratio)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Add detailed pyramid information to PiecesSection:
**Content Enhancements:**
- Clarified that Pyramids have 4 face values (perfect squares)
- Added face values with perfect square notation: "36 (6²), 25 (5²), 16 (4²), 4 (2²)"
- Added "How face selection works" section with 4 detailed rules
- Added example showing multiple capture scenarios
- Added visual board example demonstrating 3 different capture options from a single position
**Visual Improvements:**
- Cropped board showing White Pyramid at H5 with capture options
- Demonstrates flexibility of face selection (64 for multiple, 49 for equality, 25 for equality)
- All options clearly listed with mathematical relations
**Translations:**
- Added 15+ new translation keys to en.json and de.json
- Full German translations for all new pyramid content
This provides players with a complete understanding of how Pyramids work and why they're special pieces with multiple strategic uses.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Add detailed formulas, examples, strategies, and quick reference tables to the Harmony guide section:
**Content Enhancements:**
- Added "How to check" formulas for each progression type (Arithmetic, Geometric, Harmonic)
- Added visual examples with mathematical checks for each type
- Added strategy tips for each progression type (what pieces work best)
- Added detailed rules section with 5 specific rules (Enemy Territory, Straight Line, Adjacent Placement, Survival Rule, Victory)
- Added Strategy section with 5 subsections:
- Start with 2, Add the Third
- Use Common Values
- Protect the Line
- Block Opponent's Harmonies
- Calculate Before You Declare
- Added Quick Reference tables showing common harmonies for each type
**Visual Design:**
- Keep existing visual board examples (cropped)
- Color-coded sections: Green (Arithmetic), Yellow (Geometric), Blue (Harmonic)
- Example formulas in monospace font
- Strategy tips in colored callout boxes
**Translations:**
- Added 30+ new translation keys to en.json and de.json
- Full German translations for all new content
This merges the comprehensive old guide content with the new visual board examples, providing both detailed mathematical explanations and interactive visual demonstrations.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Extract OverviewSection, PiecesSection, CaptureSection, HarmonySection, and VictorySection into separate files in guide-sections/ directory
- Reduce PlayingGuideModal.tsx from 1628 lines to 519 lines
- Remove unused imports from all refactored files
- Each section is now independently maintainable and testable
This refactoring makes it easier to enhance individual sections (e.g., adding comprehensive content to Harmony section).
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Added cropped board example showing a winning geometric progression
- White pieces 4, 8, 16 in enemy territory (rows 5-8)
- Shows Black pieces unable to break the harmony
- Example demonstrates the primary victory condition clearly
- Added title and caption translations for English and German
The visual makes it immediately clear what a winning position looks like.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Added 3 cropped board examples showing each progression type
- Arithmetic: 6, 9, 12 in a row (9 = (6+12)/2)
- Geometric: 4, 8, 16 in a row (8² = 4×16)
- Harmonic: 6, 8, 12 in a row (2×6×12 = 8×(6+12))
- All examples show White pieces in enemy territory (rows 5-8)
- Pieces arranged in horizontal rows to demonstrate line requirement
- Added captions for each progression type
- Updated English and German translations with caption keys
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Added 5 cropped board examples showing each capture type
- Equality: W 25 captures B 25
- Multiple: W 64 captures B 16 (64÷16=4)
- Sum: W 9 + helper 16 = B 25
- Difference: W 30 - helper 10 = B 20
- Product: W 5 × helper 5 = B 25
- Simplified capture descriptions for clarity
- Added captions to all board examples
- Updated English and German translations with caption keys
Each example shows a minimal board snippet focusing on the relevant pieces.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Added example values for each piece type (Circle, Triangle, Square) with visual renderings
- Expanded Pyramid subsection showing both Black and White pyramids with their face values
- Corrected piece counts (25 total per side: 12 circles, 6 triangles, 6 squares, 1 pyramid)
- Simplified movement descriptions for clarity
- Updated English and German translations with new keys
Pyramid values:
- Black: 36, 25, 16, 4
- White: 64, 49, 36, 25
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Added full initial setup array with all 48 pieces in starting positions
- Rendered board using RithmomachiaBoard component at 0.6 scale
- Added caption translation key for board visual
- Simplified language in "How to Play" steps for easier comprehension
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>