Extract problem rendering logic into shared generateProblemStackFunction()
to serve as single source of truth for both full worksheets and previews.
- Add typstHelpers.ts with generateTypstHelpers() for colors/ten-frames/diagonal-split
- Add generateProblemStackFunction() returning problem-stack() Typst function
- Update typstGenerator.ts to use shared function instead of inline code
- Remove 117 lines of duplicate problem rendering code from typstGenerator
This ensures worksheet and preview always render identically.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Add comprehensive documentation about the code factoring mistake made
during addition worksheet preview implementation. Document the correct
pattern: extract to shared utility instead of copy/paste.
- Explain why "factor not fork" is critical
- Show correct pattern: extract to shared function
- Show wrong pattern: copy/paste between files
- Add red flags to watch for
- Document specific example from this implementation
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Move worksheet settings loading to server-side (page.tsx Server Component)
- Generate stable seed on server to prevent inconsistent renders
- Extract shared generateWorksheetPreview() function for reuse
- Pre-generate worksheet SVG preview during SSR
- Pass initialPreview to client via props
- Use React Query initialData to avoid Suspense flash
- Only use initialData on first render, fetch fresh on settings changes
- Add comprehensive logging to track SSR generation and client hydration
Benefits:
- Zero loading flash on initial page load
- Instant preview display with server-rendered data
- Settings and preview load in parallel on server
- Subsequent updates still work via debounced client-side fetching
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Extract AdditionWorksheetClient as separate client component
- Add comprehensive debug logging to track state changes
- Add logging to ConfigPanel for debugging derived state
- Prepare for SSR settings loading in next commit
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Remove FK constraint from worksheet_settings table via migration 0014
- Update schema definition to remove .references() call
- Remove user existence check from POST /api/worksheets/settings
- Guest users can now save worksheet preferences without user account
- Matches pattern used by room_members table for arcade games
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Bug Fixes:
1. Invalid URL during SSR
- Problem: Fetch with relative URL '/api/...' failed during server-side rendering
- Fix: Use absolute URL with window.location.origin (client) or localhost fallback (SSR)
- Impact: Preview now loads correctly during SSR and hydration
2. Foreign key constraint for guest users
- Problem: Settings save failed with SQLITE_CONSTRAINT_FOREIGNKEY for users not in DB
- Fix: Check if user exists before attempting save
- Behavior: Guest users skip save silently (returns success: false)
- Impact: App works for both logged-in and guest users
3. UI handling for guest users
- Don't show error when settings can't be saved
- Only show "Settings saved" indicator when actually saved
- Silently skip for guest users - settings still work (just not persisted)
Result: Worksheets now work for all users without errors
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
API Routes:
- GET /api/worksheets/settings?type=addition - Load user's saved config
- POST /api/worksheets/settings - Save current config (upsert)
- Uses parseAdditionConfig() for validation and migration
- Returns defaults if no saved settings exist
UI Integration:
- Load saved settings on mount (preserves derived state)
- Auto-save settings 1 second after changes (debounced)
- Only save after initial load completes (prevents overwriting on mount)
- Exclude transient fields (date, seed, rows, total) from persistence
- Show "Saving settings..." and "✓ Settings saved at HH:MM:SS" indicator
User Experience:
- Settings persist across sessions automatically
- New seed generated on each page load (fresh problems)
- Date field always starts empty (filled at generation time)
- Derived state (rows, total) recalculated from primary state
Technical Details:
- Type-safe with Zod validation
- Automatic schema migration (v1 → v2 when we add fields)
- Graceful fallback to defaults on errors
- 1s debounce prevents excessive saves during rapid changes
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Add --> statement-breakpoint between CREATE TABLE and CREATE INDEX
- Required by better-sqlite3 to handle multiple statements
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Create worksheet_settings table with versioned JSON config storage
- Foreign key to users with cascade delete
- Index on (user_id, worksheet_type) for efficient lookups
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Database Schema:
- Add worksheet_settings table with JSON blob storage
- Flexible design supports multiple worksheet types (addition, subtraction, etc.)
- No schema changes needed when adding new worksheet types or settings
- Foreign key to users with cascade delete
Type Safety & Validation:
- Zod schemas for runtime validation of all configs
- Versioned configs (v1, v2, etc.) with automatic migration
- parseAdditionConfig() validates and migrates old configs to latest
- serializeAdditionConfig() ensures version field is always set
- Falls back to sensible defaults for invalid/corrupted data
Schema Evolution:
- Add new settings: create v2 schema + migration function
- Add new worksheet types: just add new Zod schema, zero DB migration
- Backwards compatible: old configs auto-upgrade when loaded
- Forward compatible: unknown versions fall back to defaults
Benefits:
- Full TypeScript type inference from Zod schemas
- Runtime validation catches bad data before it reaches app
- Future-proof architecture for worksheet expansion
- Comprehensive guide in CONFIG_SCHEMA_GUIDE.md
Example:
```typescript
// Save (adds version automatically):
serializeAdditionConfig({ problemsPerPage: 20, cols: 5, ... })
// Load (validates + migrates):
const config = parseAdditionConfig(dbRow.config)
// config is typed, validated, and latest version!
```
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Make ten-frames row conditional per-problem, not globally
- Only add row if problem requires regrouping in any place value
- Problems without regrouping now have answer boxes directly after line
- No unnecessary gap for problems that don't need ten-frames
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Add ten-frames visualization for regrouping concepts
- Stacked vertical layout (top frame = carry, bottom frame = current place)
- Square cells with differentiated borders (0.8pt outer, 0.4pt internal at 30% opacity)
- Place value color coding matches answer boxes
- Ten-frames sized at 90% of cell width for proper alignment
- 2.5pt gap between tens and ones ten-frames for visual clarity
- Add "for all place values" option for ten-frames
- Show ten-frames only for regrouping (default) or for all problems
- Dynamic label updates based on checkbox state
- Indented sub-option UI in config panel
- Improve typography with "New Computer Modern Math" font
- Fix layout calculations
- Remove gutter-based spacing for consistent problem sizing
- Dynamic cell size based on ten-frames option
- Proper height allocation for ten-frames row
- Add cache busting for ten-frames settings in preview
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Removed 3D abacus creator from main branch due to Docker build failures
with OpenSCAD AppImage extraction in GitHub Actions. The feature will be
reworked in the feature/3d-abacus-creator branch.
Removed:
- src/app/create/abacus/ - 3D abacus creator page
- src/app/api/abacus/ - API routes for 3D generation
- src/components/3d-print/ - 3D-related components
- Dockerfile: OpenSCAD and BOSL2 builder stages
- Dockerfile: OpenGL/Qt runtime dependencies
- Dockerfile: tmp/3d-jobs directory creation
- Create hub: 3D abacus card
This restores successful Docker builds and unblocks deployments.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Add worksheet creator translations to de, es, goh, hi, ja, la
- Config panel, preview, and generation UI text
- Consistent with English version structure
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- New preview API route generates SVG previews before download
- FlashcardPreview component with Suspense and pagination
- generateFlashcardSvgs utility for server-side rendering
- Refactor flashcard page to use preview system
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Add addition worksheet creator card with green accent
- Update grid layout to support 4 columns on xl screens
- Features: tunable difficulty, regrouping practice, PDF generation
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Problem generator with tunable difficulty (regrouping percentages)
- Progressive difficulty option (easy → hard)
- Mulberry32 PRNG for reproducible problem sets
- Validation and type definitions
- Main page component with layout configuration
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Show validation errors, details, and Typst compilation errors
- Multi-page preview with pagination controls
- Debounced updates to prevent lag during configuration
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Generate separate Typst document for each page (returns string[])
- Wrap content in non-breakable block to ensure single-page output
- Compile each page via stdin/stdout to avoid multi-page file issues
- Calculate problem box size based on actual rows on each page
- Problems scale to fill available vertical space dynamically
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Fixed improper div nesting that was causing JSX syntax errors:
- Indented grid div to be properly inside data-component div
- Added missing closing divs for proper nesting structure
- Aligned all closing tags with their opening tags
Structure now correctly:
PageWithNav > outer wrapper > data-component > (h1, p, grid) > (controls, preview)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
The printer emoji with variation selector (🖨️) was causing a syntax
error. Replaced with base printer emoji (🖨) without variation selector.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Added outer wrapper div with minHeight and background color to match
the pattern used by other pages (flashcards, calendar). This ensures
content doesn't appear under the fixed navigation bar.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Added navTitle translation key to all 7 language files
- Wrapped abacus page with PageWithNav component
- Now matches navigation pattern of other create pages
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Add trailing comma in cropToActiveBeads config
- Format console.log call for better readability
- Format postMessage call parameters
- Add approved bash commands for icon testing to local settings
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Replace custom bounding box calculations with the new cropToActiveBeads prop:
- Remove 100+ lines of manual bbox calculation code (getAbacusBoundingBox)
- Use AbacusStatic instead of AbacusReact for simpler SSR
- Let cropToActiveBeads handle all cropping logic with padding config
- Simplify script to just parse dimensions from cropped SVG and center in canvas
This eliminates code duplication and leverages the precise cropping utilities
now available in the package.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Implemented i18n across all three create pages with translations for 7 languages (en, de, ja, hi, es, la, goh):
- Created comprehensive translation files in src/i18n/locales/create/
- Updated /create (hub) page with all card content translations
- Updated /create/abacus page with parameter labels and help text
- Updated /create/flashcards page with UI elements and status messages
- Integrated create translations into main messages system
Translation coverage includes:
- Page titles and subtitles
- Feature descriptions and lists
- Form labels and placeholders
- Button text (normal and loading states)
- Error messages
- Help text and instructions
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
This commit implements two enhancements to the calendar feature:
1. i18n Support:
- Created translation files for all 7 languages (en, de, ja, hi, es, la, goh)
- Updated CalendarConfigPanel to use translations for UI labels
- Updated CalendarPreview to use translations for status messages
- Updated calendar page to use translations for page title/subtitle
- Added calendarMessages export and integrated into main messages.ts
2. Cropped Abacus Day Numbers:
- Enabled cropToActiveBeads feature for monthly calendar day abacuses
- Abacuses now fill calendar cells more efficiently
- Extracts cropped viewBox from rendered SVG for proper scaling
- Uses custom padding (top: 8, bottom: 2, left: 5, right: 5)
- Dynamically scales cropped abacuses to fit cells
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Implement fully-featured daily calendar generation with enhanced visual design:
Daily Calendar Features:
- Beautiful single-page-per-day design with decorative borders
- Blue double-border frame for visual polish
- Light blue header section with uppercase month name (Georgia serif)
- Large prominent day-of-week text (42pt)
- 2.5x larger day abacus as main focal point
- Styled notes section with warm yellow background
- Full support for all paper sizes (US Letter, A4, A3, Tabloid)
Preview System:
- Add live preview support for daily calendars
- Generate composite SVG for preview (avoid Typst multi-page export issues)
- Show "Live Preview (First Day)" label for daily format
- Use same composite SVG approach as monthly calendars
Locale Detection:
- Auto-detect paper size from browser locale on page load
- US Letter for US, CA, MX, GT, PA, DO, PR, PH
- A4 for all other countries
- Hardcoded mapping (stable, no external deps needed)
Code Quality:
- Remove all debug console.log statements
- Fix unused imports
- Format and lint all files
Technical Details:
- Daily preview creates single composite SVG with embedded abacuses
- Uses Typst's responsive layout (percentages) for multi-size support
- Matches monthly calendar's single-image-export pattern
- Full PDF generation uses Typst #page() for multi-page output
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Replace fragile regex parsing in generateDayIcon.tsx with proper bead
position calculations using numberToAbacusState(), calculateStandardDimensions(),
and calculateBeadPosition() from @soroban/abacus-react
- Add query parameter support to /icon route for testing different days (e.g. /icon?day=15)
- Fix icon cropping to properly show only active beads with dynamic viewBox
- Validate day parameter (1-31) and return 400 for invalid values
- Different cache duration for production (1 hour) vs testing (1 minute)
Results:
- Day 1: 48.88px height (minimal)
- Day 5: 48.88px height (single heaven bead)
- Day 25: 100.18px height (many active beads)
- Day 31: 93.88px height
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Update developer guidelines to be more explicit about dev server handling:
- Add pnpm dev to the list of forbidden commands
- Explicitly forbid killing processes on port 3000
- Emphasize that the user manages the dev server, not Claude Code
- Improve clarity with bold formatting
This prevents Claude Code from interfering with user-managed dev processes.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Migrate 3D abacus preview generation from server-side OpenSCAD to client-side
WASM execution using web workers.
Changes:
- Add openscad-wasm-prebuilt dependency for browser-based rendering
- Refactor STLPreview to use web worker for non-blocking WASM execution
- Create openscad.worker.ts for isolated 3D model generation
- Add abacus-inline.scad template for parametric abacus generation
- Improve preview debouncing (500ms) and error handling
Benefits:
- No server-side OpenSCAD dependency required
- Faster preview updates (no network roundtrip)
- Better UX with non-blocking worker execution
- Consistent rendering across environments
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Clean up debugging output from production code:
- Remove bead click analysis logging from InteractiveAbacus
- Remove sessionStorage operation logging from HomeHeroContext
These were useful during development but should not be in production.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
4 columns is a more practical default for initial preview and printing.
13 columns produces very large files and takes longer to render.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Remove white/gray bordered inner containers around abacuses
- Center abacuses directly in cards using flex
- Reduce card padding from p:4 to p:2
- Tighten margins between elements (mb:3→mb:2)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Previously made them smaller when user wanted them larger.
Now: single-digit 200×400px, multi-digit 320×350px, both with scaleFactor 1.2
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Fixed inconsistent abacus sizing in guide:
- Replaced aspect ratio containers with fixed dimensions
- Single-digit abacuses: 120px × 240px (consistent vertical layout)
- Multi-digit abacuses: 180px × 200px (horizontal layout)
- Added inner wrapper div for proper AbacusReact sizing
- Centered containers with mx: 'auto'
- Adjusted scale factors for better fit (0.7 and 0.8)
Also added with-fixed-nav class to guide page to prevent white bar
under navigation.
Fixes: Inconsistent abacus sizes in guide page screenshot
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Better approach to fixed nav spacing:
- Removed wrapper div from PageWithNav (was creating white gap)
- Added .with-fixed-nav utility class in globals.css
- Pages apply class to their root element (with background)
- Padding is applied to the element with the background, not a wrapper
This prevents the white bar issue because the background element itself
has the padding, so the background extends under the nav properly.
Applied to calendar page as example. Other pages can add the class as needed.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Improvements to the fixed nav spacing system:
1. Define nav heights as CSS variables in :root (globals.css):
- --app-nav-height-full: 72px
- --app-nav-height-minimal: 92px
2. Nav components reference and use these variables:
- Set --app-nav-height to the appropriate variant
- Use min-height based on the variable (circular reference)
3. Changed PageWithNav from padding to margin:
- Prevents background gap under nav
- Backgrounds now properly extend under the fixed nav
Single source of truth: Change values in globals.css and everything
updates automatically - both the nav sizing and the content spacing.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Prevent content from appearing underneath the fixed navigation by:
- Adding content wrapper in PageWithNav with top padding
- Using CSS variable --app-nav-height set by AppNavBar itself
- Different heights for full nav (72px) vs minimal nav (92px)
This systematically solves the fixed nav overlap issue for all pages
using PageWithNav without requiring per-page adjustments.
Fixes: Calendar creator title showing under nav
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
The HomeHeroProvider is provided site-wide, and isHeroVisible defaults to
true. This caused the navigation to use transparent "hero mode" styling on
all pages, not just the home page when the hero is visible.
Added pathname check to ensure transparent styling and hidden branding only
apply when actually on the home page (pathname === '/') with a visible hero.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
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>
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>