- Add xmlns="http://www.w3.org/2000/svg" to AbacusStatic root svg element
- Fixes "failed to parse SVG (missing root node)" Typst error
- React's renderToStaticMarkup doesn't add xmlns by default
- Required for standalone SVG files used outside HTML context
- Added debug logging to calendar generation route
Root cause: Typst's SVG parser requires explicit XML namespace declaration.
React assumes SVGs are embedded in HTML where xmlns is optional.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Switch from AbacusReact to AbacusStatic in generateCalendarAbacus.tsx
- Fixes "failed to parse SVG (missing root node)" Typst error
- AbacusReact can't be rendered server-side with renderToStaticMarkup
- AbacusStatic is designed for pure server-side rendering
Root cause: AbacusReact has "use client" directive and uses hooks,
which causes renderToStaticMarkup to generate invalid/empty SVG.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Attempt to fix "Cannot access 'Z' before initialization" error during
static generation by forcing dynamic rendering.
Note: This doesn't fully resolve the initialization issue, but prevents
build from attempting static generation which causes the error.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
CalendarPreview changes:
- Remove showNumbers display from all abaci for cleaner calendar look
- Increase day abaci scaleFactor from 0.35 to 1.0 (fills grid squares)
- Remove year from month title (shows only month name)
CalendarConfigPanel changes:
- Replace link to /create with inline AbacusDisplayDropdown
- Add preview abacus showing current style (value=12, 2 columns)
- Users can now edit abacus styles directly in calendar page
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Change SVG image paths from absolute to relative in typstGenerator
- Execute typst compilation from tempDir with cwd option
- Fixes "file not found" error caused by Typst doubling absolute paths
Root cause: Typst treats absolute paths as relative and prepends working
directory, resulting in incorrect paths like:
/tmp/calendar-123/tmp/calendar-123/year.svg
Solution: Use relative paths ("year.svg") and run from tempDir.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Create /test-static-abacus page demonstrating pure Server Component usage
- Uses @soroban/abacus-react/static import (no client code)
- Renders 12 abacus displays with zero client-side JavaScript
- Verifies RSC export path works correctly in Next.js build
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Creates /test-static-abacus route to demonstrate and verify that
AbacusStatic works correctly in React Server Components.
The page:
- Has NO "use client" directive (pure Server Component)
- Renders 12 different abacus values as static preview cards
- Uses compact mode and hideInactiveBeads for clean displays
- Includes visual confirmation that RSC rendering works
This serves as both a test and reference implementation for
using AbacusStatic in Next.js App Router Server Components.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Replace manual theme style definitions with ABACUS_THEMES presets:
- MyAbacus.tsx: Use ABACUS_THEMES.light and ABACUS_THEMES.trophy
- HeroAbacus.tsx: Use ABACUS_THEMES.light
- LevelSliderDisplay.tsx: Use ABACUS_THEMES.dark
Eliminates ~60 lines of duplicate theme style code.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Add test comment to trigger automatic deployment and verify that
compose-updater successfully updates the container without crashing
with the new separate project names configuration.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Remove all parallax hover effects and wobble physics from 3D enhancement system
as they were distracting and worsened the user experience.
Changes:
- Remove Abacus3DPhysics interface completely
- Remove physics3d prop from AbacusConfig and component
- Remove calculateParallaxOffset utility function
- Remove mouse tracking infrastructure (containerRef, mousePos, handleMouseMove)
- Update enhanced3d type to only support 'subtle' | 'realistic' (removed 'delightful')
- Update all 3D utilities to remove delightful mode support
- Remove all Delightful stories from Storybook
- Update Interactive Playground to remove parallax controls
- Change MyAbacus from delightful to realistic mode
The 3D enhancement system now focuses purely on visual improvements
(materials, lighting, textures) without any motion-based effects.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Added delightful 3D mode to the hero and open states of MyAbacus:
- Glossy heaven beads + satin earth beads for premium feel
- Dramatic lighting for impact
- Wood grain texture on golden frame
- Hover parallax enabled for interactive depth
- Only applies to hero/open modes (not button mode)
The giant hero abacus on the home page now has satisfying
material rendering and interactive parallax effects.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
**Changes:**
- Removed wobble physics feature (was janky and distracting)
- Increased wood grain opacity from 0.15 → 0.4 (realistic) and 0.45 (delightful)
- Enhanced wood grain pattern with bolder strokes and more visible knots
- Removed getWobbleRotation utility function
- Simplified Abacus3DPhysics interface to only hoverParallax
- Updated all stories to remove wobble references
- Removed velocity tracking code from Bead component
Wood grain is now much more visible on frame elements without
affecting bead spacing or layout.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Replace laggy JavaScript scroll tracking with proper CSS positioning:
Before:
- Used position: fixed with JavaScript scroll listener
- Calculated top position dynamically: calc(60vh - ${scrollY}px)
- Caused noticeable lag on mobile and slower browsers
After:
- Hero mode: position: absolute (scrolls naturally with document)
- Button mode: position: fixed (stays in viewport at bottom-right)
- Open mode: position: fixed (stays in viewport at center)
- Zero JavaScript scroll tracking needed
Result: Buttery smooth scrolling on all devices with zero lag.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Move hero abacus down from 50vh to 60vh to prevent it from overlapping
the subtitle text ("master the ancient art..." etc.) on the home page.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Fix two critical issues with the trophy abacus system:
1. Hero abacus appearing on all pages:
- Root cause: HomeHeroProvider now wraps all pages globally
- Solution: Use pathname check to detect actual home page routes
- Only show hero mode on: /, /en, /de, /ja, /hi, /es, /la
2. Z-index conflicts causing layering issues:
- AppNavBar had hardcoded z-index: 1000 (DROPDOWN layer)
- Should use Z_INDEX.NAV_BAR (100) for proper layering
- Tooltip had z-index: 50, should use Z_INDEX.TOOLTIP (1000)
This ensures:
- Hero abacus only appears on home page, not all pages
- Trophy abacus (z-index 30001) appears above ALL content
- Nav bar and tooltips use correct z-index constants
- No stacking context conflicts
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
The hero abacus was scrolling in the opposite direction of page content due to incorrect math. Fixed by subtracting scroll position instead of adding it.
Change: top: calc(50vh - ${scrollY}px) instead of calc(50vh + ${scrollY}px)
Now the abacus properly scrolls up with the page content when scrolling down.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Implement a single abacus component that seamlessly transitions between three states: hero mode on the home page, compact button in the corner, and full-screen modal display.
Key features:
- Hero mode: Large white abacus at top-center of home page, interactive with beads
- Button mode: Golden mini abacus in bottom-right corner when hero scrolled away or on other pages
- Open mode: Full-screen golden abacus with blur backdrop, accessible from button
- Smooth fly-to-corner animation when scrolling past hero section
- Two-way sync between hero and trophy abacus values via HomeHeroContext
- Highest z-index layer (30000+) to appear above all content
Implementation:
- Create MyAbacus component handling all three states with smooth transitions
- Add MyAbacusContext for global open/close state management
- Move HomeHeroProvider to ClientProviders for global access
- Replace HeroAbacus with HeroSection placeholder on home page
- Fix flashcard z-index by creating proper stacking context (z-index: 1)
- Add MY_ABACUS z-index constants (30000-30001)
- Add calendar page components with correct styled-system imports
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
OpenSCAD 2021.01 (Debian stable) has CGAL geometry bugs that cause
non-zero exit status when processing complex STL operations, but it
still produces valid output files.
Instead of failing the job when OpenSCAD exits with non-zero status,
we now check if the output file was created. If it exists, we proceed
with the job. Only if the file is missing do we treat it as a failure.
This allows 3D model generation to work on production despite the
older OpenSCAD version's CGAL warnings.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Next.js build was failing because routes using headers() were being
statically generated during build time. Added `export const dynamic = 'force-dynamic'`
to:
- /api/player-stats (uses getViewerId which reads headers)
- /api/debug/active-players (uses getViewerId which reads headers)
- /opengraph-image (reads filesystem during render)
Build errors:
- Route /api/player-stats couldn't be rendered statically because it used `headers`
- Route /api/debug/active-players couldn't be rendered statically because it used `headers`
- Error occurred prerendering page "/opengraph-image"
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Add comprehensive preview system for arcade games:
- GamePreview component: Renders any arcade game in preview mode
- MockArcadeEnvironment: Provides isolated context for previews
- MockArcadeHooks: Mock implementations of useArcadeSession, etc.
- MockGameStates: Pre-defined game states for each arcade game
- ViewportContext: Track and respond to viewport size changes
Enables rendering game components outside of arcade rooms for:
- Documentation and guides
- Marketing/showcase pages
- Testing and development
- Game selection interfaces
Mock states include setup, playing, and results phases for all
five arcade games (matching, complement-race, memory-quiz,
card-sorting, rithmomachia).
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Reorganize create functionality into a central hub page:
- Move flashcard creation to /create/flashcards
- Add new creation hub at /create with options for:
- Flashcards (existing feature)
- Abacus 3D models (new feature)
- Future creation tools
The hub page provides a clean navigation interface for all
content creation features.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Implement comprehensive per-player statistics tracking across arcade games:
- Database schema: player_stats table with per-player metrics
- Migration: 0013_add_player_stats.sql for schema deployment
- Type system: Universal GameResult types supporting all game modes
(competitive, cooperative, solo, head-to-head)
- API endpoints:
- POST /api/player-stats/record-game - Record game results
- GET /api/player-stats - Fetch all user's players' stats
- GET /api/player-stats/[playerId] - Fetch specific player stats
- React hooks:
- useRecordGameResult() - Mutation hook with cache invalidation
- usePlayerStats() - Query hooks for fetching stats
- Game integration: Matching game now records stats on completion
- UI updates: /games page displays per-player stats in player cards
Stats tracked: games played, wins, losses, best time, accuracy,
per-game breakdowns (JSON), favorite game type, last played date.
Supports cooperative games via metadata.isTeamVictory flag where
all players share win/loss outcome.
Documentation added:
- GAME_STATS_COMPARISON.md - Cross-game analysis
- PER_PLAYER_STATS_ARCHITECTURE.md - System design
- MATCHING_GAME_STATS_INTEGRATION.md - Integration guide
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Major improvements to the games hero carousel:
- Added smooth autoplay (4s delay, stops on interaction/hover)
- Made carousel full-width to reduce virtual scrolling artifacts
- Added horizontal padding for better buffer on edges
- Restructured layout: carousel is now full-width, rest of content
remains constrained to max-width
- Removed containScroll setting to improve infinite loop behavior
The carousel now smoothly rotates through games automatically and
has better visual consistency at the loop edges.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Add embla-carousel-autoplay dependency to enable smooth automatic
rotation of games in the hero carousel.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Added overflowX: hidden to page container to clip carousel at viewport
level. This prevents the infinite carousel from making the entire page
horizontally scrollable while still allowing game cards to visually
extend beyond their container.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
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>