Fixed bug where bead highlights were not showing in the tutorial
because column validation used incorrect minValidColumn calculation
(5 - abacusColumns) instead of checking against actual column range.
Changed from:
if (columnIndex < minValidColumn) return
To:
if (columnIndex < 0 || columnIndex >= abacusColumns) return
This ensures highlights work correctly regardless of abacusColumns value.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
After making this mistake twice:
1. First time (ffae9c1b): Added tsx to deps for calendar scripts
2. Second time (379698fe): Almost did it again
Document why this is wrong and what to do instead:
- Move code to src/ for Next.js bundling
- Keep tsx in devDependencies only
- Never run TypeScript at runtime in production
- Add --format pdf flag to calendar PDF generation route
- Upgrade Typst from v0.11.1 to v0.13.0 in Dockerfile
- Typst v0.11.1 doesn't support --format flag with stdin/stdout
- Fixes "could not infer output format" error in production
The calendar generation scripts were in scripts/ directory which wasn't
included in tsconfig.server.json, so they weren't being compiled during
build. This required tsx in production just to import .tsx files at runtime.
Changes:
- Moved generateCalendarComposite.tsx and generateCalendarAbacus.tsx to src/utils/calendar/
- Removed CLI interface code from these files (they're now pure utility modules)
- Updated imports in API routes to use @/utils/calendar/...
- Moved tsx back to devDependencies where it belongs
- Removed scripts/ copy from Dockerfile (no longer needed)
Now these files are compiled to JavaScript during the build process and
don't require tsx at runtime. This fixes the architecture issue properly.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Calendar generation scripts need tsx at runtime to execute TypeScript files,
but tsx was in devDependencies which are not installed in production builds.
This caused calendar preview and PDF generation to fail in production with:
"EACCES: permission denied, mkdir '/nonexistent'" when trying to run npx tsx
Moving tsx to dependencies ensures it's available in production for:
- apps/web/scripts/generateCalendarComposite.tsx
- apps/web/scripts/generateCalendarAbacus.tsx
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Create /api/create/calendar/preview endpoint using Typst compilation
- Refactor CalendarPreview to use useSuspenseQuery for data fetching
- Add Suspense boundary in calendar page with loading fallback
- Preview now matches PDF exactly as both use Typst rendering
- Remove post-generation SVG replacement to preserve Typst preview
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Add calculateAbacusDimensions utility to AbacusUtils
- Refactor AbacusStatic to use shared dimension calculator
- Update calendar composite generator to use shared utility
- Export calculateAbacusDimensions from index and static entry points
- Ensures consistent sizing between preview and PDF generation
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Render calendar using AbacusStatic components directly in browser
- Remove API call and React Query dependency for preview
- Show live preview that updates instantly when month/year changes
- Display generated PDF SVG after user clicks generate button
- Eliminates unnecessary server round-trip for interactive preview
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Change generate API to return JSON instead of binary PDF
- Include base64-encoded PDF and SVG preview in response
- Update client to decode base64 PDF and trigger download
- Store SVG preview for display after generation
- Improve error handling to surface actual error messages
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Separate month name and year display in calendar header
- Render year as abacus element (15% of page width)
- Increase grid line weight from 1px to 2px
- Change grid color from light gray (#ddd) to dark (#333)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Next.js bundler flags react-dom/server imports even in API routes during static analysis.
Using dynamic import (await import()) ensures it only loads at runtime on the server.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Changes:
- Scripts now export JSX elements (generateAbacusElement) or accept renderToString param
- API route imports react-dom/server (API routes are server-only, allowed)
- renderToStaticMarkup called in API route, not in imported scripts
- CLI interfaces use dynamic require() for react-dom/server when run directly
This satisfies Next.js requirement that react-dom/server not be imported in files that could be client-bundled.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Changes:
- Refactored scripts/generateCalendarAbacus.tsx to export generateAbacusSVG function
- Refactored scripts/generateCalendarComposite.tsx to export generateCalendarComposite function
- Updated API route to import and call functions directly instead of spawning subprocesses
- Removed tsx from production Dockerfile (not needed - Next.js transpiles at build time)
- Kept scripts and abacus-react package copies in Dockerfile (needed for imports)
This eliminates the need for tsx at runtime and improves performance by avoiding subprocess overhead.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Fixed persistent overlap by using proper SVG nesting:
Previous approach: Extracted SVG content and used g transforms
- Problem: Inner coordinates remained in original 120x230 space
- Caused overlapping when elements had absolute positioning
New approach: Nested SVG elements with viewBox
- Each abacus rendered at natural scale=1 (120×230)
- Wrapped in <svg> with:
- x, y: position in parent calendar
- width, height: scaled display size
- viewBox="0 0 120 230": maps content to display size
- Creates isolated coordinate space per abacus
This is the correct SVG pattern for embedding scaled content.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
BREAKING FIX: Monthly calendars were overflowing to multiple pages
New "page-first" design approach:
- Generate entire calendar as one composite SVG (850x1100px)
- Includes title, year abacus, weekday headers, and all day abacuses
- Typst scales the single image to fit page (width: 100%, fit: "contain")
- Impossible to overflow - it's just one scalable image
Benefits:
✅ Guaranteed single-page layout across all paper sizes
✅ No grid overflow issues
✅ Consistent rendering regardless of month length
✅ Fast generation (~97KB SVG vs multiple small files)
Implementation:
- Created generateCalendarComposite.tsx script
- Updated route to use composite for monthly, individual SVGs for daily
- Simplified generateMonthlyTypst to just scale one image
Daily calendars unchanged (intentionally multi-page, one per day).
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Design changes for better single-page fit:
- Reduce margins: US Letter 0.5in (was 1in), A4 1.3cm (was 2.5cm)
- Compact header: inline year abacus next to title (20% width vs 35%)
- Smaller fonts: title 18pt (was 24pt), weekdays 9pt (was 12pt)
- Tighter grid: 2pt gutter (was 4pt), added row-gutter
- Reduced vertical spacing: 0.5em (was 1.5em) after header
This ensures monthly calendars fit on one page across all paper sizes:
- US Letter (8.5" × 11")
- A4 (210mm × 297mm)
- A3 (297mm × 420mm)
- Tabloid (11" × 17")
Daily calendars continue to use one page per day (intentional multi-page).
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- 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>