Compare commits

...

70 Commits

Author SHA1 Message Date
semantic-release-bot
11d0c341a8 chore(abacus-react): release v2.8.3 [skip ci]
## [2.8.3](https://github.com/antialias/soroban-abacus-flashcards/compare/abacus-react-v2.8.2...abacus-react-v2.8.3) (2025-11-05)

### Bug Fixes

* **tutorial:** correct column validation for bead highlights ([9ba1824](9ba1824226))
* **tutorial:** fix overlay rendering, arrow indicators, and bead visibility ([a804316](a80431608d))
* **web,docker:** add --format flag for Typst and upgrade to v0.13.0 ([19b9d7a](19b9d7a74f))
* **web:** move tsx to production dependencies for calendar generation ([ffae9c1](ffae9c1bdb))
2025-11-05 00:51:55 +00:00
Thomas Hallock
ece2ffb40f chore: remove debug logging from tutorial fixes
Remove console.log statements added during debugging:
- Bead style merge logging in AbacusReact
- Extra bead props calculation logging
- Yellow highlight bead render logging in AbacusAnimatedBead

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-04 18:50:35 -06:00
Thomas Hallock
a80431608d fix(tutorial): fix overlay rendering, arrow indicators, and bead visibility
Multiple fixes for tutorial regression issues:

- Fix overlay rendering: Changed from <g> to <foreignObject> to support
  DOM components (Radix UI Tooltip), fixing hydration errors
- Fix overlay positioning: Use calculateBeadPosition for accurate placement
- Fix arrow indicators: Reduce size, fix transform origin for center-based
  scaling, correct direction logic for heaven vs earth beads, add black
  stroke for contrast on yellow backgrounds, remove unnecessary circle
- Fix bead visibility: Implement proper opacity cascade (customStyle ->
  active -> hideInactiveBeads + hover), use opacity 0 instead of
  conditional rendering to enable hover detection
- Add abacus-level hover: Track hover state at abacus level and propagate
  to beads so inactive beads appear on hover over any part of abacus

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-04 18:50:35 -06:00
Thomas Hallock
9ba1824226 fix(tutorial): correct column validation for bead highlights
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>
2025-11-04 18:50:35 -06:00
semantic-release-bot
17970f6e9a chore(release): 4.68.0 [skip ci]
## [4.68.0](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.67.1...v4.68.0) (2025-11-04)

### Features

* **abacus-react:** add AbacusStatic for React Server Components ([3b8e864](3b8e864cfa))
* **abacus-react:** add core utility functions for state management ([e65541c](e65541c100))
* **abacus-react:** add layout and educational props ([35bbcec](35bbcecb9e))
* **abacus-react:** add pre-defined theme presets ([cf1f950](cf1f950c7c))
* **abacus-react:** add React hooks for abacus calculations ([de038d2](de038d2afc))
* **abacus-react:** add separate /static export path for React Server Components ([ed69f6b](ed69f6b917))
* **abacus-react:** add shared dimension calculator for consistent sizing ([e5ba772](e5ba772fde))
* **abacus-react:** export new utilities, hooks, and themes ([ce4e44d](ce4e44d630))
* **abacus:** add nativeAbacusNumbers setting to schema and UI ([79f7347](79f7347d48))
* add 3D printing support for abacus models ([dafdfdd](dafdfdd233))
* add comprehensive metadata, SEO, and make AbacusReact SSR-compatible ([0922ea1](0922ea10b7))
* add comprehensive Storybook coverage and migration guide ([7a4a37e](7a4a37ec6d))
* add game preview system with mock arcade environment ([25880cc](25880cc7e4))
* add per-player stats tracking system ([613301c](613301cd13))
* add Strategy & Tactics section to Rithmomachia guide ([81ead65](81ead65680))
* add unified trophy abacus with hero mode integration ([6620418](6620418a70))
* **arcade:** add ability to deactivate remote players without kicking user ([3628426](3628426a56))
* **arcade:** add native abacus numbers support to pressure gauge ([1d525c7](1d525c7b53))
* **arcade:** add Rithmomachia (Battle of Numbers) game ([2fc0a05](2fc0a05f7f))
* **arcade:** add yjs-demo collaborative game and Yjs persistence layer ([d568955](d568955d6a))
* **arcade:** auto-create room when user has none ([ff88c3a](ff88c3a1b8))
* **card-sorting:** add activity feed notifications for collaborative mode ([1461414](1461414ef4))
* **card-sorting:** add auto-submit countdown for perfect sequences ([780a716](780a7161bc))
* **card-sorting:** add bezier curves to connecting arrows ([4d8e873](4d8e873358))
* **card-sorting:** add CardPosition type and position syncing ([656f5a7](656f5a7838))
* **card-sorting:** add collapsible stats sidebar for spectators ([6527c26](6527c26a81))
* **card-sorting:** add game mode selector UI to setup phase ([d25b888](d25b888ffb))
* **card-sorting:** add GameMode type system for multiplayer support ([fd76533](fd765335ef))
* **card-sorting:** add green border to correctly positioned cards ([16fca86](16fca86b76)), closes [#22c55](https://github.com/antialias/soroban-abacus-flashcards/issues/22c55)
* **card-sorting:** add player emoji indicators on moving cards ([3a82099](3a82099757))
* **card-sorting:** add react-spring animations for real-time sync ([c367e0c](c367e0ceec))
* **card-sorting:** add smooth transition to drop shadow ([b0b93d0](b0b93d0175))
* **card-sorting:** add spectator mode UI enhancements ([ee7345d](ee7345d641)), closes [#6366f1](https://github.com/antialias/soroban-abacus-flashcards/issues/6366f1) [#8b5cf6](https://github.com/antialias/soroban-abacus-flashcards/issues/8b5cf6)
* **card-sorting:** add team scoring UI for collaborative mode ([ed6f177](ed6f177914)), closes [#a78](https://github.com/antialias/soroban-abacus-flashcards/issues/a78) [#8b5cf6](https://github.com/antialias/soroban-abacus-flashcards/issues/8b5cf6)
* **card-sorting:** add updateCardPositions action to Provider ([f6ed4a2](f6ed4a27a2))
* **card-sorting:** auto-arrange prefix/suffix cards in corners ([4ba7f24](4ba7f24717))
* **card-sorting:** fade correctly positioned cards to 50% opacity ([7028cfc](7028cfc511))
* **card-sorting:** gentler spring animation for locked cards ([47189cb](47189cb6e7))
* **card-sorting:** implement continuous bezier curve paths ([2d93024](2d9302410f))
* **card-sorting:** improve card distribution for natural scattered look ([0b0503f](0b0503f035))
* **card-sorting:** make player emoji fill entire card background ([2e7a02c](2e7a02c9e4))
* **card-sorting:** optimize results screen for mobile ([d188789](d188789069))
* **card-sorting:** redesign setup screen with modern UI ([73cf967](73cf967492))
* **card-sorting:** scale correctly positioned cards to 50% ([222dc55](222dc555fa))
* **card-sorting:** shrink/fade cards in correct suffix as well ([8f6feec](8f6feec4f2))
* **card-sorting:** smooth spring transition from game table to results grid ([c5f39d5](c5f39d51eb))
* **card-sorting:** wrap prefix/suffix cards to multiple rows ([e3184dd](e3184dd0d4))
* complete 3D enhancement integration for all three proposals ([5ac55cc](5ac55cc149))
* **create-room:** replace hardcoded game grid with dynamic Radix Select dropdown ([83d0ba2](83d0ba26f5))
* dynamic day-of-month favicon using subprocess pattern ([4d0795a](4d0795a9df))
* dynamically crop favicon to active beads for maximum size ([5670322](567032296a))
* enable 3D enhancement on hero/open MyAbacus modes ([37e330f](37e330f26e))
* **games:** add autoplay and improve carousel layout ([9f51edf](9f51edfaa9))
* **games:** add horizontal scroll support to carousels ([a224abb](a224abb6f6))
* **games:** add rotating games hero carousel ([24231e6](24231e6b2e))
* **i18n:** add dynamic locale switching without page reload ([fe9bfea](fe9bfeabf9))
* **i18n:** add global language selector to navigation ([0506360](0506360117))
* **i18n:** add homepage translations for all supported languages ([8c9d35a](8c9d35a3b4))
* **i18n:** add Old High German (goh) language support ([b334a15](b334a15255))
* **i18n:** complete Old High German translations for all locales ([0b06a1c](0b06a1ce00))
* **i18n:** internationalize games page and tutorial content ([4253964](4253964af1))
* **i18n:** internationalize homepage with English translations ([40cff14](40cff143c7))
* **i18n:** migrate from react-i18next to next-intl ([9016b76](9016b76024))
* **i18n:** update games page hero section copy ([6333c60](6333c60352))
* install embla-carousel-autoplay for games carousel ([946e5d1](946e5d1910))
* install embla-carousel-react for player profile carousel ([642ae95](642ae95738))
* internationalize guide page with 6 languages ([e9c320b](e9c320bb10))
* internationalize tutorial player ([26d41cf](26d41cfd05))
* optimize card sorting for mobile displays ([b443ee9](b443ee9cdc))
* Redesign Rithmomachia setup page with dramatic medieval theme ([6ae4d13](6ae4d13dc7))
* **rithmomachia:** add 80% opacity to guide modal when not hovered ([4a78485](4a78485d2e))
* **rithmomachia:** add CaptureContext for capture dialog state management ([d7eb957](d7eb957a8d))
* **rithmomachia:** add ghost panel preview for guide docking ([c0d6526](c0d6526d30))
* **rithmomachia:** add guide docking with resizable panels ([f457f1a](f457f1a1c2))
* **rithmomachia:** add helper piece selection for mathematical captures ([cae3359](cae3359587))
* **rithmomachia:** add helpful error messages for failed captures ([b172440](b172440a41))
* **rithmomachia:** add initial board visual to guide Overview section ([d42bcff](d42bcff0d9))
* **rithmomachia:** Add interactive playing guide modal ([3121d82](3121d8240a))
* **rithmomachia:** add number bond visualization and helper placeholders ([82d8913](82d89131f0))
* **rithmomachia:** add ratio capture example to guide ([9150b0c](9150b0c678))
* **rithmomachia:** add standalone guide page route ([3fcc79f](3fcc79fe9e))
* **rithmomachia:** add useBoardLayout hook for centralized layout calculations ([27f1c98](27f1c989d5))
* **rithmomachia:** add usePieceSelection hook for selection state management ([275f401](275f401e3c))
* **rithmomachia:** add visual board examples to Capture section ([74bc3c0](74bc3c0dcf))
* **rithmomachia:** add visual board examples to Harmony section ([1d5f01c](1d5f01c966))
* **rithmomachia:** add visual winning example to Victory section ([b7fac78](b7fac78829))
* **rithmomachia:** auto-size tab labels with react-textfit ([9fd5406](9fd54067ce))
* **rithmomachia:** cycle through valid helpers with dynamic number tooltips ([4829e41](4829e41ea1))
* **rithmomachia:** enhance capture relation UI with smooth animations ([0a30801](0a308016e9))
* **rithmomachia:** enhance Harmony section with comprehensive content ([f555856](f5558563ea))
* **rithmomachia:** enhance Pieces section with visual examples and pyramid details ([55aff82](55aff829f4))
* **rithmomachia:** enhance Pyramid section with comprehensive details ([9fde1ef](9fde1ef9e7))
* **rithmomachia:** guide defaults to docked right on open ([11f674d](11f674d542))
* **rithmomachia:** improve guide pieces section layout ([a270bfc](a270bfc0cc))
* **rithmomachia:** improve guide UX and add persistence ([b314740](b314740697))
* **rithmomachia:** improve roster status notice UX ([e27df45](e27df45256))
* **rithmomachia:** integrate roster warning into game nav ([8a11594](8a11594203))
* **rithmomachia:** make guide modal ultra-responsive down to 150px width ([0474197](04741971b2))
* **rithmomachia:** recreate original guide modal header layout ([2489695](24896957d0))
* **rithmomachia:** show capture error on hover instead of click ([339b678](339b6780f6))
* **rithmomachia:** show pyramid face numbers on hover instead of selection ([b0c4523](b0c4523c0b))
* **rithmomachia:** show pyramid face numbers when selected ([5c186f3](5c186f3947))
* **rithmomachia:** show pyramid face numbers when selected with subtle animation ([5c2ddbe](5c2ddbef05))
* **rithmomachia:** show real preview layout when dragging guide to dock ([17d2460](17d2460a87))
* **rithmomachia:** simplify guide language for clarity ([85cb630](85cb630add))
* **rithmomachia:** skip helper selection UI and auto-select first valid helper ([be2a00e](be2a00e8b3))
* **rithmomachia:** Update harmony system to classical three-piece proportions ([08c9762](08c97620f5))
* **rithmomachia:** Update to traditional board setup with 25 pieces per side ([0769eaa](0769eaaa1d))
* **rithmomachia:** use actual piece SVGs in number bond with 2.5s rotation animation ([976a7de](976a7de949))
* **room-share:** add QR code button for easy mobile joining ([349290a](349290ac6a))
* show rithmomachia turn in nav ([7c89bfe](7c89bfef9c))
* switch to royal color theme with transparent background ([944ad65](944ad6574e)), closes [#fbbf24](https://github.com/antialias/soroban-abacus-flashcards/issues/fbbf24) [#f59e0](https://github.com/antialias/soroban-abacus-flashcards/issues/f59e0) [#a855f7](https://github.com/antialias/soroban-abacus-flashcards/issues/a855f7) [#7e22](https://github.com/antialias/soroban-abacus-flashcards/issues/7e22)
* **web:** add test page for AbacusStatic RSC compatibility ([903dea2](903dea2584))
* **web:** add test page for AbacusStatic Server Component ([3588d5a](3588d5acde))
* **web:** add Typst-based preview endpoint with React Suspense ([599a758](599a758471))
* **web:** add year abacus to calendar header and make grid bolder ([867c7ee](867c7ee172)), closes [#333](https://github.com/antialias/soroban-abacus-flashcards/issues/333)
* **web:** improve calendar abacus preview styling ([8439727](8439727b15))
* **web:** optimize monthly calendar for single-page layout ([b277a89](b277a89415))
* **web:** redesign monthly calendar as single composite SVG ([8ce8038](8ce8038bae))

### Bug Fixes

* **abacus-react:** add data-testid attributes back to beads for testing ([23ae1b0](23ae1b0c6f))
* **abacus-react:** correct column highlighting offset in AbacusStatic ([0641eb7](0641eb719e))
* **abacus-react:** fix animations by preventing component remounting ([be7d4c4](be7d4c4713))
* **abacus-react:** restore original AbacusReact measurements and positioning ([88c0baa](88c0baaad9))
* add xmlns to AbacusStatic for Typst SVG parsing ([98cd019](98cd019d4a))
* adjust hero abacus position to avoid covering subtitle ([f03d341](f03d341314))
* **arcade:** add automatic retry for version conflict rejections ([fbcde25](fbcde2505f))
* **arcade:** allow deactivating players from users who left the room ([7c1c2d7](7c1c2d7beb))
* **arcade:** implement optimistic locking in session manager ([71fd66d](71fd66d96a))
* board rotation now properly fills height in portrait mode ([b5a96ea](b5a96eaeb1))
* **card-sorting:** add border radius to outer card container ([a922eba](a922eba73c))
* **card-sorting:** add debug logging for spring animations ([d42947e](d42947eb8d))
* **card-sorting:** add missing gameMode support after hard reset ([a832325](a832325deb))
* **card-sorting:** add missing useMemo import ([949d76d](949d76d844))
* **card-sorting:** add overflow hidden to clip rounded corners ([84c66fe](84c66feec6))
* **card-sorting:** adjust connecting paths for scaled cards ([829c741](829c741e55))
* **card-sorting:** adjust game board for spectator panels ([fc5cf12](fc5cf1216f))
* **card-sorting:** adjust viewport dimensions for spectator panels ([4dce16c](4dce16cca4))
* **card-sorting:** animate cards from game board to results grid ([17d45fe](17d45fe88c))
* **card-sorting:** correct suffix card detection in auto-arrange ([d02ab59](d02ab5922c))
* **card-sorting:** enable card scaling for spectators ([6b095c3](6b095c3383))
* **card-sorting:** enable New Game button during active gameplay ([f3f6eca](f3f6eca1db))
* **card-sorting:** end drag immediately when card becomes locked ([ae45298](ae45298ec4))
* **card-sorting:** filter local player from emoji overlays on dragged cards ([dc2d94a](dc2d94aaa5))
* **card-sorting:** fix results panel layout to not cover cards ([4b4fbfe](4b4fbfef32))
* **card-sorting:** hide activity notifications in spectator mode ([5cca279](5cca279687))
* **card-sorting:** keep arrow sequence numbers upright ([79c9469](79c94699fa))
* **card-sorting:** lock correctly positioned prefix/suffix cards ([170abed](170abed231))
* **card-sorting:** lock spring positions after initial animation completes ([275cc62](275cc62a52))
* **card-sorting:** New Game now restarts with same settings instantly ([f3687ed](f3687ed236))
* **card-sorting:** only shrink/fade cards in correct prefix ([51368c6](51368c6ec5))
* **card-sorting:** preserve card positions on pause/resume ([0d8af09](0d8af09517))
* **card-sorting:** preserve rotation when starting drag ([3364144](3364144fb6))
* **card-sorting:** prevent duplicate START_GAME moves on Play Again ([a0b14f8](a0b14f87e9))
* **card-sorting:** prevent ghost movements with proper optimistic updates ([bd014be](bd014bec4f))
* **card-sorting:** prevent infinite loop when all cards are correct ([34785f4](34785f466f))
* **card-sorting:** prevent infinite loop with tolerance-based position comparison ([627b873](627b873382))
* **card-sorting:** prevent position jump when clicking rotated cards ([564a00f](564a00f82b))
* **card-sorting:** prevent replaying own movements from server ([308168a](308168a7fb))
* **card-sorting:** prevent springs from reinitializing on window resize ([30953b8](30953b8c4a))
* **card-sorting:** prevent springs from resetting after animation ([8aff60c](8aff60ce3f))
* **card-sorting:** remove hasAnimatedRef logic causing backwards animation ([a44aa5a](a44aa5a4c2))
* **card-sorting:** remove remaining reveal numbers references ([15c53ea](15c53ea4eb))
* **card-sorting:** restore prefix/suffix card shrinking visual feedback ([f5fb4d7](f5fb4d7b76))
* **card-sorting:** show only active players in team members section ([fa9f1a5](fa9f1a568f))
* **card-sorting:** smooth scale animation while dragging cards ([0eefc33](0eefc332ac))
* **card-sorting:** stabilize inferred sequence for locked cards during drag ([b0cd194](b0cd194838))
* **card-sorting:** use empty deps array for useSprings to prevent recreation ([cee399e](cee399ed15))
* **card-sorting:** use ref to track initialized state and prevent re-animation ([f389afa](f389afa831))
* **card-sorting:** use same coordinate system for game board and results ([6972fdf](6972fdf110))
* **complement-race:** prevent delivery move thrashing in steam sprint mode ([e1258ee](e1258ee041))
* configure favicon metadata and improve bead visibility ([e1369fa](e1369fa275))
* copy entire packages/core and packages/templates ([0ccada0](0ccada0ca7))
* correct hero abacus scroll direction to flow with page content ([4232746](423274657c))
* correct Typst template path in Dockerfile ([4c518de](4c518decb7))
* delete existing user sessions before creating new ones ([0cced47](0cced47a0f))
* **docker:** add scripts, abacus-react, and tsx for production calendar generation ([33eb90e](33eb90e316))
* extract pure SVG content from AbacusReact renders ([b07f1c4](b07f1c4216))
* **games:** prevent horizontal page scroll from carousel overflow ([5a8c98f](5a8c98fc10))
* **games:** smooth scroll feel for carousel wheel navigation ([f80a73b](f80a73b35c))
* **games:** use specific transition properties for smooth carousel loop ([187271e](187271e515))
* **i18n:** eliminate FOUC by loading messages server-side ([4d4d930](4d4d930bd3))
* **i18n:** use useMessages() for tutorial translations ([95b0105](95b0105ca3))
* include column posts in favicon bounding box ([0b2f481](0b2f48106a))
* increase server update debounce to 2000ms for low bandwidth ([633ff12](633ff12750))
* Integrate threshold input into Point Victory card ([b29bbee](b29bbeefca))
* mark dynamic routes as force-dynamic to prevent static generation errors ([d7b35d9](d7b35d9544))
* **nav:** show full navigation on /games page ([d3fe6ac](d3fe6acbb0))
* **qr-button:** improve layout and z-index ([646a422](646a4228d0))
* **qr-button:** increase mini QR code size to 80px ([61ac737](61ac7378bd))
* **qr-button:** increase mini QR code to 84px ([3fae5ea](3fae5ea6fa))
* **qr-button:** make button square and increase QR size ([dc2d466](dc2d46663b))
* **qr-button:** match height of stacked buttons ([81f202d](81f202d215))
* reduce padding to minimize gap below last bead ([0e529be](0e529be789))
* remove distracting parallax and wobble 3D effects ([28a2d40](28a2d40996))
* remove wobble physics and enhance wood grain visibility ([5d97673](5d97673406))
* resolve z-index layering and hero abacus visibility issues ([ed9a050](ed9a050d64))
* rewrite 3D stories to use props instead of CSS wrappers ([26bdb11](26bdb11237))
* **rithmomachia:** add missing i18next dependencies ([91154d9](91154d9364))
* **rithmomachia:** add missing pyramid section keys to Japanese (ja.json) ([dae615e](dae615ee72))
* **rithmomachia:** adjust error dialog sizing to prevent text clipping ([cda1126](cda1126cb0))
* **rithmomachia:** adjust roster notice position to not overlap nav ([7093223](709322373a))
* **rithmomachia:** change undock icon to pop-out arrow ([2a91748](2a91748493))
* **rithmomachia:** correct board dimensions to 16x8 and restore original layout values ([cfac277](cfac277505))
* **rithmomachia:** Correct board setup to match reference image exactly ([618e563](618e56358d))
* **rithmomachia:** correct makeMove parameter types for capture handling ([aafb64f](aafb64f3e3))
* **rithmomachia:** fix guide modal resize drift by calculating from initial state ([1bcd99c](1bcd99c949))
* **rithmomachia:** fix harmony section translation structure for hi/ja/es ([14259a1](14259a19a9))
* **rithmomachia:** fix modal resizing zoom issue ([4fa20f4](4fa20f44cb))
* **rithmomachia:** Fix TypeScript errors in playing guide modal ([4834ece](4834ece98e))
* **rithmomachia:** handle pyramid pieces in hover error tooltip ([56f3164](56f3164155))
* **rithmomachia:** implement proper board cropping and highlighting in guide ([d0a8fcd](d0a8fcdea6))
* **rithmomachia:** improve guide modal tab navigation at narrow widths ([a673177](a673177bec))
* **rithmomachia:** reconnect player assignment UI and fix setup layout ([a1a0374](a1a0374fac))
* **rithmomachia:** render guide as docked in preview panel ([190f8cf](190f8cf302))
* **rithmomachia:** show actual values in tooltips for non-helper relations ([774c6b0](774c6b0ce7))
* **rithmomachia:** show guest-friendly message when they can't fix too many players ([54bfd2f](54bfd2fac8))
* **rithmomachia:** smooth guide dragging from docked state without jump ([8f4a79c](8f4a79c9b0))
* **rithmomachia:** validate move path before showing capture error on hover ([bd49964](bd49964186))
* **room-info:** hide Leave Room button when user is alone ([5927f61](5927f61c3c))
* separate horizontal and vertical bounding box logic ([83090df](83090df4df))
* tolerate OpenSCAD CGAL warnings if output file is created ([88993f3](88993f3662))
* use absolute positioning for hero abacus to eliminate scroll lag ([096104b](096104b094))
* use Debian base for deps stage to match runner for binary compatibility ([f8fe6e4](f8fe6e4a41))
* use default BOSL2 branch instead of non-existent v2.0.0 tag ([f4ffc5b](f4ffc5b027))
* use nested SVG viewBox for actual cropping, not just scaling ([440b492](440b492e85))
* various game improvements and UI enhancements ([b67cf61](b67cf610c5))
* **web,docker:** add --format flag for Typst and upgrade to v0.13.0 ([19b9d7a](19b9d7a74f))
* **web:** add dynamic export to rithmomachia page ([329e623](329e623212))
* **web:** fix Typst PDF generation path resolution ([7ce1287](7ce1287525))
* **web:** generate styled-system artifacts during build ([293390a](293390ae35))
* **web:** move react-dom/server import to API route to satisfy Next.js ([00a8bc3](00a8bc3e5e))
* **web:** move tsx to production dependencies for calendar generation ([ffae9c1](ffae9c1bdb))
* **web:** prevent abacus overlap in composite calendar ([448f93c](448f93c1e2)), closes [#f0f0f0](https://github.com/antialias/soroban-abacus-flashcards/issues/f0f0f0)
* **web:** use AbacusStatic for calendar SVG generation ([08c6a41](08c6a419e2))
* **web:** use dynamic import for react-dom/server in API route ([4f93c7d](4f93c7d996))
* **web:** use nested SVG elements to prevent coordinate space conflicts ([f9cbee8](f9cbee8fcd))

### Performance Improvements

* optimize Docker image size to reduce build failures ([9ca3106](9ca3106361))

### Code Refactoring

* **card-sorting:** remove reveal numbers feature ([ea5e3e8](ea5e3e838b))
* **card-sorting:** send complete card sequence instead of individual moves ([e4df843](e4df8432b9))
* **games:** implement carousel, fix victories bug, add conditional stats ([82c133f](82c133f742))
* **games:** move page title to nav bar ([712ee58](712ee58e59))
* **games:** remove redundant subtitle below nav ([ad5bb87](ad5bb87325))
* **games:** remove wheel scrolling, enable overflow visible carousel ([876513c](876513c9cc))
* reorganize Harmony and Victory guide sections ([fb629c4](fb629c44ea))
* restructure /create page into hub with sub-pages ([b91b23d](b91b23d95f))
* **rithmomachia:** extract board and capture components (phase 2+3) ([a0a867b](a0a867b271))
* **rithmomachia:** extract CaptureErrorDialog component (Phase 2 partial) ([f0a066d](f0a066d8f0))
* **rithmomachia:** extract constants and coordinate utilities (Phase 1) ([eace0ed](eace0ed529))
* **rithmomachia:** extract guide sections into separate files ([765525d](765525dc45))
* **rithmomachia:** extract hooks (phase 5) ([324a659](324a65992f))
* **rithmomachia:** extract phase components (phase 4) ([11364f6](11364f6394))
* **rithmomachia:** extract reusable components from SetupPhase ([3abc325](3abc325ea2))
* **rithmomachia:** make setup phase UI more compact ([e55f848](e55f848a26))
* **rithmomachia:** redesign error notification with modern UI ([dfeeb0e](dfeeb0e0db)), closes [#1e293](https://github.com/antialias/soroban-abacus-flashcards/issues/1e293) [#0f172](https://github.com/antialias/soroban-abacus-flashcards/issues/0f172) [#f1f5f9](https://github.com/antialias/soroban-abacus-flashcards/issues/f1f5f9)
* **rithmomachia:** simplify capture error dialog to one-liner ([82a5eb2](82a5eb2e4b))
* **rithmomachia:** Update board setup to authoritative CSV layout ([0471da5](0471da598d))
* **rithmomachia:** update capture components to use CaptureContext ([2ab6ab5](2ab6ab5799))
* **rithmomachia:** use useBoardLayout and usePieceSelection in BoardDisplay ([0ab7a1d](0ab7a1df32))
* use AbacusReact for dynamic Open Graph image ([9c20f12](9c20f12bac))
* **web:** import utility functions from abacus-react ([7228bbc](7228bbc2eb))
* **web:** move calendar generators to src/utils for proper compilation ([379698f](379698fea3))
* **web:** return calendar SVG preview with PDF generation ([14a5de0](14a5de0dfa))
* **web:** use ABACUS_THEMES instead of manual style definitions ([9f7f001](9f7f001d74))
* **web:** use client-side React rendering for live calendar preview ([f880cbe](f880cbe4bf))
* **web:** use compact prop for inline mini-abacus ([ff1d60a](ff1d60a233))
* **web:** use direct function imports instead of execSync for calendar generation ([9f1715f](9f1715f085))
* **web:** use stdin/stdout for Typst compilation ([06f68cc](06f68cc74c))

### Documentation

* **abacus-react:** add Storybook stories for AbacusStatic ([4f9dc46](4f9dc4666d))
* **abacus-react:** add Storybook stories for new features ([6a1cec0](6a1cec06a7))
* **abacus-react:** export AbacusStatic and update README ([74f2d97](74f2d97434))
* **abacus-react:** update documentation for new features ([35d8734](35d8734a3a))
* **abacus-react:** update README with /static import path for RSC ([72a4c2b](72a4c2b80c))
* add 3D enhancement documentation to README ([cc96802](cc96802df8))
* add critical section on never adding tsx to production dependencies ([770cfc3](770cfc3aca))
* add database migration guide and playing guide modal spec ([5a29af7](5a29af78e2))
* add deployment verification guidelines to prevent false positives ([3d8da23](3d8da2348b))
* **card-sorting:** add comprehensive multiplayer plan ([008ccea](008ccead0f))
* **rithmomachia:** Add concise one-page playing guide ([e3c1f10](e3c1f10233))
* update workflow to require manual testing before commits ([0991796](0991796f1e))

### Styles

* **rithmomachia:** improve divider styling and make tabs responsive ([88ca35e](88ca35e044)), closes [#e5e7](https://github.com/antialias/soroban-abacus-flashcards/issues/e5e7) [#9ca3](https://github.com/antialias/soroban-abacus-flashcards/issues/9ca3)
* **rithmomachia:** improve pyramid face numbers visibility and contrast ([94e5e6a](94e5e6a268)), closes [#fbbf24](https://github.com/antialias/soroban-abacus-flashcards/issues/fbbf24) [#b45309](https://github.com/antialias/soroban-abacus-flashcards/issues/b45309)
* **rithmomachia:** increase pyramid face numbers size and boldness ([7bf2d73](7bf2d730d3))

### Tests

* trigger compose-updater deployment test ([2b06aae](2b06aae394))
* verify compose-updater automatic deployment cycle ([af0552c](af0552ccd9))
2025-11-04 22:18:28 +00:00
Thomas Hallock
770cfc3aca docs: add critical section on never adding tsx to production dependencies
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
2025-11-04 16:14:16 -06:00
semantic-release-bot
2f086ebb82 chore(release): 4.68.0 [skip ci]
## [4.68.0](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.67.1...v4.68.0) (2025-11-04)

### Features

* **abacus-react:** add AbacusStatic for React Server Components ([3b8e864](3b8e864cfa))
* **abacus-react:** add core utility functions for state management ([e65541c](e65541c100))
* **abacus-react:** add layout and educational props ([35bbcec](35bbcecb9e))
* **abacus-react:** add pre-defined theme presets ([cf1f950](cf1f950c7c))
* **abacus-react:** add React hooks for abacus calculations ([de038d2](de038d2afc))
* **abacus-react:** add separate /static export path for React Server Components ([ed69f6b](ed69f6b917))
* **abacus-react:** add shared dimension calculator for consistent sizing ([e5ba772](e5ba772fde))
* **abacus-react:** export new utilities, hooks, and themes ([ce4e44d](ce4e44d630))
* **abacus:** add nativeAbacusNumbers setting to schema and UI ([79f7347](79f7347d48))
* add 3D printing support for abacus models ([dafdfdd](dafdfdd233))
* add comprehensive metadata, SEO, and make AbacusReact SSR-compatible ([0922ea1](0922ea10b7))
* add comprehensive Storybook coverage and migration guide ([7a4a37e](7a4a37ec6d))
* add game preview system with mock arcade environment ([25880cc](25880cc7e4))
* add per-player stats tracking system ([613301c](613301cd13))
* add Strategy & Tactics section to Rithmomachia guide ([81ead65](81ead65680))
* add unified trophy abacus with hero mode integration ([6620418](6620418a70))
* **arcade:** add ability to deactivate remote players without kicking user ([3628426](3628426a56))
* **arcade:** add native abacus numbers support to pressure gauge ([1d525c7](1d525c7b53))
* **arcade:** add Rithmomachia (Battle of Numbers) game ([2fc0a05](2fc0a05f7f))
* **arcade:** add yjs-demo collaborative game and Yjs persistence layer ([d568955](d568955d6a))
* **arcade:** auto-create room when user has none ([ff88c3a](ff88c3a1b8))
* **card-sorting:** add activity feed notifications for collaborative mode ([1461414](1461414ef4))
* **card-sorting:** add auto-submit countdown for perfect sequences ([780a716](780a7161bc))
* **card-sorting:** add bezier curves to connecting arrows ([4d8e873](4d8e873358))
* **card-sorting:** add CardPosition type and position syncing ([656f5a7](656f5a7838))
* **card-sorting:** add collapsible stats sidebar for spectators ([6527c26](6527c26a81))
* **card-sorting:** add game mode selector UI to setup phase ([d25b888](d25b888ffb))
* **card-sorting:** add GameMode type system for multiplayer support ([fd76533](fd765335ef))
* **card-sorting:** add green border to correctly positioned cards ([16fca86](16fca86b76)), closes [#22c55](https://github.com/antialias/soroban-abacus-flashcards/issues/22c55)
* **card-sorting:** add player emoji indicators on moving cards ([3a82099](3a82099757))
* **card-sorting:** add react-spring animations for real-time sync ([c367e0c](c367e0ceec))
* **card-sorting:** add smooth transition to drop shadow ([b0b93d0](b0b93d0175))
* **card-sorting:** add spectator mode UI enhancements ([ee7345d](ee7345d641)), closes [#6366f1](https://github.com/antialias/soroban-abacus-flashcards/issues/6366f1) [#8b5cf6](https://github.com/antialias/soroban-abacus-flashcards/issues/8b5cf6)
* **card-sorting:** add team scoring UI for collaborative mode ([ed6f177](ed6f177914)), closes [#a78](https://github.com/antialias/soroban-abacus-flashcards/issues/a78) [#8b5cf6](https://github.com/antialias/soroban-abacus-flashcards/issues/8b5cf6)
* **card-sorting:** add updateCardPositions action to Provider ([f6ed4a2](f6ed4a27a2))
* **card-sorting:** auto-arrange prefix/suffix cards in corners ([4ba7f24](4ba7f24717))
* **card-sorting:** fade correctly positioned cards to 50% opacity ([7028cfc](7028cfc511))
* **card-sorting:** gentler spring animation for locked cards ([47189cb](47189cb6e7))
* **card-sorting:** implement continuous bezier curve paths ([2d93024](2d9302410f))
* **card-sorting:** improve card distribution for natural scattered look ([0b0503f](0b0503f035))
* **card-sorting:** make player emoji fill entire card background ([2e7a02c](2e7a02c9e4))
* **card-sorting:** optimize results screen for mobile ([d188789](d188789069))
* **card-sorting:** redesign setup screen with modern UI ([73cf967](73cf967492))
* **card-sorting:** scale correctly positioned cards to 50% ([222dc55](222dc555fa))
* **card-sorting:** shrink/fade cards in correct suffix as well ([8f6feec](8f6feec4f2))
* **card-sorting:** smooth spring transition from game table to results grid ([c5f39d5](c5f39d51eb))
* **card-sorting:** wrap prefix/suffix cards to multiple rows ([e3184dd](e3184dd0d4))
* complete 3D enhancement integration for all three proposals ([5ac55cc](5ac55cc149))
* **create-room:** replace hardcoded game grid with dynamic Radix Select dropdown ([83d0ba2](83d0ba26f5))
* dynamic day-of-month favicon using subprocess pattern ([4d0795a](4d0795a9df))
* dynamically crop favicon to active beads for maximum size ([5670322](567032296a))
* enable 3D enhancement on hero/open MyAbacus modes ([37e330f](37e330f26e))
* **games:** add autoplay and improve carousel layout ([9f51edf](9f51edfaa9))
* **games:** add horizontal scroll support to carousels ([a224abb](a224abb6f6))
* **games:** add rotating games hero carousel ([24231e6](24231e6b2e))
* **i18n:** add dynamic locale switching without page reload ([fe9bfea](fe9bfeabf9))
* **i18n:** add global language selector to navigation ([0506360](0506360117))
* **i18n:** add homepage translations for all supported languages ([8c9d35a](8c9d35a3b4))
* **i18n:** add Old High German (goh) language support ([b334a15](b334a15255))
* **i18n:** complete Old High German translations for all locales ([0b06a1c](0b06a1ce00))
* **i18n:** internationalize games page and tutorial content ([4253964](4253964af1))
* **i18n:** internationalize homepage with English translations ([40cff14](40cff143c7))
* **i18n:** migrate from react-i18next to next-intl ([9016b76](9016b76024))
* **i18n:** update games page hero section copy ([6333c60](6333c60352))
* install embla-carousel-autoplay for games carousel ([946e5d1](946e5d1910))
* install embla-carousel-react for player profile carousel ([642ae95](642ae95738))
* internationalize guide page with 6 languages ([e9c320b](e9c320bb10))
* internationalize tutorial player ([26d41cf](26d41cfd05))
* optimize card sorting for mobile displays ([b443ee9](b443ee9cdc))
* Redesign Rithmomachia setup page with dramatic medieval theme ([6ae4d13](6ae4d13dc7))
* **rithmomachia:** add 80% opacity to guide modal when not hovered ([4a78485](4a78485d2e))
* **rithmomachia:** add CaptureContext for capture dialog state management ([d7eb957](d7eb957a8d))
* **rithmomachia:** add ghost panel preview for guide docking ([c0d6526](c0d6526d30))
* **rithmomachia:** add guide docking with resizable panels ([f457f1a](f457f1a1c2))
* **rithmomachia:** add helper piece selection for mathematical captures ([cae3359](cae3359587))
* **rithmomachia:** add helpful error messages for failed captures ([b172440](b172440a41))
* **rithmomachia:** add initial board visual to guide Overview section ([d42bcff](d42bcff0d9))
* **rithmomachia:** Add interactive playing guide modal ([3121d82](3121d8240a))
* **rithmomachia:** add number bond visualization and helper placeholders ([82d8913](82d89131f0))
* **rithmomachia:** add ratio capture example to guide ([9150b0c](9150b0c678))
* **rithmomachia:** add standalone guide page route ([3fcc79f](3fcc79fe9e))
* **rithmomachia:** add useBoardLayout hook for centralized layout calculations ([27f1c98](27f1c989d5))
* **rithmomachia:** add usePieceSelection hook for selection state management ([275f401](275f401e3c))
* **rithmomachia:** add visual board examples to Capture section ([74bc3c0](74bc3c0dcf))
* **rithmomachia:** add visual board examples to Harmony section ([1d5f01c](1d5f01c966))
* **rithmomachia:** add visual winning example to Victory section ([b7fac78](b7fac78829))
* **rithmomachia:** auto-size tab labels with react-textfit ([9fd5406](9fd54067ce))
* **rithmomachia:** cycle through valid helpers with dynamic number tooltips ([4829e41](4829e41ea1))
* **rithmomachia:** enhance capture relation UI with smooth animations ([0a30801](0a308016e9))
* **rithmomachia:** enhance Harmony section with comprehensive content ([f555856](f5558563ea))
* **rithmomachia:** enhance Pieces section with visual examples and pyramid details ([55aff82](55aff829f4))
* **rithmomachia:** enhance Pyramid section with comprehensive details ([9fde1ef](9fde1ef9e7))
* **rithmomachia:** guide defaults to docked right on open ([11f674d](11f674d542))
* **rithmomachia:** improve guide pieces section layout ([a270bfc](a270bfc0cc))
* **rithmomachia:** improve guide UX and add persistence ([b314740](b314740697))
* **rithmomachia:** improve roster status notice UX ([e27df45](e27df45256))
* **rithmomachia:** integrate roster warning into game nav ([8a11594](8a11594203))
* **rithmomachia:** make guide modal ultra-responsive down to 150px width ([0474197](04741971b2))
* **rithmomachia:** recreate original guide modal header layout ([2489695](24896957d0))
* **rithmomachia:** show capture error on hover instead of click ([339b678](339b6780f6))
* **rithmomachia:** show pyramid face numbers on hover instead of selection ([b0c4523](b0c4523c0b))
* **rithmomachia:** show pyramid face numbers when selected ([5c186f3](5c186f3947))
* **rithmomachia:** show pyramid face numbers when selected with subtle animation ([5c2ddbe](5c2ddbef05))
* **rithmomachia:** show real preview layout when dragging guide to dock ([17d2460](17d2460a87))
* **rithmomachia:** simplify guide language for clarity ([85cb630](85cb630add))
* **rithmomachia:** skip helper selection UI and auto-select first valid helper ([be2a00e](be2a00e8b3))
* **rithmomachia:** Update harmony system to classical three-piece proportions ([08c9762](08c97620f5))
* **rithmomachia:** Update to traditional board setup with 25 pieces per side ([0769eaa](0769eaaa1d))
* **rithmomachia:** use actual piece SVGs in number bond with 2.5s rotation animation ([976a7de](976a7de949))
* **room-share:** add QR code button for easy mobile joining ([349290a](349290ac6a))
* show rithmomachia turn in nav ([7c89bfe](7c89bfef9c))
* switch to royal color theme with transparent background ([944ad65](944ad6574e)), closes [#fbbf24](https://github.com/antialias/soroban-abacus-flashcards/issues/fbbf24) [#f59e0](https://github.com/antialias/soroban-abacus-flashcards/issues/f59e0) [#a855f7](https://github.com/antialias/soroban-abacus-flashcards/issues/a855f7) [#7e22](https://github.com/antialias/soroban-abacus-flashcards/issues/7e22)
* **web:** add test page for AbacusStatic RSC compatibility ([903dea2](903dea2584))
* **web:** add test page for AbacusStatic Server Component ([3588d5a](3588d5acde))
* **web:** add Typst-based preview endpoint with React Suspense ([599a758](599a758471))
* **web:** add year abacus to calendar header and make grid bolder ([867c7ee](867c7ee172)), closes [#333](https://github.com/antialias/soroban-abacus-flashcards/issues/333)
* **web:** improve calendar abacus preview styling ([8439727](8439727b15))
* **web:** optimize monthly calendar for single-page layout ([b277a89](b277a89415))
* **web:** redesign monthly calendar as single composite SVG ([8ce8038](8ce8038bae))

### Bug Fixes

* **abacus-react:** add data-testid attributes back to beads for testing ([23ae1b0](23ae1b0c6f))
* **abacus-react:** correct column highlighting offset in AbacusStatic ([0641eb7](0641eb719e))
* **abacus-react:** fix animations by preventing component remounting ([be7d4c4](be7d4c4713))
* **abacus-react:** restore original AbacusReact measurements and positioning ([88c0baa](88c0baaad9))
* add xmlns to AbacusStatic for Typst SVG parsing ([98cd019](98cd019d4a))
* adjust hero abacus position to avoid covering subtitle ([f03d341](f03d341314))
* **arcade:** add automatic retry for version conflict rejections ([fbcde25](fbcde2505f))
* **arcade:** allow deactivating players from users who left the room ([7c1c2d7](7c1c2d7beb))
* **arcade:** implement optimistic locking in session manager ([71fd66d](71fd66d96a))
* board rotation now properly fills height in portrait mode ([b5a96ea](b5a96eaeb1))
* **card-sorting:** add border radius to outer card container ([a922eba](a922eba73c))
* **card-sorting:** add debug logging for spring animations ([d42947e](d42947eb8d))
* **card-sorting:** add missing gameMode support after hard reset ([a832325](a832325deb))
* **card-sorting:** add missing useMemo import ([949d76d](949d76d844))
* **card-sorting:** add overflow hidden to clip rounded corners ([84c66fe](84c66feec6))
* **card-sorting:** adjust connecting paths for scaled cards ([829c741](829c741e55))
* **card-sorting:** adjust game board for spectator panels ([fc5cf12](fc5cf1216f))
* **card-sorting:** adjust viewport dimensions for spectator panels ([4dce16c](4dce16cca4))
* **card-sorting:** animate cards from game board to results grid ([17d45fe](17d45fe88c))
* **card-sorting:** correct suffix card detection in auto-arrange ([d02ab59](d02ab5922c))
* **card-sorting:** enable card scaling for spectators ([6b095c3](6b095c3383))
* **card-sorting:** enable New Game button during active gameplay ([f3f6eca](f3f6eca1db))
* **card-sorting:** end drag immediately when card becomes locked ([ae45298](ae45298ec4))
* **card-sorting:** filter local player from emoji overlays on dragged cards ([dc2d94a](dc2d94aaa5))
* **card-sorting:** fix results panel layout to not cover cards ([4b4fbfe](4b4fbfef32))
* **card-sorting:** hide activity notifications in spectator mode ([5cca279](5cca279687))
* **card-sorting:** keep arrow sequence numbers upright ([79c9469](79c94699fa))
* **card-sorting:** lock correctly positioned prefix/suffix cards ([170abed](170abed231))
* **card-sorting:** lock spring positions after initial animation completes ([275cc62](275cc62a52))
* **card-sorting:** New Game now restarts with same settings instantly ([f3687ed](f3687ed236))
* **card-sorting:** only shrink/fade cards in correct prefix ([51368c6](51368c6ec5))
* **card-sorting:** preserve card positions on pause/resume ([0d8af09](0d8af09517))
* **card-sorting:** preserve rotation when starting drag ([3364144](3364144fb6))
* **card-sorting:** prevent duplicate START_GAME moves on Play Again ([a0b14f8](a0b14f87e9))
* **card-sorting:** prevent ghost movements with proper optimistic updates ([bd014be](bd014bec4f))
* **card-sorting:** prevent infinite loop when all cards are correct ([34785f4](34785f466f))
* **card-sorting:** prevent infinite loop with tolerance-based position comparison ([627b873](627b873382))
* **card-sorting:** prevent position jump when clicking rotated cards ([564a00f](564a00f82b))
* **card-sorting:** prevent replaying own movements from server ([308168a](308168a7fb))
* **card-sorting:** prevent springs from reinitializing on window resize ([30953b8](30953b8c4a))
* **card-sorting:** prevent springs from resetting after animation ([8aff60c](8aff60ce3f))
* **card-sorting:** remove hasAnimatedRef logic causing backwards animation ([a44aa5a](a44aa5a4c2))
* **card-sorting:** remove remaining reveal numbers references ([15c53ea](15c53ea4eb))
* **card-sorting:** restore prefix/suffix card shrinking visual feedback ([f5fb4d7](f5fb4d7b76))
* **card-sorting:** show only active players in team members section ([fa9f1a5](fa9f1a568f))
* **card-sorting:** smooth scale animation while dragging cards ([0eefc33](0eefc332ac))
* **card-sorting:** stabilize inferred sequence for locked cards during drag ([b0cd194](b0cd194838))
* **card-sorting:** use empty deps array for useSprings to prevent recreation ([cee399e](cee399ed15))
* **card-sorting:** use ref to track initialized state and prevent re-animation ([f389afa](f389afa831))
* **card-sorting:** use same coordinate system for game board and results ([6972fdf](6972fdf110))
* **complement-race:** prevent delivery move thrashing in steam sprint mode ([e1258ee](e1258ee041))
* configure favicon metadata and improve bead visibility ([e1369fa](e1369fa275))
* copy entire packages/core and packages/templates ([0ccada0](0ccada0ca7))
* correct hero abacus scroll direction to flow with page content ([4232746](423274657c))
* correct Typst template path in Dockerfile ([4c518de](4c518decb7))
* delete existing user sessions before creating new ones ([0cced47](0cced47a0f))
* **docker:** add scripts, abacus-react, and tsx for production calendar generation ([33eb90e](33eb90e316))
* extract pure SVG content from AbacusReact renders ([b07f1c4](b07f1c4216))
* **games:** prevent horizontal page scroll from carousel overflow ([5a8c98f](5a8c98fc10))
* **games:** smooth scroll feel for carousel wheel navigation ([f80a73b](f80a73b35c))
* **games:** use specific transition properties for smooth carousel loop ([187271e](187271e515))
* **i18n:** eliminate FOUC by loading messages server-side ([4d4d930](4d4d930bd3))
* **i18n:** use useMessages() for tutorial translations ([95b0105](95b0105ca3))
* include column posts in favicon bounding box ([0b2f481](0b2f48106a))
* increase server update debounce to 2000ms for low bandwidth ([633ff12](633ff12750))
* Integrate threshold input into Point Victory card ([b29bbee](b29bbeefca))
* mark dynamic routes as force-dynamic to prevent static generation errors ([d7b35d9](d7b35d9544))
* **nav:** show full navigation on /games page ([d3fe6ac](d3fe6acbb0))
* **qr-button:** improve layout and z-index ([646a422](646a4228d0))
* **qr-button:** increase mini QR code size to 80px ([61ac737](61ac7378bd))
* **qr-button:** increase mini QR code to 84px ([3fae5ea](3fae5ea6fa))
* **qr-button:** make button square and increase QR size ([dc2d466](dc2d46663b))
* **qr-button:** match height of stacked buttons ([81f202d](81f202d215))
* reduce padding to minimize gap below last bead ([0e529be](0e529be789))
* remove distracting parallax and wobble 3D effects ([28a2d40](28a2d40996))
* remove wobble physics and enhance wood grain visibility ([5d97673](5d97673406))
* resolve z-index layering and hero abacus visibility issues ([ed9a050](ed9a050d64))
* rewrite 3D stories to use props instead of CSS wrappers ([26bdb11](26bdb11237))
* **rithmomachia:** add missing i18next dependencies ([91154d9](91154d9364))
* **rithmomachia:** add missing pyramid section keys to Japanese (ja.json) ([dae615e](dae615ee72))
* **rithmomachia:** adjust error dialog sizing to prevent text clipping ([cda1126](cda1126cb0))
* **rithmomachia:** adjust roster notice position to not overlap nav ([7093223](709322373a))
* **rithmomachia:** change undock icon to pop-out arrow ([2a91748](2a91748493))
* **rithmomachia:** correct board dimensions to 16x8 and restore original layout values ([cfac277](cfac277505))
* **rithmomachia:** Correct board setup to match reference image exactly ([618e563](618e56358d))
* **rithmomachia:** correct makeMove parameter types for capture handling ([aafb64f](aafb64f3e3))
* **rithmomachia:** fix guide modal resize drift by calculating from initial state ([1bcd99c](1bcd99c949))
* **rithmomachia:** fix harmony section translation structure for hi/ja/es ([14259a1](14259a19a9))
* **rithmomachia:** fix modal resizing zoom issue ([4fa20f4](4fa20f44cb))
* **rithmomachia:** Fix TypeScript errors in playing guide modal ([4834ece](4834ece98e))
* **rithmomachia:** handle pyramid pieces in hover error tooltip ([56f3164](56f3164155))
* **rithmomachia:** implement proper board cropping and highlighting in guide ([d0a8fcd](d0a8fcdea6))
* **rithmomachia:** improve guide modal tab navigation at narrow widths ([a673177](a673177bec))
* **rithmomachia:** reconnect player assignment UI and fix setup layout ([a1a0374](a1a0374fac))
* **rithmomachia:** render guide as docked in preview panel ([190f8cf](190f8cf302))
* **rithmomachia:** show actual values in tooltips for non-helper relations ([774c6b0](774c6b0ce7))
* **rithmomachia:** show guest-friendly message when they can't fix too many players ([54bfd2f](54bfd2fac8))
* **rithmomachia:** smooth guide dragging from docked state without jump ([8f4a79c](8f4a79c9b0))
* **rithmomachia:** validate move path before showing capture error on hover ([bd49964](bd49964186))
* **room-info:** hide Leave Room button when user is alone ([5927f61](5927f61c3c))
* separate horizontal and vertical bounding box logic ([83090df](83090df4df))
* tolerate OpenSCAD CGAL warnings if output file is created ([88993f3](88993f3662))
* use absolute positioning for hero abacus to eliminate scroll lag ([096104b](096104b094))
* use Debian base for deps stage to match runner for binary compatibility ([f8fe6e4](f8fe6e4a41))
* use default BOSL2 branch instead of non-existent v2.0.0 tag ([f4ffc5b](f4ffc5b027))
* use nested SVG viewBox for actual cropping, not just scaling ([440b492](440b492e85))
* various game improvements and UI enhancements ([b67cf61](b67cf610c5))
* **web,docker:** add --format flag for Typst and upgrade to v0.13.0 ([19b9d7a](19b9d7a74f))
* **web:** add dynamic export to rithmomachia page ([329e623](329e623212))
* **web:** fix Typst PDF generation path resolution ([7ce1287](7ce1287525))
* **web:** generate styled-system artifacts during build ([293390a](293390ae35))
* **web:** move react-dom/server import to API route to satisfy Next.js ([00a8bc3](00a8bc3e5e))
* **web:** move tsx to production dependencies for calendar generation ([ffae9c1](ffae9c1bdb))
* **web:** prevent abacus overlap in composite calendar ([448f93c](448f93c1e2)), closes [#f0f0f0](https://github.com/antialias/soroban-abacus-flashcards/issues/f0f0f0)
* **web:** use AbacusStatic for calendar SVG generation ([08c6a41](08c6a419e2))
* **web:** use dynamic import for react-dom/server in API route ([4f93c7d](4f93c7d996))
* **web:** use nested SVG elements to prevent coordinate space conflicts ([f9cbee8](f9cbee8fcd))

### Performance Improvements

* optimize Docker image size to reduce build failures ([9ca3106](9ca3106361))

### Code Refactoring

* **card-sorting:** remove reveal numbers feature ([ea5e3e8](ea5e3e838b))
* **card-sorting:** send complete card sequence instead of individual moves ([e4df843](e4df8432b9))
* **games:** implement carousel, fix victories bug, add conditional stats ([82c133f](82c133f742))
* **games:** move page title to nav bar ([712ee58](712ee58e59))
* **games:** remove redundant subtitle below nav ([ad5bb87](ad5bb87325))
* **games:** remove wheel scrolling, enable overflow visible carousel ([876513c](876513c9cc))
* reorganize Harmony and Victory guide sections ([fb629c4](fb629c44ea))
* restructure /create page into hub with sub-pages ([b91b23d](b91b23d95f))
* **rithmomachia:** extract board and capture components (phase 2+3) ([a0a867b](a0a867b271))
* **rithmomachia:** extract CaptureErrorDialog component (Phase 2 partial) ([f0a066d](f0a066d8f0))
* **rithmomachia:** extract constants and coordinate utilities (Phase 1) ([eace0ed](eace0ed529))
* **rithmomachia:** extract guide sections into separate files ([765525d](765525dc45))
* **rithmomachia:** extract hooks (phase 5) ([324a659](324a65992f))
* **rithmomachia:** extract phase components (phase 4) ([11364f6](11364f6394))
* **rithmomachia:** extract reusable components from SetupPhase ([3abc325](3abc325ea2))
* **rithmomachia:** make setup phase UI more compact ([e55f848](e55f848a26))
* **rithmomachia:** redesign error notification with modern UI ([dfeeb0e](dfeeb0e0db)), closes [#1e293](https://github.com/antialias/soroban-abacus-flashcards/issues/1e293) [#0f172](https://github.com/antialias/soroban-abacus-flashcards/issues/0f172) [#f1f5f9](https://github.com/antialias/soroban-abacus-flashcards/issues/f1f5f9)
* **rithmomachia:** simplify capture error dialog to one-liner ([82a5eb2](82a5eb2e4b))
* **rithmomachia:** Update board setup to authoritative CSV layout ([0471da5](0471da598d))
* **rithmomachia:** update capture components to use CaptureContext ([2ab6ab5](2ab6ab5799))
* **rithmomachia:** use useBoardLayout and usePieceSelection in BoardDisplay ([0ab7a1d](0ab7a1df32))
* use AbacusReact for dynamic Open Graph image ([9c20f12](9c20f12bac))
* **web:** import utility functions from abacus-react ([7228bbc](7228bbc2eb))
* **web:** move calendar generators to src/utils for proper compilation ([379698f](379698fea3))
* **web:** return calendar SVG preview with PDF generation ([14a5de0](14a5de0dfa))
* **web:** use ABACUS_THEMES instead of manual style definitions ([9f7f001](9f7f001d74))
* **web:** use client-side React rendering for live calendar preview ([f880cbe](f880cbe4bf))
* **web:** use compact prop for inline mini-abacus ([ff1d60a](ff1d60a233))
* **web:** use direct function imports instead of execSync for calendar generation ([9f1715f](9f1715f085))
* **web:** use stdin/stdout for Typst compilation ([06f68cc](06f68cc74c))

### Documentation

* **abacus-react:** add Storybook stories for AbacusStatic ([4f9dc46](4f9dc4666d))
* **abacus-react:** add Storybook stories for new features ([6a1cec0](6a1cec06a7))
* **abacus-react:** export AbacusStatic and update README ([74f2d97](74f2d97434))
* **abacus-react:** update documentation for new features ([35d8734](35d8734a3a))
* **abacus-react:** update README with /static import path for RSC ([72a4c2b](72a4c2b80c))
* add 3D enhancement documentation to README ([cc96802](cc96802df8))
* add database migration guide and playing guide modal spec ([5a29af7](5a29af78e2))
* add deployment verification guidelines to prevent false positives ([3d8da23](3d8da2348b))
* **card-sorting:** add comprehensive multiplayer plan ([008ccea](008ccead0f))
* **rithmomachia:** Add concise one-page playing guide ([e3c1f10](e3c1f10233))
* update workflow to require manual testing before commits ([0991796](0991796f1e))

### Styles

* **rithmomachia:** improve divider styling and make tabs responsive ([88ca35e](88ca35e044)), closes [#e5e7](https://github.com/antialias/soroban-abacus-flashcards/issues/e5e7) [#9ca3](https://github.com/antialias/soroban-abacus-flashcards/issues/9ca3)
* **rithmomachia:** improve pyramid face numbers visibility and contrast ([94e5e6a](94e5e6a268)), closes [#fbbf24](https://github.com/antialias/soroban-abacus-flashcards/issues/fbbf24) [#b45309](https://github.com/antialias/soroban-abacus-flashcards/issues/b45309)
* **rithmomachia:** increase pyramid face numbers size and boldness ([7bf2d73](7bf2d730d3))

### Tests

* trigger compose-updater deployment test ([2b06aae](2b06aae394))
* verify compose-updater automatic deployment cycle ([af0552c](af0552ccd9))
2025-11-04 21:58:12 +00:00
Thomas Hallock
19b9d7a74f fix(web,docker): add --format flag for Typst and upgrade to v0.13.0
- 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
2025-11-04 15:54:01 -06:00
semantic-release-bot
bf0a0bf01b chore(release): 4.68.0 [skip ci]
## [4.68.0](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.67.1...v4.68.0) (2025-11-04)

### Features

* **abacus-react:** add AbacusStatic for React Server Components ([3b8e864](3b8e864cfa))
* **abacus-react:** add core utility functions for state management ([e65541c](e65541c100))
* **abacus-react:** add layout and educational props ([35bbcec](35bbcecb9e))
* **abacus-react:** add pre-defined theme presets ([cf1f950](cf1f950c7c))
* **abacus-react:** add React hooks for abacus calculations ([de038d2](de038d2afc))
* **abacus-react:** add separate /static export path for React Server Components ([ed69f6b](ed69f6b917))
* **abacus-react:** add shared dimension calculator for consistent sizing ([e5ba772](e5ba772fde))
* **abacus-react:** export new utilities, hooks, and themes ([ce4e44d](ce4e44d630))
* **abacus:** add nativeAbacusNumbers setting to schema and UI ([79f7347](79f7347d48))
* add 3D printing support for abacus models ([dafdfdd](dafdfdd233))
* add comprehensive metadata, SEO, and make AbacusReact SSR-compatible ([0922ea1](0922ea10b7))
* add comprehensive Storybook coverage and migration guide ([7a4a37e](7a4a37ec6d))
* add game preview system with mock arcade environment ([25880cc](25880cc7e4))
* add per-player stats tracking system ([613301c](613301cd13))
* add Strategy & Tactics section to Rithmomachia guide ([81ead65](81ead65680))
* add unified trophy abacus with hero mode integration ([6620418](6620418a70))
* **arcade:** add ability to deactivate remote players without kicking user ([3628426](3628426a56))
* **arcade:** add native abacus numbers support to pressure gauge ([1d525c7](1d525c7b53))
* **arcade:** add Rithmomachia (Battle of Numbers) game ([2fc0a05](2fc0a05f7f))
* **arcade:** add yjs-demo collaborative game and Yjs persistence layer ([d568955](d568955d6a))
* **arcade:** auto-create room when user has none ([ff88c3a](ff88c3a1b8))
* **card-sorting:** add activity feed notifications for collaborative mode ([1461414](1461414ef4))
* **card-sorting:** add auto-submit countdown for perfect sequences ([780a716](780a7161bc))
* **card-sorting:** add bezier curves to connecting arrows ([4d8e873](4d8e873358))
* **card-sorting:** add CardPosition type and position syncing ([656f5a7](656f5a7838))
* **card-sorting:** add collapsible stats sidebar for spectators ([6527c26](6527c26a81))
* **card-sorting:** add game mode selector UI to setup phase ([d25b888](d25b888ffb))
* **card-sorting:** add GameMode type system for multiplayer support ([fd76533](fd765335ef))
* **card-sorting:** add green border to correctly positioned cards ([16fca86](16fca86b76)), closes [#22c55](https://github.com/antialias/soroban-abacus-flashcards/issues/22c55)
* **card-sorting:** add player emoji indicators on moving cards ([3a82099](3a82099757))
* **card-sorting:** add react-spring animations for real-time sync ([c367e0c](c367e0ceec))
* **card-sorting:** add smooth transition to drop shadow ([b0b93d0](b0b93d0175))
* **card-sorting:** add spectator mode UI enhancements ([ee7345d](ee7345d641)), closes [#6366f1](https://github.com/antialias/soroban-abacus-flashcards/issues/6366f1) [#8b5cf6](https://github.com/antialias/soroban-abacus-flashcards/issues/8b5cf6)
* **card-sorting:** add team scoring UI for collaborative mode ([ed6f177](ed6f177914)), closes [#a78](https://github.com/antialias/soroban-abacus-flashcards/issues/a78) [#8b5cf6](https://github.com/antialias/soroban-abacus-flashcards/issues/8b5cf6)
* **card-sorting:** add updateCardPositions action to Provider ([f6ed4a2](f6ed4a27a2))
* **card-sorting:** auto-arrange prefix/suffix cards in corners ([4ba7f24](4ba7f24717))
* **card-sorting:** fade correctly positioned cards to 50% opacity ([7028cfc](7028cfc511))
* **card-sorting:** gentler spring animation for locked cards ([47189cb](47189cb6e7))
* **card-sorting:** implement continuous bezier curve paths ([2d93024](2d9302410f))
* **card-sorting:** improve card distribution for natural scattered look ([0b0503f](0b0503f035))
* **card-sorting:** make player emoji fill entire card background ([2e7a02c](2e7a02c9e4))
* **card-sorting:** optimize results screen for mobile ([d188789](d188789069))
* **card-sorting:** redesign setup screen with modern UI ([73cf967](73cf967492))
* **card-sorting:** scale correctly positioned cards to 50% ([222dc55](222dc555fa))
* **card-sorting:** shrink/fade cards in correct suffix as well ([8f6feec](8f6feec4f2))
* **card-sorting:** smooth spring transition from game table to results grid ([c5f39d5](c5f39d51eb))
* **card-sorting:** wrap prefix/suffix cards to multiple rows ([e3184dd](e3184dd0d4))
* complete 3D enhancement integration for all three proposals ([5ac55cc](5ac55cc149))
* **create-room:** replace hardcoded game grid with dynamic Radix Select dropdown ([83d0ba2](83d0ba26f5))
* dynamic day-of-month favicon using subprocess pattern ([4d0795a](4d0795a9df))
* dynamically crop favicon to active beads for maximum size ([5670322](567032296a))
* enable 3D enhancement on hero/open MyAbacus modes ([37e330f](37e330f26e))
* **games:** add autoplay and improve carousel layout ([9f51edf](9f51edfaa9))
* **games:** add horizontal scroll support to carousels ([a224abb](a224abb6f6))
* **games:** add rotating games hero carousel ([24231e6](24231e6b2e))
* **i18n:** add dynamic locale switching without page reload ([fe9bfea](fe9bfeabf9))
* **i18n:** add global language selector to navigation ([0506360](0506360117))
* **i18n:** add homepage translations for all supported languages ([8c9d35a](8c9d35a3b4))
* **i18n:** add Old High German (goh) language support ([b334a15](b334a15255))
* **i18n:** complete Old High German translations for all locales ([0b06a1c](0b06a1ce00))
* **i18n:** internationalize games page and tutorial content ([4253964](4253964af1))
* **i18n:** internationalize homepage with English translations ([40cff14](40cff143c7))
* **i18n:** migrate from react-i18next to next-intl ([9016b76](9016b76024))
* **i18n:** update games page hero section copy ([6333c60](6333c60352))
* install embla-carousel-autoplay for games carousel ([946e5d1](946e5d1910))
* install embla-carousel-react for player profile carousel ([642ae95](642ae95738))
* internationalize guide page with 6 languages ([e9c320b](e9c320bb10))
* internationalize tutorial player ([26d41cf](26d41cfd05))
* optimize card sorting for mobile displays ([b443ee9](b443ee9cdc))
* Redesign Rithmomachia setup page with dramatic medieval theme ([6ae4d13](6ae4d13dc7))
* **rithmomachia:** add 80% opacity to guide modal when not hovered ([4a78485](4a78485d2e))
* **rithmomachia:** add CaptureContext for capture dialog state management ([d7eb957](d7eb957a8d))
* **rithmomachia:** add ghost panel preview for guide docking ([c0d6526](c0d6526d30))
* **rithmomachia:** add guide docking with resizable panels ([f457f1a](f457f1a1c2))
* **rithmomachia:** add helper piece selection for mathematical captures ([cae3359](cae3359587))
* **rithmomachia:** add helpful error messages for failed captures ([b172440](b172440a41))
* **rithmomachia:** add initial board visual to guide Overview section ([d42bcff](d42bcff0d9))
* **rithmomachia:** Add interactive playing guide modal ([3121d82](3121d8240a))
* **rithmomachia:** add number bond visualization and helper placeholders ([82d8913](82d89131f0))
* **rithmomachia:** add ratio capture example to guide ([9150b0c](9150b0c678))
* **rithmomachia:** add standalone guide page route ([3fcc79f](3fcc79fe9e))
* **rithmomachia:** add useBoardLayout hook for centralized layout calculations ([27f1c98](27f1c989d5))
* **rithmomachia:** add usePieceSelection hook for selection state management ([275f401](275f401e3c))
* **rithmomachia:** add visual board examples to Capture section ([74bc3c0](74bc3c0dcf))
* **rithmomachia:** add visual board examples to Harmony section ([1d5f01c](1d5f01c966))
* **rithmomachia:** add visual winning example to Victory section ([b7fac78](b7fac78829))
* **rithmomachia:** auto-size tab labels with react-textfit ([9fd5406](9fd54067ce))
* **rithmomachia:** cycle through valid helpers with dynamic number tooltips ([4829e41](4829e41ea1))
* **rithmomachia:** enhance capture relation UI with smooth animations ([0a30801](0a308016e9))
* **rithmomachia:** enhance Harmony section with comprehensive content ([f555856](f5558563ea))
* **rithmomachia:** enhance Pieces section with visual examples and pyramid details ([55aff82](55aff829f4))
* **rithmomachia:** enhance Pyramid section with comprehensive details ([9fde1ef](9fde1ef9e7))
* **rithmomachia:** guide defaults to docked right on open ([11f674d](11f674d542))
* **rithmomachia:** improve guide pieces section layout ([a270bfc](a270bfc0cc))
* **rithmomachia:** improve guide UX and add persistence ([b314740](b314740697))
* **rithmomachia:** improve roster status notice UX ([e27df45](e27df45256))
* **rithmomachia:** integrate roster warning into game nav ([8a11594](8a11594203))
* **rithmomachia:** make guide modal ultra-responsive down to 150px width ([0474197](04741971b2))
* **rithmomachia:** recreate original guide modal header layout ([2489695](24896957d0))
* **rithmomachia:** show capture error on hover instead of click ([339b678](339b6780f6))
* **rithmomachia:** show pyramid face numbers on hover instead of selection ([b0c4523](b0c4523c0b))
* **rithmomachia:** show pyramid face numbers when selected ([5c186f3](5c186f3947))
* **rithmomachia:** show pyramid face numbers when selected with subtle animation ([5c2ddbe](5c2ddbef05))
* **rithmomachia:** show real preview layout when dragging guide to dock ([17d2460](17d2460a87))
* **rithmomachia:** simplify guide language for clarity ([85cb630](85cb630add))
* **rithmomachia:** skip helper selection UI and auto-select first valid helper ([be2a00e](be2a00e8b3))
* **rithmomachia:** Update harmony system to classical three-piece proportions ([08c9762](08c97620f5))
* **rithmomachia:** Update to traditional board setup with 25 pieces per side ([0769eaa](0769eaaa1d))
* **rithmomachia:** use actual piece SVGs in number bond with 2.5s rotation animation ([976a7de](976a7de949))
* **room-share:** add QR code button for easy mobile joining ([349290a](349290ac6a))
* show rithmomachia turn in nav ([7c89bfe](7c89bfef9c))
* switch to royal color theme with transparent background ([944ad65](944ad6574e)), closes [#fbbf24](https://github.com/antialias/soroban-abacus-flashcards/issues/fbbf24) [#f59e0](https://github.com/antialias/soroban-abacus-flashcards/issues/f59e0) [#a855f7](https://github.com/antialias/soroban-abacus-flashcards/issues/a855f7) [#7e22](https://github.com/antialias/soroban-abacus-flashcards/issues/7e22)
* **web:** add test page for AbacusStatic RSC compatibility ([903dea2](903dea2584))
* **web:** add test page for AbacusStatic Server Component ([3588d5a](3588d5acde))
* **web:** add Typst-based preview endpoint with React Suspense ([599a758](599a758471))
* **web:** add year abacus to calendar header and make grid bolder ([867c7ee](867c7ee172)), closes [#333](https://github.com/antialias/soroban-abacus-flashcards/issues/333)
* **web:** improve calendar abacus preview styling ([8439727](8439727b15))
* **web:** optimize monthly calendar for single-page layout ([b277a89](b277a89415))
* **web:** redesign monthly calendar as single composite SVG ([8ce8038](8ce8038bae))

### Bug Fixes

* **abacus-react:** add data-testid attributes back to beads for testing ([23ae1b0](23ae1b0c6f))
* **abacus-react:** correct column highlighting offset in AbacusStatic ([0641eb7](0641eb719e))
* **abacus-react:** fix animations by preventing component remounting ([be7d4c4](be7d4c4713))
* **abacus-react:** restore original AbacusReact measurements and positioning ([88c0baa](88c0baaad9))
* add xmlns to AbacusStatic for Typst SVG parsing ([98cd019](98cd019d4a))
* adjust hero abacus position to avoid covering subtitle ([f03d341](f03d341314))
* **arcade:** add automatic retry for version conflict rejections ([fbcde25](fbcde2505f))
* **arcade:** allow deactivating players from users who left the room ([7c1c2d7](7c1c2d7beb))
* **arcade:** implement optimistic locking in session manager ([71fd66d](71fd66d96a))
* board rotation now properly fills height in portrait mode ([b5a96ea](b5a96eaeb1))
* **card-sorting:** add border radius to outer card container ([a922eba](a922eba73c))
* **card-sorting:** add debug logging for spring animations ([d42947e](d42947eb8d))
* **card-sorting:** add missing gameMode support after hard reset ([a832325](a832325deb))
* **card-sorting:** add missing useMemo import ([949d76d](949d76d844))
* **card-sorting:** add overflow hidden to clip rounded corners ([84c66fe](84c66feec6))
* **card-sorting:** adjust connecting paths for scaled cards ([829c741](829c741e55))
* **card-sorting:** adjust game board for spectator panels ([fc5cf12](fc5cf1216f))
* **card-sorting:** adjust viewport dimensions for spectator panels ([4dce16c](4dce16cca4))
* **card-sorting:** animate cards from game board to results grid ([17d45fe](17d45fe88c))
* **card-sorting:** correct suffix card detection in auto-arrange ([d02ab59](d02ab5922c))
* **card-sorting:** enable card scaling for spectators ([6b095c3](6b095c3383))
* **card-sorting:** enable New Game button during active gameplay ([f3f6eca](f3f6eca1db))
* **card-sorting:** end drag immediately when card becomes locked ([ae45298](ae45298ec4))
* **card-sorting:** filter local player from emoji overlays on dragged cards ([dc2d94a](dc2d94aaa5))
* **card-sorting:** fix results panel layout to not cover cards ([4b4fbfe](4b4fbfef32))
* **card-sorting:** hide activity notifications in spectator mode ([5cca279](5cca279687))
* **card-sorting:** keep arrow sequence numbers upright ([79c9469](79c94699fa))
* **card-sorting:** lock correctly positioned prefix/suffix cards ([170abed](170abed231))
* **card-sorting:** lock spring positions after initial animation completes ([275cc62](275cc62a52))
* **card-sorting:** New Game now restarts with same settings instantly ([f3687ed](f3687ed236))
* **card-sorting:** only shrink/fade cards in correct prefix ([51368c6](51368c6ec5))
* **card-sorting:** preserve card positions on pause/resume ([0d8af09](0d8af09517))
* **card-sorting:** preserve rotation when starting drag ([3364144](3364144fb6))
* **card-sorting:** prevent duplicate START_GAME moves on Play Again ([a0b14f8](a0b14f87e9))
* **card-sorting:** prevent ghost movements with proper optimistic updates ([bd014be](bd014bec4f))
* **card-sorting:** prevent infinite loop when all cards are correct ([34785f4](34785f466f))
* **card-sorting:** prevent infinite loop with tolerance-based position comparison ([627b873](627b873382))
* **card-sorting:** prevent position jump when clicking rotated cards ([564a00f](564a00f82b))
* **card-sorting:** prevent replaying own movements from server ([308168a](308168a7fb))
* **card-sorting:** prevent springs from reinitializing on window resize ([30953b8](30953b8c4a))
* **card-sorting:** prevent springs from resetting after animation ([8aff60c](8aff60ce3f))
* **card-sorting:** remove hasAnimatedRef logic causing backwards animation ([a44aa5a](a44aa5a4c2))
* **card-sorting:** remove remaining reveal numbers references ([15c53ea](15c53ea4eb))
* **card-sorting:** restore prefix/suffix card shrinking visual feedback ([f5fb4d7](f5fb4d7b76))
* **card-sorting:** show only active players in team members section ([fa9f1a5](fa9f1a568f))
* **card-sorting:** smooth scale animation while dragging cards ([0eefc33](0eefc332ac))
* **card-sorting:** stabilize inferred sequence for locked cards during drag ([b0cd194](b0cd194838))
* **card-sorting:** use empty deps array for useSprings to prevent recreation ([cee399e](cee399ed15))
* **card-sorting:** use ref to track initialized state and prevent re-animation ([f389afa](f389afa831))
* **card-sorting:** use same coordinate system for game board and results ([6972fdf](6972fdf110))
* **complement-race:** prevent delivery move thrashing in steam sprint mode ([e1258ee](e1258ee041))
* configure favicon metadata and improve bead visibility ([e1369fa](e1369fa275))
* copy entire packages/core and packages/templates ([0ccada0](0ccada0ca7))
* correct hero abacus scroll direction to flow with page content ([4232746](423274657c))
* correct Typst template path in Dockerfile ([4c518de](4c518decb7))
* delete existing user sessions before creating new ones ([0cced47](0cced47a0f))
* **docker:** add scripts, abacus-react, and tsx for production calendar generation ([33eb90e](33eb90e316))
* extract pure SVG content from AbacusReact renders ([b07f1c4](b07f1c4216))
* **games:** prevent horizontal page scroll from carousel overflow ([5a8c98f](5a8c98fc10))
* **games:** smooth scroll feel for carousel wheel navigation ([f80a73b](f80a73b35c))
* **games:** use specific transition properties for smooth carousel loop ([187271e](187271e515))
* **i18n:** eliminate FOUC by loading messages server-side ([4d4d930](4d4d930bd3))
* **i18n:** use useMessages() for tutorial translations ([95b0105](95b0105ca3))
* include column posts in favicon bounding box ([0b2f481](0b2f48106a))
* increase server update debounce to 2000ms for low bandwidth ([633ff12](633ff12750))
* Integrate threshold input into Point Victory card ([b29bbee](b29bbeefca))
* mark dynamic routes as force-dynamic to prevent static generation errors ([d7b35d9](d7b35d9544))
* **nav:** show full navigation on /games page ([d3fe6ac](d3fe6acbb0))
* **qr-button:** improve layout and z-index ([646a422](646a4228d0))
* **qr-button:** increase mini QR code size to 80px ([61ac737](61ac7378bd))
* **qr-button:** increase mini QR code to 84px ([3fae5ea](3fae5ea6fa))
* **qr-button:** make button square and increase QR size ([dc2d466](dc2d46663b))
* **qr-button:** match height of stacked buttons ([81f202d](81f202d215))
* reduce padding to minimize gap below last bead ([0e529be](0e529be789))
* remove distracting parallax and wobble 3D effects ([28a2d40](28a2d40996))
* remove wobble physics and enhance wood grain visibility ([5d97673](5d97673406))
* resolve z-index layering and hero abacus visibility issues ([ed9a050](ed9a050d64))
* rewrite 3D stories to use props instead of CSS wrappers ([26bdb11](26bdb11237))
* **rithmomachia:** add missing i18next dependencies ([91154d9](91154d9364))
* **rithmomachia:** add missing pyramid section keys to Japanese (ja.json) ([dae615e](dae615ee72))
* **rithmomachia:** adjust error dialog sizing to prevent text clipping ([cda1126](cda1126cb0))
* **rithmomachia:** adjust roster notice position to not overlap nav ([7093223](709322373a))
* **rithmomachia:** change undock icon to pop-out arrow ([2a91748](2a91748493))
* **rithmomachia:** correct board dimensions to 16x8 and restore original layout values ([cfac277](cfac277505))
* **rithmomachia:** Correct board setup to match reference image exactly ([618e563](618e56358d))
* **rithmomachia:** correct makeMove parameter types for capture handling ([aafb64f](aafb64f3e3))
* **rithmomachia:** fix guide modal resize drift by calculating from initial state ([1bcd99c](1bcd99c949))
* **rithmomachia:** fix harmony section translation structure for hi/ja/es ([14259a1](14259a19a9))
* **rithmomachia:** fix modal resizing zoom issue ([4fa20f4](4fa20f44cb))
* **rithmomachia:** Fix TypeScript errors in playing guide modal ([4834ece](4834ece98e))
* **rithmomachia:** handle pyramid pieces in hover error tooltip ([56f3164](56f3164155))
* **rithmomachia:** implement proper board cropping and highlighting in guide ([d0a8fcd](d0a8fcdea6))
* **rithmomachia:** improve guide modal tab navigation at narrow widths ([a673177](a673177bec))
* **rithmomachia:** reconnect player assignment UI and fix setup layout ([a1a0374](a1a0374fac))
* **rithmomachia:** render guide as docked in preview panel ([190f8cf](190f8cf302))
* **rithmomachia:** show actual values in tooltips for non-helper relations ([774c6b0](774c6b0ce7))
* **rithmomachia:** show guest-friendly message when they can't fix too many players ([54bfd2f](54bfd2fac8))
* **rithmomachia:** smooth guide dragging from docked state without jump ([8f4a79c](8f4a79c9b0))
* **rithmomachia:** validate move path before showing capture error on hover ([bd49964](bd49964186))
* **room-info:** hide Leave Room button when user is alone ([5927f61](5927f61c3c))
* separate horizontal and vertical bounding box logic ([83090df](83090df4df))
* tolerate OpenSCAD CGAL warnings if output file is created ([88993f3](88993f3662))
* use absolute positioning for hero abacus to eliminate scroll lag ([096104b](096104b094))
* use Debian base for deps stage to match runner for binary compatibility ([f8fe6e4](f8fe6e4a41))
* use default BOSL2 branch instead of non-existent v2.0.0 tag ([f4ffc5b](f4ffc5b027))
* use nested SVG viewBox for actual cropping, not just scaling ([440b492](440b492e85))
* various game improvements and UI enhancements ([b67cf61](b67cf610c5))
* **web:** add dynamic export to rithmomachia page ([329e623](329e623212))
* **web:** fix Typst PDF generation path resolution ([7ce1287](7ce1287525))
* **web:** generate styled-system artifacts during build ([293390a](293390ae35))
* **web:** move react-dom/server import to API route to satisfy Next.js ([00a8bc3](00a8bc3e5e))
* **web:** move tsx to production dependencies for calendar generation ([ffae9c1](ffae9c1bdb))
* **web:** prevent abacus overlap in composite calendar ([448f93c](448f93c1e2)), closes [#f0f0f0](https://github.com/antialias/soroban-abacus-flashcards/issues/f0f0f0)
* **web:** use AbacusStatic for calendar SVG generation ([08c6a41](08c6a419e2))
* **web:** use dynamic import for react-dom/server in API route ([4f93c7d](4f93c7d996))
* **web:** use nested SVG elements to prevent coordinate space conflicts ([f9cbee8](f9cbee8fcd))

### Performance Improvements

* optimize Docker image size to reduce build failures ([9ca3106](9ca3106361))

### Code Refactoring

* **card-sorting:** remove reveal numbers feature ([ea5e3e8](ea5e3e838b))
* **card-sorting:** send complete card sequence instead of individual moves ([e4df843](e4df8432b9))
* **games:** implement carousel, fix victories bug, add conditional stats ([82c133f](82c133f742))
* **games:** move page title to nav bar ([712ee58](712ee58e59))
* **games:** remove redundant subtitle below nav ([ad5bb87](ad5bb87325))
* **games:** remove wheel scrolling, enable overflow visible carousel ([876513c](876513c9cc))
* reorganize Harmony and Victory guide sections ([fb629c4](fb629c44ea))
* restructure /create page into hub with sub-pages ([b91b23d](b91b23d95f))
* **rithmomachia:** extract board and capture components (phase 2+3) ([a0a867b](a0a867b271))
* **rithmomachia:** extract CaptureErrorDialog component (Phase 2 partial) ([f0a066d](f0a066d8f0))
* **rithmomachia:** extract constants and coordinate utilities (Phase 1) ([eace0ed](eace0ed529))
* **rithmomachia:** extract guide sections into separate files ([765525d](765525dc45))
* **rithmomachia:** extract hooks (phase 5) ([324a659](324a65992f))
* **rithmomachia:** extract phase components (phase 4) ([11364f6](11364f6394))
* **rithmomachia:** extract reusable components from SetupPhase ([3abc325](3abc325ea2))
* **rithmomachia:** make setup phase UI more compact ([e55f848](e55f848a26))
* **rithmomachia:** redesign error notification with modern UI ([dfeeb0e](dfeeb0e0db)), closes [#1e293](https://github.com/antialias/soroban-abacus-flashcards/issues/1e293) [#0f172](https://github.com/antialias/soroban-abacus-flashcards/issues/0f172) [#f1f5f9](https://github.com/antialias/soroban-abacus-flashcards/issues/f1f5f9)
* **rithmomachia:** simplify capture error dialog to one-liner ([82a5eb2](82a5eb2e4b))
* **rithmomachia:** Update board setup to authoritative CSV layout ([0471da5](0471da598d))
* **rithmomachia:** update capture components to use CaptureContext ([2ab6ab5](2ab6ab5799))
* **rithmomachia:** use useBoardLayout and usePieceSelection in BoardDisplay ([0ab7a1d](0ab7a1df32))
* use AbacusReact for dynamic Open Graph image ([9c20f12](9c20f12bac))
* **web:** import utility functions from abacus-react ([7228bbc](7228bbc2eb))
* **web:** move calendar generators to src/utils for proper compilation ([379698f](379698fea3))
* **web:** return calendar SVG preview with PDF generation ([14a5de0](14a5de0dfa))
* **web:** use ABACUS_THEMES instead of manual style definitions ([9f7f001](9f7f001d74))
* **web:** use client-side React rendering for live calendar preview ([f880cbe](f880cbe4bf))
* **web:** use compact prop for inline mini-abacus ([ff1d60a](ff1d60a233))
* **web:** use direct function imports instead of execSync for calendar generation ([9f1715f](9f1715f085))
* **web:** use stdin/stdout for Typst compilation ([06f68cc](06f68cc74c))

### Documentation

* **abacus-react:** add Storybook stories for AbacusStatic ([4f9dc46](4f9dc4666d))
* **abacus-react:** add Storybook stories for new features ([6a1cec0](6a1cec06a7))
* **abacus-react:** export AbacusStatic and update README ([74f2d97](74f2d97434))
* **abacus-react:** update documentation for new features ([35d8734](35d8734a3a))
* **abacus-react:** update README with /static import path for RSC ([72a4c2b](72a4c2b80c))
* add 3D enhancement documentation to README ([cc96802](cc96802df8))
* add database migration guide and playing guide modal spec ([5a29af7](5a29af78e2))
* add deployment verification guidelines to prevent false positives ([3d8da23](3d8da2348b))
* **card-sorting:** add comprehensive multiplayer plan ([008ccea](008ccead0f))
* **rithmomachia:** Add concise one-page playing guide ([e3c1f10](e3c1f10233))
* update workflow to require manual testing before commits ([0991796](0991796f1e))

### Styles

* **rithmomachia:** improve divider styling and make tabs responsive ([88ca35e](88ca35e044)), closes [#e5e7](https://github.com/antialias/soroban-abacus-flashcards/issues/e5e7) [#9ca3](https://github.com/antialias/soroban-abacus-flashcards/issues/9ca3)
* **rithmomachia:** improve pyramid face numbers visibility and contrast ([94e5e6a](94e5e6a268)), closes [#fbbf24](https://github.com/antialias/soroban-abacus-flashcards/issues/fbbf24) [#b45309](https://github.com/antialias/soroban-abacus-flashcards/issues/b45309)
* **rithmomachia:** increase pyramid face numbers size and boldness ([7bf2d73](7bf2d730d3))

### Tests

* trigger compose-updater deployment test ([2b06aae](2b06aae394))
* verify compose-updater automatic deployment cycle ([af0552c](af0552ccd9))
2025-11-04 20:30:29 +00:00
Thomas Hallock
379698fea3 refactor(web): move calendar generators to src/utils for proper compilation
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>
2025-11-04 14:26:17 -06:00
Thomas Hallock
ffae9c1bdb fix(web): move tsx to production dependencies for calendar generation
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>
2025-11-04 14:00:26 -06:00
semantic-release-bot
16ccaf2c8b chore(abacus-react): release v2.8.2 [skip ci]
## [2.8.2](https://github.com/antialias/soroban-abacus-flashcards/compare/abacus-react-v2.8.1...abacus-react-v2.8.2) (2025-11-04)

### Bug Fixes

* **abacus-react:** add data-testid attributes back to beads for testing ([23ae1b0](23ae1b0c6f))
2025-11-04 19:25:18 +00:00
Thomas Hallock
23ae1b0c6f fix(abacus-react): add data-testid attributes back to beads for testing
After the refactor to shared SVG rendering, data-testid attributes were
removed from beads, causing controlled-input tests to fail. Added them
back to both AbacusAnimatedBead and AbacusStaticBead for test compatibility.

Test IDs follow pattern: bead-place-{placeValue}-{type}[-pos-{position}]
Examples: bead-place-0-heaven, bead-place-0-earth-pos-0

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-04 13:24:04 -06:00
semantic-release-bot
e852afddc5 chore(abacus-react): release v2.8.1 [skip ci]
## [2.8.1](https://github.com/antialias/soroban-abacus-flashcards/compare/abacus-react-v2.8.0...abacus-react-v2.8.1) (2025-11-04)

### Bug Fixes

* **abacus-react:** fix animations by preventing component remounting ([be7d4c4](be7d4c4713))
* **abacus-react:** restore original AbacusReact measurements and positioning ([88c0baa](88c0baaad9))
2025-11-04 19:10:18 +00:00
Thomas Hallock
645140648a chore(abacus-react): remove debug logging and backup file 2025-11-04 13:09:01 -06:00
Thomas Hallock
be7d4c4713 fix(abacus-react): fix animations by preventing component remounting
The issue was that WrappedBeadComponent was causing all beads to remount
on every render, preventing React Spring animations from working. Even
though the wrapper was memoized with useCallback, any dependency change
caused React to see it as a completely new component type, unmounting all
old beads and mounting new ones at their new positions (instant jump instead
of animation).

Solution: Refactor to use calculateExtraBeadProps pattern instead of wrapper
- Pass AbacusAnimatedBead directly as BeadComponent (stable reference)
- Add calculateExtraBeadProps function to AbacusSVGRenderer interface
- This function computes animation props (enableAnimation, physicsConfig, etc.)
  without changing the component type
- Result: Beads update props instead of remounting, allowing animations to work

Key changes:
- AbacusSVGRenderer: Accept calculateExtraBeadProps prop
- AbacusSVGRenderer: Call calculateExtraBeadProps for each bead, spread result
- AbacusReact: Replace WrappedBeadComponent with calculateExtraBeadProps callback
- AbacusReact: Pass AbacusAnimatedBead directly (not wrapped)
- AbacusSVGRenderer: Change BeadComponent type to React.ComponentType<any>
- AbacusSVGRenderer: Use stable keys: bead-pv{placeValue}-{type}-{position}

Debugging logs added temporarily to verify fix works.
2025-11-04 13:09:01 -06:00
Thomas Hallock
88c0baaad9 fix(abacus-react): restore original AbacusReact measurements and positioning
This restores the exact dimension calculations and bead positioning
formulas from the original useAbacusDimensions hook and inline positioning
logic, ensuring correct visual layout and maintaining animations.

Changes:
- Fix barY calculation: use heavenEarthGap directly (30px), not +labelSpace
- Restore original Typst positioning formulas for all beads:
  * Heaven inactive: heavenEarthGap - inactiveGap - beadSize/2
  * Earth positioning now accounts for earthActive count correctly
- Pass empty columnLabels array to calculateStandardDimensions from
  AbacusReact since it renders labels separately at y=-20
- Add columnState parameter to calculateBeadPosition() for accurate
  inactive earth bead positioning
- Update AbacusSVGRenderer to pass column state when calculating positions

This fixes the issue where beads appeared at wrong positions after the
refactor due to incorrect dimension calculations.

Related: AbacusStatic continues to work correctly with labelSpace since
it renders labels within the SVG coordinate space.
2025-11-04 13:09:01 -06:00
semantic-release-bot
20ab40b2df chore(abacus-react): release v2.8.0 [skip ci]
# [2.8.0](https://github.com/antialias/soroban-abacus-flashcards/compare/abacus-react-v2.7.1...abacus-react-v2.8.0) (2025-11-04)

### Bug Fixes

* **docker:** add scripts, abacus-react, and tsx for production calendar generation ([33eb90e](33eb90e316))
* **web:** generate styled-system artifacts during build ([293390a](293390ae35))
* **web:** move react-dom/server import to API route to satisfy Next.js ([00a8bc3](00a8bc3e5e))
* **web:** prevent abacus overlap in composite calendar ([448f93c](448f93c1e2)), closes [#f0f0f0](https://github.com/antialias/soroban-abacus-flashcards/issues/f0f0f0)
* **web:** use dynamic import for react-dom/server in API route ([4f93c7d](4f93c7d996))
* **web:** use nested SVG elements to prevent coordinate space conflicts ([f9cbee8](f9cbee8fcd))

### Features

* **abacus-react:** add shared dimension calculator for consistent sizing ([e5ba772](e5ba772fde))
* **web:** add Typst-based preview endpoint with React Suspense ([599a758](599a758471))
* **web:** add year abacus to calendar header and make grid bolder ([867c7ee](867c7ee172)), closes [#333](https://github.com/antialias/soroban-abacus-flashcards/issues/333)
* **web:** optimize monthly calendar for single-page layout ([b277a89](b277a89415))
* **web:** redesign monthly calendar as single composite SVG ([8ce8038](8ce8038bae))
2025-11-04 16:44:44 +00:00
Thomas Hallock
06f68cc74c refactor(web): use stdin/stdout for Typst compilation
- Pipe Typst document content via stdin instead of writing .typ files
- Read PDF/SVG output from stdout instead of temp files
- Keep SVG files in temp directory (Typst requires file paths)
- Reduces disk I/O and eliminates .typ/.pdf file writes
- Add 50MB maxBuffer for large calendar PDFs

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-04 10:43:21 -06:00
Thomas Hallock
599a758471 feat(web): add Typst-based preview endpoint with React Suspense
- 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>
2025-11-04 10:43:21 -06:00
Thomas Hallock
e5ba772fde feat(abacus-react): add shared dimension calculator for consistent sizing
- 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>
2025-11-04 10:43:21 -06:00
Thomas Hallock
293390ae35 fix(web): generate styled-system artifacts during build
- Add @pandacss/dev to build script before Next.js build
- Ensures styled-system directory is generated in CI/CD pipeline
- Fixes "Cannot find module styled-system/css" build errors

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-04 10:43:21 -06:00
Thomas Hallock
f880cbe4bf refactor(web): use client-side React rendering for live calendar preview
- 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>
2025-11-04 10:43:21 -06:00
Thomas Hallock
14a5de0dfa refactor(web): return calendar SVG preview with PDF generation
- 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>
2025-11-04 10:43:21 -06:00
Thomas Hallock
867c7ee172 feat(web): add year abacus to calendar header and make grid bolder
- 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>
2025-11-04 10:43:21 -06:00
semantic-release-bot
3a20b46185 chore(release): 4.68.0 [skip ci]
## [4.68.0](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.67.1...v4.68.0) (2025-11-04)

### Features

* **abacus-react:** add AbacusStatic for React Server Components ([3b8e864](3b8e864cfa))
* **abacus-react:** add core utility functions for state management ([e65541c](e65541c100))
* **abacus-react:** add layout and educational props ([35bbcec](35bbcecb9e))
* **abacus-react:** add pre-defined theme presets ([cf1f950](cf1f950c7c))
* **abacus-react:** add React hooks for abacus calculations ([de038d2](de038d2afc))
* **abacus-react:** add separate /static export path for React Server Components ([ed69f6b](ed69f6b917))
* **abacus-react:** export new utilities, hooks, and themes ([ce4e44d](ce4e44d630))
* **abacus:** add nativeAbacusNumbers setting to schema and UI ([79f7347](79f7347d48))
* add 3D printing support for abacus models ([dafdfdd](dafdfdd233))
* add comprehensive metadata, SEO, and make AbacusReact SSR-compatible ([0922ea1](0922ea10b7))
* add comprehensive Storybook coverage and migration guide ([7a4a37e](7a4a37ec6d))
* add game preview system with mock arcade environment ([25880cc](25880cc7e4))
* add per-player stats tracking system ([613301c](613301cd13))
* add Strategy & Tactics section to Rithmomachia guide ([81ead65](81ead65680))
* add unified trophy abacus with hero mode integration ([6620418](6620418a70))
* **arcade:** add ability to deactivate remote players without kicking user ([3628426](3628426a56))
* **arcade:** add native abacus numbers support to pressure gauge ([1d525c7](1d525c7b53))
* **arcade:** add Rithmomachia (Battle of Numbers) game ([2fc0a05](2fc0a05f7f))
* **arcade:** add yjs-demo collaborative game and Yjs persistence layer ([d568955](d568955d6a))
* **arcade:** auto-create room when user has none ([ff88c3a](ff88c3a1b8))
* **card-sorting:** add activity feed notifications for collaborative mode ([1461414](1461414ef4))
* **card-sorting:** add auto-submit countdown for perfect sequences ([780a716](780a7161bc))
* **card-sorting:** add bezier curves to connecting arrows ([4d8e873](4d8e873358))
* **card-sorting:** add CardPosition type and position syncing ([656f5a7](656f5a7838))
* **card-sorting:** add collapsible stats sidebar for spectators ([6527c26](6527c26a81))
* **card-sorting:** add game mode selector UI to setup phase ([d25b888](d25b888ffb))
* **card-sorting:** add GameMode type system for multiplayer support ([fd76533](fd765335ef))
* **card-sorting:** add green border to correctly positioned cards ([16fca86](16fca86b76)), closes [#22c55](https://github.com/antialias/soroban-abacus-flashcards/issues/22c55)
* **card-sorting:** add player emoji indicators on moving cards ([3a82099](3a82099757))
* **card-sorting:** add react-spring animations for real-time sync ([c367e0c](c367e0ceec))
* **card-sorting:** add smooth transition to drop shadow ([b0b93d0](b0b93d0175))
* **card-sorting:** add spectator mode UI enhancements ([ee7345d](ee7345d641)), closes [#6366f1](https://github.com/antialias/soroban-abacus-flashcards/issues/6366f1) [#8b5cf6](https://github.com/antialias/soroban-abacus-flashcards/issues/8b5cf6)
* **card-sorting:** add team scoring UI for collaborative mode ([ed6f177](ed6f177914)), closes [#a78](https://github.com/antialias/soroban-abacus-flashcards/issues/a78) [#8b5cf6](https://github.com/antialias/soroban-abacus-flashcards/issues/8b5cf6)
* **card-sorting:** add updateCardPositions action to Provider ([f6ed4a2](f6ed4a27a2))
* **card-sorting:** auto-arrange prefix/suffix cards in corners ([4ba7f24](4ba7f24717))
* **card-sorting:** fade correctly positioned cards to 50% opacity ([7028cfc](7028cfc511))
* **card-sorting:** gentler spring animation for locked cards ([47189cb](47189cb6e7))
* **card-sorting:** implement continuous bezier curve paths ([2d93024](2d9302410f))
* **card-sorting:** improve card distribution for natural scattered look ([0b0503f](0b0503f035))
* **card-sorting:** make player emoji fill entire card background ([2e7a02c](2e7a02c9e4))
* **card-sorting:** optimize results screen for mobile ([d188789](d188789069))
* **card-sorting:** redesign setup screen with modern UI ([73cf967](73cf967492))
* **card-sorting:** scale correctly positioned cards to 50% ([222dc55](222dc555fa))
* **card-sorting:** shrink/fade cards in correct suffix as well ([8f6feec](8f6feec4f2))
* **card-sorting:** smooth spring transition from game table to results grid ([c5f39d5](c5f39d51eb))
* **card-sorting:** wrap prefix/suffix cards to multiple rows ([e3184dd](e3184dd0d4))
* complete 3D enhancement integration for all three proposals ([5ac55cc](5ac55cc149))
* **create-room:** replace hardcoded game grid with dynamic Radix Select dropdown ([83d0ba2](83d0ba26f5))
* dynamic day-of-month favicon using subprocess pattern ([4d0795a](4d0795a9df))
* dynamically crop favicon to active beads for maximum size ([5670322](567032296a))
* enable 3D enhancement on hero/open MyAbacus modes ([37e330f](37e330f26e))
* **games:** add autoplay and improve carousel layout ([9f51edf](9f51edfaa9))
* **games:** add horizontal scroll support to carousels ([a224abb](a224abb6f6))
* **games:** add rotating games hero carousel ([24231e6](24231e6b2e))
* **i18n:** add dynamic locale switching without page reload ([fe9bfea](fe9bfeabf9))
* **i18n:** add global language selector to navigation ([0506360](0506360117))
* **i18n:** add homepage translations for all supported languages ([8c9d35a](8c9d35a3b4))
* **i18n:** add Old High German (goh) language support ([b334a15](b334a15255))
* **i18n:** complete Old High German translations for all locales ([0b06a1c](0b06a1ce00))
* **i18n:** internationalize games page and tutorial content ([4253964](4253964af1))
* **i18n:** internationalize homepage with English translations ([40cff14](40cff143c7))
* **i18n:** migrate from react-i18next to next-intl ([9016b76](9016b76024))
* **i18n:** update games page hero section copy ([6333c60](6333c60352))
* install embla-carousel-autoplay for games carousel ([946e5d1](946e5d1910))
* install embla-carousel-react for player profile carousel ([642ae95](642ae95738))
* internationalize guide page with 6 languages ([e9c320b](e9c320bb10))
* internationalize tutorial player ([26d41cf](26d41cfd05))
* optimize card sorting for mobile displays ([b443ee9](b443ee9cdc))
* Redesign Rithmomachia setup page with dramatic medieval theme ([6ae4d13](6ae4d13dc7))
* **rithmomachia:** add 80% opacity to guide modal when not hovered ([4a78485](4a78485d2e))
* **rithmomachia:** add CaptureContext for capture dialog state management ([d7eb957](d7eb957a8d))
* **rithmomachia:** add ghost panel preview for guide docking ([c0d6526](c0d6526d30))
* **rithmomachia:** add guide docking with resizable panels ([f457f1a](f457f1a1c2))
* **rithmomachia:** add helper piece selection for mathematical captures ([cae3359](cae3359587))
* **rithmomachia:** add helpful error messages for failed captures ([b172440](b172440a41))
* **rithmomachia:** add initial board visual to guide Overview section ([d42bcff](d42bcff0d9))
* **rithmomachia:** Add interactive playing guide modal ([3121d82](3121d8240a))
* **rithmomachia:** add number bond visualization and helper placeholders ([82d8913](82d89131f0))
* **rithmomachia:** add ratio capture example to guide ([9150b0c](9150b0c678))
* **rithmomachia:** add standalone guide page route ([3fcc79f](3fcc79fe9e))
* **rithmomachia:** add useBoardLayout hook for centralized layout calculations ([27f1c98](27f1c989d5))
* **rithmomachia:** add usePieceSelection hook for selection state management ([275f401](275f401e3c))
* **rithmomachia:** add visual board examples to Capture section ([74bc3c0](74bc3c0dcf))
* **rithmomachia:** add visual board examples to Harmony section ([1d5f01c](1d5f01c966))
* **rithmomachia:** add visual winning example to Victory section ([b7fac78](b7fac78829))
* **rithmomachia:** auto-size tab labels with react-textfit ([9fd5406](9fd54067ce))
* **rithmomachia:** cycle through valid helpers with dynamic number tooltips ([4829e41](4829e41ea1))
* **rithmomachia:** enhance capture relation UI with smooth animations ([0a30801](0a308016e9))
* **rithmomachia:** enhance Harmony section with comprehensive content ([f555856](f5558563ea))
* **rithmomachia:** enhance Pieces section with visual examples and pyramid details ([55aff82](55aff829f4))
* **rithmomachia:** enhance Pyramid section with comprehensive details ([9fde1ef](9fde1ef9e7))
* **rithmomachia:** guide defaults to docked right on open ([11f674d](11f674d542))
* **rithmomachia:** improve guide pieces section layout ([a270bfc](a270bfc0cc))
* **rithmomachia:** improve guide UX and add persistence ([b314740](b314740697))
* **rithmomachia:** improve roster status notice UX ([e27df45](e27df45256))
* **rithmomachia:** integrate roster warning into game nav ([8a11594](8a11594203))
* **rithmomachia:** make guide modal ultra-responsive down to 150px width ([0474197](04741971b2))
* **rithmomachia:** recreate original guide modal header layout ([2489695](24896957d0))
* **rithmomachia:** show capture error on hover instead of click ([339b678](339b6780f6))
* **rithmomachia:** show pyramid face numbers on hover instead of selection ([b0c4523](b0c4523c0b))
* **rithmomachia:** show pyramid face numbers when selected ([5c186f3](5c186f3947))
* **rithmomachia:** show pyramid face numbers when selected with subtle animation ([5c2ddbe](5c2ddbef05))
* **rithmomachia:** show real preview layout when dragging guide to dock ([17d2460](17d2460a87))
* **rithmomachia:** simplify guide language for clarity ([85cb630](85cb630add))
* **rithmomachia:** skip helper selection UI and auto-select first valid helper ([be2a00e](be2a00e8b3))
* **rithmomachia:** Update harmony system to classical three-piece proportions ([08c9762](08c97620f5))
* **rithmomachia:** Update to traditional board setup with 25 pieces per side ([0769eaa](0769eaaa1d))
* **rithmomachia:** use actual piece SVGs in number bond with 2.5s rotation animation ([976a7de](976a7de949))
* **room-share:** add QR code button for easy mobile joining ([349290a](349290ac6a))
* show rithmomachia turn in nav ([7c89bfe](7c89bfef9c))
* switch to royal color theme with transparent background ([944ad65](944ad6574e)), closes [#fbbf24](https://github.com/antialias/soroban-abacus-flashcards/issues/fbbf24) [#f59e0](https://github.com/antialias/soroban-abacus-flashcards/issues/f59e0) [#a855f7](https://github.com/antialias/soroban-abacus-flashcards/issues/a855f7) [#7e22](https://github.com/antialias/soroban-abacus-flashcards/issues/7e22)
* **web:** add test page for AbacusStatic RSC compatibility ([903dea2](903dea2584))
* **web:** add test page for AbacusStatic Server Component ([3588d5a](3588d5acde))
* **web:** improve calendar abacus preview styling ([8439727](8439727b15))
* **web:** optimize monthly calendar for single-page layout ([b277a89](b277a89415))
* **web:** redesign monthly calendar as single composite SVG ([8ce8038](8ce8038bae))

### Bug Fixes

* **abacus-react:** correct column highlighting offset in AbacusStatic ([0641eb7](0641eb719e))
* add xmlns to AbacusStatic for Typst SVG parsing ([98cd019](98cd019d4a))
* adjust hero abacus position to avoid covering subtitle ([f03d341](f03d341314))
* **arcade:** add automatic retry for version conflict rejections ([fbcde25](fbcde2505f))
* **arcade:** allow deactivating players from users who left the room ([7c1c2d7](7c1c2d7beb))
* **arcade:** implement optimistic locking in session manager ([71fd66d](71fd66d96a))
* board rotation now properly fills height in portrait mode ([b5a96ea](b5a96eaeb1))
* **card-sorting:** add border radius to outer card container ([a922eba](a922eba73c))
* **card-sorting:** add debug logging for spring animations ([d42947e](d42947eb8d))
* **card-sorting:** add missing gameMode support after hard reset ([a832325](a832325deb))
* **card-sorting:** add missing useMemo import ([949d76d](949d76d844))
* **card-sorting:** add overflow hidden to clip rounded corners ([84c66fe](84c66feec6))
* **card-sorting:** adjust connecting paths for scaled cards ([829c741](829c741e55))
* **card-sorting:** adjust game board for spectator panels ([fc5cf12](fc5cf1216f))
* **card-sorting:** adjust viewport dimensions for spectator panels ([4dce16c](4dce16cca4))
* **card-sorting:** animate cards from game board to results grid ([17d45fe](17d45fe88c))
* **card-sorting:** correct suffix card detection in auto-arrange ([d02ab59](d02ab5922c))
* **card-sorting:** enable card scaling for spectators ([6b095c3](6b095c3383))
* **card-sorting:** enable New Game button during active gameplay ([f3f6eca](f3f6eca1db))
* **card-sorting:** end drag immediately when card becomes locked ([ae45298](ae45298ec4))
* **card-sorting:** filter local player from emoji overlays on dragged cards ([dc2d94a](dc2d94aaa5))
* **card-sorting:** fix results panel layout to not cover cards ([4b4fbfe](4b4fbfef32))
* **card-sorting:** hide activity notifications in spectator mode ([5cca279](5cca279687))
* **card-sorting:** keep arrow sequence numbers upright ([79c9469](79c94699fa))
* **card-sorting:** lock correctly positioned prefix/suffix cards ([170abed](170abed231))
* **card-sorting:** lock spring positions after initial animation completes ([275cc62](275cc62a52))
* **card-sorting:** New Game now restarts with same settings instantly ([f3687ed](f3687ed236))
* **card-sorting:** only shrink/fade cards in correct prefix ([51368c6](51368c6ec5))
* **card-sorting:** preserve card positions on pause/resume ([0d8af09](0d8af09517))
* **card-sorting:** preserve rotation when starting drag ([3364144](3364144fb6))
* **card-sorting:** prevent duplicate START_GAME moves on Play Again ([a0b14f8](a0b14f87e9))
* **card-sorting:** prevent ghost movements with proper optimistic updates ([bd014be](bd014bec4f))
* **card-sorting:** prevent infinite loop when all cards are correct ([34785f4](34785f466f))
* **card-sorting:** prevent infinite loop with tolerance-based position comparison ([627b873](627b873382))
* **card-sorting:** prevent position jump when clicking rotated cards ([564a00f](564a00f82b))
* **card-sorting:** prevent replaying own movements from server ([308168a](308168a7fb))
* **card-sorting:** prevent springs from reinitializing on window resize ([30953b8](30953b8c4a))
* **card-sorting:** prevent springs from resetting after animation ([8aff60c](8aff60ce3f))
* **card-sorting:** remove hasAnimatedRef logic causing backwards animation ([a44aa5a](a44aa5a4c2))
* **card-sorting:** remove remaining reveal numbers references ([15c53ea](15c53ea4eb))
* **card-sorting:** restore prefix/suffix card shrinking visual feedback ([f5fb4d7](f5fb4d7b76))
* **card-sorting:** show only active players in team members section ([fa9f1a5](fa9f1a568f))
* **card-sorting:** smooth scale animation while dragging cards ([0eefc33](0eefc332ac))
* **card-sorting:** stabilize inferred sequence for locked cards during drag ([b0cd194](b0cd194838))
* **card-sorting:** use empty deps array for useSprings to prevent recreation ([cee399e](cee399ed15))
* **card-sorting:** use ref to track initialized state and prevent re-animation ([f389afa](f389afa831))
* **card-sorting:** use same coordinate system for game board and results ([6972fdf](6972fdf110))
* **complement-race:** prevent delivery move thrashing in steam sprint mode ([e1258ee](e1258ee041))
* configure favicon metadata and improve bead visibility ([e1369fa](e1369fa275))
* copy entire packages/core and packages/templates ([0ccada0](0ccada0ca7))
* correct hero abacus scroll direction to flow with page content ([4232746](423274657c))
* correct Typst template path in Dockerfile ([4c518de](4c518decb7))
* delete existing user sessions before creating new ones ([0cced47](0cced47a0f))
* **docker:** add scripts, abacus-react, and tsx for production calendar generation ([33eb90e](33eb90e316))
* extract pure SVG content from AbacusReact renders ([b07f1c4](b07f1c4216))
* **games:** prevent horizontal page scroll from carousel overflow ([5a8c98f](5a8c98fc10))
* **games:** smooth scroll feel for carousel wheel navigation ([f80a73b](f80a73b35c))
* **games:** use specific transition properties for smooth carousel loop ([187271e](187271e515))
* **i18n:** eliminate FOUC by loading messages server-side ([4d4d930](4d4d930bd3))
* **i18n:** use useMessages() for tutorial translations ([95b0105](95b0105ca3))
* include column posts in favicon bounding box ([0b2f481](0b2f48106a))
* increase server update debounce to 2000ms for low bandwidth ([633ff12](633ff12750))
* Integrate threshold input into Point Victory card ([b29bbee](b29bbeefca))
* mark dynamic routes as force-dynamic to prevent static generation errors ([d7b35d9](d7b35d9544))
* **nav:** show full navigation on /games page ([d3fe6ac](d3fe6acbb0))
* **qr-button:** improve layout and z-index ([646a422](646a4228d0))
* **qr-button:** increase mini QR code size to 80px ([61ac737](61ac7378bd))
* **qr-button:** increase mini QR code to 84px ([3fae5ea](3fae5ea6fa))
* **qr-button:** make button square and increase QR size ([dc2d466](dc2d46663b))
* **qr-button:** match height of stacked buttons ([81f202d](81f202d215))
* reduce padding to minimize gap below last bead ([0e529be](0e529be789))
* remove distracting parallax and wobble 3D effects ([28a2d40](28a2d40996))
* remove wobble physics and enhance wood grain visibility ([5d97673](5d97673406))
* resolve z-index layering and hero abacus visibility issues ([ed9a050](ed9a050d64))
* rewrite 3D stories to use props instead of CSS wrappers ([26bdb11](26bdb11237))
* **rithmomachia:** add missing i18next dependencies ([91154d9](91154d9364))
* **rithmomachia:** add missing pyramid section keys to Japanese (ja.json) ([dae615e](dae615ee72))
* **rithmomachia:** adjust error dialog sizing to prevent text clipping ([cda1126](cda1126cb0))
* **rithmomachia:** adjust roster notice position to not overlap nav ([7093223](709322373a))
* **rithmomachia:** change undock icon to pop-out arrow ([2a91748](2a91748493))
* **rithmomachia:** correct board dimensions to 16x8 and restore original layout values ([cfac277](cfac277505))
* **rithmomachia:** Correct board setup to match reference image exactly ([618e563](618e56358d))
* **rithmomachia:** correct makeMove parameter types for capture handling ([aafb64f](aafb64f3e3))
* **rithmomachia:** fix guide modal resize drift by calculating from initial state ([1bcd99c](1bcd99c949))
* **rithmomachia:** fix harmony section translation structure for hi/ja/es ([14259a1](14259a19a9))
* **rithmomachia:** fix modal resizing zoom issue ([4fa20f4](4fa20f44cb))
* **rithmomachia:** Fix TypeScript errors in playing guide modal ([4834ece](4834ece98e))
* **rithmomachia:** handle pyramid pieces in hover error tooltip ([56f3164](56f3164155))
* **rithmomachia:** implement proper board cropping and highlighting in guide ([d0a8fcd](d0a8fcdea6))
* **rithmomachia:** improve guide modal tab navigation at narrow widths ([a673177](a673177bec))
* **rithmomachia:** reconnect player assignment UI and fix setup layout ([a1a0374](a1a0374fac))
* **rithmomachia:** render guide as docked in preview panel ([190f8cf](190f8cf302))
* **rithmomachia:** show actual values in tooltips for non-helper relations ([774c6b0](774c6b0ce7))
* **rithmomachia:** show guest-friendly message when they can't fix too many players ([54bfd2f](54bfd2fac8))
* **rithmomachia:** smooth guide dragging from docked state without jump ([8f4a79c](8f4a79c9b0))
* **rithmomachia:** validate move path before showing capture error on hover ([bd49964](bd49964186))
* **room-info:** hide Leave Room button when user is alone ([5927f61](5927f61c3c))
* separate horizontal and vertical bounding box logic ([83090df](83090df4df))
* tolerate OpenSCAD CGAL warnings if output file is created ([88993f3](88993f3662))
* use absolute positioning for hero abacus to eliminate scroll lag ([096104b](096104b094))
* use Debian base for deps stage to match runner for binary compatibility ([f8fe6e4](f8fe6e4a41))
* use default BOSL2 branch instead of non-existent v2.0.0 tag ([f4ffc5b](f4ffc5b027))
* use nested SVG viewBox for actual cropping, not just scaling ([440b492](440b492e85))
* various game improvements and UI enhancements ([b67cf61](b67cf610c5))
* **web:** add dynamic export to rithmomachia page ([329e623](329e623212))
* **web:** fix Typst PDF generation path resolution ([7ce1287](7ce1287525))
* **web:** move react-dom/server import to API route to satisfy Next.js ([00a8bc3](00a8bc3e5e))
* **web:** prevent abacus overlap in composite calendar ([448f93c](448f93c1e2)), closes [#f0f0f0](https://github.com/antialias/soroban-abacus-flashcards/issues/f0f0f0)
* **web:** use AbacusStatic for calendar SVG generation ([08c6a41](08c6a419e2))
* **web:** use dynamic import for react-dom/server in API route ([4f93c7d](4f93c7d996))
* **web:** use nested SVG elements to prevent coordinate space conflicts ([f9cbee8](f9cbee8fcd))

### Performance Improvements

* optimize Docker image size to reduce build failures ([9ca3106](9ca3106361))

### Code Refactoring

* **card-sorting:** remove reveal numbers feature ([ea5e3e8](ea5e3e838b))
* **card-sorting:** send complete card sequence instead of individual moves ([e4df843](e4df8432b9))
* **games:** implement carousel, fix victories bug, add conditional stats ([82c133f](82c133f742))
* **games:** move page title to nav bar ([712ee58](712ee58e59))
* **games:** remove redundant subtitle below nav ([ad5bb87](ad5bb87325))
* **games:** remove wheel scrolling, enable overflow visible carousel ([876513c](876513c9cc))
* reorganize Harmony and Victory guide sections ([fb629c4](fb629c44ea))
* restructure /create page into hub with sub-pages ([b91b23d](b91b23d95f))
* **rithmomachia:** extract board and capture components (phase 2+3) ([a0a867b](a0a867b271))
* **rithmomachia:** extract CaptureErrorDialog component (Phase 2 partial) ([f0a066d](f0a066d8f0))
* **rithmomachia:** extract constants and coordinate utilities (Phase 1) ([eace0ed](eace0ed529))
* **rithmomachia:** extract guide sections into separate files ([765525d](765525dc45))
* **rithmomachia:** extract hooks (phase 5) ([324a659](324a65992f))
* **rithmomachia:** extract phase components (phase 4) ([11364f6](11364f6394))
* **rithmomachia:** extract reusable components from SetupPhase ([3abc325](3abc325ea2))
* **rithmomachia:** make setup phase UI more compact ([e55f848](e55f848a26))
* **rithmomachia:** redesign error notification with modern UI ([dfeeb0e](dfeeb0e0db)), closes [#1e293](https://github.com/antialias/soroban-abacus-flashcards/issues/1e293) [#0f172](https://github.com/antialias/soroban-abacus-flashcards/issues/0f172) [#f1f5f9](https://github.com/antialias/soroban-abacus-flashcards/issues/f1f5f9)
* **rithmomachia:** simplify capture error dialog to one-liner ([82a5eb2](82a5eb2e4b))
* **rithmomachia:** Update board setup to authoritative CSV layout ([0471da5](0471da598d))
* **rithmomachia:** update capture components to use CaptureContext ([2ab6ab5](2ab6ab5799))
* **rithmomachia:** use useBoardLayout and usePieceSelection in BoardDisplay ([0ab7a1d](0ab7a1df32))
* use AbacusReact for dynamic Open Graph image ([9c20f12](9c20f12bac))
* **web:** import utility functions from abacus-react ([7228bbc](7228bbc2eb))
* **web:** use ABACUS_THEMES instead of manual style definitions ([9f7f001](9f7f001d74))
* **web:** use compact prop for inline mini-abacus ([ff1d60a](ff1d60a233))
* **web:** use direct function imports instead of execSync for calendar generation ([9f1715f](9f1715f085))

### Documentation

* **abacus-react:** add Storybook stories for AbacusStatic ([4f9dc46](4f9dc4666d))
* **abacus-react:** add Storybook stories for new features ([6a1cec0](6a1cec06a7))
* **abacus-react:** export AbacusStatic and update README ([74f2d97](74f2d97434))
* **abacus-react:** update documentation for new features ([35d8734](35d8734a3a))
* **abacus-react:** update README with /static import path for RSC ([72a4c2b](72a4c2b80c))
* add 3D enhancement documentation to README ([cc96802](cc96802df8))
* add database migration guide and playing guide modal spec ([5a29af7](5a29af78e2))
* add deployment verification guidelines to prevent false positives ([3d8da23](3d8da2348b))
* **card-sorting:** add comprehensive multiplayer plan ([008ccea](008ccead0f))
* **rithmomachia:** Add concise one-page playing guide ([e3c1f10](e3c1f10233))
* update workflow to require manual testing before commits ([0991796](0991796f1e))

### Styles

* **rithmomachia:** improve divider styling and make tabs responsive ([88ca35e](88ca35e044)), closes [#e5e7](https://github.com/antialias/soroban-abacus-flashcards/issues/e5e7) [#9ca3](https://github.com/antialias/soroban-abacus-flashcards/issues/9ca3)
* **rithmomachia:** improve pyramid face numbers visibility and contrast ([94e5e6a](94e5e6a268)), closes [#fbbf24](https://github.com/antialias/soroban-abacus-flashcards/issues/fbbf24) [#b45309](https://github.com/antialias/soroban-abacus-flashcards/issues/b45309)
* **rithmomachia:** increase pyramid face numbers size and boldness ([7bf2d73](7bf2d730d3))

### Tests

* trigger compose-updater deployment test ([2b06aae](2b06aae394))
* verify compose-updater automatic deployment cycle ([af0552c](af0552ccd9))
2025-11-04 14:58:17 +00:00
Thomas Hallock
4f93c7d996 fix(web): use dynamic import for react-dom/server in API route
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>
2025-11-04 08:55:05 -06:00
semantic-release-bot
5956217979 chore(release): 4.68.0 [skip ci]
## [4.68.0](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.67.1...v4.68.0) (2025-11-04)

### Features

* **abacus-react:** add AbacusStatic for React Server Components ([3b8e864](3b8e864cfa))
* **abacus-react:** add core utility functions for state management ([e65541c](e65541c100))
* **abacus-react:** add layout and educational props ([35bbcec](35bbcecb9e))
* **abacus-react:** add pre-defined theme presets ([cf1f950](cf1f950c7c))
* **abacus-react:** add React hooks for abacus calculations ([de038d2](de038d2afc))
* **abacus-react:** add separate /static export path for React Server Components ([ed69f6b](ed69f6b917))
* **abacus-react:** export new utilities, hooks, and themes ([ce4e44d](ce4e44d630))
* **abacus:** add nativeAbacusNumbers setting to schema and UI ([79f7347](79f7347d48))
* add 3D printing support for abacus models ([dafdfdd](dafdfdd233))
* add comprehensive metadata, SEO, and make AbacusReact SSR-compatible ([0922ea1](0922ea10b7))
* add comprehensive Storybook coverage and migration guide ([7a4a37e](7a4a37ec6d))
* add game preview system with mock arcade environment ([25880cc](25880cc7e4))
* add per-player stats tracking system ([613301c](613301cd13))
* add Strategy & Tactics section to Rithmomachia guide ([81ead65](81ead65680))
* add unified trophy abacus with hero mode integration ([6620418](6620418a70))
* **arcade:** add ability to deactivate remote players without kicking user ([3628426](3628426a56))
* **arcade:** add native abacus numbers support to pressure gauge ([1d525c7](1d525c7b53))
* **arcade:** add Rithmomachia (Battle of Numbers) game ([2fc0a05](2fc0a05f7f))
* **arcade:** add yjs-demo collaborative game and Yjs persistence layer ([d568955](d568955d6a))
* **arcade:** auto-create room when user has none ([ff88c3a](ff88c3a1b8))
* **card-sorting:** add activity feed notifications for collaborative mode ([1461414](1461414ef4))
* **card-sorting:** add auto-submit countdown for perfect sequences ([780a716](780a7161bc))
* **card-sorting:** add bezier curves to connecting arrows ([4d8e873](4d8e873358))
* **card-sorting:** add CardPosition type and position syncing ([656f5a7](656f5a7838))
* **card-sorting:** add collapsible stats sidebar for spectators ([6527c26](6527c26a81))
* **card-sorting:** add game mode selector UI to setup phase ([d25b888](d25b888ffb))
* **card-sorting:** add GameMode type system for multiplayer support ([fd76533](fd765335ef))
* **card-sorting:** add green border to correctly positioned cards ([16fca86](16fca86b76)), closes [#22c55](https://github.com/antialias/soroban-abacus-flashcards/issues/22c55)
* **card-sorting:** add player emoji indicators on moving cards ([3a82099](3a82099757))
* **card-sorting:** add react-spring animations for real-time sync ([c367e0c](c367e0ceec))
* **card-sorting:** add smooth transition to drop shadow ([b0b93d0](b0b93d0175))
* **card-sorting:** add spectator mode UI enhancements ([ee7345d](ee7345d641)), closes [#6366f1](https://github.com/antialias/soroban-abacus-flashcards/issues/6366f1) [#8b5cf6](https://github.com/antialias/soroban-abacus-flashcards/issues/8b5cf6)
* **card-sorting:** add team scoring UI for collaborative mode ([ed6f177](ed6f177914)), closes [#a78](https://github.com/antialias/soroban-abacus-flashcards/issues/a78) [#8b5cf6](https://github.com/antialias/soroban-abacus-flashcards/issues/8b5cf6)
* **card-sorting:** add updateCardPositions action to Provider ([f6ed4a2](f6ed4a27a2))
* **card-sorting:** auto-arrange prefix/suffix cards in corners ([4ba7f24](4ba7f24717))
* **card-sorting:** fade correctly positioned cards to 50% opacity ([7028cfc](7028cfc511))
* **card-sorting:** gentler spring animation for locked cards ([47189cb](47189cb6e7))
* **card-sorting:** implement continuous bezier curve paths ([2d93024](2d9302410f))
* **card-sorting:** improve card distribution for natural scattered look ([0b0503f](0b0503f035))
* **card-sorting:** make player emoji fill entire card background ([2e7a02c](2e7a02c9e4))
* **card-sorting:** optimize results screen for mobile ([d188789](d188789069))
* **card-sorting:** redesign setup screen with modern UI ([73cf967](73cf967492))
* **card-sorting:** scale correctly positioned cards to 50% ([222dc55](222dc555fa))
* **card-sorting:** shrink/fade cards in correct suffix as well ([8f6feec](8f6feec4f2))
* **card-sorting:** smooth spring transition from game table to results grid ([c5f39d5](c5f39d51eb))
* **card-sorting:** wrap prefix/suffix cards to multiple rows ([e3184dd](e3184dd0d4))
* complete 3D enhancement integration for all three proposals ([5ac55cc](5ac55cc149))
* **create-room:** replace hardcoded game grid with dynamic Radix Select dropdown ([83d0ba2](83d0ba26f5))
* dynamic day-of-month favicon using subprocess pattern ([4d0795a](4d0795a9df))
* dynamically crop favicon to active beads for maximum size ([5670322](567032296a))
* enable 3D enhancement on hero/open MyAbacus modes ([37e330f](37e330f26e))
* **games:** add autoplay and improve carousel layout ([9f51edf](9f51edfaa9))
* **games:** add horizontal scroll support to carousels ([a224abb](a224abb6f6))
* **games:** add rotating games hero carousel ([24231e6](24231e6b2e))
* **i18n:** add dynamic locale switching without page reload ([fe9bfea](fe9bfeabf9))
* **i18n:** add global language selector to navigation ([0506360](0506360117))
* **i18n:** add homepage translations for all supported languages ([8c9d35a](8c9d35a3b4))
* **i18n:** add Old High German (goh) language support ([b334a15](b334a15255))
* **i18n:** complete Old High German translations for all locales ([0b06a1c](0b06a1ce00))
* **i18n:** internationalize games page and tutorial content ([4253964](4253964af1))
* **i18n:** internationalize homepage with English translations ([40cff14](40cff143c7))
* **i18n:** migrate from react-i18next to next-intl ([9016b76](9016b76024))
* **i18n:** update games page hero section copy ([6333c60](6333c60352))
* install embla-carousel-autoplay for games carousel ([946e5d1](946e5d1910))
* install embla-carousel-react for player profile carousel ([642ae95](642ae95738))
* internationalize guide page with 6 languages ([e9c320b](e9c320bb10))
* internationalize tutorial player ([26d41cf](26d41cfd05))
* optimize card sorting for mobile displays ([b443ee9](b443ee9cdc))
* Redesign Rithmomachia setup page with dramatic medieval theme ([6ae4d13](6ae4d13dc7))
* **rithmomachia:** add 80% opacity to guide modal when not hovered ([4a78485](4a78485d2e))
* **rithmomachia:** add CaptureContext for capture dialog state management ([d7eb957](d7eb957a8d))
* **rithmomachia:** add ghost panel preview for guide docking ([c0d6526](c0d6526d30))
* **rithmomachia:** add guide docking with resizable panels ([f457f1a](f457f1a1c2))
* **rithmomachia:** add helper piece selection for mathematical captures ([cae3359](cae3359587))
* **rithmomachia:** add helpful error messages for failed captures ([b172440](b172440a41))
* **rithmomachia:** add initial board visual to guide Overview section ([d42bcff](d42bcff0d9))
* **rithmomachia:** Add interactive playing guide modal ([3121d82](3121d8240a))
* **rithmomachia:** add number bond visualization and helper placeholders ([82d8913](82d89131f0))
* **rithmomachia:** add ratio capture example to guide ([9150b0c](9150b0c678))
* **rithmomachia:** add standalone guide page route ([3fcc79f](3fcc79fe9e))
* **rithmomachia:** add useBoardLayout hook for centralized layout calculations ([27f1c98](27f1c989d5))
* **rithmomachia:** add usePieceSelection hook for selection state management ([275f401](275f401e3c))
* **rithmomachia:** add visual board examples to Capture section ([74bc3c0](74bc3c0dcf))
* **rithmomachia:** add visual board examples to Harmony section ([1d5f01c](1d5f01c966))
* **rithmomachia:** add visual winning example to Victory section ([b7fac78](b7fac78829))
* **rithmomachia:** auto-size tab labels with react-textfit ([9fd5406](9fd54067ce))
* **rithmomachia:** cycle through valid helpers with dynamic number tooltips ([4829e41](4829e41ea1))
* **rithmomachia:** enhance capture relation UI with smooth animations ([0a30801](0a308016e9))
* **rithmomachia:** enhance Harmony section with comprehensive content ([f555856](f5558563ea))
* **rithmomachia:** enhance Pieces section with visual examples and pyramid details ([55aff82](55aff829f4))
* **rithmomachia:** enhance Pyramid section with comprehensive details ([9fde1ef](9fde1ef9e7))
* **rithmomachia:** guide defaults to docked right on open ([11f674d](11f674d542))
* **rithmomachia:** improve guide pieces section layout ([a270bfc](a270bfc0cc))
* **rithmomachia:** improve guide UX and add persistence ([b314740](b314740697))
* **rithmomachia:** improve roster status notice UX ([e27df45](e27df45256))
* **rithmomachia:** integrate roster warning into game nav ([8a11594](8a11594203))
* **rithmomachia:** make guide modal ultra-responsive down to 150px width ([0474197](04741971b2))
* **rithmomachia:** recreate original guide modal header layout ([2489695](24896957d0))
* **rithmomachia:** show capture error on hover instead of click ([339b678](339b6780f6))
* **rithmomachia:** show pyramid face numbers on hover instead of selection ([b0c4523](b0c4523c0b))
* **rithmomachia:** show pyramid face numbers when selected ([5c186f3](5c186f3947))
* **rithmomachia:** show pyramid face numbers when selected with subtle animation ([5c2ddbe](5c2ddbef05))
* **rithmomachia:** show real preview layout when dragging guide to dock ([17d2460](17d2460a87))
* **rithmomachia:** simplify guide language for clarity ([85cb630](85cb630add))
* **rithmomachia:** skip helper selection UI and auto-select first valid helper ([be2a00e](be2a00e8b3))
* **rithmomachia:** Update harmony system to classical three-piece proportions ([08c9762](08c97620f5))
* **rithmomachia:** Update to traditional board setup with 25 pieces per side ([0769eaa](0769eaaa1d))
* **rithmomachia:** use actual piece SVGs in number bond with 2.5s rotation animation ([976a7de](976a7de949))
* **room-share:** add QR code button for easy mobile joining ([349290a](349290ac6a))
* show rithmomachia turn in nav ([7c89bfe](7c89bfef9c))
* switch to royal color theme with transparent background ([944ad65](944ad6574e)), closes [#fbbf24](https://github.com/antialias/soroban-abacus-flashcards/issues/fbbf24) [#f59e0](https://github.com/antialias/soroban-abacus-flashcards/issues/f59e0) [#a855f7](https://github.com/antialias/soroban-abacus-flashcards/issues/a855f7) [#7e22](https://github.com/antialias/soroban-abacus-flashcards/issues/7e22)
* **web:** add test page for AbacusStatic RSC compatibility ([903dea2](903dea2584))
* **web:** add test page for AbacusStatic Server Component ([3588d5a](3588d5acde))
* **web:** improve calendar abacus preview styling ([8439727](8439727b15))
* **web:** optimize monthly calendar for single-page layout ([b277a89](b277a89415))
* **web:** redesign monthly calendar as single composite SVG ([8ce8038](8ce8038bae))

### Bug Fixes

* **abacus-react:** correct column highlighting offset in AbacusStatic ([0641eb7](0641eb719e))
* add xmlns to AbacusStatic for Typst SVG parsing ([98cd019](98cd019d4a))
* adjust hero abacus position to avoid covering subtitle ([f03d341](f03d341314))
* **arcade:** add automatic retry for version conflict rejections ([fbcde25](fbcde2505f))
* **arcade:** allow deactivating players from users who left the room ([7c1c2d7](7c1c2d7beb))
* **arcade:** implement optimistic locking in session manager ([71fd66d](71fd66d96a))
* board rotation now properly fills height in portrait mode ([b5a96ea](b5a96eaeb1))
* **card-sorting:** add border radius to outer card container ([a922eba](a922eba73c))
* **card-sorting:** add debug logging for spring animations ([d42947e](d42947eb8d))
* **card-sorting:** add missing gameMode support after hard reset ([a832325](a832325deb))
* **card-sorting:** add missing useMemo import ([949d76d](949d76d844))
* **card-sorting:** add overflow hidden to clip rounded corners ([84c66fe](84c66feec6))
* **card-sorting:** adjust connecting paths for scaled cards ([829c741](829c741e55))
* **card-sorting:** adjust game board for spectator panels ([fc5cf12](fc5cf1216f))
* **card-sorting:** adjust viewport dimensions for spectator panels ([4dce16c](4dce16cca4))
* **card-sorting:** animate cards from game board to results grid ([17d45fe](17d45fe88c))
* **card-sorting:** correct suffix card detection in auto-arrange ([d02ab59](d02ab5922c))
* **card-sorting:** enable card scaling for spectators ([6b095c3](6b095c3383))
* **card-sorting:** enable New Game button during active gameplay ([f3f6eca](f3f6eca1db))
* **card-sorting:** end drag immediately when card becomes locked ([ae45298](ae45298ec4))
* **card-sorting:** filter local player from emoji overlays on dragged cards ([dc2d94a](dc2d94aaa5))
* **card-sorting:** fix results panel layout to not cover cards ([4b4fbfe](4b4fbfef32))
* **card-sorting:** hide activity notifications in spectator mode ([5cca279](5cca279687))
* **card-sorting:** keep arrow sequence numbers upright ([79c9469](79c94699fa))
* **card-sorting:** lock correctly positioned prefix/suffix cards ([170abed](170abed231))
* **card-sorting:** lock spring positions after initial animation completes ([275cc62](275cc62a52))
* **card-sorting:** New Game now restarts with same settings instantly ([f3687ed](f3687ed236))
* **card-sorting:** only shrink/fade cards in correct prefix ([51368c6](51368c6ec5))
* **card-sorting:** preserve card positions on pause/resume ([0d8af09](0d8af09517))
* **card-sorting:** preserve rotation when starting drag ([3364144](3364144fb6))
* **card-sorting:** prevent duplicate START_GAME moves on Play Again ([a0b14f8](a0b14f87e9))
* **card-sorting:** prevent ghost movements with proper optimistic updates ([bd014be](bd014bec4f))
* **card-sorting:** prevent infinite loop when all cards are correct ([34785f4](34785f466f))
* **card-sorting:** prevent infinite loop with tolerance-based position comparison ([627b873](627b873382))
* **card-sorting:** prevent position jump when clicking rotated cards ([564a00f](564a00f82b))
* **card-sorting:** prevent replaying own movements from server ([308168a](308168a7fb))
* **card-sorting:** prevent springs from reinitializing on window resize ([30953b8](30953b8c4a))
* **card-sorting:** prevent springs from resetting after animation ([8aff60c](8aff60ce3f))
* **card-sorting:** remove hasAnimatedRef logic causing backwards animation ([a44aa5a](a44aa5a4c2))
* **card-sorting:** remove remaining reveal numbers references ([15c53ea](15c53ea4eb))
* **card-sorting:** restore prefix/suffix card shrinking visual feedback ([f5fb4d7](f5fb4d7b76))
* **card-sorting:** show only active players in team members section ([fa9f1a5](fa9f1a568f))
* **card-sorting:** smooth scale animation while dragging cards ([0eefc33](0eefc332ac))
* **card-sorting:** stabilize inferred sequence for locked cards during drag ([b0cd194](b0cd194838))
* **card-sorting:** use empty deps array for useSprings to prevent recreation ([cee399e](cee399ed15))
* **card-sorting:** use ref to track initialized state and prevent re-animation ([f389afa](f389afa831))
* **card-sorting:** use same coordinate system for game board and results ([6972fdf](6972fdf110))
* **complement-race:** prevent delivery move thrashing in steam sprint mode ([e1258ee](e1258ee041))
* configure favicon metadata and improve bead visibility ([e1369fa](e1369fa275))
* copy entire packages/core and packages/templates ([0ccada0](0ccada0ca7))
* correct hero abacus scroll direction to flow with page content ([4232746](423274657c))
* correct Typst template path in Dockerfile ([4c518de](4c518decb7))
* delete existing user sessions before creating new ones ([0cced47](0cced47a0f))
* **docker:** add scripts, abacus-react, and tsx for production calendar generation ([33eb90e](33eb90e316))
* extract pure SVG content from AbacusReact renders ([b07f1c4](b07f1c4216))
* **games:** prevent horizontal page scroll from carousel overflow ([5a8c98f](5a8c98fc10))
* **games:** smooth scroll feel for carousel wheel navigation ([f80a73b](f80a73b35c))
* **games:** use specific transition properties for smooth carousel loop ([187271e](187271e515))
* **i18n:** eliminate FOUC by loading messages server-side ([4d4d930](4d4d930bd3))
* **i18n:** use useMessages() for tutorial translations ([95b0105](95b0105ca3))
* include column posts in favicon bounding box ([0b2f481](0b2f48106a))
* increase server update debounce to 2000ms for low bandwidth ([633ff12](633ff12750))
* Integrate threshold input into Point Victory card ([b29bbee](b29bbeefca))
* mark dynamic routes as force-dynamic to prevent static generation errors ([d7b35d9](d7b35d9544))
* **nav:** show full navigation on /games page ([d3fe6ac](d3fe6acbb0))
* **qr-button:** improve layout and z-index ([646a422](646a4228d0))
* **qr-button:** increase mini QR code size to 80px ([61ac737](61ac7378bd))
* **qr-button:** increase mini QR code to 84px ([3fae5ea](3fae5ea6fa))
* **qr-button:** make button square and increase QR size ([dc2d466](dc2d46663b))
* **qr-button:** match height of stacked buttons ([81f202d](81f202d215))
* reduce padding to minimize gap below last bead ([0e529be](0e529be789))
* remove distracting parallax and wobble 3D effects ([28a2d40](28a2d40996))
* remove wobble physics and enhance wood grain visibility ([5d97673](5d97673406))
* resolve z-index layering and hero abacus visibility issues ([ed9a050](ed9a050d64))
* rewrite 3D stories to use props instead of CSS wrappers ([26bdb11](26bdb11237))
* **rithmomachia:** add missing i18next dependencies ([91154d9](91154d9364))
* **rithmomachia:** add missing pyramid section keys to Japanese (ja.json) ([dae615e](dae615ee72))
* **rithmomachia:** adjust error dialog sizing to prevent text clipping ([cda1126](cda1126cb0))
* **rithmomachia:** adjust roster notice position to not overlap nav ([7093223](709322373a))
* **rithmomachia:** change undock icon to pop-out arrow ([2a91748](2a91748493))
* **rithmomachia:** correct board dimensions to 16x8 and restore original layout values ([cfac277](cfac277505))
* **rithmomachia:** Correct board setup to match reference image exactly ([618e563](618e56358d))
* **rithmomachia:** correct makeMove parameter types for capture handling ([aafb64f](aafb64f3e3))
* **rithmomachia:** fix guide modal resize drift by calculating from initial state ([1bcd99c](1bcd99c949))
* **rithmomachia:** fix harmony section translation structure for hi/ja/es ([14259a1](14259a19a9))
* **rithmomachia:** fix modal resizing zoom issue ([4fa20f4](4fa20f44cb))
* **rithmomachia:** Fix TypeScript errors in playing guide modal ([4834ece](4834ece98e))
* **rithmomachia:** handle pyramid pieces in hover error tooltip ([56f3164](56f3164155))
* **rithmomachia:** implement proper board cropping and highlighting in guide ([d0a8fcd](d0a8fcdea6))
* **rithmomachia:** improve guide modal tab navigation at narrow widths ([a673177](a673177bec))
* **rithmomachia:** reconnect player assignment UI and fix setup layout ([a1a0374](a1a0374fac))
* **rithmomachia:** render guide as docked in preview panel ([190f8cf](190f8cf302))
* **rithmomachia:** show actual values in tooltips for non-helper relations ([774c6b0](774c6b0ce7))
* **rithmomachia:** show guest-friendly message when they can't fix too many players ([54bfd2f](54bfd2fac8))
* **rithmomachia:** smooth guide dragging from docked state without jump ([8f4a79c](8f4a79c9b0))
* **rithmomachia:** validate move path before showing capture error on hover ([bd49964](bd49964186))
* **room-info:** hide Leave Room button when user is alone ([5927f61](5927f61c3c))
* separate horizontal and vertical bounding box logic ([83090df](83090df4df))
* tolerate OpenSCAD CGAL warnings if output file is created ([88993f3](88993f3662))
* use absolute positioning for hero abacus to eliminate scroll lag ([096104b](096104b094))
* use Debian base for deps stage to match runner for binary compatibility ([f8fe6e4](f8fe6e4a41))
* use default BOSL2 branch instead of non-existent v2.0.0 tag ([f4ffc5b](f4ffc5b027))
* use nested SVG viewBox for actual cropping, not just scaling ([440b492](440b492e85))
* various game improvements and UI enhancements ([b67cf61](b67cf610c5))
* **web:** add dynamic export to rithmomachia page ([329e623](329e623212))
* **web:** fix Typst PDF generation path resolution ([7ce1287](7ce1287525))
* **web:** move react-dom/server import to API route to satisfy Next.js ([00a8bc3](00a8bc3e5e))
* **web:** prevent abacus overlap in composite calendar ([448f93c](448f93c1e2)), closes [#f0f0f0](https://github.com/antialias/soroban-abacus-flashcards/issues/f0f0f0)
* **web:** use AbacusStatic for calendar SVG generation ([08c6a41](08c6a419e2))
* **web:** use nested SVG elements to prevent coordinate space conflicts ([f9cbee8](f9cbee8fcd))

### Performance Improvements

* optimize Docker image size to reduce build failures ([9ca3106](9ca3106361))

### Code Refactoring

* **card-sorting:** remove reveal numbers feature ([ea5e3e8](ea5e3e838b))
* **card-sorting:** send complete card sequence instead of individual moves ([e4df843](e4df8432b9))
* **games:** implement carousel, fix victories bug, add conditional stats ([82c133f](82c133f742))
* **games:** move page title to nav bar ([712ee58](712ee58e59))
* **games:** remove redundant subtitle below nav ([ad5bb87](ad5bb87325))
* **games:** remove wheel scrolling, enable overflow visible carousel ([876513c](876513c9cc))
* reorganize Harmony and Victory guide sections ([fb629c4](fb629c44ea))
* restructure /create page into hub with sub-pages ([b91b23d](b91b23d95f))
* **rithmomachia:** extract board and capture components (phase 2+3) ([a0a867b](a0a867b271))
* **rithmomachia:** extract CaptureErrorDialog component (Phase 2 partial) ([f0a066d](f0a066d8f0))
* **rithmomachia:** extract constants and coordinate utilities (Phase 1) ([eace0ed](eace0ed529))
* **rithmomachia:** extract guide sections into separate files ([765525d](765525dc45))
* **rithmomachia:** extract hooks (phase 5) ([324a659](324a65992f))
* **rithmomachia:** extract phase components (phase 4) ([11364f6](11364f6394))
* **rithmomachia:** extract reusable components from SetupPhase ([3abc325](3abc325ea2))
* **rithmomachia:** make setup phase UI more compact ([e55f848](e55f848a26))
* **rithmomachia:** redesign error notification with modern UI ([dfeeb0e](dfeeb0e0db)), closes [#1e293](https://github.com/antialias/soroban-abacus-flashcards/issues/1e293) [#0f172](https://github.com/antialias/soroban-abacus-flashcards/issues/0f172) [#f1f5f9](https://github.com/antialias/soroban-abacus-flashcards/issues/f1f5f9)
* **rithmomachia:** simplify capture error dialog to one-liner ([82a5eb2](82a5eb2e4b))
* **rithmomachia:** Update board setup to authoritative CSV layout ([0471da5](0471da598d))
* **rithmomachia:** update capture components to use CaptureContext ([2ab6ab5](2ab6ab5799))
* **rithmomachia:** use useBoardLayout and usePieceSelection in BoardDisplay ([0ab7a1d](0ab7a1df32))
* use AbacusReact for dynamic Open Graph image ([9c20f12](9c20f12bac))
* **web:** import utility functions from abacus-react ([7228bbc](7228bbc2eb))
* **web:** use ABACUS_THEMES instead of manual style definitions ([9f7f001](9f7f001d74))
* **web:** use compact prop for inline mini-abacus ([ff1d60a](ff1d60a233))
* **web:** use direct function imports instead of execSync for calendar generation ([9f1715f](9f1715f085))

### Documentation

* **abacus-react:** add Storybook stories for AbacusStatic ([4f9dc46](4f9dc4666d))
* **abacus-react:** add Storybook stories for new features ([6a1cec0](6a1cec06a7))
* **abacus-react:** export AbacusStatic and update README ([74f2d97](74f2d97434))
* **abacus-react:** update documentation for new features ([35d8734](35d8734a3a))
* **abacus-react:** update README with /static import path for RSC ([72a4c2b](72a4c2b80c))
* add 3D enhancement documentation to README ([cc96802](cc96802df8))
* add database migration guide and playing guide modal spec ([5a29af7](5a29af78e2))
* add deployment verification guidelines to prevent false positives ([3d8da23](3d8da2348b))
* **card-sorting:** add comprehensive multiplayer plan ([008ccea](008ccead0f))
* **rithmomachia:** Add concise one-page playing guide ([e3c1f10](e3c1f10233))
* update workflow to require manual testing before commits ([0991796](0991796f1e))

### Styles

* **rithmomachia:** improve divider styling and make tabs responsive ([88ca35e](88ca35e044)), closes [#e5e7](https://github.com/antialias/soroban-abacus-flashcards/issues/e5e7) [#9ca3](https://github.com/antialias/soroban-abacus-flashcards/issues/9ca3)
* **rithmomachia:** improve pyramid face numbers visibility and contrast ([94e5e6a](94e5e6a268)), closes [#fbbf24](https://github.com/antialias/soroban-abacus-flashcards/issues/fbbf24) [#b45309](https://github.com/antialias/soroban-abacus-flashcards/issues/b45309)
* **rithmomachia:** increase pyramid face numbers size and boldness ([7bf2d73](7bf2d730d3))

### Tests

* trigger compose-updater deployment test ([2b06aae](2b06aae394))
* verify compose-updater automatic deployment cycle ([af0552c](af0552ccd9))
2025-11-04 14:51:17 +00:00
Thomas Hallock
00a8bc3e5e fix(web): move react-dom/server import to API route to satisfy Next.js
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>
2025-11-04 08:48:14 -06:00
semantic-release-bot
42016acec1 chore(release): 4.68.0 [skip ci]
## [4.68.0](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.67.1...v4.68.0) (2025-11-04)

### Features

* **abacus-react:** add AbacusStatic for React Server Components ([3b8e864](3b8e864cfa))
* **abacus-react:** add core utility functions for state management ([e65541c](e65541c100))
* **abacus-react:** add layout and educational props ([35bbcec](35bbcecb9e))
* **abacus-react:** add pre-defined theme presets ([cf1f950](cf1f950c7c))
* **abacus-react:** add React hooks for abacus calculations ([de038d2](de038d2afc))
* **abacus-react:** add separate /static export path for React Server Components ([ed69f6b](ed69f6b917))
* **abacus-react:** export new utilities, hooks, and themes ([ce4e44d](ce4e44d630))
* **abacus:** add nativeAbacusNumbers setting to schema and UI ([79f7347](79f7347d48))
* add 3D printing support for abacus models ([dafdfdd](dafdfdd233))
* add comprehensive metadata, SEO, and make AbacusReact SSR-compatible ([0922ea1](0922ea10b7))
* add comprehensive Storybook coverage and migration guide ([7a4a37e](7a4a37ec6d))
* add game preview system with mock arcade environment ([25880cc](25880cc7e4))
* add per-player stats tracking system ([613301c](613301cd13))
* add Strategy & Tactics section to Rithmomachia guide ([81ead65](81ead65680))
* add unified trophy abacus with hero mode integration ([6620418](6620418a70))
* **arcade:** add ability to deactivate remote players without kicking user ([3628426](3628426a56))
* **arcade:** add native abacus numbers support to pressure gauge ([1d525c7](1d525c7b53))
* **arcade:** add Rithmomachia (Battle of Numbers) game ([2fc0a05](2fc0a05f7f))
* **arcade:** add yjs-demo collaborative game and Yjs persistence layer ([d568955](d568955d6a))
* **arcade:** auto-create room when user has none ([ff88c3a](ff88c3a1b8))
* **card-sorting:** add activity feed notifications for collaborative mode ([1461414](1461414ef4))
* **card-sorting:** add auto-submit countdown for perfect sequences ([780a716](780a7161bc))
* **card-sorting:** add bezier curves to connecting arrows ([4d8e873](4d8e873358))
* **card-sorting:** add CardPosition type and position syncing ([656f5a7](656f5a7838))
* **card-sorting:** add collapsible stats sidebar for spectators ([6527c26](6527c26a81))
* **card-sorting:** add game mode selector UI to setup phase ([d25b888](d25b888ffb))
* **card-sorting:** add GameMode type system for multiplayer support ([fd76533](fd765335ef))
* **card-sorting:** add green border to correctly positioned cards ([16fca86](16fca86b76)), closes [#22c55](https://github.com/antialias/soroban-abacus-flashcards/issues/22c55)
* **card-sorting:** add player emoji indicators on moving cards ([3a82099](3a82099757))
* **card-sorting:** add react-spring animations for real-time sync ([c367e0c](c367e0ceec))
* **card-sorting:** add smooth transition to drop shadow ([b0b93d0](b0b93d0175))
* **card-sorting:** add spectator mode UI enhancements ([ee7345d](ee7345d641)), closes [#6366f1](https://github.com/antialias/soroban-abacus-flashcards/issues/6366f1) [#8b5cf6](https://github.com/antialias/soroban-abacus-flashcards/issues/8b5cf6)
* **card-sorting:** add team scoring UI for collaborative mode ([ed6f177](ed6f177914)), closes [#a78](https://github.com/antialias/soroban-abacus-flashcards/issues/a78) [#8b5cf6](https://github.com/antialias/soroban-abacus-flashcards/issues/8b5cf6)
* **card-sorting:** add updateCardPositions action to Provider ([f6ed4a2](f6ed4a27a2))
* **card-sorting:** auto-arrange prefix/suffix cards in corners ([4ba7f24](4ba7f24717))
* **card-sorting:** fade correctly positioned cards to 50% opacity ([7028cfc](7028cfc511))
* **card-sorting:** gentler spring animation for locked cards ([47189cb](47189cb6e7))
* **card-sorting:** implement continuous bezier curve paths ([2d93024](2d9302410f))
* **card-sorting:** improve card distribution for natural scattered look ([0b0503f](0b0503f035))
* **card-sorting:** make player emoji fill entire card background ([2e7a02c](2e7a02c9e4))
* **card-sorting:** optimize results screen for mobile ([d188789](d188789069))
* **card-sorting:** redesign setup screen with modern UI ([73cf967](73cf967492))
* **card-sorting:** scale correctly positioned cards to 50% ([222dc55](222dc555fa))
* **card-sorting:** shrink/fade cards in correct suffix as well ([8f6feec](8f6feec4f2))
* **card-sorting:** smooth spring transition from game table to results grid ([c5f39d5](c5f39d51eb))
* **card-sorting:** wrap prefix/suffix cards to multiple rows ([e3184dd](e3184dd0d4))
* complete 3D enhancement integration for all three proposals ([5ac55cc](5ac55cc149))
* **create-room:** replace hardcoded game grid with dynamic Radix Select dropdown ([83d0ba2](83d0ba26f5))
* dynamic day-of-month favicon using subprocess pattern ([4d0795a](4d0795a9df))
* dynamically crop favicon to active beads for maximum size ([5670322](567032296a))
* enable 3D enhancement on hero/open MyAbacus modes ([37e330f](37e330f26e))
* **games:** add autoplay and improve carousel layout ([9f51edf](9f51edfaa9))
* **games:** add horizontal scroll support to carousels ([a224abb](a224abb6f6))
* **games:** add rotating games hero carousel ([24231e6](24231e6b2e))
* **i18n:** add dynamic locale switching without page reload ([fe9bfea](fe9bfeabf9))
* **i18n:** add global language selector to navigation ([0506360](0506360117))
* **i18n:** add homepage translations for all supported languages ([8c9d35a](8c9d35a3b4))
* **i18n:** add Old High German (goh) language support ([b334a15](b334a15255))
* **i18n:** complete Old High German translations for all locales ([0b06a1c](0b06a1ce00))
* **i18n:** internationalize games page and tutorial content ([4253964](4253964af1))
* **i18n:** internationalize homepage with English translations ([40cff14](40cff143c7))
* **i18n:** migrate from react-i18next to next-intl ([9016b76](9016b76024))
* **i18n:** update games page hero section copy ([6333c60](6333c60352))
* install embla-carousel-autoplay for games carousel ([946e5d1](946e5d1910))
* install embla-carousel-react for player profile carousel ([642ae95](642ae95738))
* internationalize guide page with 6 languages ([e9c320b](e9c320bb10))
* internationalize tutorial player ([26d41cf](26d41cfd05))
* optimize card sorting for mobile displays ([b443ee9](b443ee9cdc))
* Redesign Rithmomachia setup page with dramatic medieval theme ([6ae4d13](6ae4d13dc7))
* **rithmomachia:** add 80% opacity to guide modal when not hovered ([4a78485](4a78485d2e))
* **rithmomachia:** add CaptureContext for capture dialog state management ([d7eb957](d7eb957a8d))
* **rithmomachia:** add ghost panel preview for guide docking ([c0d6526](c0d6526d30))
* **rithmomachia:** add guide docking with resizable panels ([f457f1a](f457f1a1c2))
* **rithmomachia:** add helper piece selection for mathematical captures ([cae3359](cae3359587))
* **rithmomachia:** add helpful error messages for failed captures ([b172440](b172440a41))
* **rithmomachia:** add initial board visual to guide Overview section ([d42bcff](d42bcff0d9))
* **rithmomachia:** Add interactive playing guide modal ([3121d82](3121d8240a))
* **rithmomachia:** add number bond visualization and helper placeholders ([82d8913](82d89131f0))
* **rithmomachia:** add ratio capture example to guide ([9150b0c](9150b0c678))
* **rithmomachia:** add standalone guide page route ([3fcc79f](3fcc79fe9e))
* **rithmomachia:** add useBoardLayout hook for centralized layout calculations ([27f1c98](27f1c989d5))
* **rithmomachia:** add usePieceSelection hook for selection state management ([275f401](275f401e3c))
* **rithmomachia:** add visual board examples to Capture section ([74bc3c0](74bc3c0dcf))
* **rithmomachia:** add visual board examples to Harmony section ([1d5f01c](1d5f01c966))
* **rithmomachia:** add visual winning example to Victory section ([b7fac78](b7fac78829))
* **rithmomachia:** auto-size tab labels with react-textfit ([9fd5406](9fd54067ce))
* **rithmomachia:** cycle through valid helpers with dynamic number tooltips ([4829e41](4829e41ea1))
* **rithmomachia:** enhance capture relation UI with smooth animations ([0a30801](0a308016e9))
* **rithmomachia:** enhance Harmony section with comprehensive content ([f555856](f5558563ea))
* **rithmomachia:** enhance Pieces section with visual examples and pyramid details ([55aff82](55aff829f4))
* **rithmomachia:** enhance Pyramid section with comprehensive details ([9fde1ef](9fde1ef9e7))
* **rithmomachia:** guide defaults to docked right on open ([11f674d](11f674d542))
* **rithmomachia:** improve guide pieces section layout ([a270bfc](a270bfc0cc))
* **rithmomachia:** improve guide UX and add persistence ([b314740](b314740697))
* **rithmomachia:** improve roster status notice UX ([e27df45](e27df45256))
* **rithmomachia:** integrate roster warning into game nav ([8a11594](8a11594203))
* **rithmomachia:** make guide modal ultra-responsive down to 150px width ([0474197](04741971b2))
* **rithmomachia:** recreate original guide modal header layout ([2489695](24896957d0))
* **rithmomachia:** show capture error on hover instead of click ([339b678](339b6780f6))
* **rithmomachia:** show pyramid face numbers on hover instead of selection ([b0c4523](b0c4523c0b))
* **rithmomachia:** show pyramid face numbers when selected ([5c186f3](5c186f3947))
* **rithmomachia:** show pyramid face numbers when selected with subtle animation ([5c2ddbe](5c2ddbef05))
* **rithmomachia:** show real preview layout when dragging guide to dock ([17d2460](17d2460a87))
* **rithmomachia:** simplify guide language for clarity ([85cb630](85cb630add))
* **rithmomachia:** skip helper selection UI and auto-select first valid helper ([be2a00e](be2a00e8b3))
* **rithmomachia:** Update harmony system to classical three-piece proportions ([08c9762](08c97620f5))
* **rithmomachia:** Update to traditional board setup with 25 pieces per side ([0769eaa](0769eaaa1d))
* **rithmomachia:** use actual piece SVGs in number bond with 2.5s rotation animation ([976a7de](976a7de949))
* **room-share:** add QR code button for easy mobile joining ([349290a](349290ac6a))
* show rithmomachia turn in nav ([7c89bfe](7c89bfef9c))
* switch to royal color theme with transparent background ([944ad65](944ad6574e)), closes [#fbbf24](https://github.com/antialias/soroban-abacus-flashcards/issues/fbbf24) [#f59e0](https://github.com/antialias/soroban-abacus-flashcards/issues/f59e0) [#a855f7](https://github.com/antialias/soroban-abacus-flashcards/issues/a855f7) [#7e22](https://github.com/antialias/soroban-abacus-flashcards/issues/7e22)
* **web:** add test page for AbacusStatic RSC compatibility ([903dea2](903dea2584))
* **web:** add test page for AbacusStatic Server Component ([3588d5a](3588d5acde))
* **web:** improve calendar abacus preview styling ([8439727](8439727b15))
* **web:** optimize monthly calendar for single-page layout ([b277a89](b277a89415))
* **web:** redesign monthly calendar as single composite SVG ([8ce8038](8ce8038bae))

### Bug Fixes

* **abacus-react:** correct column highlighting offset in AbacusStatic ([0641eb7](0641eb719e))
* add xmlns to AbacusStatic for Typst SVG parsing ([98cd019](98cd019d4a))
* adjust hero abacus position to avoid covering subtitle ([f03d341](f03d341314))
* **arcade:** add automatic retry for version conflict rejections ([fbcde25](fbcde2505f))
* **arcade:** allow deactivating players from users who left the room ([7c1c2d7](7c1c2d7beb))
* **arcade:** implement optimistic locking in session manager ([71fd66d](71fd66d96a))
* board rotation now properly fills height in portrait mode ([b5a96ea](b5a96eaeb1))
* **card-sorting:** add border radius to outer card container ([a922eba](a922eba73c))
* **card-sorting:** add debug logging for spring animations ([d42947e](d42947eb8d))
* **card-sorting:** add missing gameMode support after hard reset ([a832325](a832325deb))
* **card-sorting:** add missing useMemo import ([949d76d](949d76d844))
* **card-sorting:** add overflow hidden to clip rounded corners ([84c66fe](84c66feec6))
* **card-sorting:** adjust connecting paths for scaled cards ([829c741](829c741e55))
* **card-sorting:** adjust game board for spectator panels ([fc5cf12](fc5cf1216f))
* **card-sorting:** adjust viewport dimensions for spectator panels ([4dce16c](4dce16cca4))
* **card-sorting:** animate cards from game board to results grid ([17d45fe](17d45fe88c))
* **card-sorting:** correct suffix card detection in auto-arrange ([d02ab59](d02ab5922c))
* **card-sorting:** enable card scaling for spectators ([6b095c3](6b095c3383))
* **card-sorting:** enable New Game button during active gameplay ([f3f6eca](f3f6eca1db))
* **card-sorting:** end drag immediately when card becomes locked ([ae45298](ae45298ec4))
* **card-sorting:** filter local player from emoji overlays on dragged cards ([dc2d94a](dc2d94aaa5))
* **card-sorting:** fix results panel layout to not cover cards ([4b4fbfe](4b4fbfef32))
* **card-sorting:** hide activity notifications in spectator mode ([5cca279](5cca279687))
* **card-sorting:** keep arrow sequence numbers upright ([79c9469](79c94699fa))
* **card-sorting:** lock correctly positioned prefix/suffix cards ([170abed](170abed231))
* **card-sorting:** lock spring positions after initial animation completes ([275cc62](275cc62a52))
* **card-sorting:** New Game now restarts with same settings instantly ([f3687ed](f3687ed236))
* **card-sorting:** only shrink/fade cards in correct prefix ([51368c6](51368c6ec5))
* **card-sorting:** preserve card positions on pause/resume ([0d8af09](0d8af09517))
* **card-sorting:** preserve rotation when starting drag ([3364144](3364144fb6))
* **card-sorting:** prevent duplicate START_GAME moves on Play Again ([a0b14f8](a0b14f87e9))
* **card-sorting:** prevent ghost movements with proper optimistic updates ([bd014be](bd014bec4f))
* **card-sorting:** prevent infinite loop when all cards are correct ([34785f4](34785f466f))
* **card-sorting:** prevent infinite loop with tolerance-based position comparison ([627b873](627b873382))
* **card-sorting:** prevent position jump when clicking rotated cards ([564a00f](564a00f82b))
* **card-sorting:** prevent replaying own movements from server ([308168a](308168a7fb))
* **card-sorting:** prevent springs from reinitializing on window resize ([30953b8](30953b8c4a))
* **card-sorting:** prevent springs from resetting after animation ([8aff60c](8aff60ce3f))
* **card-sorting:** remove hasAnimatedRef logic causing backwards animation ([a44aa5a](a44aa5a4c2))
* **card-sorting:** remove remaining reveal numbers references ([15c53ea](15c53ea4eb))
* **card-sorting:** restore prefix/suffix card shrinking visual feedback ([f5fb4d7](f5fb4d7b76))
* **card-sorting:** show only active players in team members section ([fa9f1a5](fa9f1a568f))
* **card-sorting:** smooth scale animation while dragging cards ([0eefc33](0eefc332ac))
* **card-sorting:** stabilize inferred sequence for locked cards during drag ([b0cd194](b0cd194838))
* **card-sorting:** use empty deps array for useSprings to prevent recreation ([cee399e](cee399ed15))
* **card-sorting:** use ref to track initialized state and prevent re-animation ([f389afa](f389afa831))
* **card-sorting:** use same coordinate system for game board and results ([6972fdf](6972fdf110))
* **complement-race:** prevent delivery move thrashing in steam sprint mode ([e1258ee](e1258ee041))
* configure favicon metadata and improve bead visibility ([e1369fa](e1369fa275))
* copy entire packages/core and packages/templates ([0ccada0](0ccada0ca7))
* correct hero abacus scroll direction to flow with page content ([4232746](423274657c))
* correct Typst template path in Dockerfile ([4c518de](4c518decb7))
* delete existing user sessions before creating new ones ([0cced47](0cced47a0f))
* **docker:** add scripts, abacus-react, and tsx for production calendar generation ([33eb90e](33eb90e316))
* extract pure SVG content from AbacusReact renders ([b07f1c4](b07f1c4216))
* **games:** prevent horizontal page scroll from carousel overflow ([5a8c98f](5a8c98fc10))
* **games:** smooth scroll feel for carousel wheel navigation ([f80a73b](f80a73b35c))
* **games:** use specific transition properties for smooth carousel loop ([187271e](187271e515))
* **i18n:** eliminate FOUC by loading messages server-side ([4d4d930](4d4d930bd3))
* **i18n:** use useMessages() for tutorial translations ([95b0105](95b0105ca3))
* include column posts in favicon bounding box ([0b2f481](0b2f48106a))
* increase server update debounce to 2000ms for low bandwidth ([633ff12](633ff12750))
* Integrate threshold input into Point Victory card ([b29bbee](b29bbeefca))
* mark dynamic routes as force-dynamic to prevent static generation errors ([d7b35d9](d7b35d9544))
* **nav:** show full navigation on /games page ([d3fe6ac](d3fe6acbb0))
* **qr-button:** improve layout and z-index ([646a422](646a4228d0))
* **qr-button:** increase mini QR code size to 80px ([61ac737](61ac7378bd))
* **qr-button:** increase mini QR code to 84px ([3fae5ea](3fae5ea6fa))
* **qr-button:** make button square and increase QR size ([dc2d466](dc2d46663b))
* **qr-button:** match height of stacked buttons ([81f202d](81f202d215))
* reduce padding to minimize gap below last bead ([0e529be](0e529be789))
* remove distracting parallax and wobble 3D effects ([28a2d40](28a2d40996))
* remove wobble physics and enhance wood grain visibility ([5d97673](5d97673406))
* resolve z-index layering and hero abacus visibility issues ([ed9a050](ed9a050d64))
* rewrite 3D stories to use props instead of CSS wrappers ([26bdb11](26bdb11237))
* **rithmomachia:** add missing i18next dependencies ([91154d9](91154d9364))
* **rithmomachia:** add missing pyramid section keys to Japanese (ja.json) ([dae615e](dae615ee72))
* **rithmomachia:** adjust error dialog sizing to prevent text clipping ([cda1126](cda1126cb0))
* **rithmomachia:** adjust roster notice position to not overlap nav ([7093223](709322373a))
* **rithmomachia:** change undock icon to pop-out arrow ([2a91748](2a91748493))
* **rithmomachia:** correct board dimensions to 16x8 and restore original layout values ([cfac277](cfac277505))
* **rithmomachia:** Correct board setup to match reference image exactly ([618e563](618e56358d))
* **rithmomachia:** correct makeMove parameter types for capture handling ([aafb64f](aafb64f3e3))
* **rithmomachia:** fix guide modal resize drift by calculating from initial state ([1bcd99c](1bcd99c949))
* **rithmomachia:** fix harmony section translation structure for hi/ja/es ([14259a1](14259a19a9))
* **rithmomachia:** fix modal resizing zoom issue ([4fa20f4](4fa20f44cb))
* **rithmomachia:** Fix TypeScript errors in playing guide modal ([4834ece](4834ece98e))
* **rithmomachia:** handle pyramid pieces in hover error tooltip ([56f3164](56f3164155))
* **rithmomachia:** implement proper board cropping and highlighting in guide ([d0a8fcd](d0a8fcdea6))
* **rithmomachia:** improve guide modal tab navigation at narrow widths ([a673177](a673177bec))
* **rithmomachia:** reconnect player assignment UI and fix setup layout ([a1a0374](a1a0374fac))
* **rithmomachia:** render guide as docked in preview panel ([190f8cf](190f8cf302))
* **rithmomachia:** show actual values in tooltips for non-helper relations ([774c6b0](774c6b0ce7))
* **rithmomachia:** show guest-friendly message when they can't fix too many players ([54bfd2f](54bfd2fac8))
* **rithmomachia:** smooth guide dragging from docked state without jump ([8f4a79c](8f4a79c9b0))
* **rithmomachia:** validate move path before showing capture error on hover ([bd49964](bd49964186))
* **room-info:** hide Leave Room button when user is alone ([5927f61](5927f61c3c))
* separate horizontal and vertical bounding box logic ([83090df](83090df4df))
* tolerate OpenSCAD CGAL warnings if output file is created ([88993f3](88993f3662))
* use absolute positioning for hero abacus to eliminate scroll lag ([096104b](096104b094))
* use Debian base for deps stage to match runner for binary compatibility ([f8fe6e4](f8fe6e4a41))
* use default BOSL2 branch instead of non-existent v2.0.0 tag ([f4ffc5b](f4ffc5b027))
* use nested SVG viewBox for actual cropping, not just scaling ([440b492](440b492e85))
* various game improvements and UI enhancements ([b67cf61](b67cf610c5))
* **web:** add dynamic export to rithmomachia page ([329e623](329e623212))
* **web:** fix Typst PDF generation path resolution ([7ce1287](7ce1287525))
* **web:** prevent abacus overlap in composite calendar ([448f93c](448f93c1e2)), closes [#f0f0f0](https://github.com/antialias/soroban-abacus-flashcards/issues/f0f0f0)
* **web:** use AbacusStatic for calendar SVG generation ([08c6a41](08c6a419e2))
* **web:** use nested SVG elements to prevent coordinate space conflicts ([f9cbee8](f9cbee8fcd))

### Performance Improvements

* optimize Docker image size to reduce build failures ([9ca3106](9ca3106361))

### Code Refactoring

* **card-sorting:** remove reveal numbers feature ([ea5e3e8](ea5e3e838b))
* **card-sorting:** send complete card sequence instead of individual moves ([e4df843](e4df8432b9))
* **games:** implement carousel, fix victories bug, add conditional stats ([82c133f](82c133f742))
* **games:** move page title to nav bar ([712ee58](712ee58e59))
* **games:** remove redundant subtitle below nav ([ad5bb87](ad5bb87325))
* **games:** remove wheel scrolling, enable overflow visible carousel ([876513c](876513c9cc))
* reorganize Harmony and Victory guide sections ([fb629c4](fb629c44ea))
* restructure /create page into hub with sub-pages ([b91b23d](b91b23d95f))
* **rithmomachia:** extract board and capture components (phase 2+3) ([a0a867b](a0a867b271))
* **rithmomachia:** extract CaptureErrorDialog component (Phase 2 partial) ([f0a066d](f0a066d8f0))
* **rithmomachia:** extract constants and coordinate utilities (Phase 1) ([eace0ed](eace0ed529))
* **rithmomachia:** extract guide sections into separate files ([765525d](765525dc45))
* **rithmomachia:** extract hooks (phase 5) ([324a659](324a65992f))
* **rithmomachia:** extract phase components (phase 4) ([11364f6](11364f6394))
* **rithmomachia:** extract reusable components from SetupPhase ([3abc325](3abc325ea2))
* **rithmomachia:** make setup phase UI more compact ([e55f848](e55f848a26))
* **rithmomachia:** redesign error notification with modern UI ([dfeeb0e](dfeeb0e0db)), closes [#1e293](https://github.com/antialias/soroban-abacus-flashcards/issues/1e293) [#0f172](https://github.com/antialias/soroban-abacus-flashcards/issues/0f172) [#f1f5f9](https://github.com/antialias/soroban-abacus-flashcards/issues/f1f5f9)
* **rithmomachia:** simplify capture error dialog to one-liner ([82a5eb2](82a5eb2e4b))
* **rithmomachia:** Update board setup to authoritative CSV layout ([0471da5](0471da598d))
* **rithmomachia:** update capture components to use CaptureContext ([2ab6ab5](2ab6ab5799))
* **rithmomachia:** use useBoardLayout and usePieceSelection in BoardDisplay ([0ab7a1d](0ab7a1df32))
* use AbacusReact for dynamic Open Graph image ([9c20f12](9c20f12bac))
* **web:** import utility functions from abacus-react ([7228bbc](7228bbc2eb))
* **web:** use ABACUS_THEMES instead of manual style definitions ([9f7f001](9f7f001d74))
* **web:** use compact prop for inline mini-abacus ([ff1d60a](ff1d60a233))
* **web:** use direct function imports instead of execSync for calendar generation ([9f1715f](9f1715f085))

### Documentation

* **abacus-react:** add Storybook stories for AbacusStatic ([4f9dc46](4f9dc4666d))
* **abacus-react:** add Storybook stories for new features ([6a1cec0](6a1cec06a7))
* **abacus-react:** export AbacusStatic and update README ([74f2d97](74f2d97434))
* **abacus-react:** update documentation for new features ([35d8734](35d8734a3a))
* **abacus-react:** update README with /static import path for RSC ([72a4c2b](72a4c2b80c))
* add 3D enhancement documentation to README ([cc96802](cc96802df8))
* add database migration guide and playing guide modal spec ([5a29af7](5a29af78e2))
* add deployment verification guidelines to prevent false positives ([3d8da23](3d8da2348b))
* **card-sorting:** add comprehensive multiplayer plan ([008ccea](008ccead0f))
* **rithmomachia:** Add concise one-page playing guide ([e3c1f10](e3c1f10233))
* update workflow to require manual testing before commits ([0991796](0991796f1e))

### Styles

* **rithmomachia:** improve divider styling and make tabs responsive ([88ca35e](88ca35e044)), closes [#e5e7](https://github.com/antialias/soroban-abacus-flashcards/issues/e5e7) [#9ca3](https://github.com/antialias/soroban-abacus-flashcards/issues/9ca3)
* **rithmomachia:** improve pyramid face numbers visibility and contrast ([94e5e6a](94e5e6a268)), closes [#fbbf24](https://github.com/antialias/soroban-abacus-flashcards/issues/fbbf24) [#b45309](https://github.com/antialias/soroban-abacus-flashcards/issues/b45309)
* **rithmomachia:** increase pyramid face numbers size and boldness ([7bf2d73](7bf2d730d3))

### Tests

* trigger compose-updater deployment test ([2b06aae](2b06aae394))
* verify compose-updater automatic deployment cycle ([af0552c](af0552ccd9))
2025-11-04 14:30:40 +00:00
Thomas Hallock
9f1715f085 refactor(web): use direct function imports instead of execSync for calendar generation
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>
2025-11-04 08:27:41 -06:00
Thomas Hallock
33eb90e316 fix(docker): add scripts, abacus-react, and tsx for production calendar generation
- Copy scripts directory to production container (needed for generateCalendarComposite.tsx)
- Copy abacus-react package (scripts import from @soroban/abacus-react/static)
- Install tsx globally to fix npm EACCES /nonexistent error in production

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-04 08:27:41 -06:00
Thomas Hallock
f9cbee8fcd fix(web): use nested SVG elements to prevent coordinate space conflicts
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>
2025-11-04 08:27:41 -06:00
semantic-release-bot
8aaec90e11 chore(release): 4.68.0 [skip ci]
## [4.68.0](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.67.1...v4.68.0) (2025-11-04)

### Features

* **abacus-react:** add AbacusStatic for React Server Components ([3b8e864](3b8e864cfa))
* **abacus-react:** add core utility functions for state management ([e65541c](e65541c100))
* **abacus-react:** add layout and educational props ([35bbcec](35bbcecb9e))
* **abacus-react:** add pre-defined theme presets ([cf1f950](cf1f950c7c))
* **abacus-react:** add React hooks for abacus calculations ([de038d2](de038d2afc))
* **abacus-react:** add separate /static export path for React Server Components ([ed69f6b](ed69f6b917))
* **abacus-react:** export new utilities, hooks, and themes ([ce4e44d](ce4e44d630))
* **abacus:** add nativeAbacusNumbers setting to schema and UI ([79f7347](79f7347d48))
* add 3D printing support for abacus models ([dafdfdd](dafdfdd233))
* add comprehensive metadata, SEO, and make AbacusReact SSR-compatible ([0922ea1](0922ea10b7))
* add comprehensive Storybook coverage and migration guide ([7a4a37e](7a4a37ec6d))
* add game preview system with mock arcade environment ([25880cc](25880cc7e4))
* add per-player stats tracking system ([613301c](613301cd13))
* add Strategy & Tactics section to Rithmomachia guide ([81ead65](81ead65680))
* add unified trophy abacus with hero mode integration ([6620418](6620418a70))
* **arcade:** add ability to deactivate remote players without kicking user ([3628426](3628426a56))
* **arcade:** add native abacus numbers support to pressure gauge ([1d525c7](1d525c7b53))
* **arcade:** add Rithmomachia (Battle of Numbers) game ([2fc0a05](2fc0a05f7f))
* **arcade:** add yjs-demo collaborative game and Yjs persistence layer ([d568955](d568955d6a))
* **arcade:** auto-create room when user has none ([ff88c3a](ff88c3a1b8))
* **card-sorting:** add activity feed notifications for collaborative mode ([1461414](1461414ef4))
* **card-sorting:** add auto-submit countdown for perfect sequences ([780a716](780a7161bc))
* **card-sorting:** add bezier curves to connecting arrows ([4d8e873](4d8e873358))
* **card-sorting:** add CardPosition type and position syncing ([656f5a7](656f5a7838))
* **card-sorting:** add collapsible stats sidebar for spectators ([6527c26](6527c26a81))
* **card-sorting:** add game mode selector UI to setup phase ([d25b888](d25b888ffb))
* **card-sorting:** add GameMode type system for multiplayer support ([fd76533](fd765335ef))
* **card-sorting:** add green border to correctly positioned cards ([16fca86](16fca86b76)), closes [#22c55](https://github.com/antialias/soroban-abacus-flashcards/issues/22c55)
* **card-sorting:** add player emoji indicators on moving cards ([3a82099](3a82099757))
* **card-sorting:** add react-spring animations for real-time sync ([c367e0c](c367e0ceec))
* **card-sorting:** add smooth transition to drop shadow ([b0b93d0](b0b93d0175))
* **card-sorting:** add spectator mode UI enhancements ([ee7345d](ee7345d641)), closes [#6366f1](https://github.com/antialias/soroban-abacus-flashcards/issues/6366f1) [#8b5cf6](https://github.com/antialias/soroban-abacus-flashcards/issues/8b5cf6)
* **card-sorting:** add team scoring UI for collaborative mode ([ed6f177](ed6f177914)), closes [#a78](https://github.com/antialias/soroban-abacus-flashcards/issues/a78) [#8b5cf6](https://github.com/antialias/soroban-abacus-flashcards/issues/8b5cf6)
* **card-sorting:** add updateCardPositions action to Provider ([f6ed4a2](f6ed4a27a2))
* **card-sorting:** auto-arrange prefix/suffix cards in corners ([4ba7f24](4ba7f24717))
* **card-sorting:** fade correctly positioned cards to 50% opacity ([7028cfc](7028cfc511))
* **card-sorting:** gentler spring animation for locked cards ([47189cb](47189cb6e7))
* **card-sorting:** implement continuous bezier curve paths ([2d93024](2d9302410f))
* **card-sorting:** improve card distribution for natural scattered look ([0b0503f](0b0503f035))
* **card-sorting:** make player emoji fill entire card background ([2e7a02c](2e7a02c9e4))
* **card-sorting:** optimize results screen for mobile ([d188789](d188789069))
* **card-sorting:** redesign setup screen with modern UI ([73cf967](73cf967492))
* **card-sorting:** scale correctly positioned cards to 50% ([222dc55](222dc555fa))
* **card-sorting:** shrink/fade cards in correct suffix as well ([8f6feec](8f6feec4f2))
* **card-sorting:** smooth spring transition from game table to results grid ([c5f39d5](c5f39d51eb))
* **card-sorting:** wrap prefix/suffix cards to multiple rows ([e3184dd](e3184dd0d4))
* complete 3D enhancement integration for all three proposals ([5ac55cc](5ac55cc149))
* **create-room:** replace hardcoded game grid with dynamic Radix Select dropdown ([83d0ba2](83d0ba26f5))
* dynamic day-of-month favicon using subprocess pattern ([4d0795a](4d0795a9df))
* dynamically crop favicon to active beads for maximum size ([5670322](567032296a))
* enable 3D enhancement on hero/open MyAbacus modes ([37e330f](37e330f26e))
* **games:** add autoplay and improve carousel layout ([9f51edf](9f51edfaa9))
* **games:** add horizontal scroll support to carousels ([a224abb](a224abb6f6))
* **games:** add rotating games hero carousel ([24231e6](24231e6b2e))
* **i18n:** add dynamic locale switching without page reload ([fe9bfea](fe9bfeabf9))
* **i18n:** add global language selector to navigation ([0506360](0506360117))
* **i18n:** add homepage translations for all supported languages ([8c9d35a](8c9d35a3b4))
* **i18n:** add Old High German (goh) language support ([b334a15](b334a15255))
* **i18n:** complete Old High German translations for all locales ([0b06a1c](0b06a1ce00))
* **i18n:** internationalize games page and tutorial content ([4253964](4253964af1))
* **i18n:** internationalize homepage with English translations ([40cff14](40cff143c7))
* **i18n:** migrate from react-i18next to next-intl ([9016b76](9016b76024))
* **i18n:** update games page hero section copy ([6333c60](6333c60352))
* install embla-carousel-autoplay for games carousel ([946e5d1](946e5d1910))
* install embla-carousel-react for player profile carousel ([642ae95](642ae95738))
* internationalize guide page with 6 languages ([e9c320b](e9c320bb10))
* internationalize tutorial player ([26d41cf](26d41cfd05))
* optimize card sorting for mobile displays ([b443ee9](b443ee9cdc))
* Redesign Rithmomachia setup page with dramatic medieval theme ([6ae4d13](6ae4d13dc7))
* **rithmomachia:** add 80% opacity to guide modal when not hovered ([4a78485](4a78485d2e))
* **rithmomachia:** add CaptureContext for capture dialog state management ([d7eb957](d7eb957a8d))
* **rithmomachia:** add ghost panel preview for guide docking ([c0d6526](c0d6526d30))
* **rithmomachia:** add guide docking with resizable panels ([f457f1a](f457f1a1c2))
* **rithmomachia:** add helper piece selection for mathematical captures ([cae3359](cae3359587))
* **rithmomachia:** add helpful error messages for failed captures ([b172440](b172440a41))
* **rithmomachia:** add initial board visual to guide Overview section ([d42bcff](d42bcff0d9))
* **rithmomachia:** Add interactive playing guide modal ([3121d82](3121d8240a))
* **rithmomachia:** add number bond visualization and helper placeholders ([82d8913](82d89131f0))
* **rithmomachia:** add ratio capture example to guide ([9150b0c](9150b0c678))
* **rithmomachia:** add standalone guide page route ([3fcc79f](3fcc79fe9e))
* **rithmomachia:** add useBoardLayout hook for centralized layout calculations ([27f1c98](27f1c989d5))
* **rithmomachia:** add usePieceSelection hook for selection state management ([275f401](275f401e3c))
* **rithmomachia:** add visual board examples to Capture section ([74bc3c0](74bc3c0dcf))
* **rithmomachia:** add visual board examples to Harmony section ([1d5f01c](1d5f01c966))
* **rithmomachia:** add visual winning example to Victory section ([b7fac78](b7fac78829))
* **rithmomachia:** auto-size tab labels with react-textfit ([9fd5406](9fd54067ce))
* **rithmomachia:** cycle through valid helpers with dynamic number tooltips ([4829e41](4829e41ea1))
* **rithmomachia:** enhance capture relation UI with smooth animations ([0a30801](0a308016e9))
* **rithmomachia:** enhance Harmony section with comprehensive content ([f555856](f5558563ea))
* **rithmomachia:** enhance Pieces section with visual examples and pyramid details ([55aff82](55aff829f4))
* **rithmomachia:** enhance Pyramid section with comprehensive details ([9fde1ef](9fde1ef9e7))
* **rithmomachia:** guide defaults to docked right on open ([11f674d](11f674d542))
* **rithmomachia:** improve guide pieces section layout ([a270bfc](a270bfc0cc))
* **rithmomachia:** improve guide UX and add persistence ([b314740](b314740697))
* **rithmomachia:** improve roster status notice UX ([e27df45](e27df45256))
* **rithmomachia:** integrate roster warning into game nav ([8a11594](8a11594203))
* **rithmomachia:** make guide modal ultra-responsive down to 150px width ([0474197](04741971b2))
* **rithmomachia:** recreate original guide modal header layout ([2489695](24896957d0))
* **rithmomachia:** show capture error on hover instead of click ([339b678](339b6780f6))
* **rithmomachia:** show pyramid face numbers on hover instead of selection ([b0c4523](b0c4523c0b))
* **rithmomachia:** show pyramid face numbers when selected ([5c186f3](5c186f3947))
* **rithmomachia:** show pyramid face numbers when selected with subtle animation ([5c2ddbe](5c2ddbef05))
* **rithmomachia:** show real preview layout when dragging guide to dock ([17d2460](17d2460a87))
* **rithmomachia:** simplify guide language for clarity ([85cb630](85cb630add))
* **rithmomachia:** skip helper selection UI and auto-select first valid helper ([be2a00e](be2a00e8b3))
* **rithmomachia:** Update harmony system to classical three-piece proportions ([08c9762](08c97620f5))
* **rithmomachia:** Update to traditional board setup with 25 pieces per side ([0769eaa](0769eaaa1d))
* **rithmomachia:** use actual piece SVGs in number bond with 2.5s rotation animation ([976a7de](976a7de949))
* **room-share:** add QR code button for easy mobile joining ([349290a](349290ac6a))
* show rithmomachia turn in nav ([7c89bfe](7c89bfef9c))
* switch to royal color theme with transparent background ([944ad65](944ad6574e)), closes [#fbbf24](https://github.com/antialias/soroban-abacus-flashcards/issues/fbbf24) [#f59e0](https://github.com/antialias/soroban-abacus-flashcards/issues/f59e0) [#a855f7](https://github.com/antialias/soroban-abacus-flashcards/issues/a855f7) [#7e22](https://github.com/antialias/soroban-abacus-flashcards/issues/7e22)
* **web:** add test page for AbacusStatic RSC compatibility ([903dea2](903dea2584))
* **web:** add test page for AbacusStatic Server Component ([3588d5a](3588d5acde))
* **web:** improve calendar abacus preview styling ([8439727](8439727b15))
* **web:** optimize monthly calendar for single-page layout ([b277a89](b277a89415))
* **web:** redesign monthly calendar as single composite SVG ([8ce8038](8ce8038bae))

### Bug Fixes

* **abacus-react:** correct column highlighting offset in AbacusStatic ([0641eb7](0641eb719e))
* add xmlns to AbacusStatic for Typst SVG parsing ([98cd019](98cd019d4a))
* adjust hero abacus position to avoid covering subtitle ([f03d341](f03d341314))
* **arcade:** add automatic retry for version conflict rejections ([fbcde25](fbcde2505f))
* **arcade:** allow deactivating players from users who left the room ([7c1c2d7](7c1c2d7beb))
* **arcade:** implement optimistic locking in session manager ([71fd66d](71fd66d96a))
* board rotation now properly fills height in portrait mode ([b5a96ea](b5a96eaeb1))
* **card-sorting:** add border radius to outer card container ([a922eba](a922eba73c))
* **card-sorting:** add debug logging for spring animations ([d42947e](d42947eb8d))
* **card-sorting:** add missing gameMode support after hard reset ([a832325](a832325deb))
* **card-sorting:** add missing useMemo import ([949d76d](949d76d844))
* **card-sorting:** add overflow hidden to clip rounded corners ([84c66fe](84c66feec6))
* **card-sorting:** adjust connecting paths for scaled cards ([829c741](829c741e55))
* **card-sorting:** adjust game board for spectator panels ([fc5cf12](fc5cf1216f))
* **card-sorting:** adjust viewport dimensions for spectator panels ([4dce16c](4dce16cca4))
* **card-sorting:** animate cards from game board to results grid ([17d45fe](17d45fe88c))
* **card-sorting:** correct suffix card detection in auto-arrange ([d02ab59](d02ab5922c))
* **card-sorting:** enable card scaling for spectators ([6b095c3](6b095c3383))
* **card-sorting:** enable New Game button during active gameplay ([f3f6eca](f3f6eca1db))
* **card-sorting:** end drag immediately when card becomes locked ([ae45298](ae45298ec4))
* **card-sorting:** filter local player from emoji overlays on dragged cards ([dc2d94a](dc2d94aaa5))
* **card-sorting:** fix results panel layout to not cover cards ([4b4fbfe](4b4fbfef32))
* **card-sorting:** hide activity notifications in spectator mode ([5cca279](5cca279687))
* **card-sorting:** keep arrow sequence numbers upright ([79c9469](79c94699fa))
* **card-sorting:** lock correctly positioned prefix/suffix cards ([170abed](170abed231))
* **card-sorting:** lock spring positions after initial animation completes ([275cc62](275cc62a52))
* **card-sorting:** New Game now restarts with same settings instantly ([f3687ed](f3687ed236))
* **card-sorting:** only shrink/fade cards in correct prefix ([51368c6](51368c6ec5))
* **card-sorting:** preserve card positions on pause/resume ([0d8af09](0d8af09517))
* **card-sorting:** preserve rotation when starting drag ([3364144](3364144fb6))
* **card-sorting:** prevent duplicate START_GAME moves on Play Again ([a0b14f8](a0b14f87e9))
* **card-sorting:** prevent ghost movements with proper optimistic updates ([bd014be](bd014bec4f))
* **card-sorting:** prevent infinite loop when all cards are correct ([34785f4](34785f466f))
* **card-sorting:** prevent infinite loop with tolerance-based position comparison ([627b873](627b873382))
* **card-sorting:** prevent position jump when clicking rotated cards ([564a00f](564a00f82b))
* **card-sorting:** prevent replaying own movements from server ([308168a](308168a7fb))
* **card-sorting:** prevent springs from reinitializing on window resize ([30953b8](30953b8c4a))
* **card-sorting:** prevent springs from resetting after animation ([8aff60c](8aff60ce3f))
* **card-sorting:** remove hasAnimatedRef logic causing backwards animation ([a44aa5a](a44aa5a4c2))
* **card-sorting:** remove remaining reveal numbers references ([15c53ea](15c53ea4eb))
* **card-sorting:** restore prefix/suffix card shrinking visual feedback ([f5fb4d7](f5fb4d7b76))
* **card-sorting:** show only active players in team members section ([fa9f1a5](fa9f1a568f))
* **card-sorting:** smooth scale animation while dragging cards ([0eefc33](0eefc332ac))
* **card-sorting:** stabilize inferred sequence for locked cards during drag ([b0cd194](b0cd194838))
* **card-sorting:** use empty deps array for useSprings to prevent recreation ([cee399e](cee399ed15))
* **card-sorting:** use ref to track initialized state and prevent re-animation ([f389afa](f389afa831))
* **card-sorting:** use same coordinate system for game board and results ([6972fdf](6972fdf110))
* **complement-race:** prevent delivery move thrashing in steam sprint mode ([e1258ee](e1258ee041))
* configure favicon metadata and improve bead visibility ([e1369fa](e1369fa275))
* copy entire packages/core and packages/templates ([0ccada0](0ccada0ca7))
* correct hero abacus scroll direction to flow with page content ([4232746](423274657c))
* correct Typst template path in Dockerfile ([4c518de](4c518decb7))
* delete existing user sessions before creating new ones ([0cced47](0cced47a0f))
* extract pure SVG content from AbacusReact renders ([b07f1c4](b07f1c4216))
* **games:** prevent horizontal page scroll from carousel overflow ([5a8c98f](5a8c98fc10))
* **games:** smooth scroll feel for carousel wheel navigation ([f80a73b](f80a73b35c))
* **games:** use specific transition properties for smooth carousel loop ([187271e](187271e515))
* **i18n:** eliminate FOUC by loading messages server-side ([4d4d930](4d4d930bd3))
* **i18n:** use useMessages() for tutorial translations ([95b0105](95b0105ca3))
* include column posts in favicon bounding box ([0b2f481](0b2f48106a))
* increase server update debounce to 2000ms for low bandwidth ([633ff12](633ff12750))
* Integrate threshold input into Point Victory card ([b29bbee](b29bbeefca))
* mark dynamic routes as force-dynamic to prevent static generation errors ([d7b35d9](d7b35d9544))
* **nav:** show full navigation on /games page ([d3fe6ac](d3fe6acbb0))
* **qr-button:** improve layout and z-index ([646a422](646a4228d0))
* **qr-button:** increase mini QR code size to 80px ([61ac737](61ac7378bd))
* **qr-button:** increase mini QR code to 84px ([3fae5ea](3fae5ea6fa))
* **qr-button:** make button square and increase QR size ([dc2d466](dc2d46663b))
* **qr-button:** match height of stacked buttons ([81f202d](81f202d215))
* reduce padding to minimize gap below last bead ([0e529be](0e529be789))
* remove distracting parallax and wobble 3D effects ([28a2d40](28a2d40996))
* remove wobble physics and enhance wood grain visibility ([5d97673](5d97673406))
* resolve z-index layering and hero abacus visibility issues ([ed9a050](ed9a050d64))
* rewrite 3D stories to use props instead of CSS wrappers ([26bdb11](26bdb11237))
* **rithmomachia:** add missing i18next dependencies ([91154d9](91154d9364))
* **rithmomachia:** add missing pyramid section keys to Japanese (ja.json) ([dae615e](dae615ee72))
* **rithmomachia:** adjust error dialog sizing to prevent text clipping ([cda1126](cda1126cb0))
* **rithmomachia:** adjust roster notice position to not overlap nav ([7093223](709322373a))
* **rithmomachia:** change undock icon to pop-out arrow ([2a91748](2a91748493))
* **rithmomachia:** correct board dimensions to 16x8 and restore original layout values ([cfac277](cfac277505))
* **rithmomachia:** Correct board setup to match reference image exactly ([618e563](618e56358d))
* **rithmomachia:** correct makeMove parameter types for capture handling ([aafb64f](aafb64f3e3))
* **rithmomachia:** fix guide modal resize drift by calculating from initial state ([1bcd99c](1bcd99c949))
* **rithmomachia:** fix harmony section translation structure for hi/ja/es ([14259a1](14259a19a9))
* **rithmomachia:** fix modal resizing zoom issue ([4fa20f4](4fa20f44cb))
* **rithmomachia:** Fix TypeScript errors in playing guide modal ([4834ece](4834ece98e))
* **rithmomachia:** handle pyramid pieces in hover error tooltip ([56f3164](56f3164155))
* **rithmomachia:** implement proper board cropping and highlighting in guide ([d0a8fcd](d0a8fcdea6))
* **rithmomachia:** improve guide modal tab navigation at narrow widths ([a673177](a673177bec))
* **rithmomachia:** reconnect player assignment UI and fix setup layout ([a1a0374](a1a0374fac))
* **rithmomachia:** render guide as docked in preview panel ([190f8cf](190f8cf302))
* **rithmomachia:** show actual values in tooltips for non-helper relations ([774c6b0](774c6b0ce7))
* **rithmomachia:** show guest-friendly message when they can't fix too many players ([54bfd2f](54bfd2fac8))
* **rithmomachia:** smooth guide dragging from docked state without jump ([8f4a79c](8f4a79c9b0))
* **rithmomachia:** validate move path before showing capture error on hover ([bd49964](bd49964186))
* **room-info:** hide Leave Room button when user is alone ([5927f61](5927f61c3c))
* separate horizontal and vertical bounding box logic ([83090df](83090df4df))
* tolerate OpenSCAD CGAL warnings if output file is created ([88993f3](88993f3662))
* use absolute positioning for hero abacus to eliminate scroll lag ([096104b](096104b094))
* use Debian base for deps stage to match runner for binary compatibility ([f8fe6e4](f8fe6e4a41))
* use default BOSL2 branch instead of non-existent v2.0.0 tag ([f4ffc5b](f4ffc5b027))
* use nested SVG viewBox for actual cropping, not just scaling ([440b492](440b492e85))
* various game improvements and UI enhancements ([b67cf61](b67cf610c5))
* **web:** add dynamic export to rithmomachia page ([329e623](329e623212))
* **web:** fix Typst PDF generation path resolution ([7ce1287](7ce1287525))
* **web:** prevent abacus overlap in composite calendar ([448f93c](448f93c1e2)), closes [#f0f0f0](https://github.com/antialias/soroban-abacus-flashcards/issues/f0f0f0)
* **web:** use AbacusStatic for calendar SVG generation ([08c6a41](08c6a419e2))

### Performance Improvements

* optimize Docker image size to reduce build failures ([9ca3106](9ca3106361))

### Code Refactoring

* **card-sorting:** remove reveal numbers feature ([ea5e3e8](ea5e3e838b))
* **card-sorting:** send complete card sequence instead of individual moves ([e4df843](e4df8432b9))
* **games:** implement carousel, fix victories bug, add conditional stats ([82c133f](82c133f742))
* **games:** move page title to nav bar ([712ee58](712ee58e59))
* **games:** remove redundant subtitle below nav ([ad5bb87](ad5bb87325))
* **games:** remove wheel scrolling, enable overflow visible carousel ([876513c](876513c9cc))
* reorganize Harmony and Victory guide sections ([fb629c4](fb629c44ea))
* restructure /create page into hub with sub-pages ([b91b23d](b91b23d95f))
* **rithmomachia:** extract board and capture components (phase 2+3) ([a0a867b](a0a867b271))
* **rithmomachia:** extract CaptureErrorDialog component (Phase 2 partial) ([f0a066d](f0a066d8f0))
* **rithmomachia:** extract constants and coordinate utilities (Phase 1) ([eace0ed](eace0ed529))
* **rithmomachia:** extract guide sections into separate files ([765525d](765525dc45))
* **rithmomachia:** extract hooks (phase 5) ([324a659](324a65992f))
* **rithmomachia:** extract phase components (phase 4) ([11364f6](11364f6394))
* **rithmomachia:** extract reusable components from SetupPhase ([3abc325](3abc325ea2))
* **rithmomachia:** make setup phase UI more compact ([e55f848](e55f848a26))
* **rithmomachia:** redesign error notification with modern UI ([dfeeb0e](dfeeb0e0db)), closes [#1e293](https://github.com/antialias/soroban-abacus-flashcards/issues/1e293) [#0f172](https://github.com/antialias/soroban-abacus-flashcards/issues/0f172) [#f1f5f9](https://github.com/antialias/soroban-abacus-flashcards/issues/f1f5f9)
* **rithmomachia:** simplify capture error dialog to one-liner ([82a5eb2](82a5eb2e4b))
* **rithmomachia:** Update board setup to authoritative CSV layout ([0471da5](0471da598d))
* **rithmomachia:** update capture components to use CaptureContext ([2ab6ab5](2ab6ab5799))
* **rithmomachia:** use useBoardLayout and usePieceSelection in BoardDisplay ([0ab7a1d](0ab7a1df32))
* use AbacusReact for dynamic Open Graph image ([9c20f12](9c20f12bac))
* **web:** import utility functions from abacus-react ([7228bbc](7228bbc2eb))
* **web:** use ABACUS_THEMES instead of manual style definitions ([9f7f001](9f7f001d74))
* **web:** use compact prop for inline mini-abacus ([ff1d60a](ff1d60a233))

### Documentation

* **abacus-react:** add Storybook stories for AbacusStatic ([4f9dc46](4f9dc4666d))
* **abacus-react:** add Storybook stories for new features ([6a1cec0](6a1cec06a7))
* **abacus-react:** export AbacusStatic and update README ([74f2d97](74f2d97434))
* **abacus-react:** update documentation for new features ([35d8734](35d8734a3a))
* **abacus-react:** update README with /static import path for RSC ([72a4c2b](72a4c2b80c))
* add 3D enhancement documentation to README ([cc96802](cc96802df8))
* add database migration guide and playing guide modal spec ([5a29af7](5a29af78e2))
* add deployment verification guidelines to prevent false positives ([3d8da23](3d8da2348b))
* **card-sorting:** add comprehensive multiplayer plan ([008ccea](008ccead0f))
* **rithmomachia:** Add concise one-page playing guide ([e3c1f10](e3c1f10233))
* update workflow to require manual testing before commits ([0991796](0991796f1e))

### Styles

* **rithmomachia:** improve divider styling and make tabs responsive ([88ca35e](88ca35e044)), closes [#e5e7](https://github.com/antialias/soroban-abacus-flashcards/issues/e5e7) [#9ca3](https://github.com/antialias/soroban-abacus-flashcards/issues/9ca3)
* **rithmomachia:** improve pyramid face numbers visibility and contrast ([94e5e6a](94e5e6a268)), closes [#fbbf24](https://github.com/antialias/soroban-abacus-flashcards/issues/fbbf24) [#b45309](https://github.com/antialias/soroban-abacus-flashcards/issues/b45309)
* **rithmomachia:** increase pyramid face numbers size and boldness ([7bf2d73](7bf2d730d3))

### Tests

* trigger compose-updater deployment test ([2b06aae](2b06aae394))
* verify compose-updater automatic deployment cycle ([af0552c](af0552ccd9))
2025-11-04 02:27:21 +00:00
Thomas Hallock
448f93c1e2 fix(web): prevent abacus overlap in composite calendar
Fixed spacing issues causing abaci to overlap:

Layout improvements:
- Calculate proper scale based on cell dimensions (CELL_WIDTH / 7, CELL_HEIGHT / 6)
- Account for natural abacus size (120×230) in scale calculation
- Use MAX_SCALE_X and MAX_SCALE_Y with 90% safety margin
- Center abacus in each cell using calculated scaled dimensions
- Add debug cell borders to visualize grid (stroke="#f0f0f0")

Math:
- ABACUS_SCALE = min(MAX_SCALE_X, MAX_SCALE_Y) × 0.9
- Position = cellCenter - (scaledSize / 2)
- This ensures abaci stay within cell boundaries

Before: Hardcoded offsets (-60, -115) caused overlaps
After: Dynamic centering based on actual scaled dimensions

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-03 20:24:16 -06:00
Thomas Hallock
8ce8038bae feat(web): redesign monthly calendar as single composite SVG
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>
2025-11-03 20:21:40 -06:00
semantic-release-bot
c93409fc8c chore(release): 4.68.0 [skip ci]
## [4.68.0](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.67.1...v4.68.0) (2025-11-04)

### Features

* **abacus-react:** add AbacusStatic for React Server Components ([3b8e864](3b8e864cfa))
* **abacus-react:** add core utility functions for state management ([e65541c](e65541c100))
* **abacus-react:** add layout and educational props ([35bbcec](35bbcecb9e))
* **abacus-react:** add pre-defined theme presets ([cf1f950](cf1f950c7c))
* **abacus-react:** add React hooks for abacus calculations ([de038d2](de038d2afc))
* **abacus-react:** add separate /static export path for React Server Components ([ed69f6b](ed69f6b917))
* **abacus-react:** export new utilities, hooks, and themes ([ce4e44d](ce4e44d630))
* **abacus:** add nativeAbacusNumbers setting to schema and UI ([79f7347](79f7347d48))
* add 3D printing support for abacus models ([dafdfdd](dafdfdd233))
* add comprehensive metadata, SEO, and make AbacusReact SSR-compatible ([0922ea1](0922ea10b7))
* add comprehensive Storybook coverage and migration guide ([7a4a37e](7a4a37ec6d))
* add game preview system with mock arcade environment ([25880cc](25880cc7e4))
* add per-player stats tracking system ([613301c](613301cd13))
* add Strategy & Tactics section to Rithmomachia guide ([81ead65](81ead65680))
* add unified trophy abacus with hero mode integration ([6620418](6620418a70))
* **arcade:** add ability to deactivate remote players without kicking user ([3628426](3628426a56))
* **arcade:** add native abacus numbers support to pressure gauge ([1d525c7](1d525c7b53))
* **arcade:** add Rithmomachia (Battle of Numbers) game ([2fc0a05](2fc0a05f7f))
* **arcade:** add yjs-demo collaborative game and Yjs persistence layer ([d568955](d568955d6a))
* **arcade:** auto-create room when user has none ([ff88c3a](ff88c3a1b8))
* **card-sorting:** add activity feed notifications for collaborative mode ([1461414](1461414ef4))
* **card-sorting:** add auto-submit countdown for perfect sequences ([780a716](780a7161bc))
* **card-sorting:** add bezier curves to connecting arrows ([4d8e873](4d8e873358))
* **card-sorting:** add CardPosition type and position syncing ([656f5a7](656f5a7838))
* **card-sorting:** add collapsible stats sidebar for spectators ([6527c26](6527c26a81))
* **card-sorting:** add game mode selector UI to setup phase ([d25b888](d25b888ffb))
* **card-sorting:** add GameMode type system for multiplayer support ([fd76533](fd765335ef))
* **card-sorting:** add green border to correctly positioned cards ([16fca86](16fca86b76)), closes [#22c55](https://github.com/antialias/soroban-abacus-flashcards/issues/22c55)
* **card-sorting:** add player emoji indicators on moving cards ([3a82099](3a82099757))
* **card-sorting:** add react-spring animations for real-time sync ([c367e0c](c367e0ceec))
* **card-sorting:** add smooth transition to drop shadow ([b0b93d0](b0b93d0175))
* **card-sorting:** add spectator mode UI enhancements ([ee7345d](ee7345d641)), closes [#6366f1](https://github.com/antialias/soroban-abacus-flashcards/issues/6366f1) [#8b5cf6](https://github.com/antialias/soroban-abacus-flashcards/issues/8b5cf6)
* **card-sorting:** add team scoring UI for collaborative mode ([ed6f177](ed6f177914)), closes [#a78](https://github.com/antialias/soroban-abacus-flashcards/issues/a78) [#8b5cf6](https://github.com/antialias/soroban-abacus-flashcards/issues/8b5cf6)
* **card-sorting:** add updateCardPositions action to Provider ([f6ed4a2](f6ed4a27a2))
* **card-sorting:** auto-arrange prefix/suffix cards in corners ([4ba7f24](4ba7f24717))
* **card-sorting:** fade correctly positioned cards to 50% opacity ([7028cfc](7028cfc511))
* **card-sorting:** gentler spring animation for locked cards ([47189cb](47189cb6e7))
* **card-sorting:** implement continuous bezier curve paths ([2d93024](2d9302410f))
* **card-sorting:** improve card distribution for natural scattered look ([0b0503f](0b0503f035))
* **card-sorting:** make player emoji fill entire card background ([2e7a02c](2e7a02c9e4))
* **card-sorting:** optimize results screen for mobile ([d188789](d188789069))
* **card-sorting:** redesign setup screen with modern UI ([73cf967](73cf967492))
* **card-sorting:** scale correctly positioned cards to 50% ([222dc55](222dc555fa))
* **card-sorting:** shrink/fade cards in correct suffix as well ([8f6feec](8f6feec4f2))
* **card-sorting:** smooth spring transition from game table to results grid ([c5f39d5](c5f39d51eb))
* **card-sorting:** wrap prefix/suffix cards to multiple rows ([e3184dd](e3184dd0d4))
* complete 3D enhancement integration for all three proposals ([5ac55cc](5ac55cc149))
* **create-room:** replace hardcoded game grid with dynamic Radix Select dropdown ([83d0ba2](83d0ba26f5))
* dynamic day-of-month favicon using subprocess pattern ([4d0795a](4d0795a9df))
* dynamically crop favicon to active beads for maximum size ([5670322](567032296a))
* enable 3D enhancement on hero/open MyAbacus modes ([37e330f](37e330f26e))
* **games:** add autoplay and improve carousel layout ([9f51edf](9f51edfaa9))
* **games:** add horizontal scroll support to carousels ([a224abb](a224abb6f6))
* **games:** add rotating games hero carousel ([24231e6](24231e6b2e))
* **i18n:** add dynamic locale switching without page reload ([fe9bfea](fe9bfeabf9))
* **i18n:** add global language selector to navigation ([0506360](0506360117))
* **i18n:** add homepage translations for all supported languages ([8c9d35a](8c9d35a3b4))
* **i18n:** add Old High German (goh) language support ([b334a15](b334a15255))
* **i18n:** complete Old High German translations for all locales ([0b06a1c](0b06a1ce00))
* **i18n:** internationalize games page and tutorial content ([4253964](4253964af1))
* **i18n:** internationalize homepage with English translations ([40cff14](40cff143c7))
* **i18n:** migrate from react-i18next to next-intl ([9016b76](9016b76024))
* **i18n:** update games page hero section copy ([6333c60](6333c60352))
* install embla-carousel-autoplay for games carousel ([946e5d1](946e5d1910))
* install embla-carousel-react for player profile carousel ([642ae95](642ae95738))
* internationalize guide page with 6 languages ([e9c320b](e9c320bb10))
* internationalize tutorial player ([26d41cf](26d41cfd05))
* optimize card sorting for mobile displays ([b443ee9](b443ee9cdc))
* Redesign Rithmomachia setup page with dramatic medieval theme ([6ae4d13](6ae4d13dc7))
* **rithmomachia:** add 80% opacity to guide modal when not hovered ([4a78485](4a78485d2e))
* **rithmomachia:** add CaptureContext for capture dialog state management ([d7eb957](d7eb957a8d))
* **rithmomachia:** add ghost panel preview for guide docking ([c0d6526](c0d6526d30))
* **rithmomachia:** add guide docking with resizable panels ([f457f1a](f457f1a1c2))
* **rithmomachia:** add helper piece selection for mathematical captures ([cae3359](cae3359587))
* **rithmomachia:** add helpful error messages for failed captures ([b172440](b172440a41))
* **rithmomachia:** add initial board visual to guide Overview section ([d42bcff](d42bcff0d9))
* **rithmomachia:** Add interactive playing guide modal ([3121d82](3121d8240a))
* **rithmomachia:** add number bond visualization and helper placeholders ([82d8913](82d89131f0))
* **rithmomachia:** add ratio capture example to guide ([9150b0c](9150b0c678))
* **rithmomachia:** add standalone guide page route ([3fcc79f](3fcc79fe9e))
* **rithmomachia:** add useBoardLayout hook for centralized layout calculations ([27f1c98](27f1c989d5))
* **rithmomachia:** add usePieceSelection hook for selection state management ([275f401](275f401e3c))
* **rithmomachia:** add visual board examples to Capture section ([74bc3c0](74bc3c0dcf))
* **rithmomachia:** add visual board examples to Harmony section ([1d5f01c](1d5f01c966))
* **rithmomachia:** add visual winning example to Victory section ([b7fac78](b7fac78829))
* **rithmomachia:** auto-size tab labels with react-textfit ([9fd5406](9fd54067ce))
* **rithmomachia:** cycle through valid helpers with dynamic number tooltips ([4829e41](4829e41ea1))
* **rithmomachia:** enhance capture relation UI with smooth animations ([0a30801](0a308016e9))
* **rithmomachia:** enhance Harmony section with comprehensive content ([f555856](f5558563ea))
* **rithmomachia:** enhance Pieces section with visual examples and pyramid details ([55aff82](55aff829f4))
* **rithmomachia:** enhance Pyramid section with comprehensive details ([9fde1ef](9fde1ef9e7))
* **rithmomachia:** guide defaults to docked right on open ([11f674d](11f674d542))
* **rithmomachia:** improve guide pieces section layout ([a270bfc](a270bfc0cc))
* **rithmomachia:** improve guide UX and add persistence ([b314740](b314740697))
* **rithmomachia:** improve roster status notice UX ([e27df45](e27df45256))
* **rithmomachia:** integrate roster warning into game nav ([8a11594](8a11594203))
* **rithmomachia:** make guide modal ultra-responsive down to 150px width ([0474197](04741971b2))
* **rithmomachia:** recreate original guide modal header layout ([2489695](24896957d0))
* **rithmomachia:** show capture error on hover instead of click ([339b678](339b6780f6))
* **rithmomachia:** show pyramid face numbers on hover instead of selection ([b0c4523](b0c4523c0b))
* **rithmomachia:** show pyramid face numbers when selected ([5c186f3](5c186f3947))
* **rithmomachia:** show pyramid face numbers when selected with subtle animation ([5c2ddbe](5c2ddbef05))
* **rithmomachia:** show real preview layout when dragging guide to dock ([17d2460](17d2460a87))
* **rithmomachia:** simplify guide language for clarity ([85cb630](85cb630add))
* **rithmomachia:** skip helper selection UI and auto-select first valid helper ([be2a00e](be2a00e8b3))
* **rithmomachia:** Update harmony system to classical three-piece proportions ([08c9762](08c97620f5))
* **rithmomachia:** Update to traditional board setup with 25 pieces per side ([0769eaa](0769eaaa1d))
* **rithmomachia:** use actual piece SVGs in number bond with 2.5s rotation animation ([976a7de](976a7de949))
* **room-share:** add QR code button for easy mobile joining ([349290a](349290ac6a))
* show rithmomachia turn in nav ([7c89bfe](7c89bfef9c))
* switch to royal color theme with transparent background ([944ad65](944ad6574e)), closes [#fbbf24](https://github.com/antialias/soroban-abacus-flashcards/issues/fbbf24) [#f59e0](https://github.com/antialias/soroban-abacus-flashcards/issues/f59e0) [#a855f7](https://github.com/antialias/soroban-abacus-flashcards/issues/a855f7) [#7e22](https://github.com/antialias/soroban-abacus-flashcards/issues/7e22)
* **web:** add test page for AbacusStatic RSC compatibility ([903dea2](903dea2584))
* **web:** add test page for AbacusStatic Server Component ([3588d5a](3588d5acde))
* **web:** improve calendar abacus preview styling ([8439727](8439727b15))
* **web:** optimize monthly calendar for single-page layout ([b277a89](b277a89415))

### Bug Fixes

* **abacus-react:** correct column highlighting offset in AbacusStatic ([0641eb7](0641eb719e))
* add xmlns to AbacusStatic for Typst SVG parsing ([98cd019](98cd019d4a))
* adjust hero abacus position to avoid covering subtitle ([f03d341](f03d341314))
* **arcade:** add automatic retry for version conflict rejections ([fbcde25](fbcde2505f))
* **arcade:** allow deactivating players from users who left the room ([7c1c2d7](7c1c2d7beb))
* **arcade:** implement optimistic locking in session manager ([71fd66d](71fd66d96a))
* board rotation now properly fills height in portrait mode ([b5a96ea](b5a96eaeb1))
* **card-sorting:** add border radius to outer card container ([a922eba](a922eba73c))
* **card-sorting:** add debug logging for spring animations ([d42947e](d42947eb8d))
* **card-sorting:** add missing gameMode support after hard reset ([a832325](a832325deb))
* **card-sorting:** add missing useMemo import ([949d76d](949d76d844))
* **card-sorting:** add overflow hidden to clip rounded corners ([84c66fe](84c66feec6))
* **card-sorting:** adjust connecting paths for scaled cards ([829c741](829c741e55))
* **card-sorting:** adjust game board for spectator panels ([fc5cf12](fc5cf1216f))
* **card-sorting:** adjust viewport dimensions for spectator panels ([4dce16c](4dce16cca4))
* **card-sorting:** animate cards from game board to results grid ([17d45fe](17d45fe88c))
* **card-sorting:** correct suffix card detection in auto-arrange ([d02ab59](d02ab5922c))
* **card-sorting:** enable card scaling for spectators ([6b095c3](6b095c3383))
* **card-sorting:** enable New Game button during active gameplay ([f3f6eca](f3f6eca1db))
* **card-sorting:** end drag immediately when card becomes locked ([ae45298](ae45298ec4))
* **card-sorting:** filter local player from emoji overlays on dragged cards ([dc2d94a](dc2d94aaa5))
* **card-sorting:** fix results panel layout to not cover cards ([4b4fbfe](4b4fbfef32))
* **card-sorting:** hide activity notifications in spectator mode ([5cca279](5cca279687))
* **card-sorting:** keep arrow sequence numbers upright ([79c9469](79c94699fa))
* **card-sorting:** lock correctly positioned prefix/suffix cards ([170abed](170abed231))
* **card-sorting:** lock spring positions after initial animation completes ([275cc62](275cc62a52))
* **card-sorting:** New Game now restarts with same settings instantly ([f3687ed](f3687ed236))
* **card-sorting:** only shrink/fade cards in correct prefix ([51368c6](51368c6ec5))
* **card-sorting:** preserve card positions on pause/resume ([0d8af09](0d8af09517))
* **card-sorting:** preserve rotation when starting drag ([3364144](3364144fb6))
* **card-sorting:** prevent duplicate START_GAME moves on Play Again ([a0b14f8](a0b14f87e9))
* **card-sorting:** prevent ghost movements with proper optimistic updates ([bd014be](bd014bec4f))
* **card-sorting:** prevent infinite loop when all cards are correct ([34785f4](34785f466f))
* **card-sorting:** prevent infinite loop with tolerance-based position comparison ([627b873](627b873382))
* **card-sorting:** prevent position jump when clicking rotated cards ([564a00f](564a00f82b))
* **card-sorting:** prevent replaying own movements from server ([308168a](308168a7fb))
* **card-sorting:** prevent springs from reinitializing on window resize ([30953b8](30953b8c4a))
* **card-sorting:** prevent springs from resetting after animation ([8aff60c](8aff60ce3f))
* **card-sorting:** remove hasAnimatedRef logic causing backwards animation ([a44aa5a](a44aa5a4c2))
* **card-sorting:** remove remaining reveal numbers references ([15c53ea](15c53ea4eb))
* **card-sorting:** restore prefix/suffix card shrinking visual feedback ([f5fb4d7](f5fb4d7b76))
* **card-sorting:** show only active players in team members section ([fa9f1a5](fa9f1a568f))
* **card-sorting:** smooth scale animation while dragging cards ([0eefc33](0eefc332ac))
* **card-sorting:** stabilize inferred sequence for locked cards during drag ([b0cd194](b0cd194838))
* **card-sorting:** use empty deps array for useSprings to prevent recreation ([cee399e](cee399ed15))
* **card-sorting:** use ref to track initialized state and prevent re-animation ([f389afa](f389afa831))
* **card-sorting:** use same coordinate system for game board and results ([6972fdf](6972fdf110))
* **complement-race:** prevent delivery move thrashing in steam sprint mode ([e1258ee](e1258ee041))
* configure favicon metadata and improve bead visibility ([e1369fa](e1369fa275))
* copy entire packages/core and packages/templates ([0ccada0](0ccada0ca7))
* correct hero abacus scroll direction to flow with page content ([4232746](423274657c))
* correct Typst template path in Dockerfile ([4c518de](4c518decb7))
* delete existing user sessions before creating new ones ([0cced47](0cced47a0f))
* extract pure SVG content from AbacusReact renders ([b07f1c4](b07f1c4216))
* **games:** prevent horizontal page scroll from carousel overflow ([5a8c98f](5a8c98fc10))
* **games:** smooth scroll feel for carousel wheel navigation ([f80a73b](f80a73b35c))
* **games:** use specific transition properties for smooth carousel loop ([187271e](187271e515))
* **i18n:** eliminate FOUC by loading messages server-side ([4d4d930](4d4d930bd3))
* **i18n:** use useMessages() for tutorial translations ([95b0105](95b0105ca3))
* include column posts in favicon bounding box ([0b2f481](0b2f48106a))
* increase server update debounce to 2000ms for low bandwidth ([633ff12](633ff12750))
* Integrate threshold input into Point Victory card ([b29bbee](b29bbeefca))
* mark dynamic routes as force-dynamic to prevent static generation errors ([d7b35d9](d7b35d9544))
* **nav:** show full navigation on /games page ([d3fe6ac](d3fe6acbb0))
* **qr-button:** improve layout and z-index ([646a422](646a4228d0))
* **qr-button:** increase mini QR code size to 80px ([61ac737](61ac7378bd))
* **qr-button:** increase mini QR code to 84px ([3fae5ea](3fae5ea6fa))
* **qr-button:** make button square and increase QR size ([dc2d466](dc2d46663b))
* **qr-button:** match height of stacked buttons ([81f202d](81f202d215))
* reduce padding to minimize gap below last bead ([0e529be](0e529be789))
* remove distracting parallax and wobble 3D effects ([28a2d40](28a2d40996))
* remove wobble physics and enhance wood grain visibility ([5d97673](5d97673406))
* resolve z-index layering and hero abacus visibility issues ([ed9a050](ed9a050d64))
* rewrite 3D stories to use props instead of CSS wrappers ([26bdb11](26bdb11237))
* **rithmomachia:** add missing i18next dependencies ([91154d9](91154d9364))
* **rithmomachia:** add missing pyramid section keys to Japanese (ja.json) ([dae615e](dae615ee72))
* **rithmomachia:** adjust error dialog sizing to prevent text clipping ([cda1126](cda1126cb0))
* **rithmomachia:** adjust roster notice position to not overlap nav ([7093223](709322373a))
* **rithmomachia:** change undock icon to pop-out arrow ([2a91748](2a91748493))
* **rithmomachia:** correct board dimensions to 16x8 and restore original layout values ([cfac277](cfac277505))
* **rithmomachia:** Correct board setup to match reference image exactly ([618e563](618e56358d))
* **rithmomachia:** correct makeMove parameter types for capture handling ([aafb64f](aafb64f3e3))
* **rithmomachia:** fix guide modal resize drift by calculating from initial state ([1bcd99c](1bcd99c949))
* **rithmomachia:** fix harmony section translation structure for hi/ja/es ([14259a1](14259a19a9))
* **rithmomachia:** fix modal resizing zoom issue ([4fa20f4](4fa20f44cb))
* **rithmomachia:** Fix TypeScript errors in playing guide modal ([4834ece](4834ece98e))
* **rithmomachia:** handle pyramid pieces in hover error tooltip ([56f3164](56f3164155))
* **rithmomachia:** implement proper board cropping and highlighting in guide ([d0a8fcd](d0a8fcdea6))
* **rithmomachia:** improve guide modal tab navigation at narrow widths ([a673177](a673177bec))
* **rithmomachia:** reconnect player assignment UI and fix setup layout ([a1a0374](a1a0374fac))
* **rithmomachia:** render guide as docked in preview panel ([190f8cf](190f8cf302))
* **rithmomachia:** show actual values in tooltips for non-helper relations ([774c6b0](774c6b0ce7))
* **rithmomachia:** show guest-friendly message when they can't fix too many players ([54bfd2f](54bfd2fac8))
* **rithmomachia:** smooth guide dragging from docked state without jump ([8f4a79c](8f4a79c9b0))
* **rithmomachia:** validate move path before showing capture error on hover ([bd49964](bd49964186))
* **room-info:** hide Leave Room button when user is alone ([5927f61](5927f61c3c))
* separate horizontal and vertical bounding box logic ([83090df](83090df4df))
* tolerate OpenSCAD CGAL warnings if output file is created ([88993f3](88993f3662))
* use absolute positioning for hero abacus to eliminate scroll lag ([096104b](096104b094))
* use Debian base for deps stage to match runner for binary compatibility ([f8fe6e4](f8fe6e4a41))
* use default BOSL2 branch instead of non-existent v2.0.0 tag ([f4ffc5b](f4ffc5b027))
* use nested SVG viewBox for actual cropping, not just scaling ([440b492](440b492e85))
* various game improvements and UI enhancements ([b67cf61](b67cf610c5))
* **web:** add dynamic export to rithmomachia page ([329e623](329e623212))
* **web:** fix Typst PDF generation path resolution ([7ce1287](7ce1287525))
* **web:** use AbacusStatic for calendar SVG generation ([08c6a41](08c6a419e2))

### Performance Improvements

* optimize Docker image size to reduce build failures ([9ca3106](9ca3106361))

### Code Refactoring

* **card-sorting:** remove reveal numbers feature ([ea5e3e8](ea5e3e838b))
* **card-sorting:** send complete card sequence instead of individual moves ([e4df843](e4df8432b9))
* **games:** implement carousel, fix victories bug, add conditional stats ([82c133f](82c133f742))
* **games:** move page title to nav bar ([712ee58](712ee58e59))
* **games:** remove redundant subtitle below nav ([ad5bb87](ad5bb87325))
* **games:** remove wheel scrolling, enable overflow visible carousel ([876513c](876513c9cc))
* reorganize Harmony and Victory guide sections ([fb629c4](fb629c44ea))
* restructure /create page into hub with sub-pages ([b91b23d](b91b23d95f))
* **rithmomachia:** extract board and capture components (phase 2+3) ([a0a867b](a0a867b271))
* **rithmomachia:** extract CaptureErrorDialog component (Phase 2 partial) ([f0a066d](f0a066d8f0))
* **rithmomachia:** extract constants and coordinate utilities (Phase 1) ([eace0ed](eace0ed529))
* **rithmomachia:** extract guide sections into separate files ([765525d](765525dc45))
* **rithmomachia:** extract hooks (phase 5) ([324a659](324a65992f))
* **rithmomachia:** extract phase components (phase 4) ([11364f6](11364f6394))
* **rithmomachia:** extract reusable components from SetupPhase ([3abc325](3abc325ea2))
* **rithmomachia:** make setup phase UI more compact ([e55f848](e55f848a26))
* **rithmomachia:** redesign error notification with modern UI ([dfeeb0e](dfeeb0e0db)), closes [#1e293](https://github.com/antialias/soroban-abacus-flashcards/issues/1e293) [#0f172](https://github.com/antialias/soroban-abacus-flashcards/issues/0f172) [#f1f5f9](https://github.com/antialias/soroban-abacus-flashcards/issues/f1f5f9)
* **rithmomachia:** simplify capture error dialog to one-liner ([82a5eb2](82a5eb2e4b))
* **rithmomachia:** Update board setup to authoritative CSV layout ([0471da5](0471da598d))
* **rithmomachia:** update capture components to use CaptureContext ([2ab6ab5](2ab6ab5799))
* **rithmomachia:** use useBoardLayout and usePieceSelection in BoardDisplay ([0ab7a1d](0ab7a1df32))
* use AbacusReact for dynamic Open Graph image ([9c20f12](9c20f12bac))
* **web:** import utility functions from abacus-react ([7228bbc](7228bbc2eb))
* **web:** use ABACUS_THEMES instead of manual style definitions ([9f7f001](9f7f001d74))
* **web:** use compact prop for inline mini-abacus ([ff1d60a](ff1d60a233))

### Documentation

* **abacus-react:** add Storybook stories for AbacusStatic ([4f9dc46](4f9dc4666d))
* **abacus-react:** add Storybook stories for new features ([6a1cec0](6a1cec06a7))
* **abacus-react:** export AbacusStatic and update README ([74f2d97](74f2d97434))
* **abacus-react:** update documentation for new features ([35d8734](35d8734a3a))
* **abacus-react:** update README with /static import path for RSC ([72a4c2b](72a4c2b80c))
* add 3D enhancement documentation to README ([cc96802](cc96802df8))
* add database migration guide and playing guide modal spec ([5a29af7](5a29af78e2))
* add deployment verification guidelines to prevent false positives ([3d8da23](3d8da2348b))
* **card-sorting:** add comprehensive multiplayer plan ([008ccea](008ccead0f))
* **rithmomachia:** Add concise one-page playing guide ([e3c1f10](e3c1f10233))
* update workflow to require manual testing before commits ([0991796](0991796f1e))

### Styles

* **rithmomachia:** improve divider styling and make tabs responsive ([88ca35e](88ca35e044)), closes [#e5e7](https://github.com/antialias/soroban-abacus-flashcards/issues/e5e7) [#9ca3](https://github.com/antialias/soroban-abacus-flashcards/issues/9ca3)
* **rithmomachia:** improve pyramid face numbers visibility and contrast ([94e5e6a](94e5e6a268)), closes [#fbbf24](https://github.com/antialias/soroban-abacus-flashcards/issues/fbbf24) [#b45309](https://github.com/antialias/soroban-abacus-flashcards/issues/b45309)
* **rithmomachia:** increase pyramid face numbers size and boldness ([7bf2d73](7bf2d730d3))

### Tests

* trigger compose-updater deployment test ([2b06aae](2b06aae394))
* verify compose-updater automatic deployment cycle ([af0552c](af0552ccd9))
2025-11-04 02:20:19 +00:00
Thomas Hallock
b277a89415 feat(web): optimize monthly calendar for single-page layout
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>
2025-11-03 20:17:23 -06:00
semantic-release-bot
203f110b65 chore(abacus-react): release v2.7.1 [skip ci]
## [2.7.1](https://github.com/antialias/soroban-abacus-flashcards/compare/abacus-react-v2.7.0...abacus-react-v2.7.1) (2025-11-04)

### Bug Fixes

* add xmlns to AbacusStatic for Typst SVG parsing ([98cd019](98cd019d4a))
* **web:** use AbacusStatic for calendar SVG generation ([08c6a41](08c6a419e2))
2025-11-04 02:11:27 +00:00
Thomas Hallock
98cd019d4a fix: add xmlns to AbacusStatic for Typst SVG parsing
- 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>
2025-11-03 20:10:22 -06:00
semantic-release-bot
858a1b4976 chore(release): 4.68.0 [skip ci]
## [4.68.0](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.67.1...v4.68.0) (2025-11-04)

### Features

* **abacus-react:** add AbacusStatic for React Server Components ([3b8e864](3b8e864cfa))
* **abacus-react:** add core utility functions for state management ([e65541c](e65541c100))
* **abacus-react:** add layout and educational props ([35bbcec](35bbcecb9e))
* **abacus-react:** add pre-defined theme presets ([cf1f950](cf1f950c7c))
* **abacus-react:** add React hooks for abacus calculations ([de038d2](de038d2afc))
* **abacus-react:** add separate /static export path for React Server Components ([ed69f6b](ed69f6b917))
* **abacus-react:** export new utilities, hooks, and themes ([ce4e44d](ce4e44d630))
* **abacus:** add nativeAbacusNumbers setting to schema and UI ([79f7347](79f7347d48))
* add 3D printing support for abacus models ([dafdfdd](dafdfdd233))
* add comprehensive metadata, SEO, and make AbacusReact SSR-compatible ([0922ea1](0922ea10b7))
* add comprehensive Storybook coverage and migration guide ([7a4a37e](7a4a37ec6d))
* add game preview system with mock arcade environment ([25880cc](25880cc7e4))
* add per-player stats tracking system ([613301c](613301cd13))
* add Strategy & Tactics section to Rithmomachia guide ([81ead65](81ead65680))
* add unified trophy abacus with hero mode integration ([6620418](6620418a70))
* **arcade:** add ability to deactivate remote players without kicking user ([3628426](3628426a56))
* **arcade:** add native abacus numbers support to pressure gauge ([1d525c7](1d525c7b53))
* **arcade:** add Rithmomachia (Battle of Numbers) game ([2fc0a05](2fc0a05f7f))
* **arcade:** add yjs-demo collaborative game and Yjs persistence layer ([d568955](d568955d6a))
* **arcade:** auto-create room when user has none ([ff88c3a](ff88c3a1b8))
* **card-sorting:** add activity feed notifications for collaborative mode ([1461414](1461414ef4))
* **card-sorting:** add auto-submit countdown for perfect sequences ([780a716](780a7161bc))
* **card-sorting:** add bezier curves to connecting arrows ([4d8e873](4d8e873358))
* **card-sorting:** add CardPosition type and position syncing ([656f5a7](656f5a7838))
* **card-sorting:** add collapsible stats sidebar for spectators ([6527c26](6527c26a81))
* **card-sorting:** add game mode selector UI to setup phase ([d25b888](d25b888ffb))
* **card-sorting:** add GameMode type system for multiplayer support ([fd76533](fd765335ef))
* **card-sorting:** add green border to correctly positioned cards ([16fca86](16fca86b76)), closes [#22c55](https://github.com/antialias/soroban-abacus-flashcards/issues/22c55)
* **card-sorting:** add player emoji indicators on moving cards ([3a82099](3a82099757))
* **card-sorting:** add react-spring animations for real-time sync ([c367e0c](c367e0ceec))
* **card-sorting:** add smooth transition to drop shadow ([b0b93d0](b0b93d0175))
* **card-sorting:** add spectator mode UI enhancements ([ee7345d](ee7345d641)), closes [#6366f1](https://github.com/antialias/soroban-abacus-flashcards/issues/6366f1) [#8b5cf6](https://github.com/antialias/soroban-abacus-flashcards/issues/8b5cf6)
* **card-sorting:** add team scoring UI for collaborative mode ([ed6f177](ed6f177914)), closes [#a78](https://github.com/antialias/soroban-abacus-flashcards/issues/a78) [#8b5cf6](https://github.com/antialias/soroban-abacus-flashcards/issues/8b5cf6)
* **card-sorting:** add updateCardPositions action to Provider ([f6ed4a2](f6ed4a27a2))
* **card-sorting:** auto-arrange prefix/suffix cards in corners ([4ba7f24](4ba7f24717))
* **card-sorting:** fade correctly positioned cards to 50% opacity ([7028cfc](7028cfc511))
* **card-sorting:** gentler spring animation for locked cards ([47189cb](47189cb6e7))
* **card-sorting:** implement continuous bezier curve paths ([2d93024](2d9302410f))
* **card-sorting:** improve card distribution for natural scattered look ([0b0503f](0b0503f035))
* **card-sorting:** make player emoji fill entire card background ([2e7a02c](2e7a02c9e4))
* **card-sorting:** optimize results screen for mobile ([d188789](d188789069))
* **card-sorting:** redesign setup screen with modern UI ([73cf967](73cf967492))
* **card-sorting:** scale correctly positioned cards to 50% ([222dc55](222dc555fa))
* **card-sorting:** shrink/fade cards in correct suffix as well ([8f6feec](8f6feec4f2))
* **card-sorting:** smooth spring transition from game table to results grid ([c5f39d5](c5f39d51eb))
* **card-sorting:** wrap prefix/suffix cards to multiple rows ([e3184dd](e3184dd0d4))
* complete 3D enhancement integration for all three proposals ([5ac55cc](5ac55cc149))
* **create-room:** replace hardcoded game grid with dynamic Radix Select dropdown ([83d0ba2](83d0ba26f5))
* dynamic day-of-month favicon using subprocess pattern ([4d0795a](4d0795a9df))
* dynamically crop favicon to active beads for maximum size ([5670322](567032296a))
* enable 3D enhancement on hero/open MyAbacus modes ([37e330f](37e330f26e))
* **games:** add autoplay and improve carousel layout ([9f51edf](9f51edfaa9))
* **games:** add horizontal scroll support to carousels ([a224abb](a224abb6f6))
* **games:** add rotating games hero carousel ([24231e6](24231e6b2e))
* **i18n:** add dynamic locale switching without page reload ([fe9bfea](fe9bfeabf9))
* **i18n:** add global language selector to navigation ([0506360](0506360117))
* **i18n:** add homepage translations for all supported languages ([8c9d35a](8c9d35a3b4))
* **i18n:** add Old High German (goh) language support ([b334a15](b334a15255))
* **i18n:** complete Old High German translations for all locales ([0b06a1c](0b06a1ce00))
* **i18n:** internationalize games page and tutorial content ([4253964](4253964af1))
* **i18n:** internationalize homepage with English translations ([40cff14](40cff143c7))
* **i18n:** migrate from react-i18next to next-intl ([9016b76](9016b76024))
* **i18n:** update games page hero section copy ([6333c60](6333c60352))
* install embla-carousel-autoplay for games carousel ([946e5d1](946e5d1910))
* install embla-carousel-react for player profile carousel ([642ae95](642ae95738))
* internationalize guide page with 6 languages ([e9c320b](e9c320bb10))
* internationalize tutorial player ([26d41cf](26d41cfd05))
* optimize card sorting for mobile displays ([b443ee9](b443ee9cdc))
* Redesign Rithmomachia setup page with dramatic medieval theme ([6ae4d13](6ae4d13dc7))
* **rithmomachia:** add 80% opacity to guide modal when not hovered ([4a78485](4a78485d2e))
* **rithmomachia:** add CaptureContext for capture dialog state management ([d7eb957](d7eb957a8d))
* **rithmomachia:** add ghost panel preview for guide docking ([c0d6526](c0d6526d30))
* **rithmomachia:** add guide docking with resizable panels ([f457f1a](f457f1a1c2))
* **rithmomachia:** add helper piece selection for mathematical captures ([cae3359](cae3359587))
* **rithmomachia:** add helpful error messages for failed captures ([b172440](b172440a41))
* **rithmomachia:** add initial board visual to guide Overview section ([d42bcff](d42bcff0d9))
* **rithmomachia:** Add interactive playing guide modal ([3121d82](3121d8240a))
* **rithmomachia:** add number bond visualization and helper placeholders ([82d8913](82d89131f0))
* **rithmomachia:** add ratio capture example to guide ([9150b0c](9150b0c678))
* **rithmomachia:** add standalone guide page route ([3fcc79f](3fcc79fe9e))
* **rithmomachia:** add useBoardLayout hook for centralized layout calculations ([27f1c98](27f1c989d5))
* **rithmomachia:** add usePieceSelection hook for selection state management ([275f401](275f401e3c))
* **rithmomachia:** add visual board examples to Capture section ([74bc3c0](74bc3c0dcf))
* **rithmomachia:** add visual board examples to Harmony section ([1d5f01c](1d5f01c966))
* **rithmomachia:** add visual winning example to Victory section ([b7fac78](b7fac78829))
* **rithmomachia:** auto-size tab labels with react-textfit ([9fd5406](9fd54067ce))
* **rithmomachia:** cycle through valid helpers with dynamic number tooltips ([4829e41](4829e41ea1))
* **rithmomachia:** enhance capture relation UI with smooth animations ([0a30801](0a308016e9))
* **rithmomachia:** enhance Harmony section with comprehensive content ([f555856](f5558563ea))
* **rithmomachia:** enhance Pieces section with visual examples and pyramid details ([55aff82](55aff829f4))
* **rithmomachia:** enhance Pyramid section with comprehensive details ([9fde1ef](9fde1ef9e7))
* **rithmomachia:** guide defaults to docked right on open ([11f674d](11f674d542))
* **rithmomachia:** improve guide pieces section layout ([a270bfc](a270bfc0cc))
* **rithmomachia:** improve guide UX and add persistence ([b314740](b314740697))
* **rithmomachia:** improve roster status notice UX ([e27df45](e27df45256))
* **rithmomachia:** integrate roster warning into game nav ([8a11594](8a11594203))
* **rithmomachia:** make guide modal ultra-responsive down to 150px width ([0474197](04741971b2))
* **rithmomachia:** recreate original guide modal header layout ([2489695](24896957d0))
* **rithmomachia:** show capture error on hover instead of click ([339b678](339b6780f6))
* **rithmomachia:** show pyramid face numbers on hover instead of selection ([b0c4523](b0c4523c0b))
* **rithmomachia:** show pyramid face numbers when selected ([5c186f3](5c186f3947))
* **rithmomachia:** show pyramid face numbers when selected with subtle animation ([5c2ddbe](5c2ddbef05))
* **rithmomachia:** show real preview layout when dragging guide to dock ([17d2460](17d2460a87))
* **rithmomachia:** simplify guide language for clarity ([85cb630](85cb630add))
* **rithmomachia:** skip helper selection UI and auto-select first valid helper ([be2a00e](be2a00e8b3))
* **rithmomachia:** Update harmony system to classical three-piece proportions ([08c9762](08c97620f5))
* **rithmomachia:** Update to traditional board setup with 25 pieces per side ([0769eaa](0769eaaa1d))
* **rithmomachia:** use actual piece SVGs in number bond with 2.5s rotation animation ([976a7de](976a7de949))
* **room-share:** add QR code button for easy mobile joining ([349290a](349290ac6a))
* show rithmomachia turn in nav ([7c89bfe](7c89bfef9c))
* switch to royal color theme with transparent background ([944ad65](944ad6574e)), closes [#fbbf24](https://github.com/antialias/soroban-abacus-flashcards/issues/fbbf24) [#f59e0](https://github.com/antialias/soroban-abacus-flashcards/issues/f59e0) [#a855f7](https://github.com/antialias/soroban-abacus-flashcards/issues/a855f7) [#7e22](https://github.com/antialias/soroban-abacus-flashcards/issues/7e22)
* **web:** add test page for AbacusStatic RSC compatibility ([903dea2](903dea2584))
* **web:** add test page for AbacusStatic Server Component ([3588d5a](3588d5acde))
* **web:** improve calendar abacus preview styling ([8439727](8439727b15))

### Bug Fixes

* **abacus-react:** correct column highlighting offset in AbacusStatic ([0641eb7](0641eb719e))
* adjust hero abacus position to avoid covering subtitle ([f03d341](f03d341314))
* **arcade:** add automatic retry for version conflict rejections ([fbcde25](fbcde2505f))
* **arcade:** allow deactivating players from users who left the room ([7c1c2d7](7c1c2d7beb))
* **arcade:** implement optimistic locking in session manager ([71fd66d](71fd66d96a))
* board rotation now properly fills height in portrait mode ([b5a96ea](b5a96eaeb1))
* **card-sorting:** add border radius to outer card container ([a922eba](a922eba73c))
* **card-sorting:** add debug logging for spring animations ([d42947e](d42947eb8d))
* **card-sorting:** add missing gameMode support after hard reset ([a832325](a832325deb))
* **card-sorting:** add missing useMemo import ([949d76d](949d76d844))
* **card-sorting:** add overflow hidden to clip rounded corners ([84c66fe](84c66feec6))
* **card-sorting:** adjust connecting paths for scaled cards ([829c741](829c741e55))
* **card-sorting:** adjust game board for spectator panels ([fc5cf12](fc5cf1216f))
* **card-sorting:** adjust viewport dimensions for spectator panels ([4dce16c](4dce16cca4))
* **card-sorting:** animate cards from game board to results grid ([17d45fe](17d45fe88c))
* **card-sorting:** correct suffix card detection in auto-arrange ([d02ab59](d02ab5922c))
* **card-sorting:** enable card scaling for spectators ([6b095c3](6b095c3383))
* **card-sorting:** enable New Game button during active gameplay ([f3f6eca](f3f6eca1db))
* **card-sorting:** end drag immediately when card becomes locked ([ae45298](ae45298ec4))
* **card-sorting:** filter local player from emoji overlays on dragged cards ([dc2d94a](dc2d94aaa5))
* **card-sorting:** fix results panel layout to not cover cards ([4b4fbfe](4b4fbfef32))
* **card-sorting:** hide activity notifications in spectator mode ([5cca279](5cca279687))
* **card-sorting:** keep arrow sequence numbers upright ([79c9469](79c94699fa))
* **card-sorting:** lock correctly positioned prefix/suffix cards ([170abed](170abed231))
* **card-sorting:** lock spring positions after initial animation completes ([275cc62](275cc62a52))
* **card-sorting:** New Game now restarts with same settings instantly ([f3687ed](f3687ed236))
* **card-sorting:** only shrink/fade cards in correct prefix ([51368c6](51368c6ec5))
* **card-sorting:** preserve card positions on pause/resume ([0d8af09](0d8af09517))
* **card-sorting:** preserve rotation when starting drag ([3364144](3364144fb6))
* **card-sorting:** prevent duplicate START_GAME moves on Play Again ([a0b14f8](a0b14f87e9))
* **card-sorting:** prevent ghost movements with proper optimistic updates ([bd014be](bd014bec4f))
* **card-sorting:** prevent infinite loop when all cards are correct ([34785f4](34785f466f))
* **card-sorting:** prevent infinite loop with tolerance-based position comparison ([627b873](627b873382))
* **card-sorting:** prevent position jump when clicking rotated cards ([564a00f](564a00f82b))
* **card-sorting:** prevent replaying own movements from server ([308168a](308168a7fb))
* **card-sorting:** prevent springs from reinitializing on window resize ([30953b8](30953b8c4a))
* **card-sorting:** prevent springs from resetting after animation ([8aff60c](8aff60ce3f))
* **card-sorting:** remove hasAnimatedRef logic causing backwards animation ([a44aa5a](a44aa5a4c2))
* **card-sorting:** remove remaining reveal numbers references ([15c53ea](15c53ea4eb))
* **card-sorting:** restore prefix/suffix card shrinking visual feedback ([f5fb4d7](f5fb4d7b76))
* **card-sorting:** show only active players in team members section ([fa9f1a5](fa9f1a568f))
* **card-sorting:** smooth scale animation while dragging cards ([0eefc33](0eefc332ac))
* **card-sorting:** stabilize inferred sequence for locked cards during drag ([b0cd194](b0cd194838))
* **card-sorting:** use empty deps array for useSprings to prevent recreation ([cee399e](cee399ed15))
* **card-sorting:** use ref to track initialized state and prevent re-animation ([f389afa](f389afa831))
* **card-sorting:** use same coordinate system for game board and results ([6972fdf](6972fdf110))
* **complement-race:** prevent delivery move thrashing in steam sprint mode ([e1258ee](e1258ee041))
* configure favicon metadata and improve bead visibility ([e1369fa](e1369fa275))
* copy entire packages/core and packages/templates ([0ccada0](0ccada0ca7))
* correct hero abacus scroll direction to flow with page content ([4232746](423274657c))
* correct Typst template path in Dockerfile ([4c518de](4c518decb7))
* delete existing user sessions before creating new ones ([0cced47](0cced47a0f))
* extract pure SVG content from AbacusReact renders ([b07f1c4](b07f1c4216))
* **games:** prevent horizontal page scroll from carousel overflow ([5a8c98f](5a8c98fc10))
* **games:** smooth scroll feel for carousel wheel navigation ([f80a73b](f80a73b35c))
* **games:** use specific transition properties for smooth carousel loop ([187271e](187271e515))
* **i18n:** eliminate FOUC by loading messages server-side ([4d4d930](4d4d930bd3))
* **i18n:** use useMessages() for tutorial translations ([95b0105](95b0105ca3))
* include column posts in favicon bounding box ([0b2f481](0b2f48106a))
* increase server update debounce to 2000ms for low bandwidth ([633ff12](633ff12750))
* Integrate threshold input into Point Victory card ([b29bbee](b29bbeefca))
* mark dynamic routes as force-dynamic to prevent static generation errors ([d7b35d9](d7b35d9544))
* **nav:** show full navigation on /games page ([d3fe6ac](d3fe6acbb0))
* **qr-button:** improve layout and z-index ([646a422](646a4228d0))
* **qr-button:** increase mini QR code size to 80px ([61ac737](61ac7378bd))
* **qr-button:** increase mini QR code to 84px ([3fae5ea](3fae5ea6fa))
* **qr-button:** make button square and increase QR size ([dc2d466](dc2d46663b))
* **qr-button:** match height of stacked buttons ([81f202d](81f202d215))
* reduce padding to minimize gap below last bead ([0e529be](0e529be789))
* remove distracting parallax and wobble 3D effects ([28a2d40](28a2d40996))
* remove wobble physics and enhance wood grain visibility ([5d97673](5d97673406))
* resolve z-index layering and hero abacus visibility issues ([ed9a050](ed9a050d64))
* rewrite 3D stories to use props instead of CSS wrappers ([26bdb11](26bdb11237))
* **rithmomachia:** add missing i18next dependencies ([91154d9](91154d9364))
* **rithmomachia:** add missing pyramid section keys to Japanese (ja.json) ([dae615e](dae615ee72))
* **rithmomachia:** adjust error dialog sizing to prevent text clipping ([cda1126](cda1126cb0))
* **rithmomachia:** adjust roster notice position to not overlap nav ([7093223](709322373a))
* **rithmomachia:** change undock icon to pop-out arrow ([2a91748](2a91748493))
* **rithmomachia:** correct board dimensions to 16x8 and restore original layout values ([cfac277](cfac277505))
* **rithmomachia:** Correct board setup to match reference image exactly ([618e563](618e56358d))
* **rithmomachia:** correct makeMove parameter types for capture handling ([aafb64f](aafb64f3e3))
* **rithmomachia:** fix guide modal resize drift by calculating from initial state ([1bcd99c](1bcd99c949))
* **rithmomachia:** fix harmony section translation structure for hi/ja/es ([14259a1](14259a19a9))
* **rithmomachia:** fix modal resizing zoom issue ([4fa20f4](4fa20f44cb))
* **rithmomachia:** Fix TypeScript errors in playing guide modal ([4834ece](4834ece98e))
* **rithmomachia:** handle pyramid pieces in hover error tooltip ([56f3164](56f3164155))
* **rithmomachia:** implement proper board cropping and highlighting in guide ([d0a8fcd](d0a8fcdea6))
* **rithmomachia:** improve guide modal tab navigation at narrow widths ([a673177](a673177bec))
* **rithmomachia:** reconnect player assignment UI and fix setup layout ([a1a0374](a1a0374fac))
* **rithmomachia:** render guide as docked in preview panel ([190f8cf](190f8cf302))
* **rithmomachia:** show actual values in tooltips for non-helper relations ([774c6b0](774c6b0ce7))
* **rithmomachia:** show guest-friendly message when they can't fix too many players ([54bfd2f](54bfd2fac8))
* **rithmomachia:** smooth guide dragging from docked state without jump ([8f4a79c](8f4a79c9b0))
* **rithmomachia:** validate move path before showing capture error on hover ([bd49964](bd49964186))
* **room-info:** hide Leave Room button when user is alone ([5927f61](5927f61c3c))
* separate horizontal and vertical bounding box logic ([83090df](83090df4df))
* tolerate OpenSCAD CGAL warnings if output file is created ([88993f3](88993f3662))
* use absolute positioning for hero abacus to eliminate scroll lag ([096104b](096104b094))
* use Debian base for deps stage to match runner for binary compatibility ([f8fe6e4](f8fe6e4a41))
* use default BOSL2 branch instead of non-existent v2.0.0 tag ([f4ffc5b](f4ffc5b027))
* use nested SVG viewBox for actual cropping, not just scaling ([440b492](440b492e85))
* various game improvements and UI enhancements ([b67cf61](b67cf610c5))
* **web:** add dynamic export to rithmomachia page ([329e623](329e623212))
* **web:** fix Typst PDF generation path resolution ([7ce1287](7ce1287525))
* **web:** use AbacusStatic for calendar SVG generation ([08c6a41](08c6a419e2))

### Performance Improvements

* optimize Docker image size to reduce build failures ([9ca3106](9ca3106361))

### Code Refactoring

* **card-sorting:** remove reveal numbers feature ([ea5e3e8](ea5e3e838b))
* **card-sorting:** send complete card sequence instead of individual moves ([e4df843](e4df8432b9))
* **games:** implement carousel, fix victories bug, add conditional stats ([82c133f](82c133f742))
* **games:** move page title to nav bar ([712ee58](712ee58e59))
* **games:** remove redundant subtitle below nav ([ad5bb87](ad5bb87325))
* **games:** remove wheel scrolling, enable overflow visible carousel ([876513c](876513c9cc))
* reorganize Harmony and Victory guide sections ([fb629c4](fb629c44ea))
* restructure /create page into hub with sub-pages ([b91b23d](b91b23d95f))
* **rithmomachia:** extract board and capture components (phase 2+3) ([a0a867b](a0a867b271))
* **rithmomachia:** extract CaptureErrorDialog component (Phase 2 partial) ([f0a066d](f0a066d8f0))
* **rithmomachia:** extract constants and coordinate utilities (Phase 1) ([eace0ed](eace0ed529))
* **rithmomachia:** extract guide sections into separate files ([765525d](765525dc45))
* **rithmomachia:** extract hooks (phase 5) ([324a659](324a65992f))
* **rithmomachia:** extract phase components (phase 4) ([11364f6](11364f6394))
* **rithmomachia:** extract reusable components from SetupPhase ([3abc325](3abc325ea2))
* **rithmomachia:** make setup phase UI more compact ([e55f848](e55f848a26))
* **rithmomachia:** redesign error notification with modern UI ([dfeeb0e](dfeeb0e0db)), closes [#1e293](https://github.com/antialias/soroban-abacus-flashcards/issues/1e293) [#0f172](https://github.com/antialias/soroban-abacus-flashcards/issues/0f172) [#f1f5f9](https://github.com/antialias/soroban-abacus-flashcards/issues/f1f5f9)
* **rithmomachia:** simplify capture error dialog to one-liner ([82a5eb2](82a5eb2e4b))
* **rithmomachia:** Update board setup to authoritative CSV layout ([0471da5](0471da598d))
* **rithmomachia:** update capture components to use CaptureContext ([2ab6ab5](2ab6ab5799))
* **rithmomachia:** use useBoardLayout and usePieceSelection in BoardDisplay ([0ab7a1d](0ab7a1df32))
* use AbacusReact for dynamic Open Graph image ([9c20f12](9c20f12bac))
* **web:** import utility functions from abacus-react ([7228bbc](7228bbc2eb))
* **web:** use ABACUS_THEMES instead of manual style definitions ([9f7f001](9f7f001d74))
* **web:** use compact prop for inline mini-abacus ([ff1d60a](ff1d60a233))

### Documentation

* **abacus-react:** add Storybook stories for AbacusStatic ([4f9dc46](4f9dc4666d))
* **abacus-react:** add Storybook stories for new features ([6a1cec0](6a1cec06a7))
* **abacus-react:** export AbacusStatic and update README ([74f2d97](74f2d97434))
* **abacus-react:** update documentation for new features ([35d8734](35d8734a3a))
* **abacus-react:** update README with /static import path for RSC ([72a4c2b](72a4c2b80c))
* add 3D enhancement documentation to README ([cc96802](cc96802df8))
* add database migration guide and playing guide modal spec ([5a29af7](5a29af78e2))
* add deployment verification guidelines to prevent false positives ([3d8da23](3d8da2348b))
* **card-sorting:** add comprehensive multiplayer plan ([008ccea](008ccead0f))
* **rithmomachia:** Add concise one-page playing guide ([e3c1f10](e3c1f10233))
* update workflow to require manual testing before commits ([0991796](0991796f1e))

### Styles

* **rithmomachia:** improve divider styling and make tabs responsive ([88ca35e](88ca35e044)), closes [#e5e7](https://github.com/antialias/soroban-abacus-flashcards/issues/e5e7) [#9ca3](https://github.com/antialias/soroban-abacus-flashcards/issues/9ca3)
* **rithmomachia:** improve pyramid face numbers visibility and contrast ([94e5e6a](94e5e6a268)), closes [#fbbf24](https://github.com/antialias/soroban-abacus-flashcards/issues/fbbf24) [#b45309](https://github.com/antialias/soroban-abacus-flashcards/issues/b45309)
* **rithmomachia:** increase pyramid face numbers size and boldness ([7bf2d73](7bf2d730d3))

### Tests

* trigger compose-updater deployment test ([2b06aae](2b06aae394))
* verify compose-updater automatic deployment cycle ([af0552c](af0552ccd9))
2025-11-04 01:28:17 +00:00
Thomas Hallock
08c6a419e2 fix(web): use AbacusStatic for calendar SVG generation
- 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>
2025-11-03 19:25:18 -06:00
semantic-release-bot
a9664bdcb4 chore(abacus-react): release v2.7.0 [skip ci]
# [2.7.0](https://github.com/antialias/soroban-abacus-flashcards/compare/abacus-react-v2.6.0...abacus-react-v2.7.0) (2025-11-04)

### Bug Fixes

* **web:** add dynamic export to rithmomachia page ([329e623](329e623212))
* **web:** fix Typst PDF generation path resolution ([7ce1287](7ce1287525))

### Features

* **abacus-react:** add separate /static export path for React Server Components ([ed69f6b](ed69f6b917))
* **web:** add test page for AbacusStatic RSC compatibility ([903dea2](903dea2584))
* **web:** improve calendar abacus preview styling ([8439727](8439727b15))
2025-11-04 00:52:55 +00:00
Thomas Hallock
329e623212 fix(web): add dynamic export to rithmomachia page
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>
2025-11-03 18:51:46 -06:00
Thomas Hallock
8439727b15 feat(web): improve calendar abacus preview styling
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>
2025-11-03 18:51:46 -06:00
Thomas Hallock
7ce1287525 fix(web): fix Typst PDF generation path resolution
- 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>
2025-11-03 18:51:46 -06:00
Thomas Hallock
903dea2584 feat(web): add test page for AbacusStatic RSC compatibility
- 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>
2025-11-03 18:51:46 -06:00
Thomas Hallock
72a4c2b80c docs(abacus-react): update README with /static import path for RSC
- Document @soroban/abacus-react/static import path
- Add note about requiring /static for React Server Components
- Update code examples to use correct import path

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-03 18:51:46 -06:00
Thomas Hallock
ed69f6b917 feat(abacus-react): add separate /static export path for React Server Components
- Create src/static.ts entry point with only server-compatible exports
- Configure Vite to build separate static.es.js and static.cjs.js bundles
- Add @soroban/abacus-react/static export path in package.json
- Enables RSC usage without importing client-side hooks/animations

Fixes build errors when importing AbacusStatic in Next.js App Router pages
without "use client" directive.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-03 18:51:46 -06:00
semantic-release-bot
9d322301ef chore(abacus-react): release v2.6.0 [skip ci]
# [2.6.0](https://github.com/antialias/soroban-abacus-flashcards/compare/abacus-react-v2.5.0...abacus-react-v2.6.0) (2025-11-04)

### Bug Fixes

* **abacus-react:** correct column highlighting offset in AbacusStatic ([0641eb7](0641eb719e))

### Features

* **abacus-react:** add AbacusStatic for React Server Components ([3b8e864](3b8e864cfa))
* **web:** add test page for AbacusStatic Server Component ([3588d5a](3588d5acde))
2025-11-04 00:31:11 +00:00
Thomas Hallock
0641eb719e fix(abacus-react): correct column highlighting offset in AbacusStatic
Column highlights were positioned incorrectly - missing the padding offset
that beads and rods include. The highlight rectangles appeared shifted to
the left of the actual columns.

Fixed by adding padding to the X position calculation, matching how beads
and column posts are positioned.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-03 18:30:03 -06:00
Thomas Hallock
3588d5acde feat(web): add test page for AbacusStatic Server Component
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>
2025-11-03 18:29:16 -06:00
Thomas Hallock
74f2d97434 docs(abacus-react): export AbacusStatic and update README
Updates package exports to include:
- AbacusStatic component
- AbacusStaticConfig type

README additions:
- New "Static Display (Server Components)" section
- Comparison table: AbacusStatic vs AbacusReact
- Server Component usage examples
- Updated features list to highlight RSC support

Provides clear guidance on when to use each component based on
requirements (interactivity, animations, SSR/SSG).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-03 18:29:16 -06:00
Thomas Hallock
4f9dc4666d docs(abacus-react): add Storybook stories for AbacusStatic
Comprehensive documentation and examples demonstrating:
- Default usage and different values
- All color schemes (place-value, monochrome, heaven-earth, alternating)
- All bead shapes (circle, diamond, square)
- Compact mode for inline displays
- Hide inactive beads
- Theme presets (light, dark, trophy)
- Column highlighting and labels
- Scaling options
- Server Component usage examples
- Preview card grids

Highlights that AbacusStatic shares logic with AbacusReact (no duplication).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-03 18:29:16 -06:00
Thomas Hallock
3b8e864cfa feat(abacus-react): add AbacusStatic for React Server Components
Implements a pure, server-component-compatible static abacus renderer
that shares core business logic with AbacusReact without code duplication.

Key features:
- No "use client" directive - works in React Server Components
- No hooks, animations, or client-side JavaScript
- Shares utilities: numberToAbacusState, color scheme logic, positioning
- Supports all visual customization: color schemes, bead shapes, themes
- Full feature parity for static displays: compact mode, hide inactive,
  column highlighting & labels, custom styles

Implementation approach:
- AbacusStaticBead: Pure SVG bead component (no hooks/animations)
- AbacusStatic: Main component using shared AbacusUtils
- Dependency injection pattern: separate rendering paths for client vs server

Bundle impact: +4KB

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-03 18:29:16 -06:00
semantic-release-bot
7418adb959 chore(abacus-react): release v2.5.0 [skip ci]
# [2.5.0](https://github.com/antialias/soroban-abacus-flashcards/compare/abacus-react-v2.4.0...abacus-react-v2.5.0) (2025-11-03)

### Features

* **abacus-react:** add core utility functions for state management ([e65541c](e65541c100))
* **abacus-react:** add layout and educational props ([35bbcec](35bbcecb9e))
* **abacus-react:** add pre-defined theme presets ([cf1f950](cf1f950c7c))
* **abacus-react:** add React hooks for abacus calculations ([de038d2](de038d2afc))
* **abacus-react:** export new utilities, hooks, and themes ([ce4e44d](ce4e44d630))
2025-11-03 23:18:22 +00:00
Thomas Hallock
7228bbc2eb refactor(web): import utility functions from abacus-react
Re-export core utility functions from @soroban/abacus-react instead of
duplicating implementations:

- beadDiff.ts: Re-exports calculateBeadDiff, calculateBeadDiffFromValues,
  areStatesEqual, etc. Keeps app-specific calculateMultiStepBeadDiffs
  and validateBeadDiff.

- abacusInstructionGenerator.ts: Re-exports numberToAbacusState,
  calculateBeadChanges, and related types. Keeps app-specific
  tutorial generation logic.

- TutorialPlayer.tsx: Import calculateBeadDiffFromValues from abacus-react

- TutorialEditor.tsx: Import calculateBeadDiffFromValues from abacus-react

Eliminates ~200 lines of duplicate utility function implementations.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-03 17:17:17 -06:00
Thomas Hallock
ff1d60a233 refactor(web): use compact prop for inline mini-abacus
Replace manual customStyles with compact={true} in AbacusTarget.tsx.

Before: 7 props + customStyles override
After: compact={true} automatically handles frame visibility

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-03 17:17:17 -06:00
Thomas Hallock
9f7f001d74 refactor(web): use ABACUS_THEMES instead of manual style definitions
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>
2025-11-03 17:17:17 -06:00
Thomas Hallock
35d8734a3a docs(abacus-react): update documentation for new features
Update README.md with:
- Theme presets usage examples
- Compact mode examples
- Column highlighting and labels
- New hooks (useAbacusDiff, useAbacusState)
- Utility functions documentation
- Updated TypeScript imports

Add ENHANCEMENT_PLAN.md:
- Documents the analysis of apps/web customizations
- Implementation roadmap with completion status
- Impact metrics

Add INTEGRATION_SUMMARY.md:
- Summary of integrated features
- Code deduplication metrics (~260-300 lines eliminated)
- Files modified in both packages

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-03 17:17:17 -06:00
Thomas Hallock
6a1cec06a7 docs(abacus-react): add Storybook stories for new features
Add comprehensive Storybook stories demonstrating:
- All 6 theme presets (light, dark, trophy, translucent, solid, traditional)
- Compact mode for inline displays
- Frame visibility control
- Column highlighting and labels
- useAbacusDiff and useAbacusState hooks
- Utility functions
- Combined feature showcases

Stories are organized under "AbacusReact/Themes & Utilities".

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-03 17:17:17 -06:00
Thomas Hallock
ce4e44d630 feat(abacus-react): export new utilities, hooks, and themes
Update index.ts to export:
- ABACUS_THEMES and AbacusThemeName
- Utility functions (numberToAbacusState, calculateBeadDiff, etc.)
- React hooks (useAbacusDiff, useAbacusState)
- New types (BeadState, AbacusState, BeadDiffResult, etc.)

All new features are now available to consumers of the package.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-03 17:17:17 -06:00
Thomas Hallock
35bbcecb9e feat(abacus-react): add layout and educational props
Add new props to AbacusConfig:
- frameVisible: Show/hide column posts and reckoning bar
- compact: Compact layout for inline displays (implies frameVisible=false)
- highlightColumns: Highlight specific columns by index
- columnLabels: Optional educational labels for columns

These props simplify common use cases:
- Inline mini-abacus displays now use just compact={true}
- Column highlighting for tutorials without custom overlays
- Frame visibility control for different contexts

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-03 17:17:17 -06:00
Thomas Hallock
cf1f950c7c feat(abacus-react): add pre-defined theme presets
Add ABACUS_THEMES with 6 pre-defined themes:
- light: Solid white frame (light backgrounds)
- dark: Translucent white with glow (dark backgrounds)
- trophy: Golden frame (achievements/rewards)
- translucent: Nearly invisible (inline/minimal UI)
- solid: Black frame (high contrast/educational)
- traditional: Brown wooden (traditional soroban aesthetic)

Eliminates ~60 lines of duplicate theme style definitions in apps/web.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-03 17:17:17 -06:00
Thomas Hallock
de038d2afc feat(abacus-react): add React hooks for abacus calculations
Add AbacusHooks.ts with memoized hooks:
- useAbacusDiff: Calculate bead differences for tutorials
- useAbacusState: Convert numbers to abacus state

These hooks provide performant, memoized access to core utility
functions for use in React components.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-03 17:17:17 -06:00
Thomas Hallock
e65541c100 feat(abacus-react): add core utility functions for state management
Add AbacusUtils.ts with comprehensive utility functions:
- numberToAbacusState: Convert numbers to bead positions
- abacusStateToNumber: Convert bead positions to numbers
- calculateBeadChanges: Find bead differences between states
- calculateBeadDiff: Full diff with order and directions
- calculateBeadDiffFromValues: Convenience wrapper
- validateAbacusValue: Validate number ranges
- areStatesEqual: Compare abacus states

These utilities eliminate ~200 lines of duplicate code in apps/web.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-03 17:17:17 -06:00
semantic-release-bot
f4ec0689ff chore(release): 4.68.0 [skip ci]
## [4.68.0](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.67.1...v4.68.0) (2025-11-03)

### Features

* **abacus:** add nativeAbacusNumbers setting to schema and UI ([79f7347](79f7347d48))
* add 3D printing support for abacus models ([dafdfdd](dafdfdd233))
* add comprehensive metadata, SEO, and make AbacusReact SSR-compatible ([0922ea1](0922ea10b7))
* add comprehensive Storybook coverage and migration guide ([7a4a37e](7a4a37ec6d))
* add game preview system with mock arcade environment ([25880cc](25880cc7e4))
* add per-player stats tracking system ([613301c](613301cd13))
* add Strategy & Tactics section to Rithmomachia guide ([81ead65](81ead65680))
* add unified trophy abacus with hero mode integration ([6620418](6620418a70))
* **arcade:** add ability to deactivate remote players without kicking user ([3628426](3628426a56))
* **arcade:** add native abacus numbers support to pressure gauge ([1d525c7](1d525c7b53))
* **arcade:** add Rithmomachia (Battle of Numbers) game ([2fc0a05](2fc0a05f7f))
* **arcade:** add yjs-demo collaborative game and Yjs persistence layer ([d568955](d568955d6a))
* **arcade:** auto-create room when user has none ([ff88c3a](ff88c3a1b8))
* **card-sorting:** add activity feed notifications for collaborative mode ([1461414](1461414ef4))
* **card-sorting:** add auto-submit countdown for perfect sequences ([780a716](780a7161bc))
* **card-sorting:** add bezier curves to connecting arrows ([4d8e873](4d8e873358))
* **card-sorting:** add CardPosition type and position syncing ([656f5a7](656f5a7838))
* **card-sorting:** add collapsible stats sidebar for spectators ([6527c26](6527c26a81))
* **card-sorting:** add game mode selector UI to setup phase ([d25b888](d25b888ffb))
* **card-sorting:** add GameMode type system for multiplayer support ([fd76533](fd765335ef))
* **card-sorting:** add green border to correctly positioned cards ([16fca86](16fca86b76)), closes [#22c55](https://github.com/antialias/soroban-abacus-flashcards/issues/22c55)
* **card-sorting:** add player emoji indicators on moving cards ([3a82099](3a82099757))
* **card-sorting:** add react-spring animations for real-time sync ([c367e0c](c367e0ceec))
* **card-sorting:** add smooth transition to drop shadow ([b0b93d0](b0b93d0175))
* **card-sorting:** add spectator mode UI enhancements ([ee7345d](ee7345d641)), closes [#6366f1](https://github.com/antialias/soroban-abacus-flashcards/issues/6366f1) [#8b5cf6](https://github.com/antialias/soroban-abacus-flashcards/issues/8b5cf6)
* **card-sorting:** add team scoring UI for collaborative mode ([ed6f177](ed6f177914)), closes [#a78](https://github.com/antialias/soroban-abacus-flashcards/issues/a78) [#8b5cf6](https://github.com/antialias/soroban-abacus-flashcards/issues/8b5cf6)
* **card-sorting:** add updateCardPositions action to Provider ([f6ed4a2](f6ed4a27a2))
* **card-sorting:** auto-arrange prefix/suffix cards in corners ([4ba7f24](4ba7f24717))
* **card-sorting:** fade correctly positioned cards to 50% opacity ([7028cfc](7028cfc511))
* **card-sorting:** gentler spring animation for locked cards ([47189cb](47189cb6e7))
* **card-sorting:** implement continuous bezier curve paths ([2d93024](2d9302410f))
* **card-sorting:** improve card distribution for natural scattered look ([0b0503f](0b0503f035))
* **card-sorting:** make player emoji fill entire card background ([2e7a02c](2e7a02c9e4))
* **card-sorting:** optimize results screen for mobile ([d188789](d188789069))
* **card-sorting:** redesign setup screen with modern UI ([73cf967](73cf967492))
* **card-sorting:** scale correctly positioned cards to 50% ([222dc55](222dc555fa))
* **card-sorting:** shrink/fade cards in correct suffix as well ([8f6feec](8f6feec4f2))
* **card-sorting:** smooth spring transition from game table to results grid ([c5f39d5](c5f39d51eb))
* **card-sorting:** wrap prefix/suffix cards to multiple rows ([e3184dd](e3184dd0d4))
* complete 3D enhancement integration for all three proposals ([5ac55cc](5ac55cc149))
* **create-room:** replace hardcoded game grid with dynamic Radix Select dropdown ([83d0ba2](83d0ba26f5))
* dynamic day-of-month favicon using subprocess pattern ([4d0795a](4d0795a9df))
* dynamically crop favicon to active beads for maximum size ([5670322](567032296a))
* enable 3D enhancement on hero/open MyAbacus modes ([37e330f](37e330f26e))
* **games:** add autoplay and improve carousel layout ([9f51edf](9f51edfaa9))
* **games:** add horizontal scroll support to carousels ([a224abb](a224abb6f6))
* **games:** add rotating games hero carousel ([24231e6](24231e6b2e))
* **i18n:** add dynamic locale switching without page reload ([fe9bfea](fe9bfeabf9))
* **i18n:** add global language selector to navigation ([0506360](0506360117))
* **i18n:** add homepage translations for all supported languages ([8c9d35a](8c9d35a3b4))
* **i18n:** add Old High German (goh) language support ([b334a15](b334a15255))
* **i18n:** complete Old High German translations for all locales ([0b06a1c](0b06a1ce00))
* **i18n:** internationalize games page and tutorial content ([4253964](4253964af1))
* **i18n:** internationalize homepage with English translations ([40cff14](40cff143c7))
* **i18n:** migrate from react-i18next to next-intl ([9016b76](9016b76024))
* **i18n:** update games page hero section copy ([6333c60](6333c60352))
* install embla-carousel-autoplay for games carousel ([946e5d1](946e5d1910))
* install embla-carousel-react for player profile carousel ([642ae95](642ae95738))
* internationalize guide page with 6 languages ([e9c320b](e9c320bb10))
* internationalize tutorial player ([26d41cf](26d41cfd05))
* optimize card sorting for mobile displays ([b443ee9](b443ee9cdc))
* Redesign Rithmomachia setup page with dramatic medieval theme ([6ae4d13](6ae4d13dc7))
* **rithmomachia:** add 80% opacity to guide modal when not hovered ([4a78485](4a78485d2e))
* **rithmomachia:** add CaptureContext for capture dialog state management ([d7eb957](d7eb957a8d))
* **rithmomachia:** add ghost panel preview for guide docking ([c0d6526](c0d6526d30))
* **rithmomachia:** add guide docking with resizable panels ([f457f1a](f457f1a1c2))
* **rithmomachia:** add helper piece selection for mathematical captures ([cae3359](cae3359587))
* **rithmomachia:** add helpful error messages for failed captures ([b172440](b172440a41))
* **rithmomachia:** add initial board visual to guide Overview section ([d42bcff](d42bcff0d9))
* **rithmomachia:** Add interactive playing guide modal ([3121d82](3121d8240a))
* **rithmomachia:** add number bond visualization and helper placeholders ([82d8913](82d89131f0))
* **rithmomachia:** add ratio capture example to guide ([9150b0c](9150b0c678))
* **rithmomachia:** add standalone guide page route ([3fcc79f](3fcc79fe9e))
* **rithmomachia:** add useBoardLayout hook for centralized layout calculations ([27f1c98](27f1c989d5))
* **rithmomachia:** add usePieceSelection hook for selection state management ([275f401](275f401e3c))
* **rithmomachia:** add visual board examples to Capture section ([74bc3c0](74bc3c0dcf))
* **rithmomachia:** add visual board examples to Harmony section ([1d5f01c](1d5f01c966))
* **rithmomachia:** add visual winning example to Victory section ([b7fac78](b7fac78829))
* **rithmomachia:** auto-size tab labels with react-textfit ([9fd5406](9fd54067ce))
* **rithmomachia:** cycle through valid helpers with dynamic number tooltips ([4829e41](4829e41ea1))
* **rithmomachia:** enhance capture relation UI with smooth animations ([0a30801](0a308016e9))
* **rithmomachia:** enhance Harmony section with comprehensive content ([f555856](f5558563ea))
* **rithmomachia:** enhance Pieces section with visual examples and pyramid details ([55aff82](55aff829f4))
* **rithmomachia:** enhance Pyramid section with comprehensive details ([9fde1ef](9fde1ef9e7))
* **rithmomachia:** guide defaults to docked right on open ([11f674d](11f674d542))
* **rithmomachia:** improve guide pieces section layout ([a270bfc](a270bfc0cc))
* **rithmomachia:** improve guide UX and add persistence ([b314740](b314740697))
* **rithmomachia:** improve roster status notice UX ([e27df45](e27df45256))
* **rithmomachia:** integrate roster warning into game nav ([8a11594](8a11594203))
* **rithmomachia:** make guide modal ultra-responsive down to 150px width ([0474197](04741971b2))
* **rithmomachia:** recreate original guide modal header layout ([2489695](24896957d0))
* **rithmomachia:** show capture error on hover instead of click ([339b678](339b6780f6))
* **rithmomachia:** show pyramid face numbers on hover instead of selection ([b0c4523](b0c4523c0b))
* **rithmomachia:** show pyramid face numbers when selected ([5c186f3](5c186f3947))
* **rithmomachia:** show pyramid face numbers when selected with subtle animation ([5c2ddbe](5c2ddbef05))
* **rithmomachia:** show real preview layout when dragging guide to dock ([17d2460](17d2460a87))
* **rithmomachia:** simplify guide language for clarity ([85cb630](85cb630add))
* **rithmomachia:** skip helper selection UI and auto-select first valid helper ([be2a00e](be2a00e8b3))
* **rithmomachia:** Update harmony system to classical three-piece proportions ([08c9762](08c97620f5))
* **rithmomachia:** Update to traditional board setup with 25 pieces per side ([0769eaa](0769eaaa1d))
* **rithmomachia:** use actual piece SVGs in number bond with 2.5s rotation animation ([976a7de](976a7de949))
* **room-share:** add QR code button for easy mobile joining ([349290a](349290ac6a))
* show rithmomachia turn in nav ([7c89bfe](7c89bfef9c))
* switch to royal color theme with transparent background ([944ad65](944ad6574e)), closes [#fbbf24](https://github.com/antialias/soroban-abacus-flashcards/issues/fbbf24) [#f59e0](https://github.com/antialias/soroban-abacus-flashcards/issues/f59e0) [#a855f7](https://github.com/antialias/soroban-abacus-flashcards/issues/a855f7) [#7e22](https://github.com/antialias/soroban-abacus-flashcards/issues/7e22)

### Bug Fixes

* adjust hero abacus position to avoid covering subtitle ([f03d341](f03d341314))
* **arcade:** add automatic retry for version conflict rejections ([fbcde25](fbcde2505f))
* **arcade:** allow deactivating players from users who left the room ([7c1c2d7](7c1c2d7beb))
* **arcade:** implement optimistic locking in session manager ([71fd66d](71fd66d96a))
* board rotation now properly fills height in portrait mode ([b5a96ea](b5a96eaeb1))
* **card-sorting:** add border radius to outer card container ([a922eba](a922eba73c))
* **card-sorting:** add debug logging for spring animations ([d42947e](d42947eb8d))
* **card-sorting:** add missing gameMode support after hard reset ([a832325](a832325deb))
* **card-sorting:** add missing useMemo import ([949d76d](949d76d844))
* **card-sorting:** add overflow hidden to clip rounded corners ([84c66fe](84c66feec6))
* **card-sorting:** adjust connecting paths for scaled cards ([829c741](829c741e55))
* **card-sorting:** adjust game board for spectator panels ([fc5cf12](fc5cf1216f))
* **card-sorting:** adjust viewport dimensions for spectator panels ([4dce16c](4dce16cca4))
* **card-sorting:** animate cards from game board to results grid ([17d45fe](17d45fe88c))
* **card-sorting:** correct suffix card detection in auto-arrange ([d02ab59](d02ab5922c))
* **card-sorting:** enable card scaling for spectators ([6b095c3](6b095c3383))
* **card-sorting:** enable New Game button during active gameplay ([f3f6eca](f3f6eca1db))
* **card-sorting:** end drag immediately when card becomes locked ([ae45298](ae45298ec4))
* **card-sorting:** filter local player from emoji overlays on dragged cards ([dc2d94a](dc2d94aaa5))
* **card-sorting:** fix results panel layout to not cover cards ([4b4fbfe](4b4fbfef32))
* **card-sorting:** hide activity notifications in spectator mode ([5cca279](5cca279687))
* **card-sorting:** keep arrow sequence numbers upright ([79c9469](79c94699fa))
* **card-sorting:** lock correctly positioned prefix/suffix cards ([170abed](170abed231))
* **card-sorting:** lock spring positions after initial animation completes ([275cc62](275cc62a52))
* **card-sorting:** New Game now restarts with same settings instantly ([f3687ed](f3687ed236))
* **card-sorting:** only shrink/fade cards in correct prefix ([51368c6](51368c6ec5))
* **card-sorting:** preserve card positions on pause/resume ([0d8af09](0d8af09517))
* **card-sorting:** preserve rotation when starting drag ([3364144](3364144fb6))
* **card-sorting:** prevent duplicate START_GAME moves on Play Again ([a0b14f8](a0b14f87e9))
* **card-sorting:** prevent ghost movements with proper optimistic updates ([bd014be](bd014bec4f))
* **card-sorting:** prevent infinite loop when all cards are correct ([34785f4](34785f466f))
* **card-sorting:** prevent infinite loop with tolerance-based position comparison ([627b873](627b873382))
* **card-sorting:** prevent position jump when clicking rotated cards ([564a00f](564a00f82b))
* **card-sorting:** prevent replaying own movements from server ([308168a](308168a7fb))
* **card-sorting:** prevent springs from reinitializing on window resize ([30953b8](30953b8c4a))
* **card-sorting:** prevent springs from resetting after animation ([8aff60c](8aff60ce3f))
* **card-sorting:** remove hasAnimatedRef logic causing backwards animation ([a44aa5a](a44aa5a4c2))
* **card-sorting:** remove remaining reveal numbers references ([15c53ea](15c53ea4eb))
* **card-sorting:** restore prefix/suffix card shrinking visual feedback ([f5fb4d7](f5fb4d7b76))
* **card-sorting:** show only active players in team members section ([fa9f1a5](fa9f1a568f))
* **card-sorting:** smooth scale animation while dragging cards ([0eefc33](0eefc332ac))
* **card-sorting:** stabilize inferred sequence for locked cards during drag ([b0cd194](b0cd194838))
* **card-sorting:** use empty deps array for useSprings to prevent recreation ([cee399e](cee399ed15))
* **card-sorting:** use ref to track initialized state and prevent re-animation ([f389afa](f389afa831))
* **card-sorting:** use same coordinate system for game board and results ([6972fdf](6972fdf110))
* **complement-race:** prevent delivery move thrashing in steam sprint mode ([e1258ee](e1258ee041))
* configure favicon metadata and improve bead visibility ([e1369fa](e1369fa275))
* copy entire packages/core and packages/templates ([0ccada0](0ccada0ca7))
* correct hero abacus scroll direction to flow with page content ([4232746](423274657c))
* correct Typst template path in Dockerfile ([4c518de](4c518decb7))
* delete existing user sessions before creating new ones ([0cced47](0cced47a0f))
* extract pure SVG content from AbacusReact renders ([b07f1c4](b07f1c4216))
* **games:** prevent horizontal page scroll from carousel overflow ([5a8c98f](5a8c98fc10))
* **games:** smooth scroll feel for carousel wheel navigation ([f80a73b](f80a73b35c))
* **games:** use specific transition properties for smooth carousel loop ([187271e](187271e515))
* **i18n:** eliminate FOUC by loading messages server-side ([4d4d930](4d4d930bd3))
* **i18n:** use useMessages() for tutorial translations ([95b0105](95b0105ca3))
* include column posts in favicon bounding box ([0b2f481](0b2f48106a))
* increase server update debounce to 2000ms for low bandwidth ([633ff12](633ff12750))
* Integrate threshold input into Point Victory card ([b29bbee](b29bbeefca))
* mark dynamic routes as force-dynamic to prevent static generation errors ([d7b35d9](d7b35d9544))
* **nav:** show full navigation on /games page ([d3fe6ac](d3fe6acbb0))
* **qr-button:** improve layout and z-index ([646a422](646a4228d0))
* **qr-button:** increase mini QR code size to 80px ([61ac737](61ac7378bd))
* **qr-button:** increase mini QR code to 84px ([3fae5ea](3fae5ea6fa))
* **qr-button:** make button square and increase QR size ([dc2d466](dc2d46663b))
* **qr-button:** match height of stacked buttons ([81f202d](81f202d215))
* reduce padding to minimize gap below last bead ([0e529be](0e529be789))
* remove distracting parallax and wobble 3D effects ([28a2d40](28a2d40996))
* remove wobble physics and enhance wood grain visibility ([5d97673](5d97673406))
* resolve z-index layering and hero abacus visibility issues ([ed9a050](ed9a050d64))
* rewrite 3D stories to use props instead of CSS wrappers ([26bdb11](26bdb11237))
* **rithmomachia:** add missing i18next dependencies ([91154d9](91154d9364))
* **rithmomachia:** add missing pyramid section keys to Japanese (ja.json) ([dae615e](dae615ee72))
* **rithmomachia:** adjust error dialog sizing to prevent text clipping ([cda1126](cda1126cb0))
* **rithmomachia:** adjust roster notice position to not overlap nav ([7093223](709322373a))
* **rithmomachia:** change undock icon to pop-out arrow ([2a91748](2a91748493))
* **rithmomachia:** correct board dimensions to 16x8 and restore original layout values ([cfac277](cfac277505))
* **rithmomachia:** Correct board setup to match reference image exactly ([618e563](618e56358d))
* **rithmomachia:** correct makeMove parameter types for capture handling ([aafb64f](aafb64f3e3))
* **rithmomachia:** fix guide modal resize drift by calculating from initial state ([1bcd99c](1bcd99c949))
* **rithmomachia:** fix harmony section translation structure for hi/ja/es ([14259a1](14259a19a9))
* **rithmomachia:** fix modal resizing zoom issue ([4fa20f4](4fa20f44cb))
* **rithmomachia:** Fix TypeScript errors in playing guide modal ([4834ece](4834ece98e))
* **rithmomachia:** handle pyramid pieces in hover error tooltip ([56f3164](56f3164155))
* **rithmomachia:** implement proper board cropping and highlighting in guide ([d0a8fcd](d0a8fcdea6))
* **rithmomachia:** improve guide modal tab navigation at narrow widths ([a673177](a673177bec))
* **rithmomachia:** reconnect player assignment UI and fix setup layout ([a1a0374](a1a0374fac))
* **rithmomachia:** render guide as docked in preview panel ([190f8cf](190f8cf302))
* **rithmomachia:** show actual values in tooltips for non-helper relations ([774c6b0](774c6b0ce7))
* **rithmomachia:** show guest-friendly message when they can't fix too many players ([54bfd2f](54bfd2fac8))
* **rithmomachia:** smooth guide dragging from docked state without jump ([8f4a79c](8f4a79c9b0))
* **rithmomachia:** validate move path before showing capture error on hover ([bd49964](bd49964186))
* **room-info:** hide Leave Room button when user is alone ([5927f61](5927f61c3c))
* separate horizontal and vertical bounding box logic ([83090df](83090df4df))
* tolerate OpenSCAD CGAL warnings if output file is created ([88993f3](88993f3662))
* use absolute positioning for hero abacus to eliminate scroll lag ([096104b](096104b094))
* use Debian base for deps stage to match runner for binary compatibility ([f8fe6e4](f8fe6e4a41))
* use default BOSL2 branch instead of non-existent v2.0.0 tag ([f4ffc5b](f4ffc5b027))
* use nested SVG viewBox for actual cropping, not just scaling ([440b492](440b492e85))
* various game improvements and UI enhancements ([b67cf61](b67cf610c5))

### Performance Improvements

* optimize Docker image size to reduce build failures ([9ca3106](9ca3106361))

### Code Refactoring

* **card-sorting:** remove reveal numbers feature ([ea5e3e8](ea5e3e838b))
* **card-sorting:** send complete card sequence instead of individual moves ([e4df843](e4df8432b9))
* **games:** implement carousel, fix victories bug, add conditional stats ([82c133f](82c133f742))
* **games:** move page title to nav bar ([712ee58](712ee58e59))
* **games:** remove redundant subtitle below nav ([ad5bb87](ad5bb87325))
* **games:** remove wheel scrolling, enable overflow visible carousel ([876513c](876513c9cc))
* reorganize Harmony and Victory guide sections ([fb629c4](fb629c44ea))
* restructure /create page into hub with sub-pages ([b91b23d](b91b23d95f))
* **rithmomachia:** extract board and capture components (phase 2+3) ([a0a867b](a0a867b271))
* **rithmomachia:** extract CaptureErrorDialog component (Phase 2 partial) ([f0a066d](f0a066d8f0))
* **rithmomachia:** extract constants and coordinate utilities (Phase 1) ([eace0ed](eace0ed529))
* **rithmomachia:** extract guide sections into separate files ([765525d](765525dc45))
* **rithmomachia:** extract hooks (phase 5) ([324a659](324a65992f))
* **rithmomachia:** extract phase components (phase 4) ([11364f6](11364f6394))
* **rithmomachia:** extract reusable components from SetupPhase ([3abc325](3abc325ea2))
* **rithmomachia:** make setup phase UI more compact ([e55f848](e55f848a26))
* **rithmomachia:** redesign error notification with modern UI ([dfeeb0e](dfeeb0e0db)), closes [#1e293](https://github.com/antialias/soroban-abacus-flashcards/issues/1e293) [#0f172](https://github.com/antialias/soroban-abacus-flashcards/issues/0f172) [#f1f5f9](https://github.com/antialias/soroban-abacus-flashcards/issues/f1f5f9)
* **rithmomachia:** simplify capture error dialog to one-liner ([82a5eb2](82a5eb2e4b))
* **rithmomachia:** Update board setup to authoritative CSV layout ([0471da5](0471da598d))
* **rithmomachia:** update capture components to use CaptureContext ([2ab6ab5](2ab6ab5799))
* **rithmomachia:** use useBoardLayout and usePieceSelection in BoardDisplay ([0ab7a1d](0ab7a1df32))
* use AbacusReact for dynamic Open Graph image ([9c20f12](9c20f12bac))

### Documentation

* add 3D enhancement documentation to README ([cc96802](cc96802df8))
* add database migration guide and playing guide modal spec ([5a29af7](5a29af78e2))
* add deployment verification guidelines to prevent false positives ([3d8da23](3d8da2348b))
* **card-sorting:** add comprehensive multiplayer plan ([008ccea](008ccead0f))
* **rithmomachia:** Add concise one-page playing guide ([e3c1f10](e3c1f10233))
* update workflow to require manual testing before commits ([0991796](0991796f1e))

### Styles

* **rithmomachia:** improve divider styling and make tabs responsive ([88ca35e](88ca35e044)), closes [#e5e7](https://github.com/antialias/soroban-abacus-flashcards/issues/e5e7) [#9ca3](https://github.com/antialias/soroban-abacus-flashcards/issues/9ca3)
* **rithmomachia:** improve pyramid face numbers visibility and contrast ([94e5e6a](94e5e6a268)), closes [#fbbf24](https://github.com/antialias/soroban-abacus-flashcards/issues/fbbf24) [#b45309](https://github.com/antialias/soroban-abacus-flashcards/issues/b45309)
* **rithmomachia:** increase pyramid face numbers size and boldness ([7bf2d73](7bf2d730d3))

### Tests

* trigger compose-updater deployment test ([2b06aae](2b06aae394))
* verify compose-updater automatic deployment cycle ([af0552c](af0552ccd9))
2025-11-03 23:06:59 +00:00
Thomas Hallock
af0552ccd9 test: verify compose-updater automatic deployment cycle
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>
2025-11-03 17:04:05 -06:00
semantic-release-bot
90421cfc38 chore(release): 4.68.0 [skip ci]
## [4.68.0](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.67.1...v4.68.0) (2025-11-03)

### Features

* **abacus:** add nativeAbacusNumbers setting to schema and UI ([79f7347](79f7347d48))
* add 3D printing support for abacus models ([dafdfdd](dafdfdd233))
* add comprehensive metadata, SEO, and make AbacusReact SSR-compatible ([0922ea1](0922ea10b7))
* add comprehensive Storybook coverage and migration guide ([7a4a37e](7a4a37ec6d))
* add game preview system with mock arcade environment ([25880cc](25880cc7e4))
* add per-player stats tracking system ([613301c](613301cd13))
* add Strategy & Tactics section to Rithmomachia guide ([81ead65](81ead65680))
* add unified trophy abacus with hero mode integration ([6620418](6620418a70))
* **arcade:** add ability to deactivate remote players without kicking user ([3628426](3628426a56))
* **arcade:** add native abacus numbers support to pressure gauge ([1d525c7](1d525c7b53))
* **arcade:** add Rithmomachia (Battle of Numbers) game ([2fc0a05](2fc0a05f7f))
* **arcade:** add yjs-demo collaborative game and Yjs persistence layer ([d568955](d568955d6a))
* **arcade:** auto-create room when user has none ([ff88c3a](ff88c3a1b8))
* **card-sorting:** add activity feed notifications for collaborative mode ([1461414](1461414ef4))
* **card-sorting:** add auto-submit countdown for perfect sequences ([780a716](780a7161bc))
* **card-sorting:** add bezier curves to connecting arrows ([4d8e873](4d8e873358))
* **card-sorting:** add CardPosition type and position syncing ([656f5a7](656f5a7838))
* **card-sorting:** add collapsible stats sidebar for spectators ([6527c26](6527c26a81))
* **card-sorting:** add game mode selector UI to setup phase ([d25b888](d25b888ffb))
* **card-sorting:** add GameMode type system for multiplayer support ([fd76533](fd765335ef))
* **card-sorting:** add green border to correctly positioned cards ([16fca86](16fca86b76)), closes [#22c55](https://github.com/antialias/soroban-abacus-flashcards/issues/22c55)
* **card-sorting:** add player emoji indicators on moving cards ([3a82099](3a82099757))
* **card-sorting:** add react-spring animations for real-time sync ([c367e0c](c367e0ceec))
* **card-sorting:** add smooth transition to drop shadow ([b0b93d0](b0b93d0175))
* **card-sorting:** add spectator mode UI enhancements ([ee7345d](ee7345d641)), closes [#6366f1](https://github.com/antialias/soroban-abacus-flashcards/issues/6366f1) [#8b5cf6](https://github.com/antialias/soroban-abacus-flashcards/issues/8b5cf6)
* **card-sorting:** add team scoring UI for collaborative mode ([ed6f177](ed6f177914)), closes [#a78](https://github.com/antialias/soroban-abacus-flashcards/issues/a78) [#8b5cf6](https://github.com/antialias/soroban-abacus-flashcards/issues/8b5cf6)
* **card-sorting:** add updateCardPositions action to Provider ([f6ed4a2](f6ed4a27a2))
* **card-sorting:** auto-arrange prefix/suffix cards in corners ([4ba7f24](4ba7f24717))
* **card-sorting:** fade correctly positioned cards to 50% opacity ([7028cfc](7028cfc511))
* **card-sorting:** gentler spring animation for locked cards ([47189cb](47189cb6e7))
* **card-sorting:** implement continuous bezier curve paths ([2d93024](2d9302410f))
* **card-sorting:** improve card distribution for natural scattered look ([0b0503f](0b0503f035))
* **card-sorting:** make player emoji fill entire card background ([2e7a02c](2e7a02c9e4))
* **card-sorting:** optimize results screen for mobile ([d188789](d188789069))
* **card-sorting:** redesign setup screen with modern UI ([73cf967](73cf967492))
* **card-sorting:** scale correctly positioned cards to 50% ([222dc55](222dc555fa))
* **card-sorting:** shrink/fade cards in correct suffix as well ([8f6feec](8f6feec4f2))
* **card-sorting:** smooth spring transition from game table to results grid ([c5f39d5](c5f39d51eb))
* **card-sorting:** wrap prefix/suffix cards to multiple rows ([e3184dd](e3184dd0d4))
* complete 3D enhancement integration for all three proposals ([5ac55cc](5ac55cc149))
* **create-room:** replace hardcoded game grid with dynamic Radix Select dropdown ([83d0ba2](83d0ba26f5))
* dynamic day-of-month favicon using subprocess pattern ([4d0795a](4d0795a9df))
* dynamically crop favicon to active beads for maximum size ([5670322](567032296a))
* enable 3D enhancement on hero/open MyAbacus modes ([37e330f](37e330f26e))
* **games:** add autoplay and improve carousel layout ([9f51edf](9f51edfaa9))
* **games:** add horizontal scroll support to carousels ([a224abb](a224abb6f6))
* **games:** add rotating games hero carousel ([24231e6](24231e6b2e))
* **i18n:** add dynamic locale switching without page reload ([fe9bfea](fe9bfeabf9))
* **i18n:** add global language selector to navigation ([0506360](0506360117))
* **i18n:** add homepage translations for all supported languages ([8c9d35a](8c9d35a3b4))
* **i18n:** add Old High German (goh) language support ([b334a15](b334a15255))
* **i18n:** complete Old High German translations for all locales ([0b06a1c](0b06a1ce00))
* **i18n:** internationalize games page and tutorial content ([4253964](4253964af1))
* **i18n:** internationalize homepage with English translations ([40cff14](40cff143c7))
* **i18n:** migrate from react-i18next to next-intl ([9016b76](9016b76024))
* **i18n:** update games page hero section copy ([6333c60](6333c60352))
* install embla-carousel-autoplay for games carousel ([946e5d1](946e5d1910))
* install embla-carousel-react for player profile carousel ([642ae95](642ae95738))
* internationalize guide page with 6 languages ([e9c320b](e9c320bb10))
* internationalize tutorial player ([26d41cf](26d41cfd05))
* optimize card sorting for mobile displays ([b443ee9](b443ee9cdc))
* Redesign Rithmomachia setup page with dramatic medieval theme ([6ae4d13](6ae4d13dc7))
* **rithmomachia:** add 80% opacity to guide modal when not hovered ([4a78485](4a78485d2e))
* **rithmomachia:** add CaptureContext for capture dialog state management ([d7eb957](d7eb957a8d))
* **rithmomachia:** add ghost panel preview for guide docking ([c0d6526](c0d6526d30))
* **rithmomachia:** add guide docking with resizable panels ([f457f1a](f457f1a1c2))
* **rithmomachia:** add helper piece selection for mathematical captures ([cae3359](cae3359587))
* **rithmomachia:** add helpful error messages for failed captures ([b172440](b172440a41))
* **rithmomachia:** add initial board visual to guide Overview section ([d42bcff](d42bcff0d9))
* **rithmomachia:** Add interactive playing guide modal ([3121d82](3121d8240a))
* **rithmomachia:** add number bond visualization and helper placeholders ([82d8913](82d89131f0))
* **rithmomachia:** add ratio capture example to guide ([9150b0c](9150b0c678))
* **rithmomachia:** add standalone guide page route ([3fcc79f](3fcc79fe9e))
* **rithmomachia:** add useBoardLayout hook for centralized layout calculations ([27f1c98](27f1c989d5))
* **rithmomachia:** add usePieceSelection hook for selection state management ([275f401](275f401e3c))
* **rithmomachia:** add visual board examples to Capture section ([74bc3c0](74bc3c0dcf))
* **rithmomachia:** add visual board examples to Harmony section ([1d5f01c](1d5f01c966))
* **rithmomachia:** add visual winning example to Victory section ([b7fac78](b7fac78829))
* **rithmomachia:** auto-size tab labels with react-textfit ([9fd5406](9fd54067ce))
* **rithmomachia:** cycle through valid helpers with dynamic number tooltips ([4829e41](4829e41ea1))
* **rithmomachia:** enhance capture relation UI with smooth animations ([0a30801](0a308016e9))
* **rithmomachia:** enhance Harmony section with comprehensive content ([f555856](f5558563ea))
* **rithmomachia:** enhance Pieces section with visual examples and pyramid details ([55aff82](55aff829f4))
* **rithmomachia:** enhance Pyramid section with comprehensive details ([9fde1ef](9fde1ef9e7))
* **rithmomachia:** guide defaults to docked right on open ([11f674d](11f674d542))
* **rithmomachia:** improve guide pieces section layout ([a270bfc](a270bfc0cc))
* **rithmomachia:** improve guide UX and add persistence ([b314740](b314740697))
* **rithmomachia:** improve roster status notice UX ([e27df45](e27df45256))
* **rithmomachia:** integrate roster warning into game nav ([8a11594](8a11594203))
* **rithmomachia:** make guide modal ultra-responsive down to 150px width ([0474197](04741971b2))
* **rithmomachia:** recreate original guide modal header layout ([2489695](24896957d0))
* **rithmomachia:** show capture error on hover instead of click ([339b678](339b6780f6))
* **rithmomachia:** show pyramid face numbers on hover instead of selection ([b0c4523](b0c4523c0b))
* **rithmomachia:** show pyramid face numbers when selected ([5c186f3](5c186f3947))
* **rithmomachia:** show pyramid face numbers when selected with subtle animation ([5c2ddbe](5c2ddbef05))
* **rithmomachia:** show real preview layout when dragging guide to dock ([17d2460](17d2460a87))
* **rithmomachia:** simplify guide language for clarity ([85cb630](85cb630add))
* **rithmomachia:** skip helper selection UI and auto-select first valid helper ([be2a00e](be2a00e8b3))
* **rithmomachia:** Update harmony system to classical three-piece proportions ([08c9762](08c97620f5))
* **rithmomachia:** Update to traditional board setup with 25 pieces per side ([0769eaa](0769eaaa1d))
* **rithmomachia:** use actual piece SVGs in number bond with 2.5s rotation animation ([976a7de](976a7de949))
* **room-share:** add QR code button for easy mobile joining ([349290a](349290ac6a))
* show rithmomachia turn in nav ([7c89bfe](7c89bfef9c))
* switch to royal color theme with transparent background ([944ad65](944ad6574e)), closes [#fbbf24](https://github.com/antialias/soroban-abacus-flashcards/issues/fbbf24) [#f59e0](https://github.com/antialias/soroban-abacus-flashcards/issues/f59e0) [#a855f7](https://github.com/antialias/soroban-abacus-flashcards/issues/a855f7) [#7e22](https://github.com/antialias/soroban-abacus-flashcards/issues/7e22)

### Bug Fixes

* adjust hero abacus position to avoid covering subtitle ([f03d341](f03d341314))
* **arcade:** add automatic retry for version conflict rejections ([fbcde25](fbcde2505f))
* **arcade:** allow deactivating players from users who left the room ([7c1c2d7](7c1c2d7beb))
* **arcade:** implement optimistic locking in session manager ([71fd66d](71fd66d96a))
* board rotation now properly fills height in portrait mode ([b5a96ea](b5a96eaeb1))
* **card-sorting:** add border radius to outer card container ([a922eba](a922eba73c))
* **card-sorting:** add debug logging for spring animations ([d42947e](d42947eb8d))
* **card-sorting:** add missing gameMode support after hard reset ([a832325](a832325deb))
* **card-sorting:** add missing useMemo import ([949d76d](949d76d844))
* **card-sorting:** add overflow hidden to clip rounded corners ([84c66fe](84c66feec6))
* **card-sorting:** adjust connecting paths for scaled cards ([829c741](829c741e55))
* **card-sorting:** adjust game board for spectator panels ([fc5cf12](fc5cf1216f))
* **card-sorting:** adjust viewport dimensions for spectator panels ([4dce16c](4dce16cca4))
* **card-sorting:** animate cards from game board to results grid ([17d45fe](17d45fe88c))
* **card-sorting:** correct suffix card detection in auto-arrange ([d02ab59](d02ab5922c))
* **card-sorting:** enable card scaling for spectators ([6b095c3](6b095c3383))
* **card-sorting:** enable New Game button during active gameplay ([f3f6eca](f3f6eca1db))
* **card-sorting:** end drag immediately when card becomes locked ([ae45298](ae45298ec4))
* **card-sorting:** filter local player from emoji overlays on dragged cards ([dc2d94a](dc2d94aaa5))
* **card-sorting:** fix results panel layout to not cover cards ([4b4fbfe](4b4fbfef32))
* **card-sorting:** hide activity notifications in spectator mode ([5cca279](5cca279687))
* **card-sorting:** keep arrow sequence numbers upright ([79c9469](79c94699fa))
* **card-sorting:** lock correctly positioned prefix/suffix cards ([170abed](170abed231))
* **card-sorting:** lock spring positions after initial animation completes ([275cc62](275cc62a52))
* **card-sorting:** New Game now restarts with same settings instantly ([f3687ed](f3687ed236))
* **card-sorting:** only shrink/fade cards in correct prefix ([51368c6](51368c6ec5))
* **card-sorting:** preserve card positions on pause/resume ([0d8af09](0d8af09517))
* **card-sorting:** preserve rotation when starting drag ([3364144](3364144fb6))
* **card-sorting:** prevent duplicate START_GAME moves on Play Again ([a0b14f8](a0b14f87e9))
* **card-sorting:** prevent ghost movements with proper optimistic updates ([bd014be](bd014bec4f))
* **card-sorting:** prevent infinite loop when all cards are correct ([34785f4](34785f466f))
* **card-sorting:** prevent infinite loop with tolerance-based position comparison ([627b873](627b873382))
* **card-sorting:** prevent position jump when clicking rotated cards ([564a00f](564a00f82b))
* **card-sorting:** prevent replaying own movements from server ([308168a](308168a7fb))
* **card-sorting:** prevent springs from reinitializing on window resize ([30953b8](30953b8c4a))
* **card-sorting:** prevent springs from resetting after animation ([8aff60c](8aff60ce3f))
* **card-sorting:** remove hasAnimatedRef logic causing backwards animation ([a44aa5a](a44aa5a4c2))
* **card-sorting:** remove remaining reveal numbers references ([15c53ea](15c53ea4eb))
* **card-sorting:** restore prefix/suffix card shrinking visual feedback ([f5fb4d7](f5fb4d7b76))
* **card-sorting:** show only active players in team members section ([fa9f1a5](fa9f1a568f))
* **card-sorting:** smooth scale animation while dragging cards ([0eefc33](0eefc332ac))
* **card-sorting:** stabilize inferred sequence for locked cards during drag ([b0cd194](b0cd194838))
* **card-sorting:** use empty deps array for useSprings to prevent recreation ([cee399e](cee399ed15))
* **card-sorting:** use ref to track initialized state and prevent re-animation ([f389afa](f389afa831))
* **card-sorting:** use same coordinate system for game board and results ([6972fdf](6972fdf110))
* **complement-race:** prevent delivery move thrashing in steam sprint mode ([e1258ee](e1258ee041))
* configure favicon metadata and improve bead visibility ([e1369fa](e1369fa275))
* copy entire packages/core and packages/templates ([0ccada0](0ccada0ca7))
* correct hero abacus scroll direction to flow with page content ([4232746](423274657c))
* correct Typst template path in Dockerfile ([4c518de](4c518decb7))
* delete existing user sessions before creating new ones ([0cced47](0cced47a0f))
* extract pure SVG content from AbacusReact renders ([b07f1c4](b07f1c4216))
* **games:** prevent horizontal page scroll from carousel overflow ([5a8c98f](5a8c98fc10))
* **games:** smooth scroll feel for carousel wheel navigation ([f80a73b](f80a73b35c))
* **games:** use specific transition properties for smooth carousel loop ([187271e](187271e515))
* **i18n:** eliminate FOUC by loading messages server-side ([4d4d930](4d4d930bd3))
* **i18n:** use useMessages() for tutorial translations ([95b0105](95b0105ca3))
* include column posts in favicon bounding box ([0b2f481](0b2f48106a))
* increase server update debounce to 2000ms for low bandwidth ([633ff12](633ff12750))
* Integrate threshold input into Point Victory card ([b29bbee](b29bbeefca))
* mark dynamic routes as force-dynamic to prevent static generation errors ([d7b35d9](d7b35d9544))
* **nav:** show full navigation on /games page ([d3fe6ac](d3fe6acbb0))
* **qr-button:** improve layout and z-index ([646a422](646a4228d0))
* **qr-button:** increase mini QR code size to 80px ([61ac737](61ac7378bd))
* **qr-button:** increase mini QR code to 84px ([3fae5ea](3fae5ea6fa))
* **qr-button:** make button square and increase QR size ([dc2d466](dc2d46663b))
* **qr-button:** match height of stacked buttons ([81f202d](81f202d215))
* reduce padding to minimize gap below last bead ([0e529be](0e529be789))
* remove distracting parallax and wobble 3D effects ([28a2d40](28a2d40996))
* remove wobble physics and enhance wood grain visibility ([5d97673](5d97673406))
* resolve z-index layering and hero abacus visibility issues ([ed9a050](ed9a050d64))
* rewrite 3D stories to use props instead of CSS wrappers ([26bdb11](26bdb11237))
* **rithmomachia:** add missing i18next dependencies ([91154d9](91154d9364))
* **rithmomachia:** add missing pyramid section keys to Japanese (ja.json) ([dae615e](dae615ee72))
* **rithmomachia:** adjust error dialog sizing to prevent text clipping ([cda1126](cda1126cb0))
* **rithmomachia:** adjust roster notice position to not overlap nav ([7093223](709322373a))
* **rithmomachia:** change undock icon to pop-out arrow ([2a91748](2a91748493))
* **rithmomachia:** correct board dimensions to 16x8 and restore original layout values ([cfac277](cfac277505))
* **rithmomachia:** Correct board setup to match reference image exactly ([618e563](618e56358d))
* **rithmomachia:** correct makeMove parameter types for capture handling ([aafb64f](aafb64f3e3))
* **rithmomachia:** fix guide modal resize drift by calculating from initial state ([1bcd99c](1bcd99c949))
* **rithmomachia:** fix harmony section translation structure for hi/ja/es ([14259a1](14259a19a9))
* **rithmomachia:** fix modal resizing zoom issue ([4fa20f4](4fa20f44cb))
* **rithmomachia:** Fix TypeScript errors in playing guide modal ([4834ece](4834ece98e))
* **rithmomachia:** handle pyramid pieces in hover error tooltip ([56f3164](56f3164155))
* **rithmomachia:** implement proper board cropping and highlighting in guide ([d0a8fcd](d0a8fcdea6))
* **rithmomachia:** improve guide modal tab navigation at narrow widths ([a673177](a673177bec))
* **rithmomachia:** reconnect player assignment UI and fix setup layout ([a1a0374](a1a0374fac))
* **rithmomachia:** render guide as docked in preview panel ([190f8cf](190f8cf302))
* **rithmomachia:** show actual values in tooltips for non-helper relations ([774c6b0](774c6b0ce7))
* **rithmomachia:** show guest-friendly message when they can't fix too many players ([54bfd2f](54bfd2fac8))
* **rithmomachia:** smooth guide dragging from docked state without jump ([8f4a79c](8f4a79c9b0))
* **rithmomachia:** validate move path before showing capture error on hover ([bd49964](bd49964186))
* **room-info:** hide Leave Room button when user is alone ([5927f61](5927f61c3c))
* separate horizontal and vertical bounding box logic ([83090df](83090df4df))
* tolerate OpenSCAD CGAL warnings if output file is created ([88993f3](88993f3662))
* use absolute positioning for hero abacus to eliminate scroll lag ([096104b](096104b094))
* use Debian base for deps stage to match runner for binary compatibility ([f8fe6e4](f8fe6e4a41))
* use default BOSL2 branch instead of non-existent v2.0.0 tag ([f4ffc5b](f4ffc5b027))
* use nested SVG viewBox for actual cropping, not just scaling ([440b492](440b492e85))
* various game improvements and UI enhancements ([b67cf61](b67cf610c5))

### Performance Improvements

* optimize Docker image size to reduce build failures ([9ca3106](9ca3106361))

### Code Refactoring

* **card-sorting:** remove reveal numbers feature ([ea5e3e8](ea5e3e838b))
* **card-sorting:** send complete card sequence instead of individual moves ([e4df843](e4df8432b9))
* **games:** implement carousel, fix victories bug, add conditional stats ([82c133f](82c133f742))
* **games:** move page title to nav bar ([712ee58](712ee58e59))
* **games:** remove redundant subtitle below nav ([ad5bb87](ad5bb87325))
* **games:** remove wheel scrolling, enable overflow visible carousel ([876513c](876513c9cc))
* reorganize Harmony and Victory guide sections ([fb629c4](fb629c44ea))
* restructure /create page into hub with sub-pages ([b91b23d](b91b23d95f))
* **rithmomachia:** extract board and capture components (phase 2+3) ([a0a867b](a0a867b271))
* **rithmomachia:** extract CaptureErrorDialog component (Phase 2 partial) ([f0a066d](f0a066d8f0))
* **rithmomachia:** extract constants and coordinate utilities (Phase 1) ([eace0ed](eace0ed529))
* **rithmomachia:** extract guide sections into separate files ([765525d](765525dc45))
* **rithmomachia:** extract hooks (phase 5) ([324a659](324a65992f))
* **rithmomachia:** extract phase components (phase 4) ([11364f6](11364f6394))
* **rithmomachia:** extract reusable components from SetupPhase ([3abc325](3abc325ea2))
* **rithmomachia:** make setup phase UI more compact ([e55f848](e55f848a26))
* **rithmomachia:** redesign error notification with modern UI ([dfeeb0e](dfeeb0e0db)), closes [#1e293](https://github.com/antialias/soroban-abacus-flashcards/issues/1e293) [#0f172](https://github.com/antialias/soroban-abacus-flashcards/issues/0f172) [#f1f5f9](https://github.com/antialias/soroban-abacus-flashcards/issues/f1f5f9)
* **rithmomachia:** simplify capture error dialog to one-liner ([82a5eb2](82a5eb2e4b))
* **rithmomachia:** Update board setup to authoritative CSV layout ([0471da5](0471da598d))
* **rithmomachia:** update capture components to use CaptureContext ([2ab6ab5](2ab6ab5799))
* **rithmomachia:** use useBoardLayout and usePieceSelection in BoardDisplay ([0ab7a1d](0ab7a1df32))
* use AbacusReact for dynamic Open Graph image ([9c20f12](9c20f12bac))

### Documentation

* add 3D enhancement documentation to README ([cc96802](cc96802df8))
* add database migration guide and playing guide modal spec ([5a29af7](5a29af78e2))
* add deployment verification guidelines to prevent false positives ([3d8da23](3d8da2348b))
* **card-sorting:** add comprehensive multiplayer plan ([008ccea](008ccead0f))
* **rithmomachia:** Add concise one-page playing guide ([e3c1f10](e3c1f10233))
* update workflow to require manual testing before commits ([0991796](0991796f1e))

### Styles

* **rithmomachia:** improve divider styling and make tabs responsive ([88ca35e](88ca35e044)), closes [#e5e7](https://github.com/antialias/soroban-abacus-flashcards/issues/e5e7) [#9ca3](https://github.com/antialias/soroban-abacus-flashcards/issues/9ca3)
* **rithmomachia:** improve pyramid face numbers visibility and contrast ([94e5e6a](94e5e6a268)), closes [#fbbf24](https://github.com/antialias/soroban-abacus-flashcards/issues/fbbf24) [#b45309](https://github.com/antialias/soroban-abacus-flashcards/issues/b45309)
* **rithmomachia:** increase pyramid face numbers size and boldness ([7bf2d73](7bf2d730d3))

### Tests

* trigger compose-updater deployment test ([2b06aae](2b06aae394))
2025-11-03 22:34:58 +00:00
Thomas Hallock
2b06aae394 test: trigger compose-updater deployment test
Testing that compose-updater successfully updates without crashing itself
2025-11-03 16:32:13 -06:00
44 changed files with 8178 additions and 1423 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -85,7 +85,7 @@ RUN ARCH=$(uname -m) && \
else \
echo "Unsupported architecture: $ARCH" && exit 1; \
fi && \
TYPST_VERSION="v0.11.1" && \
TYPST_VERSION="v0.13.0" && \
wget -q "https://github.com/typst/typst/releases/download/${TYPST_VERSION}/typst-${TYPST_ARCH}.tar.xz" && \
tar -xf "typst-${TYPST_ARCH}.tar.xz" && \
mv "typst-${TYPST_ARCH}/typst" /usr/local/bin/typst && \
@@ -156,6 +156,9 @@ COPY --from=builder --chown=nextjs:nodejs /app/packages/core ./packages/core
# Copy templates package (needed for Typst templates)
COPY --from=builder --chown=nextjs:nodejs /app/packages/templates ./packages/templates
# Copy abacus-react package (needed for calendar generation scripts)
COPY --from=builder --chown=nextjs:nodejs /app/packages/abacus-react ./packages/abacus-react
# Install Python dependencies for flashcard generation
RUN pip3 install --no-cache-dir --break-system-packages -r packages/core/requirements.txt

View File

@@ -1,5 +1,75 @@
# Claude Code Instructions for apps/web
## CRITICAL: Production Dependencies
**NEVER add TypeScript execution tools to production dependencies.**
### Forbidden Production Dependencies
The following packages must ONLY be in `devDependencies`, NEVER in `dependencies`:
-`tsx` - TypeScript execution (only for scripts during development)
-`ts-node` - TypeScript execution
- ❌ Any TypeScript compiler/executor that runs .ts/.tsx files at runtime
### Why This Matters
1. **Docker Image Size**: These tools add 50-100MB+ to production images
2. **Security**: Running TypeScript at runtime is a security risk
3. **Performance**: Production should run compiled JavaScript, not interpret TypeScript
4. **Architecture**: If you need TypeScript at runtime, the code is in the wrong place
### What To Do Instead
**❌ WRONG - Adding tsx to dependencies to run .ts/.tsx at runtime:**
```json
{
"dependencies": {
"tsx": "^4.20.5" // NEVER DO THIS
}
}
```
**✅ CORRECT - Move code to proper location:**
1. **For Next.js API routes**: Move files to `src/` so Next.js bundles them during build
- Example: `scripts/generateCalendar.tsx``src/utils/calendar/generateCalendar.tsx`
- Next.js will compile and bundle these during `npm run build`
2. **For standalone scripts**: Keep in `scripts/` and use `tsx` from devDependencies
- Only run during development/build, never at runtime
- Scripts can use `tsx` because it's available during build
3. **For server-side TypeScript**: Compile to JavaScript during build
- Use `tsc` to compile `src/` to `dist/`
- Production runs the compiled JavaScript from `dist/`
### Historical Context
**We've made this mistake TWICE:**
1. **First time (commit ffae9c1b)**: Added tsx to dependencies for calendar generation scripts
- **Fix**: Moved scripts to `src/utils/calendar/` so Next.js bundles them
2. **Second time (would have happened again)**: Almost added tsx again for same reason
- **Learning**: If you're tempted to add tsx to dependencies, the architecture is wrong
### Red Flags
If you find yourself thinking:
- "I need to add tsx to dependencies to run this .ts file in production"
- "This script needs TypeScript at runtime"
- "Production can't import this .tsx file"
**STOP.** The code is in the wrong place. Move it to `src/` for bundling.
### Enforcement
Before modifying `package.json` dependencies:
1. Check if any TypeScript execution tools are being added
2. Ask yourself: "Could this code be in `src/` instead?"
3. If unsure, ask the user before proceeding
## MANDATORY: Quality Checks for ALL Work
**BEFORE declaring ANY work complete, fixed, or working**, you MUST run and pass these checks:

1
apps/web/README.md Normal file
View File

@@ -0,0 +1 @@
# Test deployment - Mon Nov 3 16:31:57 CST 2025

View File

@@ -4,7 +4,7 @@
"private": true,
"scripts": {
"dev": "tsc -p tsconfig.server.json && tsc-alias -p tsconfig.server.json && concurrently \"node server.js\" \"npx @pandacss/dev --watch\"",
"build": "node scripts/generate-build-info.js && tsc -p tsconfig.server.json && tsc-alias -p tsconfig.server.json && next build",
"build": "node scripts/generate-build-info.js && npx @pandacss/dev && tsc -p tsconfig.server.json && tsc-alias -p tsconfig.server.json && next build",
"start": "NODE_ENV=production node server.js",
"lint": "npx @biomejs/biome lint . && npx eslint .",
"lint:fix": "npx @biomejs/biome lint . --write && npx eslint . --fix",

View File

@@ -1,35 +0,0 @@
#!/usr/bin/env tsx
/**
* Generate a simple abacus SVG (no customization for now - just get it working)
* Usage: npx tsx scripts/generateCalendarAbacus.tsx <value> <columns>
* Example: npx tsx scripts/generateCalendarAbacus.tsx 15 2
*
* Pattern copied directly from working generateDayIcon.tsx
*/
import React from 'react'
import { renderToStaticMarkup } from 'react-dom/server'
import { AbacusReact } from '@soroban/abacus-react'
const value = parseInt(process.argv[2], 10)
const columns = parseInt(process.argv[3], 10)
if (isNaN(value) || isNaN(columns)) {
console.error('Usage: npx tsx scripts/generateCalendarAbacus.tsx <value> <columns>')
process.exit(1)
}
// Use exact same pattern as generateDayIcon - inline customStyles
const abacusMarkup = renderToStaticMarkup(
<AbacusReact
value={value}
columns={columns}
scaleFactor={1}
animated={false}
interactive={false}
showNumbers={false}
/>
)
process.stdout.write(abacusMarkup)

View File

@@ -1,10 +1,12 @@
import { type NextRequest, NextResponse } from 'next/server'
import { writeFileSync, readFileSync, mkdirSync, rmSync } from 'fs'
import { writeFileSync, mkdirSync, rmSync } from 'fs'
import { tmpdir } from 'os'
import { join } from 'path'
import { execSync } from 'child_process'
import { generateMonthlyTypst, generateDailyTypst, getDaysInMonth } from '../utils/typstGenerator'
import type { AbacusConfig } from '@soroban/abacus-react'
import { generateCalendarComposite } from '@/utils/calendar/generateCalendarComposite'
import { generateAbacusElement } from '@/utils/calendar/generateCalendarAbacus'
interface CalendarRequest {
month: number
@@ -18,6 +20,9 @@ export async function POST(request: NextRequest) {
let tempDir: string | null = null
try {
// Dynamic import to avoid Next.js bundler issues with react-dom/server
const { renderToStaticMarkup } = await import('react-dom/server')
const body: CalendarRequest = await request.json()
const { month, year, format, paperSize, abacusConfig } = body
@@ -26,58 +31,67 @@ export async function POST(request: NextRequest) {
return NextResponse.json({ error: 'Invalid month or year' }, { status: 400 })
}
// Create temp directory
// Create temp directory for SVG files
tempDir = join(tmpdir(), `calendar-${Date.now()}-${Math.random()}`)
mkdirSync(tempDir, { recursive: true })
// Generate SVGs using script (avoids Next.js react-dom/server restriction)
// Generate and write SVG files
const daysInMonth = getDaysInMonth(year, month)
const maxDay = format === 'daily' ? daysInMonth : 31 // For monthly, pre-generate all
const scriptPath = join(process.cwd(), 'scripts', 'generateCalendarAbacus.tsx')
let typstContent: string
// Generate day SVGs (1 to maxDay)
for (let day = 1; day <= maxDay; day++) {
const svg = execSync(`npx tsx "${scriptPath}" ${day} 2`, {
encoding: 'utf-8',
cwd: process.cwd(),
if (format === 'monthly') {
// Generate single composite SVG for monthly calendar
const calendarSvg = generateCalendarComposite({
month,
year,
renderToString: renderToStaticMarkup
})
if (!calendarSvg || calendarSvg.trim().length === 0) {
throw new Error('Generated empty composite calendar SVG')
}
writeFileSync(join(tempDir, 'calendar.svg'), calendarSvg)
// Generate Typst document
typstContent = generateMonthlyTypst({
month,
year,
paperSize,
daysInMonth,
})
} else {
// Daily format: generate individual SVGs for each day
for (let day = 1; day <= daysInMonth; day++) {
const svg = renderToStaticMarkup(generateAbacusElement(day, 2))
if (!svg || svg.trim().length === 0) {
throw new Error(`Generated empty SVG for day ${day}`)
}
writeFileSync(join(tempDir, `day-${day}.svg`), svg)
}
// Generate year SVG
const yearColumns = Math.max(1, Math.ceil(Math.log10(year + 1)))
const yearSvg = renderToStaticMarkup(generateAbacusElement(year, yearColumns))
if (!yearSvg || yearSvg.trim().length === 0) {
throw new Error(`Generated empty SVG for year ${year}`)
}
writeFileSync(join(tempDir, 'year.svg'), yearSvg)
// Generate Typst document
typstContent = generateDailyTypst({
month,
year,
paperSize,
daysInMonth,
})
writeFileSync(join(tempDir, `day-${day}.svg`), svg)
}
// Generate year SVG
const yearColumns = Math.max(1, Math.ceil(Math.log10(year + 1)))
const yearSvg = execSync(`npx tsx "${scriptPath}" ${year} ${yearColumns}`, {
encoding: 'utf-8',
cwd: process.cwd(),
})
writeFileSync(join(tempDir, 'year.svg'), yearSvg)
// Generate Typst document
const typstContent =
format === 'monthly'
? generateMonthlyTypst({
month,
year,
paperSize,
tempDir,
daysInMonth,
})
: generateDailyTypst({
month,
year,
paperSize,
tempDir,
daysInMonth,
})
const typstPath = join(tempDir, 'calendar.typ')
writeFileSync(typstPath, typstContent)
// Compile with Typst
const pdfPath = join(tempDir, 'calendar.pdf')
// Compile with Typst: stdin for .typ content, stdout for PDF output
let pdfBuffer: Buffer
try {
execSync(`typst compile "${typstPath}" "${pdfPath}"`, {
stdio: 'pipe',
pdfBuffer = execSync('typst compile --format pdf - -', {
input: typstContent,
cwd: tempDir, // Run in temp dir so relative paths work
maxBuffer: 50 * 1024 * 1024, // 50MB limit for large calendars
})
} catch (error) {
console.error('Typst compilation error:', error)
@@ -87,18 +101,14 @@ export async function POST(request: NextRequest) {
)
}
// Read and return PDF
const pdfBuffer = readFileSync(pdfPath)
// Clean up temp directory
rmSync(tempDir, { recursive: true, force: true })
tempDir = null
return new NextResponse(pdfBuffer, {
headers: {
'Content-Type': 'application/pdf',
'Content-Disposition': `attachment; filename="calendar-${year}-${String(month).padStart(2, '0')}.pdf"`,
},
// Return JSON with PDF
return NextResponse.json({
pdf: pdfBuffer.toString('base64'),
filename: `calendar-${year}-${String(month).padStart(2, '0')}.pdf`,
})
} catch (error) {
console.error('Error generating calendar:', error)
@@ -112,6 +122,17 @@ export async function POST(request: NextRequest) {
}
}
return NextResponse.json({ error: 'Failed to generate calendar' }, { status: 500 })
// Surface the actual error for debugging
const errorMessage = error instanceof Error ? error.message : String(error)
const errorStack = error instanceof Error ? error.stack : undefined
return NextResponse.json(
{
error: 'Failed to generate calendar',
message: errorMessage,
...(process.env.NODE_ENV === 'development' && { stack: errorStack })
},
{ status: 500 }
)
}
}

View File

@@ -0,0 +1,97 @@
import { type NextRequest, NextResponse } from 'next/server'
import { writeFileSync, mkdirSync, rmSync } from 'fs'
import { tmpdir } from 'os'
import { join } from 'path'
import { execSync } from 'child_process'
import { generateMonthlyTypst, getDaysInMonth } from '../utils/typstGenerator'
import { generateCalendarComposite } from '@/utils/calendar/generateCalendarComposite'
interface PreviewRequest {
month: number
year: number
format: 'monthly' | 'daily'
}
export const dynamic = 'force-dynamic'
export async function POST(request: NextRequest) {
let tempDir: string | null = null
try {
const body: PreviewRequest = await request.json()
const { month, year, format } = body
// Validate inputs
if (!month || month < 1 || month > 12 || !year || year < 1 || year > 9999) {
return NextResponse.json({ error: 'Invalid month or year' }, { status: 400 })
}
// Only generate preview for monthly format
if (format !== 'monthly') {
return NextResponse.json({ svg: null })
}
// Dynamic import to avoid Next.js bundler issues
const { renderToStaticMarkup } = await import('react-dom/server')
// Create temp directory for SVG file
tempDir = join(tmpdir(), `calendar-preview-${Date.now()}-${Math.random()}`)
mkdirSync(tempDir, { recursive: true })
// Generate and write composite SVG
const calendarSvg = generateCalendarComposite({
month,
year,
renderToString: renderToStaticMarkup
})
writeFileSync(join(tempDir, 'calendar.svg'), calendarSvg)
// Generate Typst document content
const daysInMonth = getDaysInMonth(year, month)
const typstContent = generateMonthlyTypst({
month,
year,
paperSize: 'us-letter',
daysInMonth,
})
// Compile with Typst: stdin for .typ content, stdout for SVG output
let svg: string
try {
svg = execSync('typst compile --format svg - -', {
input: typstContent,
encoding: 'utf8',
cwd: tempDir, // Run in temp dir so relative paths work
})
} catch (error) {
console.error('Typst compilation error:', error)
return NextResponse.json(
{ error: 'Failed to compile preview. Is Typst installed?' },
{ status: 500 }
)
}
// Clean up temp directory
rmSync(tempDir, { recursive: true, force: true })
tempDir = null
return NextResponse.json({ svg })
} catch (error) {
console.error('Error generating preview:', error)
// Clean up temp directory if it exists
if (tempDir) {
try {
rmSync(tempDir, { recursive: true, force: true })
} catch (cleanupError) {
console.error('Failed to clean up temp directory:', cleanupError)
}
}
const errorMessage = error instanceof Error ? error.message : String(error)
return NextResponse.json(
{ error: 'Failed to generate preview', message: errorMessage },
{ status: 500 }
)
}
}

View File

@@ -1,8 +1,14 @@
interface TypstConfig {
interface TypstMonthlyConfig {
month: number
year: number
paperSize: 'us-letter' | 'a4' | 'a3' | 'tabloid'
daysInMonth: number
}
interface TypstDailyConfig {
month: number
year: number
paperSize: 'us-letter' | 'a4' | 'a3' | 'tabloid'
tempDir: string
daysInMonth: number
}
@@ -44,73 +50,38 @@ interface PaperConfig {
function getPaperConfig(size: string): PaperConfig {
const configs: Record<PaperSize, PaperConfig> = {
'us-letter': { typstName: 'us-letter', marginX: '0.75in', marginY: '1in' },
a4: { typstName: 'a4', marginX: '2cm', marginY: '2.5cm' },
a3: { typstName: 'a3', marginX: '2cm', marginY: '2.5cm' },
tabloid: { typstName: 'us-tabloid', marginX: '1in', marginY: '1in' },
// Tight margins to maximize space for calendar grid
'us-letter': { typstName: 'us-letter', marginX: '0.5in', marginY: '0.5in' },
// A4 is slightly taller/narrower than US Letter - adjust margins proportionally
a4: { typstName: 'a4', marginX: '1.3cm', marginY: '1.3cm' },
// A3 is 2x area of A4 - can use same margins but will scale content larger
a3: { typstName: 'a3', marginX: '1.5cm', marginY: '1.5cm' },
// Tabloid (11" × 17") is larger - can use more margin
tabloid: { typstName: 'us-tabloid', marginX: '0.75in', marginY: '0.75in' },
}
return configs[size as PaperSize] || configs['us-letter']
}
export function generateMonthlyTypst(config: TypstConfig): string {
const { month, year, paperSize, tempDir, daysInMonth } = config
export function generateMonthlyTypst(config: TypstMonthlyConfig): string {
const { paperSize } = config
const paperConfig = getPaperConfig(paperSize)
const firstDayOfWeek = getFirstDayOfWeek(year, month)
const monthName = MONTH_NAMES[month - 1]
// Generate calendar cells with proper empty cells before the first day
let cells = ''
// Empty cells before first day
for (let i = 0; i < firstDayOfWeek; i++) {
cells += ' [],\n'
}
// Day cells
for (let day = 1; day <= daysInMonth; day++) {
cells += ` [#image("${tempDir}/day-${day}.svg", width: 90%)],\n`
}
// Single-page design: use one composite SVG that scales to fit
// This prevents overflow - Typst will scale the image to fit available space
return `#set page(
paper: "${paperConfig.typstName}",
margin: (x: ${paperConfig.marginX}, y: ${paperConfig.marginY}),
)
#set text(font: "Arial", size: 12pt)
// Title
#align(center)[
#text(size: 24pt, weight: "bold")[${monthName} ${year}]
#v(0.5em)
// Year as abacus
#image("${tempDir}/year.svg", width: 35%)
// Composite calendar SVG - scales to fit page (prevents multi-page overflow)
#align(center + horizon)[
#image("calendar.svg", width: 100%, fit: "contain")
]
#v(1.5em)
// Calendar grid
#grid(
columns: (1fr, 1fr, 1fr, 1fr, 1fr, 1fr, 1fr),
gutter: 4pt,
// Weekday headers
[#align(center)[*Sun*]],
[#align(center)[*Mon*]],
[#align(center)[*Tue*]],
[#align(center)[*Wed*]],
[#align(center)[*Thu*]],
[#align(center)[*Fri*]],
[#align(center)[*Sat*]],
// Calendar days
${cells})
`
}
export function generateDailyTypst(config: TypstConfig): string {
const { month, year, paperSize, tempDir, daysInMonth } = config
export function generateDailyTypst(config: TypstDailyConfig): string {
const { month, year, paperSize, daysInMonth } = config
const paperConfig = getPaperConfig(paperSize)
const monthName = MONTH_NAMES[month - 1]
@@ -127,14 +98,14 @@ export function generateDailyTypst(config: TypstConfig): string {
// Header: Year
#align(center)[
#v(1em)
#image("${tempDir}/year.svg", width: 30%)
#image("year.svg", width: 30%)
]
#v(2em)
// Main: Day number as large abacus
#align(center + horizon)[
#image("${tempDir}/day-${day}.svg", width: 50%)
#image("day-${day}.svg", width: 50%)
]
#v(2em)

View File

@@ -23,13 +23,11 @@ export function AbacusTarget({ number }: AbacusTargetProps) {
<AbacusReact
value={number}
columns={1}
compact={true}
interactive={false}
showNumbers={false}
hideInactiveBeads={true}
scaleFactor={0.72}
customStyles={{
columnPosts: { opacity: 0 },
}}
/>
</div>
)

View File

@@ -22,6 +22,8 @@ import { getAllGames, getGame, hasGame } from '@/lib/arcade/game-registry'
*
* Note: ModerationNotifications is handled by PageWithNav inside each game component,
* so we don't need to render it here.
*
* Test: Verifying compose-updater automatic deployment cycle
*/
export default function RoomPage() {
const router = useRouter()

View File

@@ -2,6 +2,9 @@
import { rithmomachiaGame } from '@/arcade-games/rithmomachia'
// Force dynamic rendering to avoid build-time initialization errors
export const dynamic = 'force-dynamic'
const { Provider, GameComponent } = rithmomachiaGame
export default function RithmomachiaPage() {

View File

@@ -2,7 +2,7 @@
import { css } from '../../../../../styled-system/css'
import { AbacusReact, useAbacusConfig } from '@soroban/abacus-react'
import Link from 'next/link'
import { AbacusDisplayDropdown } from '@/components/AbacusDisplayDropdown'
interface CalendarConfigPanelProps {
month: number
@@ -243,7 +243,7 @@ export function CalendarConfigPanel({
</select>
</fieldset>
{/* Abacus Styling Info */}
{/* Abacus Styling */}
<div
data-section="styling-info"
className={css({
@@ -259,7 +259,7 @@ export function CalendarConfigPanel({
color: 'gray.300',
})}
>
Using your saved abacus style:
Calendar abacus style preview:
</p>
<div
className={css({
@@ -273,22 +273,17 @@ export function CalendarConfigPanel({
columns={2}
customStyles={abacusConfig.customStyles}
scaleFactor={0.5}
showNumbers={false}
/>
</div>
<Link
href="/create"
data-action="edit-style"
<div
className={css({
display: 'block',
textAlign: 'center',
fontSize: '0.875rem',
color: 'yellow.400',
textDecoration: 'underline',
_hover: { color: 'yellow.300' },
display: 'flex',
justifyContent: 'center',
})}
>
Edit your abacus style
</Link>
<AbacusDisplayDropdown />
</div>
</div>
{/* Generate Button */}

View File

@@ -1,45 +1,43 @@
'use client'
import { useQuery } from '@tanstack/react-query'
import { css } from '../../../../../styled-system/css'
import { AbacusReact, useAbacusConfig } from '@soroban/abacus-react'
interface CalendarPreviewProps {
month: number
year: number
format: 'monthly' | 'daily'
previewSvg: string | null
}
const MONTHS = [
'January',
'February',
'March',
'April',
'May',
'June',
'July',
'August',
'September',
'October',
'November',
'December',
]
async function fetchTypstPreview(month: number, year: number, format: string): Promise<string | null> {
const response = await fetch('/api/create/calendar/preview', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ month, year, format }),
})
const WEEKDAYS = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']
if (!response.ok) {
throw new Error('Failed to fetch preview')
}
function getDaysInMonth(year: number, month: number): number {
return new Date(year, month, 0).getDate()
const data = await response.json()
return data.svg
}
function getFirstDayOfWeek(year: number, month: number): number {
return new Date(year, month - 1, 1).getDay()
}
export function CalendarPreview({ month, year, format, previewSvg }: CalendarPreviewProps) {
// Use React Query to fetch Typst-generated preview (client-side only)
const { data: typstPreviewSvg, isLoading } = useQuery({
queryKey: ['calendar-typst-preview', month, year, format],
queryFn: () => fetchTypstPreview(month, year, format),
enabled: typeof window !== 'undefined' && format === 'monthly', // Only run on client and for monthly format
})
export function CalendarPreview({ month, year, format }: CalendarPreviewProps) {
const abacusConfig = useAbacusConfig()
const daysInMonth = getDaysInMonth(year, month)
const firstDayOfWeek = getFirstDayOfWeek(year, month)
// Use generated PDF SVG if available, otherwise use Typst live preview
const displaySvg = previewSvg || typstPreviewSvg
if (format === 'daily') {
// Show loading state while fetching preview
if (isLoading || (!displaySvg && format === 'monthly')) {
return (
<div
data-component="calendar-preview"
@@ -57,108 +55,42 @@ export function CalendarPreview({ month, year, format }: CalendarPreviewProps) {
<p
className={css({
fontSize: '1.25rem',
color: 'gray.300',
marginBottom: '1.5rem',
textAlign: 'center',
})}
>
Daily format preview
</p>
<div
className={css({
bg: 'white',
padding: '3rem 2rem',
borderRadius: '8px',
boxShadow: '0 4px 6px rgba(0, 0, 0, 0.1)',
maxWidth: '400px',
width: '100%',
})}
>
{/* Year at top */}
<div
className={css({
display: 'flex',
justifyContent: 'center',
marginBottom: '2rem',
})}
>
<AbacusReact
value={year}
columns={4}
customStyles={abacusConfig.customStyles}
scaleFactor={0.4}
/>
</div>
{/* Large day number */}
<div
className={css({
display: 'flex',
justifyContent: 'center',
marginBottom: '2rem',
})}
>
<AbacusReact
value={1}
columns={2}
customStyles={abacusConfig.customStyles}
scaleFactor={0.8}
/>
</div>
{/* Date text */}
<div
className={css({
textAlign: 'center',
color: 'gray.800',
})}
>
<div
className={css({
fontSize: '1.25rem',
fontWeight: '600',
marginBottom: '0.25rem',
})}
>
{new Date(year, month - 1, 1).toLocaleDateString('en-US', {
weekday: 'long',
})}
</div>
<div
className={css({
fontSize: '1rem',
color: 'gray.600',
})}
>
{MONTHS[month - 1]} 1, {year}
</div>
</div>
</div>
<p
className={css({
fontSize: '0.875rem',
color: 'gray.400',
marginTop: '1rem',
textAlign: 'center',
})}
>
Example of first day (1 page per day for all {daysInMonth} days)
Loading preview...
</p>
</div>
)
}
// Monthly format
const calendarDays: (number | null)[] = []
// Add empty cells for days before the first day of month
for (let i = 0; i < firstDayOfWeek; i++) {
calendarDays.push(null)
}
// Add actual days
for (let day = 1; day <= daysInMonth; day++) {
calendarDays.push(day)
if (!displaySvg) {
return (
<div
data-component="calendar-preview"
className={css({
bg: 'gray.800',
borderRadius: '12px',
padding: '2rem',
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
minHeight: '600px',
})}
>
<p
className={css({
fontSize: '1.25rem',
color: 'gray.400',
textAlign: 'center',
})}
>
{format === 'daily' ? 'Daily format - preview after generation' : 'No preview available'}
</p>
</div>
)
}
return (
@@ -168,99 +100,32 @@ export function CalendarPreview({ month, year, format }: CalendarPreviewProps) {
bg: 'gray.800',
borderRadius: '12px',
padding: '2rem',
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
})}
>
<div
className={css({
textAlign: 'center',
marginBottom: '2rem',
})}
>
<h2
className={css({
fontSize: '2rem',
fontWeight: 'bold',
marginBottom: '1rem',
color: 'yellow.400',
})}
>
{MONTHS[month - 1]} {year}
</h2>
<div
className={css({
display: 'flex',
justifyContent: 'center',
})}
>
<AbacusReact
value={year}
columns={4}
customStyles={abacusConfig.customStyles}
scaleFactor={0.6}
/>
</div>
</div>
{/* Calendar Grid */}
<div
className={css({
display: 'grid',
gridTemplateColumns: 'repeat(7, 1fr)',
gap: '0.5rem',
})}
>
{/* Weekday headers */}
{WEEKDAYS.map((day) => (
<div
key={day}
className={css({
textAlign: 'center',
fontWeight: '600',
padding: '0.5rem',
color: 'yellow.400',
fontSize: '0.875rem',
})}
>
{day}
</div>
))}
{/* Calendar days */}
{calendarDays.map((day, index) => (
<div
key={index}
className={css({
aspectRatio: '1',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
bg: day ? 'gray.700' : 'transparent',
borderRadius: '6px',
padding: '0.25rem',
})}
>
{day && (
<AbacusReact
value={day}
columns={2}
customStyles={abacusConfig.customStyles}
scaleFactor={0.35}
/>
)}
</div>
))}
</div>
<p
className={css({
fontSize: '0.875rem',
color: 'gray.400',
marginTop: '1.5rem',
fontSize: '1.125rem',
color: 'yellow.400',
marginBottom: '1rem',
textAlign: 'center',
fontWeight: 'bold',
})}
>
Preview of monthly calendar layout (actual PDF will be optimized for printing)
{previewSvg ? 'Generated PDF' : 'Live Preview'}
</p>
<div
className={css({
bg: 'white',
borderRadius: '8px',
padding: '1rem',
maxWidth: '100%',
overflow: 'auto',
})}
dangerouslySetInnerHTML={{ __html: displaySvg }}
/>
</div>
)
}

View File

@@ -15,6 +15,7 @@ export default function CalendarCreatorPage() {
const [format, setFormat] = useState<'monthly' | 'daily'>('monthly')
const [paperSize, setPaperSize] = useState<'us-letter' | 'a4' | 'a3' | 'tabloid'>('us-letter')
const [isGenerating, setIsGenerating] = useState(false)
const [previewSvg, setPreviewSvg] = useState<string | null>(null)
const handleGenerate = async () => {
setIsGenerating(true)
@@ -34,21 +35,26 @@ export default function CalendarCreatorPage() {
})
if (!response.ok) {
throw new Error('Failed to generate calendar')
const errorData = await response.json().catch(() => ({}))
throw new Error(errorData.message || 'Failed to generate calendar')
}
const blob = await response.blob()
const data = await response.json()
// Convert base64 PDF to blob and trigger download
const pdfBytes = Uint8Array.from(atob(data.pdf), c => c.charCodeAt(0))
const blob = new Blob([pdfBytes], { type: 'application/pdf' })
const url = window.URL.createObjectURL(blob)
const a = document.createElement('a')
a.href = url
a.download = `calendar-${year}-${String(month).padStart(2, '0')}.pdf`
a.download = data.filename
document.body.appendChild(a)
a.click()
window.URL.revokeObjectURL(url)
document.body.removeChild(a)
} catch (error) {
console.error('Error generating calendar:', error)
alert('Failed to generate calendar. Please try again.')
alert(`Failed to generate calendar: ${error instanceof Error ? error.message : 'Unknown error'}`)
} finally {
setIsGenerating(false)
}
@@ -122,7 +128,7 @@ export default function CalendarCreatorPage() {
/>
{/* Preview */}
<CalendarPreview month={month} year={year} format={format} />
<CalendarPreview month={month} year={year} format={format} previewSvg={previewSvg} />
</div>
</div>
</div>

View File

@@ -0,0 +1,66 @@
/**
* Test page for AbacusStatic - Server Component
* This demonstrates that AbacusStatic works without "use client"
*
* Note: Uses /static import path to avoid client-side code
*/
import { AbacusStatic } from '@soroban/abacus-react/static'
export default function TestStaticAbacusPage() {
const numbers = [1, 2, 3, 4, 5, 10, 25, 50, 100, 123, 456, 789]
return (
<div style={{ padding: '40px', maxWidth: '1200px', margin: '0 auto' }}>
<h1 style={{ marginBottom: '10px' }}>AbacusStatic Test (Server Component)</h1>
<p style={{ color: '#64748b', marginBottom: '30px' }}>
This page is a React Server Component - no &quot;use client&quot; directive!
All abacus displays below are rendered on the server with zero client-side JavaScript.
</p>
<div
style={{
display: 'grid',
gridTemplateColumns: 'repeat(auto-fit, minmax(180px, 1fr))',
gap: '20px',
}}
>
{numbers.map((num) => (
<div
key={num}
style={{
padding: '20px',
background: 'white',
border: '2px solid #e2e8f0',
borderRadius: '12px',
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
gap: '10px',
}}
>
<AbacusStatic
value={num}
columns="auto"
hideInactiveBeads
compact
scaleFactor={0.9}
/>
<span style={{ fontSize: '20px', fontWeight: 'bold', color: '#475569' }}>
{num}
</span>
</div>
))}
</div>
<div style={{ marginTop: '40px', padding: '20px', background: '#f0fdf4', borderRadius: '8px' }}>
<h2 style={{ marginTop: 0, color: '#166534' }}> Success!</h2>
<p style={{ color: '#15803d' }}>
If you can see the abacus displays above, then AbacusStatic is working correctly
in React Server Components. Check the page source - you&apos;ll see pure HTML/SVG with
no client-side hydration markers!
</p>
</div>
</div>
)
}

View File

@@ -1,7 +1,7 @@
'use client'
import { useEffect, useRef } from 'react'
import { AbacusReact, useAbacusConfig } from '@soroban/abacus-react'
import { AbacusReact, useAbacusConfig, ABACUS_THEMES } from '@soroban/abacus-react'
import { css } from '../../styled-system/css'
import { useHomeHero } from '../contexts/HomeHeroContext'
@@ -17,19 +17,8 @@ export function HeroAbacus() {
const appConfig = useAbacusConfig()
const heroRef = useRef<HTMLDivElement>(null)
// Styling for structural elements (solid, no translucency)
const structuralStyles = {
columnPosts: {
fill: 'rgb(255, 255, 255)',
stroke: 'rgb(200, 200, 200)',
strokeWidth: 2,
},
reckoningBar: {
fill: 'rgb(255, 255, 255)',
stroke: 'rgb(200, 200, 200)',
strokeWidth: 3,
},
}
// Use theme preset from abacus-react instead of manual definition
const structuralStyles = ABACUS_THEMES.light
// Detect when hero scrolls out of view
useEffect(() => {

View File

@@ -3,7 +3,7 @@
import { useState, useEffect } from 'react'
import { useSpring, useTransition, animated } from '@react-spring/web'
import * as Slider from '@radix-ui/react-slider'
import { AbacusReact, StandaloneBead } from '@soroban/abacus-react'
import { AbacusReact, StandaloneBead, ABACUS_THEMES } from '@soroban/abacus-react'
import { css } from '../../styled-system/css'
import { stack } from '../../styled-system/patterns'
import { kyuLevelDetails } from '@/data/kyuLevelDetails'
@@ -260,19 +260,8 @@ function parseKyuDetails(rawText: string) {
return sections
}
// Dark theme styles matching the homepage
const darkStyles = {
columnPosts: {
fill: 'rgba(255, 255, 255, 0.3)',
stroke: 'rgba(255, 255, 255, 0.2)',
strokeWidth: 2,
},
reckoningBar: {
fill: 'rgba(255, 255, 255, 0.4)',
stroke: 'rgba(255, 255, 255, 0.25)',
strokeWidth: 3,
},
}
// Use dark theme preset from abacus-react instead of manual definition
const darkStyles = ABACUS_THEMES.dark
interface LevelSliderDisplayProps {
initialIndex?: number

View File

@@ -2,7 +2,7 @@
import { useContext, useEffect, useState } from 'react'
import { usePathname } from 'next/navigation'
import { AbacusReact, useAbacusConfig } from '@soroban/abacus-react'
import { AbacusReact, useAbacusConfig, ABACUS_THEMES } from '@soroban/abacus-react'
import { css } from '../../styled-system/css'
import { useMyAbacus } from '@/contexts/MyAbacusContext'
import { HomeHeroContext } from '@/contexts/HomeHeroContext'
@@ -56,33 +56,9 @@ export function MyAbacus() {
}
}, [isOpen])
// Hero mode styles - white structural (from original HeroAbacus)
const structuralStyles = {
columnPosts: {
fill: 'rgb(255, 255, 255)',
stroke: 'rgb(200, 200, 200)',
strokeWidth: 2,
},
reckoningBar: {
fill: 'rgb(255, 255, 255)',
stroke: 'rgb(200, 200, 200)',
strokeWidth: 3,
},
}
// Trophy abacus styles - golden/premium look
const trophyStyles = {
columnPosts: {
fill: '#fbbf24',
stroke: '#f59e0b',
strokeWidth: 3,
},
reckoningBar: {
fill: '#fbbf24',
stroke: '#f59e0b',
strokeWidth: 4,
},
}
// Use theme presets from abacus-react instead of manual definitions
const structuralStyles = ABACUS_THEMES.light
const trophyStyles = ABACUS_THEMES.trophy
return (
<>

View File

@@ -2,6 +2,7 @@
import { useCallback, useEffect, useMemo, useState } from 'react'
import Resizable from 'react-resizable-layout'
import { calculateBeadDiffFromValues } from '@soroban/abacus-react'
import { css } from '../../../styled-system/css'
import { hstack, stack, vstack } from '../../../styled-system/patterns'
import {
@@ -13,7 +14,6 @@ import {
type TutorialValidation,
} from '../../types/tutorial'
import { generateAbacusInstructions } from '../../utils/abacusInstructionGenerator'
import { calculateBeadDiffFromValues } from '../../utils/beadDiff'
import { generateSingleProblem } from '../../utils/problemGenerator'
import {
createBasicAllowedConfiguration,

View File

@@ -6,6 +6,7 @@ import {
AbacusReact,
type StepBeadHighlight,
useAbacusDisplay,
calculateBeadDiffFromValues,
} from '@soroban/abacus-react'
import { useTranslations } from 'next-intl'
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
@@ -18,7 +19,6 @@ import type {
TutorialStep,
UIState,
} from '../../types/tutorial'
import { calculateBeadDiffFromValues } from '../../utils/beadDiff'
import { generateUnifiedInstructionSequence } from '../../utils/unifiedStepGenerator'
import { CoachBar } from './CoachBar/CoachBar'
import { DecompositionWithReasons } from './DecompositionWithReasons'
@@ -970,16 +970,11 @@ function TutorialPlayerContent({
// Two-level dynamic column highlights: group terms + individual term
const dynamicColumnHighlights = useMemo(() => {
console.log('🎨 COMPUTING COLUMN HIGHLIGHTS')
console.log(' - activeTermIndices:', Array.from(activeTermIndices))
console.log(' - activeIndividualTermIndex:', activeIndividualTermIndex)
const highlights: Record<number, any> = {}
// Level 1: Group highlights (blue glow for all terms in activeTermIndices)
activeTermIndices.forEach((termIndex) => {
const columnIndex = getColumnFromTermIndex(termIndex, true) // Use group column (rhsPlace)
console.log(` - Group term ${termIndex} maps to column ${columnIndex} (using rhsPlace)`)
if (columnIndex !== null) {
highlights[columnIndex] = {
// Group background glow effect (blue)
@@ -998,16 +993,12 @@ function TutorialPlayerContent({
borderColor: '#3b82f6',
},
}
console.log(` 🔵 Added BLUE highlight for column ${columnIndex}`)
}
})
// Level 2: Individual term highlight (orange glow, overrides group styling)
if (activeIndividualTermIndex !== null) {
const individualColumnIndex = getColumnFromTermIndex(activeIndividualTermIndex, false) // Use individual column (termPlace)
console.log(
` - Individual term ${activeIndividualTermIndex} maps to column ${individualColumnIndex} (using termPlace)`
)
if (individualColumnIndex !== null) {
highlights[individualColumnIndex] = {
// Individual background glow effect (orange) - overrides group glow
@@ -1026,54 +1017,46 @@ function TutorialPlayerContent({
borderColor: '#ea580c',
},
}
console.log(
` 🟠 Added ORANGE highlight for column ${individualColumnIndex} (overriding blue)`
)
}
}
console.log(
'🎨 Final highlights:',
Object.keys(highlights).map((col) => `Column ${col}`)
)
return highlights
}, [activeTermIndices, activeIndividualTermIndex, getColumnFromTermIndex])
// Memoize custom styles calculation to avoid expensive recalculation on every render
const customStyles = useMemo(() => {
// Calculate valid column range based on abacusColumns
const minValidColumn = 5 - abacusColumns
// Start with static highlights from step configuration
const staticHighlights: Record<number, any> = {}
// Separate bead-level and column-level styles
const beadLevelHighlights: Record<number, any> = {}
const columnLevelHighlights: Record<number, any> = {}
// Process static highlights from step configuration (bead-specific)
if (currentStep.highlightBeads && Array.isArray(currentStep.highlightBeads)) {
currentStep.highlightBeads.forEach((highlight) => {
// Convert placeValue to columnIndex for AbacusReact compatibility
const columnIndex = abacusColumns - 1 - highlight.placeValue
// Skip highlights for columns that don't exist
if (columnIndex < minValidColumn) {
// Skip highlights for columns that don't exist in the rendered abacus
if (columnIndex < 0 || columnIndex >= abacusColumns) {
return
}
// Initialize column if it doesn't exist
if (!staticHighlights[columnIndex]) {
staticHighlights[columnIndex] = {}
if (!beadLevelHighlights[columnIndex]) {
beadLevelHighlights[columnIndex] = {}
}
// Add the bead style to the appropriate type
if (highlight.beadType === 'earth' && highlight.position !== undefined) {
if (!staticHighlights[columnIndex].earth) {
staticHighlights[columnIndex].earth = {}
if (!beadLevelHighlights[columnIndex].earth) {
beadLevelHighlights[columnIndex].earth = {}
}
staticHighlights[columnIndex].earth[highlight.position] = {
beadLevelHighlights[columnIndex].earth[highlight.position] = {
fill: '#fbbf24',
stroke: '#f59e0b',
strokeWidth: 3,
}
} else {
staticHighlights[columnIndex][highlight.beadType] = {
beadLevelHighlights[columnIndex][highlight.beadType] = {
fill: '#fbbf24',
stroke: '#f59e0b',
strokeWidth: 3,
@@ -1082,29 +1065,30 @@ function TutorialPlayerContent({
})
}
// Merge static and dynamic highlights (dynamic takes precedence)
const mergedHighlights = { ...staticHighlights }
// Process dynamic column highlights (column-level: backgroundGlow, numerals)
Object.keys(dynamicColumnHighlights).forEach((columnIndexStr) => {
const columnIndex = parseInt(columnIndexStr, 10)
// Skip highlights for columns that don't exist
if (columnIndex < minValidColumn) {
// Skip highlights for columns that don't exist in the rendered abacus
if (columnIndex < 0 || columnIndex >= abacusColumns) {
return
}
if (!mergedHighlights[columnIndex]) {
mergedHighlights[columnIndex] = {}
}
// Merge dynamic highlights into the column
Object.assign(mergedHighlights[columnIndex], dynamicColumnHighlights[columnIndex])
// Dynamic highlights are column-level (backgroundGlow, numerals)
columnLevelHighlights[columnIndex] = dynamicColumnHighlights[columnIndex]
})
// Build the custom styles object
const styles: any = {}
// Add column highlights if any
if (Object.keys(mergedHighlights).length > 0) {
styles.columns = mergedHighlights
// Add bead-level highlights to styles.beads
if (Object.keys(beadLevelHighlights).length > 0) {
styles.beads = beadLevelHighlights
}
// Add column-level highlights to styles.columns
if (Object.keys(columnLevelHighlights).length > 0) {
styles.columns = columnLevelHighlights
}
// Add frame styling for dark mode
@@ -1123,6 +1107,17 @@ function TutorialPlayerContent({
}
}
// Debug logging for custom styles
if (Object.keys(styles).length > 0) {
console.log('📋 TUTORIAL CUSTOM STYLES:', JSON.stringify({
beadLevelHighlights,
columnLevelHighlights,
finalStyles: styles,
currentStepHighlightBeads: currentStep.highlightBeads,
abacusColumns,
}, null, 2))
}
return Object.keys(styles).length > 0 ? styles : undefined
}, [currentStep.highlightBeads, dynamicColumnHighlights, abacusColumns, theme])
@@ -1608,7 +1603,7 @@ function TutorialPlayerContent({
columns={abacusColumns}
interactive={true}
animated={true}
scaleFactor={2.5}
scaleFactor={1.5}
colorScheme={abacusConfig.colorScheme}
beadShape={abacusConfig.beadShape}
hideInactiveBeads={abacusConfig.hideInactiveBeads}

View File

@@ -1,29 +1,30 @@
// Automatic instruction generator for abacus tutorial steps
import type { ValidPlaceValues } from '@soroban/abacus-react'
// Re-exports core types and functions from abacus-react
export interface BeadState {
heavenActive: boolean
earthActive: number // 0-4
}
export type { ValidPlaceValues } from '@soroban/abacus-react'
export {
type BeadState,
type AbacusState,
type PlaceValueBasedBead as BeadHighlight,
numberToAbacusState,
calculateBeadChanges,
} from '@soroban/abacus-react'
export interface AbacusState {
[placeValue: number]: BeadState
}
import type { ValidPlaceValues, PlaceValueBasedBead } from '@soroban/abacus-react'
import { numberToAbacusState, calculateBeadChanges } from '@soroban/abacus-react'
export interface BeadHighlight {
placeValue: ValidPlaceValues
beadType: 'heaven' | 'earth'
position?: number
}
// Type alias for internal use
type BeadHighlight = PlaceValueBasedBead
export interface StepBeadHighlight extends BeadHighlight {
// App-specific extension for step-based tutorial highlighting
export interface StepBeadHighlight extends PlaceValueBasedBead {
stepIndex: number // Which instruction step this bead belongs to
direction: 'up' | 'down' | 'activate' | 'deactivate' // Movement direction
order?: number // Order within the step (for multiple beads per step)
}
export interface GeneratedInstruction {
highlightBeads: BeadHighlight[]
highlightBeads: PlaceValueBasedBead[]
expectedAction: 'add' | 'remove' | 'multi-step'
actionDescription: string
multiStepInstructions?: string[]
@@ -40,68 +41,7 @@ export interface GeneratedInstruction {
}
}
// Convert a number to abacus state representation
export function numberToAbacusState(value: number, maxPlaces: number = 5): AbacusState {
const state: AbacusState = {}
for (let place = 0; place < maxPlaces; place++) {
const placeValueNum = 10 ** place
const digit = Math.floor(value / placeValueNum) % 10
state[place] = {
heavenActive: digit >= 5,
earthActive: digit >= 5 ? digit - 5 : digit,
}
}
return state
}
// Calculate the difference between two abacus states
export function calculateBeadChanges(
startState: AbacusState,
targetState: AbacusState
): {
additions: BeadHighlight[]
removals: BeadHighlight[]
placeValue: number
} {
const additions: BeadHighlight[] = []
const removals: BeadHighlight[] = []
let mainPlaceValue = 0
for (const placeStr in targetState) {
const place = parseInt(placeStr, 10) as ValidPlaceValues
const start = startState[place] || { heavenActive: false, earthActive: 0 }
const target = targetState[place]
// Check heaven bead changes
if (!start.heavenActive && target.heavenActive) {
additions.push({ placeValue: place, beadType: 'heaven' })
mainPlaceValue = place
} else if (start.heavenActive && !target.heavenActive) {
removals.push({ placeValue: place, beadType: 'heaven' })
mainPlaceValue = place
}
// Check earth bead changes
if (target.earthActive > start.earthActive) {
// Adding earth beads
for (let pos = start.earthActive; pos < target.earthActive; pos++) {
additions.push({ placeValue: place, beadType: 'earth', position: pos })
mainPlaceValue = place
}
} else if (target.earthActive < start.earthActive) {
// Removing earth beads
for (let pos = start.earthActive - 1; pos >= target.earthActive; pos--) {
removals.push({ placeValue: place, beadType: 'earth', position: pos })
mainPlaceValue = place
}
}
}
return { additions, removals, placeValue: mainPlaceValue }
}
// Note: numberToAbacusState and calculateBeadChanges are now re-exported from @soroban/abacus-react above
// Generate proper complement breakdown using simple bead movements
function generateProperComplementDescription(

View File

@@ -1,107 +1,25 @@
// Dynamic bead diff algorithm for calculating transitions between abacus states
// Provides arrows, highlights, and movement directions for tutorial UI
// Re-export core bead diff functionality from abacus-react
// App-specific extensions for multi-step tutorials and validation
import type { ValidPlaceValues } from '@soroban/abacus-react'
import {
export {
type BeadDiffResult,
type BeadDiffOutput,
calculateBeadDiff,
calculateBeadDiffFromValues,
areStatesEqual,
type AbacusState,
type BeadHighlight,
calculateBeadChanges,
numberToAbacusState,
} from './abacusInstructionGenerator'
type BeadState,
} from '@soroban/abacus-react'
export interface BeadDiffResult {
placeValue: ValidPlaceValues
beadType: 'heaven' | 'earth'
position?: number
direction: 'activate' | 'deactivate'
order: number // Order of operations for animations
}
export interface BeadDiffOutput {
changes: BeadDiffResult[]
highlights: BeadHighlight[]
hasChanges: boolean
summary: string
}
/**
* THE BEAD DIFF ALGORITHM
*
* Takes current and desired abacus states and returns exactly which beads
* need to move with arrows and highlights for the tutorial UI.
*
* This is the core "diff" function that keeps tutorial highlights in sync.
*/
export function calculateBeadDiff(fromState: AbacusState, toState: AbacusState): BeadDiffOutput {
const { additions, removals } = calculateBeadChanges(fromState, toState)
const changes: BeadDiffResult[] = []
const highlights: BeadHighlight[] = []
let order = 0
// Process removals first (pedagogical order: clear before adding)
removals.forEach((removal) => {
changes.push({
placeValue: removal.placeValue,
beadType: removal.beadType,
position: removal.position,
direction: 'deactivate',
order: order++,
})
highlights.push({
placeValue: removal.placeValue,
beadType: removal.beadType,
position: removal.position,
})
})
// Process additions second (pedagogical order: add after clearing)
additions.forEach((addition) => {
changes.push({
placeValue: addition.placeValue,
beadType: addition.beadType,
position: addition.position,
direction: 'activate',
order: order++,
})
highlights.push({
placeValue: addition.placeValue,
beadType: addition.beadType,
position: addition.position,
})
})
// Generate summary
const summary = generateDiffSummary(changes)
return {
changes,
highlights,
hasChanges: changes.length > 0,
summary,
}
}
/**
* Calculate bead diff from numeric values
* Convenience function for when you have numbers instead of states
*/
export function calculateBeadDiffFromValues(
fromValue: number,
toValue: number,
maxPlaces: number = 5
): BeadDiffOutput {
const fromState = numberToAbacusState(fromValue, maxPlaces)
const toState = numberToAbacusState(toValue, maxPlaces)
return calculateBeadDiff(fromState, toState)
}
import type { BeadDiffOutput, BeadDiffResult, AbacusState } from '@soroban/abacus-react'
import { calculateBeadDiffFromValues } from '@soroban/abacus-react'
/**
* Calculate step-by-step bead diffs for multi-step operations
* This is used for tutorial multi-step instructions where we want to show
* the progression through intermediate states
*
* APP-SPECIFIC FUNCTION - not in core abacus-react
*/
export function calculateMultiStepBeadDiffs(
startValue: number,
@@ -133,126 +51,10 @@ export function calculateMultiStepBeadDiffs(
return stepDiffs
}
/**
* Generate a human-readable summary of what the diff does
* Respects pedagogical order: removals first, then additions
*/
function generateDiffSummary(changes: BeadDiffResult[]): string {
if (changes.length === 0) {
return 'No changes needed'
}
// Sort by order to respect pedagogical sequence
const sortedChanges = [...changes].sort((a, b) => a.order - b.order)
const deactivations = sortedChanges.filter((c) => c.direction === 'deactivate')
const activations = sortedChanges.filter((c) => c.direction === 'activate')
const parts: string[] = []
// Process deactivations first (pedagogical order)
if (deactivations.length > 0) {
const deactivationsByPlace = groupByPlace(deactivations)
Object.entries(deactivationsByPlace).forEach(([place, beads]) => {
const placeName = getPlaceName(parseInt(place, 10))
const heavenBeads = beads.filter((b) => b.beadType === 'heaven')
const earthBeads = beads.filter((b) => b.beadType === 'earth')
if (heavenBeads.length > 0) {
parts.push(`remove heaven bead in ${placeName}`)
}
if (earthBeads.length > 0) {
const count = earthBeads.length
parts.push(`remove ${count} earth bead${count > 1 ? 's' : ''} in ${placeName}`)
}
})
}
// Process activations second (pedagogical order)
if (activations.length > 0) {
const activationsByPlace = groupByPlace(activations)
Object.entries(activationsByPlace).forEach(([place, beads]) => {
const placeName = getPlaceName(parseInt(place, 10))
const heavenBeads = beads.filter((b) => b.beadType === 'heaven')
const earthBeads = beads.filter((b) => b.beadType === 'earth')
if (heavenBeads.length > 0) {
parts.push(`add heaven bead in ${placeName}`)
}
if (earthBeads.length > 0) {
const count = earthBeads.length
parts.push(`add ${count} earth bead${count > 1 ? 's' : ''} in ${placeName}`)
}
})
}
return parts.join(', then ')
}
/**
* Group bead changes by place value
*/
function groupByPlace(changes: BeadDiffResult[]): {
[place: string]: BeadDiffResult[]
} {
return changes.reduce(
(groups, change) => {
const place = change.placeValue.toString()
if (!groups[place]) {
groups[place] = []
}
groups[place].push(change)
return groups
},
{} as { [place: string]: BeadDiffResult[] }
)
}
/**
* Get human-readable place name
*/
function getPlaceName(place: number): string {
switch (place) {
case 0:
return 'ones column'
case 1:
return 'tens column'
case 2:
return 'hundreds column'
case 3:
return 'thousands column'
default:
return `place ${place} column`
}
}
/**
* Check if two abacus states are equal
*/
export function areStatesEqual(state1: AbacusState, state2: AbacusState): boolean {
const places1 = Object.keys(state1)
.map((k) => parseInt(k, 10))
.sort()
const places2 = Object.keys(state2)
.map((k) => parseInt(k, 10))
.sort()
if (places1.length !== places2.length) return false
for (const place of places1) {
const bead1 = state1[place]
const bead2 = state2[place]
if (!bead2) return false
if (bead1.heavenActive !== bead2.heavenActive) return false
if (bead1.earthActive !== bead2.earthActive) return false
}
return true
}
/**
* Validate that a bead diff is feasible (no impossible bead states)
*
* APP-SPECIFIC FUNCTION - not in core abacus-react
*/
export function validateBeadDiff(diff: BeadDiffOutput): {
isValid: boolean
@@ -282,3 +84,20 @@ export function validateBeadDiff(diff: BeadDiffOutput): {
errors,
}
}
// Helper function for validation
function groupByPlace(changes: BeadDiffResult[]): {
[place: string]: BeadDiffResult[]
} {
return changes.reduce(
(groups, change) => {
const place = change.placeValue.toString()
if (!groups[place]) {
groups[place] = []
}
groups[place].push(change)
return groups
},
{} as { [place: string]: BeadDiffResult[] }
)
}

View File

@@ -0,0 +1,19 @@
/**
* Generate a simple abacus SVG element
* Uses AbacusStatic for server-side rendering (no client hooks)
*/
import React from 'react'
import { AbacusStatic } from '@soroban/abacus-react/static'
export function generateAbacusElement(value: number, columns: number) {
return (
<AbacusStatic
value={value}
columns={columns}
scaleFactor={1}
showNumbers={false}
frameVisible={true}
/>
)
}

View File

@@ -0,0 +1,187 @@
/**
* Generate a complete monthly calendar as a single SVG
* This prevents multi-page overflow - one image scales to fit
*/
import React from 'react'
import { AbacusStatic, calculateAbacusDimensions } from '@soroban/abacus-react/static'
interface CalendarCompositeOptions {
month: number
year: number
renderToString: (element: React.ReactElement) => string
}
const MONTH_NAMES = [
'January', 'February', 'March', 'April', 'May', 'June',
'July', 'August', 'September', 'October', 'November', 'December',
]
const WEEKDAYS = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']
function getDaysInMonth(year: number, month: number): number {
return new Date(year, month, 0).getDate()
}
function getFirstDayOfWeek(year: number, month: number): number {
return new Date(year, month - 1, 1).getDay()
}
export function generateCalendarComposite(options: CalendarCompositeOptions): string {
const { month, year, renderToString } = options
const daysInMonth = getDaysInMonth(year, month)
const firstDayOfWeek = getFirstDayOfWeek(year, month)
const monthName = MONTH_NAMES[month - 1]
// Layout constants for US Letter aspect ratio (8.5 x 11)
const WIDTH = 850
const HEIGHT = 1100
const MARGIN = 50
const CONTENT_WIDTH = WIDTH - MARGIN * 2
const CONTENT_HEIGHT = HEIGHT - MARGIN * 2
// Abacus natural size is 120x230 at scale=1
const ABACUS_NATURAL_WIDTH = 120
const ABACUS_NATURAL_HEIGHT = 230
// Calculate how many columns needed for year
const yearColumns = Math.max(1, Math.ceil(Math.log10(year + 1)))
// Year abacus dimensions (calculate first to determine header height)
// Use the shared dimension calculator so we stay in sync with AbacusStatic
const { width: yearAbacusActualWidth, height: yearAbacusActualHeight } = calculateAbacusDimensions({
columns: yearColumns,
showNumbers: false,
columnLabels: [],
})
const yearAbacusDisplayWidth = WIDTH * 0.15 // Display size on page
const yearAbacusDisplayHeight = (yearAbacusActualHeight / yearAbacusActualWidth) * yearAbacusDisplayWidth
// Header - sized to fit month name + year abacus
const MONTH_NAME_HEIGHT = 40
const HEADER_HEIGHT = MONTH_NAME_HEIGHT + yearAbacusDisplayHeight + 20 // 20px spacing
const TITLE_Y = MARGIN + 35
const yearAbacusX = (WIDTH - yearAbacusDisplayWidth) / 2
const yearAbacusY = TITLE_Y + 10
// Calendar grid
const GRID_START_Y = MARGIN + HEADER_HEIGHT
const GRID_HEIGHT = CONTENT_HEIGHT - HEADER_HEIGHT
const WEEKDAY_ROW_HEIGHT = 25
const DAY_GRID_HEIGHT = GRID_HEIGHT - WEEKDAY_ROW_HEIGHT
// 7 columns, up to 6 rows (35 cells max = 5 empty + 30 days worst case)
const CELL_WIDTH = CONTENT_WIDTH / 7
const DAY_CELL_HEIGHT = DAY_GRID_HEIGHT / 6
// Day abacus sizing - fit in cell with padding
const CELL_PADDING = 5
// Calculate max scale to fit in cell
const MAX_SCALE_X = (CELL_WIDTH - CELL_PADDING * 2) / ABACUS_NATURAL_WIDTH
const MAX_SCALE_Y = (DAY_CELL_HEIGHT - CELL_PADDING * 2) / ABACUS_NATURAL_HEIGHT
const ABACUS_SCALE = Math.min(MAX_SCALE_X, MAX_SCALE_Y) * 0.9 // 90% to leave breathing room
const SCALED_ABACUS_WIDTH = ABACUS_NATURAL_WIDTH * ABACUS_SCALE
const SCALED_ABACUS_HEIGHT = ABACUS_NATURAL_HEIGHT * ABACUS_SCALE
// Generate calendar grid
const calendarCells: (number | null)[] = []
for (let i = 0; i < firstDayOfWeek; i++) {
calendarCells.push(null)
}
for (let day = 1; day <= daysInMonth; day++) {
calendarCells.push(day)
}
// Render individual abacus SVGs as complete SVG elements
function renderAbacusSVG(value: number, columns: number, scale: number): string {
return renderToString(
<AbacusStatic
value={value}
columns={columns}
scaleFactor={scale}
showNumbers={false}
frameVisible={true}
compact={false}
/>
)
}
// Main composite SVG
const compositeSVG = `<svg xmlns="http://www.w3.org/2000/svg" width="${WIDTH}" height="${HEIGHT}" viewBox="0 0 ${WIDTH} ${HEIGHT}">
<!-- Background -->
<rect width="${WIDTH}" height="${HEIGHT}" fill="white"/>
<!-- Title: Month Name -->
<text x="${WIDTH / 2}" y="${TITLE_Y}" text-anchor="middle" font-family="Arial" font-size="32" font-weight="bold" fill="#1a1a1a">
${monthName}
</text>
<!-- Year Abacus (centered below month name) -->
${(() => {
const yearAbacusSVG = renderAbacusSVG(year, yearColumns, 1)
const yearAbacusContent = yearAbacusSVG.replace(/<svg[^>]*>/, '').replace(/<\/svg>$/, '')
return `<svg x="${yearAbacusX}" y="${yearAbacusY}" width="${yearAbacusDisplayWidth}" height="${yearAbacusDisplayHeight}"
viewBox="0 0 ${yearAbacusActualWidth} ${yearAbacusActualHeight}">
${yearAbacusContent}
</svg>`
})()}
<!-- Weekday Headers -->
${WEEKDAYS.map((day, i) => `
<text x="${MARGIN + i * CELL_WIDTH + CELL_WIDTH / 2}" y="${GRID_START_Y + 18}"
text-anchor="middle" font-family="Arial" font-size="14" font-weight="bold" fill="#555">
${day}
</text>`).join('')}
<!-- Separator line under weekdays -->
<line x1="${MARGIN}" y1="${GRID_START_Y + WEEKDAY_ROW_HEIGHT}"
x2="${WIDTH - MARGIN}" y2="${GRID_START_Y + WEEKDAY_ROW_HEIGHT}"
stroke="#333" stroke-width="2"/>
<!-- Calendar Grid Cells -->
${calendarCells.map((day, index) => {
const row = Math.floor(index / 7)
const col = index % 7
const cellX = MARGIN + col * CELL_WIDTH
const cellY = GRID_START_Y + WEEKDAY_ROW_HEIGHT + row * DAY_CELL_HEIGHT
return `
<rect x="${cellX}" y="${cellY}" width="${CELL_WIDTH}" height="${DAY_CELL_HEIGHT}"
fill="none" stroke="#333" stroke-width="2"/>`
}).join('')}
<!-- Calendar Day Abaci -->
${calendarCells.map((day, index) => {
if (day === null) return ''
const row = Math.floor(index / 7)
const col = index % 7
const cellX = MARGIN + col * CELL_WIDTH
const cellY = GRID_START_Y + WEEKDAY_ROW_HEIGHT + row * DAY_CELL_HEIGHT
// Center abacus in cell
const abacusCenterX = cellX + CELL_WIDTH / 2
const abacusCenterY = cellY + DAY_CELL_HEIGHT / 2
// Offset to top-left corner of abacus (accounting for scaled size)
const abacusX = abacusCenterX - SCALED_ABACUS_WIDTH / 2
const abacusY = abacusCenterY - SCALED_ABACUS_HEIGHT / 2
// Render at scale=1 and let the nested SVG handle scaling via viewBox
const abacusSVG = renderAbacusSVG(day, 2, 1)
const svgContent = abacusSVG.replace(/<svg[^>]*>/, '').replace(/<\/svg>$/, '')
return `
<!-- Day ${day} (row ${row}, col ${col}) -->
<svg x="${abacusX}" y="${abacusY}" width="${SCALED_ABACUS_WIDTH}" height="${SCALED_ABACUS_HEIGHT}"
viewBox="0 0 ${ABACUS_NATURAL_WIDTH} ${ABACUS_NATURAL_HEIGHT}">
${svgContent}
</svg>`
}).join('')}
</svg>`
return compositeSVG
}

View File

@@ -11,7 +11,13 @@
"Bash(git commit:*)",
"Bash(npm run build:*)",
"Bash(git reset:*)",
"Bash(cat:*)"
"Bash(cat:*)",
"Bash(pnpm --filter @soroban/abacus-react build:*)",
"Bash(git show:*)",
"Bash(pnpm build:*)",
"Bash(pnpm --filter @soroban/web build:*)",
"Bash(pnpm tsc:*)",
"Bash(AbacusReact.tsx)"
]
},
"enableAllProjectMcpServers": true,

View File

@@ -1,3 +1,96 @@
## [2.8.3](https://github.com/antialias/soroban-abacus-flashcards/compare/abacus-react-v2.8.2...abacus-react-v2.8.3) (2025-11-05)
### Bug Fixes
* **tutorial:** correct column validation for bead highlights ([9ba1824](https://github.com/antialias/soroban-abacus-flashcards/commit/9ba18242262cd63cc6c25361aaec3a4c0f66b161))
* **tutorial:** fix overlay rendering, arrow indicators, and bead visibility ([a804316](https://github.com/antialias/soroban-abacus-flashcards/commit/a80431608dbc4f54d8e4f1095936b95a258b4a72))
* **web,docker:** add --format flag for Typst and upgrade to v0.13.0 ([19b9d7a](https://github.com/antialias/soroban-abacus-flashcards/commit/19b9d7a74f549c7e93c9564e4a903e1bcd5a4bbc))
* **web:** move tsx to production dependencies for calendar generation ([ffae9c1](https://github.com/antialias/soroban-abacus-flashcards/commit/ffae9c1bdbccc5edb2e747a09d1fcad3b29e4eac))
## [2.8.2](https://github.com/antialias/soroban-abacus-flashcards/compare/abacus-react-v2.8.1...abacus-react-v2.8.2) (2025-11-04)
### Bug Fixes
* **abacus-react:** add data-testid attributes back to beads for testing ([23ae1b0](https://github.com/antialias/soroban-abacus-flashcards/commit/23ae1b0c6f878daf79a993992d43ad80a89fa790))
## [2.8.1](https://github.com/antialias/soroban-abacus-flashcards/compare/abacus-react-v2.8.0...abacus-react-v2.8.1) (2025-11-04)
### Bug Fixes
* **abacus-react:** fix animations by preventing component remounting ([be7d4c4](https://github.com/antialias/soroban-abacus-flashcards/commit/be7d4c471327534a95c4c75372680c629b5f12c2))
* **abacus-react:** restore original AbacusReact measurements and positioning ([88c0baa](https://github.com/antialias/soroban-abacus-flashcards/commit/88c0baaad9b83b60ab8cdcad92070cc049d61cc7))
# [2.8.0](https://github.com/antialias/soroban-abacus-flashcards/compare/abacus-react-v2.7.1...abacus-react-v2.8.0) (2025-11-04)
### Bug Fixes
* **docker:** add scripts, abacus-react, and tsx for production calendar generation ([33eb90e](https://github.com/antialias/soroban-abacus-flashcards/commit/33eb90e316f84650ae619f8c6c02c9e77c663d1b))
* **web:** generate styled-system artifacts during build ([293390a](https://github.com/antialias/soroban-abacus-flashcards/commit/293390ae350a6c6aa467410f68c735512104d9dd))
* **web:** move react-dom/server import to API route to satisfy Next.js ([00a8bc3](https://github.com/antialias/soroban-abacus-flashcards/commit/00a8bc3e5e8f044df280c4356d3605a852f82e84))
* **web:** prevent abacus overlap in composite calendar ([448f93c](https://github.com/antialias/soroban-abacus-flashcards/commit/448f93c1e2a7f86bc48e678d4599ca968c6d81d2)), closes [#f0f0f0](https://github.com/antialias/soroban-abacus-flashcards/issues/f0f0f0)
* **web:** use dynamic import for react-dom/server in API route ([4f93c7d](https://github.com/antialias/soroban-abacus-flashcards/commit/4f93c7d996732de4bc19e7acf2d4ce803cba88b6))
* **web:** use nested SVG elements to prevent coordinate space conflicts ([f9cbee8](https://github.com/antialias/soroban-abacus-flashcards/commit/f9cbee8fcdf80641f3b82a65fad6b8a3575525fc))
### Features
* **abacus-react:** add shared dimension calculator for consistent sizing ([e5ba772](https://github.com/antialias/soroban-abacus-flashcards/commit/e5ba772fde9839c22daec92007f052ca125c7695))
* **web:** add Typst-based preview endpoint with React Suspense ([599a758](https://github.com/antialias/soroban-abacus-flashcards/commit/599a758471c43ab0fc87301c5e7eeceed608062e))
* **web:** add year abacus to calendar header and make grid bolder ([867c7ee](https://github.com/antialias/soroban-abacus-flashcards/commit/867c7ee17251b8df13665bee9c0391961975e681)), closes [#333](https://github.com/antialias/soroban-abacus-flashcards/issues/333)
* **web:** optimize monthly calendar for single-page layout ([b277a89](https://github.com/antialias/soroban-abacus-flashcards/commit/b277a89415d1823455376c3e0f641b52f3394e7c))
* **web:** redesign monthly calendar as single composite SVG ([8ce8038](https://github.com/antialias/soroban-abacus-flashcards/commit/8ce8038baeea0b8b0fffe3215746958731bd9d6a))
## [2.7.1](https://github.com/antialias/soroban-abacus-flashcards/compare/abacus-react-v2.7.0...abacus-react-v2.7.1) (2025-11-04)
### Bug Fixes
* add xmlns to AbacusStatic for Typst SVG parsing ([98cd019](https://github.com/antialias/soroban-abacus-flashcards/commit/98cd019d4af91d7ca4e7a88f700194273476afb7))
* **web:** use AbacusStatic for calendar SVG generation ([08c6a41](https://github.com/antialias/soroban-abacus-flashcards/commit/08c6a419e25d220560eba13d6db437145e6e61b8))
# [2.7.0](https://github.com/antialias/soroban-abacus-flashcards/compare/abacus-react-v2.6.0...abacus-react-v2.7.0) (2025-11-04)
### Bug Fixes
* **web:** add dynamic export to rithmomachia page ([329e623](https://github.com/antialias/soroban-abacus-flashcards/commit/329e62321245ef62726c986c917f19a909a5b65e))
* **web:** fix Typst PDF generation path resolution ([7ce1287](https://github.com/antialias/soroban-abacus-flashcards/commit/7ce12875254a31d8acdb35ef5de7d36d215ccd92))
### Features
* **abacus-react:** add separate /static export path for React Server Components ([ed69f6b](https://github.com/antialias/soroban-abacus-flashcards/commit/ed69f6b917c543bbcaa4621a0e63745bee70f5bf))
* **web:** add test page for AbacusStatic RSC compatibility ([903dea2](https://github.com/antialias/soroban-abacus-flashcards/commit/903dea25844f1d2b3730fbcbd8478e7af1887663))
* **web:** improve calendar abacus preview styling ([8439727](https://github.com/antialias/soroban-abacus-flashcards/commit/8439727b152accf61f0c28158b92788510ca086e))
# [2.6.0](https://github.com/antialias/soroban-abacus-flashcards/compare/abacus-react-v2.5.0...abacus-react-v2.6.0) (2025-11-04)
### Bug Fixes
* **abacus-react:** correct column highlighting offset in AbacusStatic ([0641eb7](https://github.com/antialias/soroban-abacus-flashcards/commit/0641eb719ef56c67de965296006df666f83e5b08))
### Features
* **abacus-react:** add AbacusStatic for React Server Components ([3b8e864](https://github.com/antialias/soroban-abacus-flashcards/commit/3b8e864cfa3af50b1912ce7ff55003d7f6b9c229))
* **web:** add test page for AbacusStatic Server Component ([3588d5a](https://github.com/antialias/soroban-abacus-flashcards/commit/3588d5acde25588ce4db3ee32adb04ace0e394d4))
# [2.5.0](https://github.com/antialias/soroban-abacus-flashcards/compare/abacus-react-v2.4.0...abacus-react-v2.5.0) (2025-11-03)
### Features
* **abacus-react:** add core utility functions for state management ([e65541c](https://github.com/antialias/soroban-abacus-flashcards/commit/e65541c100e590a51448750c6d5178ed4f3e8eeb))
* **abacus-react:** add layout and educational props ([35bbcec](https://github.com/antialias/soroban-abacus-flashcards/commit/35bbcecb9e36f1ef5917a5a629f5e78f1f490e9c))
* **abacus-react:** add pre-defined theme presets ([cf1f950](https://github.com/antialias/soroban-abacus-flashcards/commit/cf1f950c7c5fb9ee1f0de673235d6f037be3b9d6))
* **abacus-react:** add React hooks for abacus calculations ([de038d2](https://github.com/antialias/soroban-abacus-flashcards/commit/de038d2afc26c36c1490d5ea45dace0ab812c5cc))
* **abacus-react:** export new utilities, hooks, and themes ([ce4e44d](https://github.com/antialias/soroban-abacus-flashcards/commit/ce4e44d6302746053ad40dc61bab57ef3a0a9f31))
# [2.4.0](https://github.com/antialias/soroban-abacus-flashcards/compare/abacus-react-v2.3.0...abacus-react-v2.4.0) (2025-11-03)

View File

@@ -0,0 +1,402 @@
# Abacus-React Feature Enhancement Plan
## Executive Summary
The web application has developed numerous custom patterns and workarounds for styling, layout, and interactions with the abacus component. These patterns reveal gaps in the abacus-react API that, if addressed, would significantly improve developer experience and reduce code duplication across the application.
## Priority 1: Critical Features (High Impact, High Frequency)
### 1. **Inline "Mini Abacus" Component**
**Location**: `apps/web/src/app/arcade/complement-race/components/AbacusTarget.tsx`
**Current Implementation**:
```tsx
<AbacusReact
value={number}
columns={1}
interactive={false}
showNumbers={false}
hideInactiveBeads={true}
scaleFactor={0.72}
customStyles={{
columnPosts: { opacity: 0 },
}}
/>
```
**Problem**: Creating an inline mini-abacus for displaying single digits requires multiple props and style overrides. This pattern appears throughout game UIs.
**Proposed Solution**: Add a `variant` prop with preset configurations:
```tsx
// Native API proposal
<AbacusReact
value={7}
variant="inline-digit"
// Automatically sets: columns=1, hideInactiveBeads, transparent frame, optimal scaleFactor
/>
// Or more granular:
<AbacusReact
value={7}
compact={true} // Removes frame, optimizes spacing
frameVisible={false} // Hide posts and bar
/>
```
**Benefits**:
- Single prop instead of 5+
- Consistent inline abacus appearance across the app
- Better semantic intent
---
### 2. **Theme-Aware Styling Presets**
**Locations**:
- `MyAbacus.tsx` (lines 60-85) - structural & trophy styles
- `HeroAbacus.tsx` (lines 20-32) - structural styles
- `LevelSliderDisplay.tsx` (lines 263-275) - dark theme styles
**Current Pattern**: Every component defines custom style objects for structural elements:
```tsx
const structuralStyles = {
columnPosts: {
fill: 'rgb(255, 255, 255)',
stroke: 'rgb(200, 200, 200)',
strokeWidth: 2,
},
reckoningBar: {
fill: 'rgb(255, 255, 255)',
stroke: 'rgb(200, 200, 200)',
strokeWidth: 3,
},
}
```
**Problem**: Manual style object creation for common themes is repetitive and error-prone.
**Proposed Solution**: Add theme presets to abacus-react:
```tsx
// Native API proposal
<AbacusReact
value={123}
theme="dark" // or "light", "translucent", "solid", "trophy"
/>
// Or expose theme constants
import { ABACUS_THEMES } from '@soroban/abacus-react'
<AbacusReact customStyles={ABACUS_THEMES.dark} />
```
**Benefits**:
- Eliminates ~30 lines of style definitions per component
- Ensures visual consistency
- Makes theme switching trivial
---
### 3. **Scaling Containers & Responsive Layouts**
**Locations**:
- `HeroAbacus.tsx` (lines 133-138) - manual scale transforms
- `MyAbacus.tsx` (lines 214-218) - responsive scale values
- `LevelSliderDisplay.tsx` (lines 370-379) - dynamic scale calculation
**Current Pattern**: Components manually wrap abacus in transform containers:
```tsx
<div style={{
transform: 'scale(3.5)',
transformOrigin: 'center center'
}}>
<AbacusReact value={1234} columns={4} />
</div>
```
**Problem**: Manual transform handling requires extra DOM nesting, breaks click boundaries, and makes centering complex.
**Proposed Solution**: Enhanced `scaleFactor` with responsive breakpoints:
```tsx
// Native API proposal
<AbacusReact
value={1234}
scaleFactor={{ base: 2.5, md: 3.5, lg: 4.5 }} // Responsive
scaleOrigin="center" // Handle transform origin
scaleContainer={true} // Apply correct boundaries for interaction
/>
```
**Benefits**:
- Eliminates wrapper divs
- Proper click/hover boundaries
- Built-in responsive scaling
---
## Priority 2: Developer Experience Improvements
### 4. **Bead Diff Calculation System**
**Location**: `apps/web/src/utils/beadDiff.ts` (285 lines) + `abacusInstructionGenerator.ts` (400+ lines)
**Current Implementation**: Complex utilities to calculate which beads need to move between states:
```tsx
// Current external pattern
import { calculateBeadDiffFromValues } from '@/utils/beadDiff'
const diff = calculateBeadDiffFromValues(fromValue, toValue)
const stepBeadHighlights = diff.changes.map(change => ({
placeValue: change.placeValue,
beadType: change.beadType,
direction: change.direction,
// ...
}))
```
**Problem**: Tutorial/game developers need to calculate bead movements manually. This core logic belongs in abacus-react.
**Proposed Solution**: Add a diff calculation hook:
```tsx
// Native API proposal
import { useAbacusDiff } from '@soroban/abacus-react'
function Tutorial() {
const diff = useAbacusDiff(startValue, targetValue)
return (
<AbacusReact
value={currentValue}
stepBeadHighlights={diff.highlights} // Generated by hook
// diff also includes: instructions, order, validation
/>
)
}
```
**Benefits**:
- Centralizes "diff" algorithm
- Eliminates ~500 lines of application code
- Better tested and maintained
---
### 5. **Tutorial/Step Context Provider**
**Location**: `apps/web/src/components/tutorial/TutorialContext.tsx`
**Current Pattern**: Apps need to implement complex state management for multi-step tutorial flows with reducer patterns, event tracking, and error handling.
**Problem**: Tutorial infrastructure is duplicated across components. The logic for tracking progress through abacus instruction steps is tightly coupled to application code.
**Proposed Solution**: Add optional tutorial/stepper context to abacus-react:
```tsx
// Native API proposal
import { AbacusReact, AbacusTutorial } from '@soroban/abacus-react'
<AbacusTutorial
steps={[
{ from: 0, to: 5, instruction: "Add 5" },
{ from: 5, to: 15, instruction: "Add 10" },
]}
onStepComplete={(step) => { /* analytics */ }}
onComplete={() => { /* celebration */ }}
>
<AbacusReact />
</AbacusTutorial>
```
**Benefits**:
- Reusable tutorial infrastructure
- Built-in progress tracking and validation
- Could power educational features across projects
---
## Priority 3: Nice-to-Have Enhancements
### 6. **Animation Speed Configuration**
**Location**: `LevelSliderDisplay.tsx` (lines 306-345)
**Current Pattern**: Applications control animation speed by rapidly changing the value prop:
```tsx
const intervalMs = 500 - danProgress * 490 // 500ms down to 10ms
setInterval(() => {
setAnimatedDigits(prev => {
// Rapidly change digits to simulate calculation
})
}, intervalMs)
```
**Problem**: "Rapid calculation" animation requires external interval management.
**Proposed Solution**: Add animation speed prop:
```tsx
// Native API proposal
<AbacusReact
value={calculatingValue}
animationSpeed="fast" // or "normal", "slow", or ms number
autoAnimate={true} // Animate value prop changes automatically
/>
```
**Benefits**:
- Smoother animations with internal management
- Consistent timing across the app
---
### 7. **Draggable/Positionable Abacus Cards**
**Location**: `InteractiveFlashcards.tsx`
**Current Pattern**: Complex drag-and-drop implementation wrapped around each AbacusReact instance with pointer capture, offset tracking, and rotation.
**Problem**: Making abacus instances draggable requires significant boilerplate.
**Proposed Solution**: This is probably too specific to remain external. However, a ref-based API to get bounding boxes would help:
```tsx
// Possible improvement
const abacusRef = useAbacusRef()
<AbacusReact ref={abacusRef} />
// abacusRef.current.getBoundingBox() for drag calculations
```
---
### 8. **Column Highlighting for Multi-Step Problems**
**Location**: Tutorial system extensively
**Current Pattern**: Manual column highlighting based on place values with custom overlay positioning logic.
**Problem**: Highlighting specific columns (e.g., "the tens column") requires external overlay management.
**Proposed Solution**: Add native column highlighting:
```tsx
// Native API proposal
<AbacusReact
value={123}
highlightColumns={[1]} // Highlight tens column
columnLabels={["ones", "tens", "hundreds"]} // Optional labels
/>
```
---
## Priority 4: Documentation & Exports
### 9. **Utility Functions & Types**
**Current State**: Apps re-implement utilities for working with abacus states:
- `numberToAbacusState()` - convert numbers to bead states
- `calculateBeadChanges()` - diff algorithm
- `ValidPlaceValues` type - imported but limited
**Proposed Solution**: Export more utilities from abacus-react:
```tsx
// Expanded exports
export {
// Utilities
numberToAbacusState,
abacusStateToNumber,
calculateBeadDiff,
validateAbacusValue,
// Types
AbacusState,
BeadState,
PlaceValue,
// Hooks
useAbacusDiff,
useAbacusValidation,
useAbacusState,
}
```
---
## Implementation Roadmap
### Phase 1 (Immediate) ✅ COMPLETED
1. ✅ Add `frameVisible={false}` prop
2. ✅ Add `compact` prop/variant
3. ✅ Export theme presets (ABACUS_THEMES constant)
### Phase 2 (Short-term) ✅ COMPLETED
4. ⏸️ Enhanced `scaleFactor` with responsive object support (DEFERRED - too complex, low priority)
5. ✅ Export utility functions (numberToAbacusState, calculateBeadDiff, etc.)
### Phase 3 (Medium-term) ✅ COMPLETED
6. ✅ Add `useAbacusDiff` hook
7. ✅ Add native column highlighting with `highlightColumns` and `columnLabels` props
### Phase 4 (Long-term - Future)
8. 📋 Consider tutorial context provider (needs more research)
9. 📋 Animation speed controls
## Completed Features Summary
### New Props
- `frameVisible?: boolean` - Show/hide column posts and reckoning bar
- `compact?: boolean` - Compact layout for inline display (implies frameVisible=false)
- `highlightColumns?: number[]` - Highlight specific columns by index
- `columnLabels?: string[]` - Optional labels for columns
### New Exports
- `ABACUS_THEMES` - Pre-defined theme presets (light, dark, trophy, translucent, solid, traditional)
- `AbacusThemeName` type - TypeScript type for theme names
### New Utility Functions
- `numberToAbacusState(value, maxPlaces)` - Convert number to bead positions
- `abacusStateToNumber(state)` - Convert bead positions to number
- `calculateBeadChanges(startState, targetState)` - Calculate bead differences
- `calculateBeadDiff(fromState, toState)` - Full diff with order and directions
- `calculateBeadDiffFromValues(from, to, maxPlaces)` - Convenience wrapper
- `validateAbacusValue(value, maxPlaces)` - Validate number ranges
- `areStatesEqual(state1, state2)` - Compare states
### New Hooks
- `useAbacusDiff(fromValue, toValue, maxPlaces)` - Calculate bead differences for tutorials
- `useAbacusState(value, maxPlaces)` - Convert number to abacus state (memoized)
### New Types
- `BeadState` - Bead state in a single column
- `AbacusState` - Complete abacus state
- `BeadDiffResult` - Single bead movement result
- `BeadDiffOutput` - Complete diff output
- `PlaceValueBasedBead` - Internal place-value based bead type
---
## Metrics & Impact
**Code Reduction Estimate**:
- Eliminates ~800-1000 lines of repetitive application code
- Reduces component complexity by ~40% for tutorial/game components
**Developer Experience**:
- Faster onboarding for new features using abacus
- More consistent UX across application
- Better TypeScript support and autocomplete
**Maintenance**:
- Centralized logic easier to test and debug
- Single source of truth for abacus behavior
- Easier to add new features (e.g., sound effects for different themes)
---
## Questions for Discussion
1. Should we split these into separate packages (e.g., `@soroban/abacus-tutorial`)?
2. Which theme presets should be included by default?
3. Should responsive scaling use CSS media queries or JS breakpoints?
4. How much tutorial logic belongs in the core library vs. app code?

View File

@@ -0,0 +1,162 @@
# Integration Summary
## ✅ Completed: Apps/Web Integration with Abacus-React Enhancements
### Features Implemented & Integrated
#### 1. **Theme Presets (ABACUS_THEMES)**
**Status:** ✅ Fully integrated
**Files Updated:**
- `apps/web/src/components/MyAbacus.tsx` - Now uses `ABACUS_THEMES.light` and `ABACUS_THEMES.trophy`
- `apps/web/src/components/HeroAbacus.tsx` - Now uses `ABACUS_THEMES.light`
- `apps/web/src/components/LevelSliderDisplay.tsx` - Now uses `ABACUS_THEMES.dark`
**Code Eliminated:** ~60 lines of duplicate theme style definitions
---
#### 2. **Compact Prop**
**Status:** ✅ Fully integrated
**Files Updated:**
- `apps/web/src/app/arcade/complement-race/components/AbacusTarget.tsx` - Now uses `compact={true}`
**Before:**
```tsx
<AbacusReact
value={number}
columns={1}
interactive={false}
showNumbers={false}
hideInactiveBeads={true}
scaleFactor={0.72}
customStyles={{ columnPosts: { opacity: 0 } }}
/>
```
**After:**
```tsx
<AbacusReact
value={number}
columns={1}
compact={true}
interactive={false}
showNumbers={false}
hideInactiveBeads={true}
scaleFactor={0.72}
/>
```
---
#### 3. **Utility Functions**
**Status:** ✅ Fully integrated
**Files Updated:**
- `apps/web/src/utils/beadDiff.ts` - Now re-exports from abacus-react
- `apps/web/src/utils/abacusInstructionGenerator.ts` - Now re-exports from abacus-react
- `apps/web/src/components/tutorial/TutorialPlayer.tsx` - Imports `calculateBeadDiffFromValues` from abacus-react
- `apps/web/src/components/tutorial/TutorialEditor.tsx` - Imports `calculateBeadDiffFromValues` from abacus-react
**Exports from abacus-react:**
- `numberToAbacusState()`
- `abacusStateToNumber()`
- `calculateBeadChanges()`
- `calculateBeadDiff()`
- `calculateBeadDiffFromValues()`
- `validateAbacusValue()`
- `areStatesEqual()`
**Code Eliminated:** ~200+ lines of duplicate utility implementations
---
#### 4. **React Hooks**
**Status:** ✅ Exported and ready to use
**Available Hooks:**
- `useAbacusDiff(fromValue, toValue, maxPlaces)` - Memoized bead diff calculation
- `useAbacusState(value, maxPlaces)` - Memoized state conversion
**Not yet used in app** (available for future tutorials)
---
#### 5. **Column Highlighting**
**Status:** ✅ Implemented, not yet used
**New Props:**
- `highlightColumns?: number[]` - Highlight specific columns
- `columnLabels?: string[]` - Add educational labels above columns
**Usage Example:**
```tsx
<AbacusReact
value={123}
highlightColumns={[1]}
columnLabels={['ones', 'tens', 'hundreds']}
/>
```
---
### Code Deduplication Summary
**Total Lines Eliminated:** ~260-300 lines
**Breakdown:**
- Theme style definitions: ~60 lines
- Utility function implementations: ~200 lines
- Custom styles for inline abacus: ~5-10 lines per usage
---
### Remaining Work (Optional Future Enhancements)
1. Use `highlightColumns` and `columnLabels` in tutorial components
2. Replace manual bead diff calculations with `useAbacusDiff` hook in interactive tutorials
3. Use `useAbacusState` for state inspection in debugging/development tools
4. Consider implementing `frameVisible` toggles in settings pages
---
### Files Modified
**packages/abacus-react:**
- `src/AbacusReact.tsx` - Added new props (frameVisible, compact, highlightColumns, columnLabels)
- `src/AbacusThemes.ts` - **NEW FILE** - 6 theme presets
- `src/AbacusUtils.ts` - **NEW FILE** - Core utility functions
- `src/AbacusHooks.ts` - **NEW FILE** - React hooks
- `src/index.ts` - Updated exports
- `src/AbacusReact.themes-and-utilities.stories.tsx` - **NEW FILE** - Storybook demos
- `README.md` - Updated with new features documentation
- `ENHANCEMENT_PLAN.md` - Updated with completion status
**apps/web:**
- `src/components/MyAbacus.tsx` - Using ABACUS_THEMES
- `src/components/HeroAbacus.tsx` - Using ABACUS_THEMES
- `src/components/LevelSliderDisplay.tsx` - Using ABACUS_THEMES
- `src/app/arcade/complement-race/components/AbacusTarget.tsx` - Using compact prop
- `src/components/tutorial/TutorialPlayer.tsx` - Importing from abacus-react
- `src/components/tutorial/TutorialEditor.tsx` - Importing from abacus-react
- `src/utils/beadDiff.ts` - Re-exports from abacus-react
- `src/utils/abacusInstructionGenerator.ts` - Re-exports from abacus-react
---
### Testing
✅ Build successful for packages/abacus-react
✅ TypeScript compilation passes for integrated files
✅ Runtime tests confirm functions work correctly
✅ Storybook stories demonstrate all new features
---
### Next Steps
1. Monitor app for any runtime issues with the new integrations
2. Consider using hooks in future tutorial implementations
3. Explore using column highlighting in educational content
4. Document best practices for theme usage in the app

View File

@@ -14,6 +14,7 @@ A comprehensive React component for rendering interactive Soroban (Japanese abac
- 🎓 **Tutorial system** - Built-in overlay and guidance capabilities
- 🧩 **Framework-free SVG** - Complete control over rendering
-**3D Enhancement** - Three levels of progressive 3D effects for immersive visuals
- 🚀 **Server Component support** - AbacusStatic works in React Server Components (Next.js App Router)
## Installation
@@ -86,34 +87,213 @@ Personalized colors and highlights
/>
```
### Theme Presets
Use pre-defined themes for quick styling:
```tsx
import { AbacusReact, ABACUS_THEMES } from '@soroban/abacus-react';
// Available themes: 'light', 'dark', 'trophy', 'translucent', 'solid', 'traditional'
<AbacusReact
value={123}
columns={3}
customStyles={ABACUS_THEMES.dark}
/>
<AbacusReact
value={456}
columns={3}
customStyles={ABACUS_THEMES.trophy} // Golden frame for achievements
/>
<AbacusReact
value={789}
columns={3}
customStyles={ABACUS_THEMES.traditional} // Brown wooden appearance
/>
```
**Available Themes:**
- `light` - Solid white frame with subtle gray accents (best for light backgrounds)
- `dark` - Translucent white with subtle glow (best for dark backgrounds)
- `trophy` - Golden frame with warm tones (best for achievements/rewards)
- `translucent` - Nearly invisible frame (best for inline/minimal UI)
- `solid` - Black frame (best for high contrast/educational contexts)
- `traditional` - Brown wooden appearance (best for traditional soroban aesthetic)
### Static Display (Server Components)
For static, non-interactive displays that work in React Server Components:
```tsx
// IMPORTANT: Use /static import path for RSC compatibility!
import { AbacusStatic } from '@soroban/abacus-react/static';
// ✅ Works in React Server Components - no "use client" needed!
// ✅ No JavaScript sent to client
// ✅ Perfect for SSG, SSR, and static previews
<AbacusStatic
value={123}
columns="auto"
hideInactiveBeads
compact
/>
```
**Import paths:**
- `@soroban/abacus-react` - Full package (client components with hooks/animations)
- `@soroban/abacus-react/static` - Server-compatible components only (no client code)
**Guaranteed Visual Consistency:**
Both `AbacusStatic` and `AbacusReact` share the same underlying layout engine. **Same props = same exact SVG output.** This ensures:
- Static previews match interactive versions pixel-perfect
- Server-rendered abaci look identical to client-rendered ones
- PDF generation produces accurate representations
- No visual discrepancies between environments
**Architecture: How We Guarantee Consistency**
The package uses a shared rendering architecture with dependency injection:
```
┌─────────────────────────────────────────────┐
│ Shared Utilities (AbacusUtils.ts) │
│ • calculateStandardDimensions() - Single │
│ source of truth for all layout dimensions│
│ • calculateBeadPosition() - Exact bead │
│ positioning using shared formulas │
└────────────┬────────────────────────────────┘
├──────────────────────────────────┐
↓ ↓
┌─────────────────┐ ┌─────────────────┐
│ AbacusStatic │ │ AbacusReact │
│ (Server/Static) │ │ (Interactive) │
└────────┬────────┘ └────────┬────────┘
│ │
└────────────┬───────────────────┘
┌────────────────────────┐
│ AbacusSVGRenderer │
│ • Pure SVG structure │
│ • Dependency injection │
│ • Bead component prop │
└────────────────────────┘
┌───────────────┴───────────────┐
↓ ↓
┌──────────────┐ ┌──────────────────┐
│ AbacusStatic │ │ AbacusAnimated │
│ Bead │ │ Bead │
│ (Simple SVG) │ │ (react-spring) │
└──────────────┘ └──────────────────┘
```
**Key Components:**
1. **`calculateStandardDimensions()`** - Returns complete layout dimensions (bar position, bead sizes, gaps, etc.)
2. **`calculateBeadPosition()`** - Calculates exact x,y coordinates for any bead
3. **`AbacusSVGRenderer`** - Shared SVG rendering component that accepts a bead component via dependency injection
4. **`AbacusStaticBead`** - Simple SVG shapes for static display (no hooks, RSC-compatible)
5. **`AbacusAnimatedBead`** - Client component with react-spring animations and gesture handling
This architecture eliminates code duplication (~560 lines removed in the refactor) while guaranteeing pixel-perfect consistency.
**When to use `AbacusStatic` vs `AbacusReact`:**
| Feature | AbacusStatic | AbacusReact |
|---------|--------------|-------------|
| React Server Components | ✅ Yes | ❌ No (requires "use client") |
| Client-side JavaScript | ❌ None | ✅ Yes |
| User interaction | ❌ No | ✅ Click/drag beads |
| Animations | ❌ No | ✅ Smooth transitions |
| Sound effects | ❌ No | ✅ Optional sounds |
| 3D effects | ❌ No | ✅ Yes |
| **Visual output** | **✅ Identical** | **✅ Identical** |
| Bundle size | 📦 Minimal | 📦 Full-featured |
| Use cases | Preview cards, thumbnails, static pages, PDFs | Interactive tutorials, games, tools |
```tsx
// Example: Server Component with static abacus cards
// app/flashcards/page.tsx
import { AbacusStatic } from '@soroban/abacus-react/static'
export default function FlashcardsPage() {
const numbers = [1, 5, 10, 25, 50, 100]
return (
<div className="grid grid-cols-3 gap-4">
{numbers.map(num => (
<div key={num} className="card">
<AbacusStatic value={num} columns="auto" compact />
<p>{num}</p>
</div>
))}
</div>
)
}
```
### Compact/Inline Display
Create mini abacus displays for inline use:
```tsx
// Compact mode - automatically hides frame and optimizes spacing
<AbacusReact
value={7}
columns={1}
compact={true}
hideInactiveBeads={true}
scaleFactor={0.7}
/>
// Or manually control frame visibility
<AbacusReact
value={42}
columns={2}
frameVisible={false} // Hide column posts and reckoning bar
/>
```
### Tutorial System
Educational guidance with tooltips
Educational guidance with tooltips and column highlighting
<img src="https://raw.githubusercontent.com/antialias/soroban-abacus-flashcards/main/packages/abacus-react/examples/tutorial-mode.svg" alt="Tutorial System">
```tsx
<AbacusReact
value={42}
columns={2}
columns={3}
interactive={true}
// Highlight the tens column with a label
highlightColumns={[1]} // Highlight column index 1 (tens)
columnLabels={['ones', 'tens', 'hundreds']} // Add labels to columns
overlays={[{
id: 'tip',
type: 'tooltip',
target: { type: 'bead', columnIndex: 0, beadType: 'earth', beadPosition: 1 },
content: <div>Click this bead!</div>,
target: { type: 'bead', columnIndex: 1, beadType: 'earth', beadPosition: 1 },
content: <div>Click this bead in the tens column!</div>,
offset: { x: 0, y: -30 }
}]}
callbacks={{
onBeadClick: (event) => {
if (event.columnIndex === 0 && event.beadType === 'earth' && event.position === 1) {
console.log('Correct!');
if (event.columnIndex === 1 && event.beadType === 'earth' && event.position === 1) {
console.log('Correct! You clicked the tens column.');
}
}
}}
/>
```
**Column Highlighting:**
- `highlightColumns` - Array of column indices to highlight (e.g., `[0, 2]` highlights first and third columns)
- `columnLabels` - Optional labels displayed above each column (indexed left to right)
## 3D Enhancement
Make the abacus feel tangible and satisfying with three progressive levels of 3D effects.
@@ -209,10 +389,18 @@ interface AbacusConfig {
colorPalette?: 'default' | 'colorblind' | 'mnemonic' | 'grayscale' | 'nature';
hideInactiveBeads?: boolean; // Hide/show inactive beads
// Layout & Frame
frameVisible?: boolean; // Show/hide column posts and reckoning bar
compact?: boolean; // Compact layout (implies frameVisible=false)
// Interaction
interactive?: boolean; // Enable user interactions
animated?: boolean; // Enable animations
gestures?: boolean; // Enable drag gestures
// Tutorial Features
highlightColumns?: number[]; // Highlight specific columns by index
columnLabels?: string[]; // Optional labels for columns
}
```
@@ -359,6 +547,60 @@ function AdvancedExample() {
## Hooks
### useAbacusDiff
Calculate bead differences between values for tutorials and animations:
```tsx
import { useAbacusDiff } from '@soroban/abacus-react';
function Tutorial() {
const [currentValue, setCurrentValue] = useState(5);
const targetValue = 15;
// Get diff information: which beads need to move
const diff = useAbacusDiff(currentValue, targetValue);
return (
<div>
<p>{diff.summary}</p> {/* "add heaven bead in tens column, then..." */}
<AbacusReact
value={currentValue}
stepBeadHighlights={diff.highlights} // Highlight beads that need to change
interactive
onValueChange={setCurrentValue}
/>
<p>Changes needed: {diff.changes.length}</p>
</div>
);
}
```
**Returns:**
- `changes` - Array of bead movements with direction and order
- `highlights` - Bead highlight data for stepBeadHighlights prop
- `hasChanges` - Boolean indicating if any changes needed
- `summary` - Human-readable description of changes (e.g., "add heaven bead in ones column")
### useAbacusState
Convert numbers to abacus bead states:
```tsx
import { useAbacusState } from '@soroban/abacus-react';
function BeadAnalyzer() {
const value = 123;
const state = useAbacusState(value);
// Check bead positions
const onesHasHeaven = state[0].heavenActive; // false (3 < 5)
const tensEarthCount = state[1].earthActive; // 2 (20 = 2 tens)
return <div>Ones column heaven bead: {onesHasHeaven ? 'active' : 'inactive'}</div>;
}
```
### useAbacusDimensions
Get exact sizing information for layout planning:
@@ -377,6 +619,149 @@ function MyComponent() {
}
```
## Utility Functions
Low-level functions for working with abacus states and calculations:
### numberToAbacusState
Convert a number to bead positions:
```tsx
import { numberToAbacusState } from '@soroban/abacus-react';
const state = numberToAbacusState(123, 5); // 5 columns
// Returns: {
// 0: { heavenActive: false, earthActive: 3 }, // ones = 3
// 1: { heavenActive: false, earthActive: 2 }, // tens = 2
// 2: { heavenActive: true, earthActive: 0 }, // hundreds = 1
// ...
// }
```
### abacusStateToNumber
Convert bead positions back to a number:
```tsx
import { abacusStateToNumber } from '@soroban/abacus-react';
const state = {
0: { heavenActive: false, earthActive: 3 },
1: { heavenActive: false, earthActive: 2 },
2: { heavenActive: true, earthActive: 0 }
};
const value = abacusStateToNumber(state); // 123
```
### calculateBeadDiff
Calculate the exact bead movements needed between two states:
```tsx
import { calculateBeadDiff, numberToAbacusState } from '@soroban/abacus-react';
const fromState = numberToAbacusState(5);
const toState = numberToAbacusState(15);
const diff = calculateBeadDiff(fromState, toState);
console.log(diff.summary); // "add heaven bead in tens column"
console.log(diff.changes); // Detailed array of movements with order
```
### calculateBeadDiffFromValues
Convenience wrapper for calculating diff from numbers:
```tsx
import { calculateBeadDiffFromValues } from '@soroban/abacus-react';
const diff = calculateBeadDiffFromValues(42, 57);
// Equivalent to: calculateBeadDiff(numberToAbacusState(42), numberToAbacusState(57))
```
### validateAbacusValue
Check if a value is within the supported range:
```tsx
import { validateAbacusValue } from '@soroban/abacus-react';
const result = validateAbacusValue(123456, 5); // 5 columns max
console.log(result.isValid); // false
console.log(result.error); // "Value exceeds maximum for 5 columns (max: 99999)"
```
### areStatesEqual
Compare two abacus states:
```tsx
import { areStatesEqual, numberToAbacusState } from '@soroban/abacus-react';
const state1 = numberToAbacusState(123);
const state2 = numberToAbacusState(123);
const isEqual = areStatesEqual(state1, state2); // true
```
### calculateStandardDimensions
**⚡ Core Architecture Function** - Calculate complete layout dimensions for consistent rendering.
This is the **single source of truth** for all layout dimensions, used internally by both `AbacusStatic` and `AbacusReact` to guarantee pixel-perfect consistency.
```tsx
import { calculateStandardDimensions } from '@soroban/abacus-react';
const dimensions = calculateStandardDimensions({
columns: 3,
scaleFactor: 1.5,
showNumbers: true,
columnLabels: ['ones', 'tens', 'hundreds']
});
// Returns complete layout info:
// {
// width, height, // SVG canvas size
// beadSize, // 12 * scaleFactor (standard bead size)
// rodSpacing, // 25 * scaleFactor (column spacing)
// rodWidth, // 3 * scaleFactor
// barThickness, // 2 * scaleFactor
// barY, // Reckoning bar Y position (30 * scaleFactor + labels)
// heavenY, earthY, // Inactive bead rest positions
// activeGap, // 1 * scaleFactor (gap to bar when active)
// inactiveGap, // 8 * scaleFactor (gap between active/inactive)
// adjacentSpacing, // 0.5 * scaleFactor (spacing between adjacent beads)
// padding, labelHeight, numbersHeight, totalColumns
// }
```
**Why this matters:** Same input parameters = same exact layout dimensions = pixel-perfect visual consistency across static and interactive displays.
### calculateBeadPosition
**⚡ Core Architecture Function** - Calculate exact x,y coordinates for any bead.
Used internally by `AbacusSVGRenderer` to position all beads consistently in both static and interactive modes.
```tsx
import { calculateBeadPosition, calculateStandardDimensions } from '@soroban/abacus-react';
const dimensions = calculateStandardDimensions({ columns: 3, scaleFactor: 1 });
const bead = {
type: 'heaven',
active: true,
position: 0,
placeValue: 1 // tens column
};
const position = calculateBeadPosition(bead, dimensions);
// Returns: { x: 25, y: 29 } // exact pixel coordinates
```
Useful for custom rendering or positioning tooltips/overlays relative to specific beads.
## Educational Use Cases
### Interactive Math Lessons
@@ -439,14 +824,41 @@ Full TypeScript definitions included:
```tsx
import {
// Components
AbacusReact,
// Hooks
useAbacusDiff,
useAbacusState,
useAbacusDimensions,
// Utility Functions
numberToAbacusState,
abacusStateToNumber,
calculateBeadDiff,
calculateBeadDiffFromValues,
validateAbacusValue,
areStatesEqual,
calculateStandardDimensions, // NEW: Shared layout calculator
calculateBeadPosition, // NEW: Bead position calculator
// Theme Presets
ABACUS_THEMES,
// Types
AbacusConfig,
BeadConfig,
BeadClickEvent,
AbacusCustomStyles,
AbacusOverlay,
AbacusCallbacks,
useAbacusDimensions
AbacusState,
BeadState,
BeadDiffResult,
BeadDiffOutput,
AbacusThemeName,
AbacusLayoutDimensions, // NEW: Complete layout dimensions type
BeadPositionConfig // NEW: Bead config for position calculation
} from '@soroban/abacus-react';
// All interfaces fully typed for excellent developer experience

View File

@@ -10,6 +10,11 @@
"types": "./dist/index.d.ts",
"import": "./dist/index.es.js",
"require": "./dist/index.cjs.js"
},
"./static": {
"types": "./dist/static.d.ts",
"import": "./dist/static.es.js",
"require": "./dist/static.cjs.js"
}
},
"files": [

View File

@@ -0,0 +1,390 @@
'use client'
/**
* AbacusAnimatedBead - Interactive bead component for AbacusReact (Core Architecture)
*
* This is the **client-side bead component** injected into AbacusSVGRenderer by AbacusReact.
* It provides animations and interactivity while the parent renderer handles positioning.
*
* ## Architecture Role:
* - Injected into `AbacusSVGRenderer` via dependency injection (BeadComponent prop)
* - Receives x,y position from `calculateBeadPosition()` (already calculated)
* - Adds animations and interactions on top of the shared layout
* - Used ONLY by AbacusReact (requires "use client")
*
* ## Features:
* - ✅ React Spring animations for smooth position changes
* - ✅ Drag gesture handling with @use-gesture/react
* - ✅ Direction indicators for tutorials (pulsing arrows)
* - ✅ 3D effects and gradients
* - ✅ Click and hover interactions
*
* ## Comparison:
* - `AbacusStaticBead` - Simple SVG shapes (no animations, RSC-compatible)
* - `AbacusAnimatedBead` - This component (animations, gestures, client-only)
*
* Both receive the same position from `calculateBeadPosition()`, ensuring visual consistency.
*/
import React, { useCallback, useRef } from 'react'
import { useSpring, animated, to } from '@react-spring/web'
import { useDrag } from '@use-gesture/react'
import type { BeadComponentProps } from './AbacusSVGRenderer'
import type { BeadConfig } from './AbacusReact'
interface AnimatedBeadProps extends BeadComponentProps {
// Animation controls
enableAnimation: boolean
physicsConfig: any
// Gesture handling
enableGestures: boolean
onGestureToggle?: (bead: BeadConfig, direction: 'activate' | 'deactivate') => void
// Direction indicators (for tutorials)
showDirectionIndicator?: boolean
direction?: 'activate' | 'deactivate'
isCurrentStep?: boolean
// 3D effects
enhanced3d?: 'none' | 'subtle' | 'realistic' | 'delightful'
columnIndex?: number
// Hover state from parent abacus
isAbacusHovered?: boolean
}
export function AbacusAnimatedBead({
bead,
x,
y,
size,
shape,
color,
hideInactiveBeads,
customStyle,
onClick,
onMouseEnter,
onMouseLeave,
onRef,
enableAnimation,
physicsConfig,
enableGestures,
onGestureToggle,
showDirectionIndicator,
direction,
isCurrentStep,
enhanced3d = 'none',
columnIndex,
isAbacusHovered = false,
}: AnimatedBeadProps) {
// x, y are already calculated by AbacusSVGRenderer
// Track hover state for showing hidden inactive beads
const [isHovered, setIsHovered] = React.useState(false)
// Use abacus hover if provided, otherwise use individual bead hover
const effectiveHoverState = isAbacusHovered || isHovered
// Spring animation for position
const [{ springX, springY }, api] = useSpring(() => ({
springX: x,
springY: y,
config: physicsConfig,
}))
// Arrow pulse animation for direction indicators
const [{ arrowPulse }, arrowApi] = useSpring(() => ({
arrowPulse: 1,
config: enableAnimation ? { tension: 200, friction: 10 } : { duration: 0 },
}))
const gestureStateRef = useRef({
isDragging: false,
lastDirection: null as 'activate' | 'deactivate' | null,
startY: 0,
threshold: size * 0.3,
hasGestureTriggered: false,
})
// Calculate gesture direction based on bead type
const getGestureDirection = useCallback(
(deltaY: number) => {
const movement = Math.abs(deltaY)
if (movement < gestureStateRef.current.threshold) return null
if (bead.type === 'heaven') {
return deltaY > 0 ? 'activate' : 'deactivate'
} else {
return deltaY < 0 ? 'activate' : 'deactivate'
}
},
[bead.type, size]
)
// Gesture handler
const bind = enableGestures
? useDrag(
({ event, movement: [, deltaY], first, active }) => {
if (first) {
event?.preventDefault()
gestureStateRef.current.isDragging = true
gestureStateRef.current.lastDirection = null
gestureStateRef.current.hasGestureTriggered = false
return
}
if (!active || !gestureStateRef.current.isDragging) {
if (!active) {
gestureStateRef.current.isDragging = false
gestureStateRef.current.lastDirection = null
setTimeout(() => {
gestureStateRef.current.hasGestureTriggered = false
}, 100)
}
return
}
const currentDirection = getGestureDirection(deltaY)
if (
currentDirection &&
currentDirection !== gestureStateRef.current.lastDirection
) {
gestureStateRef.current.lastDirection = currentDirection
gestureStateRef.current.hasGestureTriggered = true
onGestureToggle?.(bead, currentDirection)
}
},
{
enabled: enableGestures,
preventDefault: true,
}
)
: () => ({})
// Update spring animation when position changes
React.useEffect(() => {
if (enableAnimation) {
api.start({ springX: x, springY: y, config: physicsConfig })
} else {
api.set({ springX: x, springY: y })
}
}, [x, y, enableAnimation, api, physicsConfig])
// Pulse animation for direction indicators
React.useEffect(() => {
if (showDirectionIndicator && direction && isCurrentStep) {
const startPulse = () => {
arrowApi.start({
from: { arrowPulse: 1 },
to: async (next) => {
await next({ arrowPulse: 1.3 })
await next({ arrowPulse: 1 })
},
loop: true,
})
}
const timeoutId = setTimeout(startPulse, 200)
return () => {
clearTimeout(timeoutId)
arrowApi.stop()
}
} else {
arrowApi.set({ arrowPulse: 1 })
}
}, [showDirectionIndicator, direction, isCurrentStep, arrowApi])
// Render bead shape
const renderShape = () => {
const halfSize = size / 2
// Determine fill - use gradient for realistic mode, otherwise use color
let fillValue = customStyle?.fill || color
if (enhanced3d === 'realistic' && columnIndex !== undefined) {
if (bead.type === 'heaven') {
fillValue = `url(#bead-gradient-${columnIndex}-heaven)`
} else {
fillValue = `url(#bead-gradient-${columnIndex}-earth-${bead.position})`
}
}
// Calculate opacity based on state and settings
let opacity: number
if (customStyle?.opacity !== undefined) {
// Custom opacity always takes precedence
opacity = customStyle.opacity
} else if (bead.active) {
// Active beads are always full opacity
opacity = 1
} else if (hideInactiveBeads && effectiveHoverState) {
// Inactive beads that are hidden but being hovered show at low opacity
opacity = 0.3
} else if (hideInactiveBeads) {
// Inactive beads that are hidden and not hovered are invisible (handled below)
opacity = 0
} else {
// Inactive beads when hideInactiveBeads is false are full opacity
opacity = 1
}
const stroke = customStyle?.stroke || '#000'
const strokeWidth = customStyle?.strokeWidth || 0.5
switch (shape) {
case 'diamond':
return (
<polygon
points={`${size * 0.7},0 ${size * 1.4},${halfSize} ${size * 0.7},${size} 0,${halfSize}`}
fill={fillValue}
stroke={stroke}
strokeWidth={strokeWidth}
opacity={opacity}
/>
)
case 'square':
return (
<rect
width={size}
height={size}
fill={fillValue}
stroke={stroke}
strokeWidth={strokeWidth}
rx="1"
opacity={opacity}
/>
)
case 'circle':
default:
return (
<circle
cx={halfSize}
cy={halfSize}
r={halfSize}
fill={fillValue}
stroke={stroke}
strokeWidth={strokeWidth}
opacity={opacity}
/>
)
}
}
// Calculate offsets for shape positioning
const getXOffset = () => {
return shape === 'diamond' ? size * 0.7 : size / 2
}
const getYOffset = () => {
return size / 2
}
// Use animated.g if animations enabled, otherwise regular g
const GElement = enableAnimation ? animated.g : 'g'
const DirectionIndicatorG =
enableAnimation && showDirectionIndicator && direction ? animated.g : 'g'
// Build style object
// Show pointer cursor on hidden beads so users know they can interact
const shouldShowCursor = bead.active || !hideInactiveBeads || effectiveHoverState
const cursor = shouldShowCursor ? (enableGestures ? 'grab' : onClick ? 'pointer' : 'default') : 'default'
const beadStyle: any = enableAnimation
? {
transform: to(
[springX, springY],
(sx, sy) => `translate(${sx - getXOffset()}px, ${sy - getYOffset()}px)`
),
cursor,
touchAction: 'none' as const,
transition: 'opacity 0.2s ease-in-out',
pointerEvents: 'auto' as const, // Ensure hidden beads can still be hovered
}
: {
transform: `translate(${x - getXOffset()}px, ${y - getYOffset()}px)`,
cursor,
touchAction: 'none' as const,
transition: 'opacity 0.2s ease-in-out',
pointerEvents: 'auto' as const, // Ensure hidden beads can still be hovered
}
const handleClick = (event: React.MouseEvent) => {
// Prevent click if gesture was triggered
if (gestureStateRef.current.hasGestureTriggered) {
event.preventDefault()
return
}
onClick?.(bead, event)
}
const handleMouseEnter = (e: React.MouseEvent) => {
setIsHovered(true)
onMouseEnter?.(bead, e as any)
}
const handleMouseLeave = (e: React.MouseEvent) => {
setIsHovered(false)
onMouseLeave?.(bead, e as any)
}
return (
<>
<GElement
className={`abacus-bead ${bead.active ? 'active' : 'inactive'} ${hideInactiveBeads && !bead.active ? 'hidden-inactive' : ''}`}
data-testid={`bead-place-${bead.placeValue}-${bead.type}${bead.type === 'earth' ? `-pos-${bead.position}` : ''}`}
style={beadStyle}
{...bind()}
onClick={handleClick}
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
ref={(el) => onRef?.(bead, el as any)}
>
{renderShape()}
</GElement>
{/* Direction indicator for tutorials */}
{showDirectionIndicator && direction && (
<DirectionIndicatorG
className="direction-indicator"
style={
(enableAnimation
? {
transform: to(
[springX, springY, arrowPulse],
(sx, sy, pulse) => {
const centerX = shape === 'diamond' ? size * 0.7 : size / 2
const centerY = size / 2
// Scale from center: translate to position, then translate to center, scale, translate back
return `translate(${sx}px, ${sy}px) scale(${pulse}) translate(${-centerX}px, ${-centerY}px)`
}
),
pointerEvents: 'none' as const,
}
: {
transform: `translate(${x}px, ${y}px) translate(${-(shape === 'diamond' ? size * 0.7 : size / 2)}px, ${-size / 2}px)`,
pointerEvents: 'none' as const,
}) as any
}
>
<text
x={shape === 'diamond' ? size * 0.7 : size / 2}
y={size / 2}
textAnchor="middle"
dy=".35em"
fontSize={size * 0.7}
fill="#fbbf24"
fontWeight="bold"
stroke="#000"
strokeWidth="1.5"
paintOrder="stroke"
>
{bead.type === 'heaven'
? (direction === 'activate' ? '↓' : '↑')
: (direction === 'activate' ? '↑' : '↓')}
</text>
</DirectionIndicatorG>
)}
</>
)
}

View File

@@ -0,0 +1,71 @@
/**
* Utility hooks for working with abacus calculations and state
*/
import { useMemo } from 'react'
import {
calculateBeadDiffFromValues,
numberToAbacusState,
type BeadDiffOutput,
type AbacusState,
} from './AbacusUtils'
/**
* Hook to calculate bead differences between two values
* Useful for tutorials, animations, and highlighting which beads need to move
*
* @param fromValue - Starting value
* @param toValue - Target value
* @param maxPlaces - Maximum number of place values to consider (default: 5)
* @returns BeadDiffOutput with changes, highlights, and summary
*
* @example
* ```tsx
* function Tutorial() {
* const diff = useAbacusDiff(5, 15)
*
* return (
* <AbacusReact
* value={currentValue}
* stepBeadHighlights={diff.highlights}
* />
* )
* }
* ```
*/
export function useAbacusDiff(
fromValue: number | bigint,
toValue: number | bigint,
maxPlaces: number = 5
): BeadDiffOutput {
return useMemo(() => {
return calculateBeadDiffFromValues(fromValue, toValue, maxPlaces)
}, [fromValue, toValue, maxPlaces])
}
/**
* Hook to convert a number to abacus state
* Memoized for performance when used in components
*
* @param value - The number to convert
* @param maxPlaces - Maximum number of place values (default: 5)
* @returns AbacusState representing the bead positions
*
* @example
* ```tsx
* function MyComponent() {
* const state = useAbacusState(123)
*
* // Check if ones column has heaven bead active
* const onesHasHeaven = state[0].heavenActive
* }
* ```
*/
export function useAbacusState(
value: number | bigint,
maxPlaces: number = 5
): AbacusState {
return useMemo(() => {
return numberToAbacusState(value, maxPlaces)
}, [value, maxPlaces])
}

View File

@@ -0,0 +1,613 @@
/**
* Theme system, layout utilities, hooks, and helper functions
* Features: Theme presets, compact mode, column highlighting, hooks, utility functions
*/
import type { Meta, StoryObj } from '@storybook/react';
import React, { useState } from 'react';
import AbacusReact from './AbacusReact';
import {
ABACUS_THEMES,
AbacusThemeName,
useAbacusDiff,
useAbacusState,
numberToAbacusState,
abacusStateToNumber,
calculateBeadDiffFromValues,
validateAbacusValue,
areStatesEqual
} from './index';
const meta = {
title: 'AbacusReact/Themes & Utilities',
component: AbacusReact,
parameters: {
layout: 'centered',
},
tags: ['autodocs'],
} satisfies Meta<typeof AbacusReact>;
export default meta;
type Story = StoryObj<typeof meta>;
// ============================================================================
// THEME PRESETS
// ============================================================================
export const AllThemePresets: Story = {
render: () => {
const themes: AbacusThemeName[] = ['light', 'dark', 'trophy', 'translucent', 'solid', 'traditional'];
return (
<div style={{ display: 'flex', flexDirection: 'column', gap: '30px', padding: '20px' }}>
<h2>Theme Presets</h2>
<p>Pre-defined themes eliminate manual style object creation</p>
{themes.map((themeName) => (
<div key={themeName} style={{
background: themeName === 'dark' ? '#1a1a1a' : '#f5f5f5',
padding: '20px',
borderRadius: '8px'
}}>
<h3 style={{
marginTop: 0,
color: themeName === 'dark' ? 'white' : 'black',
textTransform: 'capitalize'
}}>
{themeName} Theme
</h3>
<AbacusReact
value={12345}
columns={5}
customStyles={ABACUS_THEMES[themeName]}
showNumbers={true}
/>
</div>
))}
</div>
);
},
};
export const LightTheme: Story = {
render: () => (
<div style={{ padding: '20px', background: '#f5f5f5' }}>
<h3>Light Theme - Best for light backgrounds</h3>
<AbacusReact
value={12345}
columns={5}
customStyles={ABACUS_THEMES.light}
showNumbers={true}
/>
</div>
),
};
export const DarkTheme: Story = {
render: () => (
<div style={{ padding: '20px', background: '#1a1a1a' }}>
<h3 style={{ color: 'white' }}>Dark Theme - Best for dark backgrounds</h3>
<AbacusReact
value={12345}
columns={5}
customStyles={ABACUS_THEMES.dark}
showNumbers={true}
/>
</div>
),
};
export const TrophyTheme: Story = {
render: () => (
<div style={{ padding: '20px', background: '#f0f0f0' }}>
<h3>Trophy Theme - Golden frame for achievements</h3>
<AbacusReact
value={9999}
columns={4}
customStyles={ABACUS_THEMES.trophy}
showNumbers={true}
/>
</div>
),
};
export const TraditionalTheme: Story = {
render: () => (
<div style={{ padding: '20px', background: '#f5f5f0' }}>
<h3>Traditional Theme - Brown wooden soroban aesthetic</h3>
<AbacusReact
value={8765}
columns={4}
customStyles={ABACUS_THEMES.traditional}
showNumbers={true}
/>
</div>
),
};
// ============================================================================
// COMPACT MODE & FRAME VISIBILITY
// ============================================================================
export const CompactMode: Story = {
render: () => (
<div style={{ padding: '20px' }}>
<h3>Compact Mode - Inline mini-abacus displays</h3>
<p>Perfect for inline number displays, badges, or game UI</p>
<div style={{ display: 'flex', gap: '20px', alignItems: 'center', marginTop: '20px' }}>
<span>Single digits: </span>
{[1, 2, 3, 4, 5, 6, 7, 8, 9].map(num => (
<AbacusReact
key={num}
value={num}
columns={1}
compact={true}
hideInactiveBeads={true}
scaleFactor={0.6}
/>
))}
</div>
<div style={{ marginTop: '30px' }}>
<h4>Two-digit compact displays:</h4>
<div style={{ display: 'flex', gap: '15px', alignItems: 'center' }}>
{[12, 34, 56, 78, 99].map(num => (
<AbacusReact
key={num}
value={num}
columns={2}
compact={true}
hideInactiveBeads={true}
scaleFactor={0.7}
/>
))}
</div>
</div>
</div>
),
};
export const FrameVisibilityControl: Story = {
render: () => {
const [frameVisible, setFrameVisible] = useState(true);
return (
<div style={{ padding: '20px' }}>
<h3>Frame Visibility Control</h3>
<p>Toggle column posts and reckoning bar on/off</p>
<div style={{ marginBottom: '20px' }}>
<label>
<input
type="checkbox"
checked={frameVisible}
onChange={(e) => setFrameVisible(e.target.checked)}
/>
{' '}Show Frame
</label>
</div>
<AbacusReact
value={12345}
columns={5}
frameVisible={frameVisible}
showNumbers={true}
/>
</div>
);
},
};
// ============================================================================
// COLUMN HIGHLIGHTING & LABELS
// ============================================================================
export const ColumnHighlighting: Story = {
render: () => (
<div style={{ padding: '20px' }}>
<h3>Column Highlighting</h3>
<p>Highlight specific columns for educational purposes</p>
<div style={{ marginBottom: '30px' }}>
<h4>Highlight ones column:</h4>
<AbacusReact
value={12345}
columns={5}
highlightColumns={[0]}
showNumbers={true}
/>
</div>
<div style={{ marginBottom: '30px' }}>
<h4>Highlight tens and hundreds:</h4>
<AbacusReact
value={12345}
columns={5}
highlightColumns={[1, 2]}
showNumbers={true}
/>
</div>
<div>
<h4>Highlight all columns:</h4>
<AbacusReact
value={12345}
columns={5}
highlightColumns={[0, 1, 2, 3, 4]}
showNumbers={true}
/>
</div>
</div>
),
};
export const ColumnLabels: Story = {
render: () => (
<div style={{ padding: '20px' }}>
<h3>Column Labels</h3>
<p>Add educational labels above columns</p>
<div style={{ marginBottom: '30px' }}>
<h4>Standard place value labels:</h4>
<AbacusReact
value={12345}
columns={5}
columnLabels={['ones', 'tens', 'hundreds', 'thousands', '10k']}
showNumbers={true}
/>
</div>
<div>
<h4>Custom labels:</h4>
<AbacusReact
value={789}
columns={3}
columnLabels={['1s', '10s', '100s']}
highlightColumns={[1]}
showNumbers={true}
/>
</div>
</div>
),
};
export const ColumnHighlightingWithLabels: Story = {
render: () => (
<div style={{ padding: '20px' }}>
<h3>Combined: Column Highlighting + Labels</h3>
<p>Perfect for tutorials showing which column to work with</p>
<AbacusReact
value={42}
columns={3}
highlightColumns={[1]}
columnLabels={['ones', 'tens', 'hundreds']}
showNumbers={true}
/>
<p style={{ marginTop: '20px', fontStyle: 'italic' }}>
"Add 10 to the tens column"
</p>
</div>
),
};
// ============================================================================
// HOOKS: useAbacusDiff
// ============================================================================
function AbacusDiffDemo() {
const [currentValue, setCurrentValue] = useState(5);
const targetValue = 23;
const diff = useAbacusDiff(currentValue, targetValue);
return (
<div style={{ padding: '20px' }}>
<h3>useAbacusDiff Hook</h3>
<p>Automatically calculate which beads need to move</p>
<div style={{ marginBottom: '20px' }}>
<p><strong>Current value:</strong> {currentValue}</p>
<p><strong>Target value:</strong> {targetValue}</p>
<p><strong>Instructions:</strong> {diff.summary}</p>
<p><strong>Changes needed:</strong> {diff.changes.length}</p>
</div>
<AbacusReact
value={currentValue}
columns={2}
stepBeadHighlights={diff.highlights}
showNumbers={true}
interactive={true}
onValueChange={setCurrentValue}
/>
<div style={{ marginTop: '20px' }}>
<button onClick={() => setCurrentValue(5)}>Reset to 5</button>
{' '}
<button onClick={() => setCurrentValue(targetValue)}>Jump to target (23)</button>
</div>
{diff.hasChanges ? (
<div style={{ marginTop: '20px', color: '#666' }}>
<p><strong>Detailed changes:</strong></p>
<pre style={{ fontSize: '12px' }}>
{JSON.stringify(diff.changes, null, 2)}
</pre>
</div>
) : (
<p style={{ marginTop: '20px', color: 'green', fontWeight: 'bold' }}>
Target reached!
</p>
)}
</div>
);
}
export const UseAbacusDiffHook: Story = {
render: () => <AbacusDiffDemo />,
};
// ============================================================================
// HOOKS: useAbacusState
// ============================================================================
function AbacusStateDemo() {
const [value, setValue] = useState(123);
const state = useAbacusState(value);
return (
<div style={{ padding: '20px' }}>
<h3>useAbacusState Hook</h3>
<p>Convert numbers to bead positions (memoized)</p>
<div style={{ marginBottom: '20px' }}>
<label>
Value:
<input
type="number"
value={value}
onChange={(e) => setValue(parseInt(e.target.value) || 0)}
style={{ marginLeft: '10px', width: '100px' }}
/>
</label>
</div>
<AbacusReact
value={value}
columns={3}
showNumbers={true}
/>
<div style={{ marginTop: '20px', fontSize: '14px' }}>
<p><strong>Bead State Analysis:</strong></p>
<table style={{ borderCollapse: 'collapse', marginTop: '10px' }}>
<thead>
<tr style={{ background: '#f0f0f0' }}>
<th style={{ border: '1px solid #ccc', padding: '8px' }}>Place</th>
<th style={{ border: '1px solid #ccc', padding: '8px' }}>Heaven Active?</th>
<th style={{ border: '1px solid #ccc', padding: '8px' }}>Earth Count</th>
<th style={{ border: '1px solid #ccc', padding: '8px' }}>Digit</th>
</tr>
</thead>
<tbody>
{[0, 1, 2].map(place => {
const placeState = state[place];
const digit = (placeState.heavenActive ? 5 : 0) + placeState.earthActive;
const placeName = ['Ones', 'Tens', 'Hundreds'][place];
return (
<tr key={place}>
<td style={{ border: '1px solid #ccc', padding: '8px' }}>{placeName}</td>
<td style={{ border: '1px solid #ccc', padding: '8px', textAlign: 'center' }}>
{placeState.heavenActive ? '✓' : '✗'}
</td>
<td style={{ border: '1px solid #ccc', padding: '8px', textAlign: 'center' }}>
{placeState.earthActive}
</td>
<td style={{ border: '1px solid #ccc', padding: '8px', textAlign: 'center' }}>
{digit}
</td>
</tr>
);
})}
</tbody>
</table>
</div>
</div>
);
}
export const UseAbacusStateHook: Story = {
render: () => <AbacusStateDemo />,
};
// ============================================================================
// UTILITY FUNCTIONS
// ============================================================================
function UtilityFunctionsDemo() {
const [inputValue, setInputValue] = useState(123);
const state = numberToAbacusState(inputValue, 5);
const backToNumber = abacusStateToNumber(state);
const fromValue = 42;
const toValue = 57;
const diff = calculateBeadDiffFromValues(fromValue, toValue);
const validation1 = validateAbacusValue(inputValue, 5);
const validation2 = validateAbacusValue(123456, 5);
const state1 = numberToAbacusState(100);
const state2 = numberToAbacusState(100);
const state3 = numberToAbacusState(200);
const areEqual1 = areStatesEqual(state1, state2);
const areEqual2 = areStatesEqual(state1, state3);
return (
<div style={{ padding: '20px' }}>
<h3>Utility Functions</h3>
<p>Low-level functions for working with abacus states</p>
<div style={{ marginBottom: '30px' }}>
<h4>numberToAbacusState & abacusStateToNumber:</h4>
<label>
Input value:
<input
type="number"
value={inputValue}
onChange={(e) => setInputValue(parseInt(e.target.value) || 0)}
style={{ marginLeft: '10px', width: '100px' }}
/>
</label>
<pre style={{ background: '#f5f5f5', padding: '10px', marginTop: '10px', fontSize: '12px' }}>
{`numberToAbacusState(${inputValue}, 5) = ${JSON.stringify(state, null, 2)}`}
</pre>
<pre style={{ background: '#f5f5f5', padding: '10px', fontSize: '12px' }}>
{`abacusStateToNumber(state) = ${backToNumber}`}
</pre>
</div>
<div style={{ marginBottom: '30px' }}>
<h4>calculateBeadDiffFromValues:</h4>
<p>From {fromValue} to {toValue}:</p>
<pre style={{ background: '#f5f5f5', padding: '10px', fontSize: '12px' }}>
{`Summary: ${diff.summary}\nChanges: ${diff.changes.length}`}
</pre>
</div>
<div style={{ marginBottom: '30px' }}>
<h4>validateAbacusValue:</h4>
<pre style={{ background: '#f5f5f5', padding: '10px', fontSize: '12px' }}>
{`validateAbacusValue(${inputValue}, 5):\n isValid: ${validation1.isValid}\n error: ${validation1.error || 'none'}`}
</pre>
<pre style={{ background: '#f5f5f5', padding: '10px', fontSize: '12px' }}>
{`validateAbacusValue(123456, 5):\n isValid: ${validation2.isValid}\n error: ${validation2.error || 'none'}`}
</pre>
</div>
<div>
<h4>areStatesEqual:</h4>
<pre style={{ background: '#f5f5f5', padding: '10px', fontSize: '12px' }}>
{`areStatesEqual(state(100), state(100)) = ${areEqual1}\nareStatesEqual(state(100), state(200)) = ${areEqual2}`}
</pre>
</div>
</div>
);
}
export const UtilityFunctions: Story = {
render: () => <UtilityFunctionsDemo />,
};
// ============================================================================
// COMBINED FEATURES
// ============================================================================
export const AllFeaturesShowcase: Story = {
render: () => {
const [value, setValue] = useState(42);
const targetValue = 75;
const diff = useAbacusDiff(value, targetValue);
return (
<div style={{ padding: '20px', maxWidth: '800px' }}>
<h2>All New Features Combined</h2>
<p>Theme preset + column highlighting + labels + diff hook</p>
<div style={{
background: '#1a1a1a',
padding: '30px',
borderRadius: '8px',
marginTop: '20px'
}}>
<h3 style={{ color: 'white', marginTop: 0 }}>
Tutorial: Add to reach {targetValue}
</h3>
<p style={{ color: '#ccc' }}>
<strong>Current:</strong> {value} <strong>Target:</strong> {targetValue}
</p>
<p style={{ color: '#fbbf24' }}>
<strong>Instructions:</strong> {diff.summary}
</p>
<AbacusReact
value={value}
columns={2}
customStyles={ABACUS_THEMES.dark}
highlightColumns={[0, 1]}
columnLabels={['ones', 'tens']}
stepBeadHighlights={diff.highlights}
showNumbers={true}
interactive={true}
onValueChange={setValue}
/>
<div style={{ marginTop: '20px' }}>
<button onClick={() => setValue(42)}>Reset</button>
{' '}
<button onClick={() => setValue(targetValue)}>Show Answer</button>
</div>
{!diff.hasChanges && (
<p style={{ color: '#4ade80', fontWeight: 'bold', marginTop: '20px' }}>
🎉 Perfect! You reached the target!
</p>
)}
</div>
</div>
);
},
};
export const CompactThemeComparison: Story = {
render: () => {
const themes: AbacusThemeName[] = ['light', 'dark', 'trophy', 'traditional'];
return (
<div style={{ padding: '20px' }}>
<h3>Compact Mode with Different Themes</h3>
<p>Inline displays work with all theme presets</p>
<div style={{ display: 'flex', flexDirection: 'column', gap: '20px', marginTop: '20px' }}>
{themes.map(theme => (
<div
key={theme}
style={{
background: theme === 'dark' ? '#1a1a1a' : '#f5f5f5',
padding: '15px',
borderRadius: '6px'
}}
>
<h4 style={{
margin: '0 0 15px 0',
color: theme === 'dark' ? 'white' : 'black',
textTransform: 'capitalize'
}}>
{theme}:
</h4>
<div style={{ display: 'flex', gap: '15px', alignItems: 'center' }}>
{[7, 42, 99].map(num => (
<div key={num}>
<AbacusReact
value={num}
columns={num < 10 ? 1 : 2}
compact={true}
hideInactiveBeads={true}
customStyles={ABACUS_THEMES[theme]}
scaleFactor={0.7}
/>
</div>
))}
</div>
</div>
))}
</div>
</div>
);
},
};

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,383 @@
/**
* AbacusSVGRenderer - Shared SVG rendering component (Core Architecture)
*
* This is the **single SVG renderer** used by both AbacusStatic and AbacusReact to guarantee
* pixel-perfect visual consistency. It implements dependency injection to support different
* bead components while maintaining identical layout.
*
* ## Architecture Role:
* ```
* AbacusStatic + AbacusReact
* ↓
* calculateStandardDimensions() ← Single source for all layout dimensions
* ↓
* AbacusSVGRenderer ← This component (shared structure)
* ↓
* calculateBeadPosition() ← Exact positioning for every bead
* ↓
* BeadComponent (injected) ← AbacusStaticBead OR AbacusAnimatedBead
* ```
*
* ## Key Features:
* - ✅ No "use client" directive - works in React Server Components
* - ✅ No hooks or state - pure rendering from props
* - ✅ Dependency injection for bead components
* - ✅ Supports 3D gradients, background glows, overlays (via props)
* - ✅ Same props → same dimensions → same positions → same layout
*
* ## Why This Matters:
* Before this architecture, AbacusStatic and AbacusReact had ~700 lines of duplicate
* SVG rendering code with separate dimension calculations. This led to layout inconsistencies.
* Now they share this single renderer, eliminating duplication and guaranteeing consistency.
*/
import React from 'react'
import type { AbacusLayoutDimensions } from './AbacusUtils'
import type { BeadConfig, AbacusCustomStyles, ValidPlaceValues } from './AbacusReact'
import { numberToAbacusState, calculateBeadPosition, type AbacusState } from './AbacusUtils'
/**
* Props that bead components must accept
*/
export interface BeadComponentProps {
bead: BeadConfig
x: number
y: number
size: number
shape: 'circle' | 'diamond' | 'square'
color: string
hideInactiveBeads: boolean
customStyle?: {
fill?: string
stroke?: string
strokeWidth?: number
opacity?: number
}
onClick?: (bead: BeadConfig, event?: React.MouseEvent) => void
onMouseEnter?: (bead: BeadConfig, event?: React.MouseEvent) => void
onMouseLeave?: (bead: BeadConfig, event?: React.MouseEvent) => void
onRef?: (bead: BeadConfig, element: SVGElement | null) => void
}
/**
* Props for the SVG renderer
*/
export interface AbacusSVGRendererProps {
// Core data
value: number | bigint
columns: number
state: AbacusState
beadConfigs: BeadConfig[][] // Array of columns, each containing beads
// Layout
dimensions: AbacusLayoutDimensions
scaleFactor?: number
// Appearance
beadShape: 'circle' | 'diamond' | 'square'
colorScheme: string
colorPalette: string
hideInactiveBeads: boolean
frameVisible: boolean
showNumbers: boolean
customStyles?: AbacusCustomStyles
interactive?: boolean // Enable interactive CSS styles
// Tutorial features
highlightColumns?: number[]
columnLabels?: string[]
// 3D Enhancement (optional - only used by AbacusReact)
defsContent?: React.ReactNode // Custom defs content (gradients, patterns, etc.)
// Additional content (overlays, etc.)
children?: React.ReactNode // Rendered at the end of the SVG
// Dependency injection
BeadComponent: React.ComponentType<any> // Accept any bead component (base props + extra props)
getBeadColor: (bead: BeadConfig, totalColumns: number, colorScheme: string, colorPalette: string) => string
// Event handlers (optional, passed through to beads)
onBeadClick?: (bead: BeadConfig, event?: React.MouseEvent) => void
onBeadMouseEnter?: (bead: BeadConfig, event?: React.MouseEvent) => void
onBeadMouseLeave?: (bead: BeadConfig, event?: React.MouseEvent) => void
onBeadRef?: (bead: BeadConfig, element: SVGElement | null) => void
// Extra props calculator (for animations, gestures, etc.)
// This function is called for each bead to get extra props
calculateExtraBeadProps?: (bead: BeadConfig, baseProps: BeadComponentProps) => Record<string, any>
}
/**
* Pure SVG renderer for abacus
* Uses dependency injection to support both static and animated beads
*/
export function AbacusSVGRenderer({
value,
columns,
state,
beadConfigs,
dimensions,
scaleFactor = 1,
beadShape,
colorScheme,
colorPalette,
hideInactiveBeads,
frameVisible,
showNumbers,
customStyles,
interactive = false,
highlightColumns = [],
columnLabels = [],
defsContent,
children,
BeadComponent,
getBeadColor,
onBeadClick,
onBeadMouseEnter,
onBeadMouseLeave,
onBeadRef,
calculateExtraBeadProps,
}: AbacusSVGRendererProps) {
const { width, height, rodSpacing, barY, beadSize, barThickness, labelHeight, numbersHeight } = dimensions
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width={width * scaleFactor}
height={height * scaleFactor}
viewBox={`0 0 ${width} ${height}`}
className={`abacus-svg ${hideInactiveBeads ? 'hide-inactive-mode' : ''} ${interactive ? 'interactive' : ''}`}
style={{ overflow: 'visible', display: 'block' }}
>
<defs>
<style>{`
/* CSS-based opacity system for hidden inactive beads */
.abacus-bead {
transition: opacity 0.2s ease-in-out;
}
/* Hidden inactive beads are invisible by default */
.hide-inactive-mode .abacus-bead.hidden-inactive {
opacity: 0;
}
/* Interactive abacus: When hovering over the abacus, hidden inactive beads become semi-transparent */
.abacus-svg.hide-inactive-mode.interactive:hover .abacus-bead.hidden-inactive {
opacity: 0.5;
}
/* Interactive abacus: When hovering over a specific hidden inactive bead, it becomes fully visible */
.hide-inactive-mode.interactive .abacus-bead.hidden-inactive:hover {
opacity: 1 !important;
}
/* Non-interactive abacus: Hidden inactive beads always stay at opacity 0 */
.abacus-svg.hide-inactive-mode:not(.interactive) .abacus-bead.hidden-inactive {
opacity: 0 !important;
}
`}</style>
{/* Custom defs content (for 3D gradients, patterns, etc.) */}
{defsContent}
</defs>
{/* Background glow effects - rendered behind everything */}
{Array.from({ length: columns }, (_, colIndex) => {
const placeValue = columns - 1 - colIndex
const columnStyles = customStyles?.columns?.[colIndex]
const backgroundGlow = columnStyles?.backgroundGlow
if (!backgroundGlow) return null
const x = colIndex * rodSpacing + rodSpacing / 2
const glowWidth = rodSpacing + (backgroundGlow.spread || 0)
const glowHeight = height + (backgroundGlow.spread || 0)
return (
<rect
key={`background-glow-pv${placeValue}`}
x={x - glowWidth / 2}
y={-(backgroundGlow.spread || 0) / 2}
width={glowWidth}
height={glowHeight}
fill={backgroundGlow.fill || 'rgba(59, 130, 246, 0.2)'}
filter={backgroundGlow.blur ? `blur(${backgroundGlow.blur}px)` : 'none'}
opacity={backgroundGlow.opacity ?? 0.6}
rx={8}
style={{ pointerEvents: 'none' }}
/>
)
})}
{/* Column highlights */}
{highlightColumns.map((colIndex) => {
if (colIndex < 0 || colIndex >= columns) return null
const x = colIndex * rodSpacing + rodSpacing / 2
const highlightWidth = rodSpacing * 0.9
const highlightHeight = height - labelHeight - numbersHeight
return (
<rect
key={`column-highlight-${colIndex}`}
x={x - highlightWidth / 2}
y={labelHeight}
width={highlightWidth}
height={highlightHeight}
fill="rgba(59, 130, 246, 0.15)"
stroke="rgba(59, 130, 246, 0.4)"
strokeWidth={2}
rx={6}
style={{ pointerEvents: 'none' }}
/>
)
})}
{/* Column labels */}
{columnLabels.map((label, colIndex) => {
if (!label || colIndex >= columns) return null
const x = colIndex * rodSpacing + rodSpacing / 2
return (
<text
key={`column-label-${colIndex}`}
x={x}
y={labelHeight / 2 + 5}
textAnchor="middle"
fontSize="14"
fontWeight="600"
fill="rgba(0, 0, 0, 0.7)"
style={{ pointerEvents: 'none', userSelect: 'none' }}
>
{label}
</text>
)
})}
{/* Rods (column posts) */}
{frameVisible && beadConfigs.map((_, colIndex) => {
const placeValue = columns - 1 - colIndex
const x = colIndex * rodSpacing + rodSpacing / 2
// Apply custom column post styling (column-specific overrides global)
const columnStyles = customStyles?.columns?.[colIndex]
const globalColumnPosts = customStyles?.columnPosts
const rodStyle = {
fill: columnStyles?.columnPost?.fill || globalColumnPosts?.fill || 'rgb(0, 0, 0, 0.1)',
stroke: columnStyles?.columnPost?.stroke || globalColumnPosts?.stroke || 'none',
strokeWidth: columnStyles?.columnPost?.strokeWidth ?? globalColumnPosts?.strokeWidth ?? 0,
opacity: columnStyles?.columnPost?.opacity ?? globalColumnPosts?.opacity ?? 1,
}
return (
<rect
key={`rod-pv${placeValue}`}
x={x - dimensions.rodWidth / 2}
y={labelHeight}
width={dimensions.rodWidth}
height={height - labelHeight - numbersHeight}
fill={rodStyle.fill}
stroke={rodStyle.stroke}
strokeWidth={rodStyle.strokeWidth}
opacity={rodStyle.opacity}
className="column-post"
/>
)
})}
{/* Reckoning bar */}
{frameVisible && (
<rect
x={0}
y={barY}
width={columns * rodSpacing}
height={barThickness}
fill={customStyles?.reckoningBar?.fill || 'rgb(0, 0, 0, 0.15)'}
stroke={customStyles?.reckoningBar?.stroke || 'rgba(0, 0, 0, 0.3)'}
strokeWidth={customStyles?.reckoningBar?.strokeWidth || 2}
opacity={customStyles?.reckoningBar?.opacity ?? 1}
/>
)}
{/* Beads - delegated to injected component */}
{beadConfigs.map((columnBeads, colIndex) => {
const placeValue = columns - 1 - colIndex
// Get column state for inactive earth bead positioning
const columnState = state[placeValue] || { heavenActive: false, earthActive: 0 }
return (
<g key={`column-${colIndex}`}>
{columnBeads.map((bead, beadIndex) => {
// Calculate position using shared utility with column state for accurate positioning
const position = calculateBeadPosition(bead, dimensions, { earthActive: columnState.earthActive })
const color = getBeadColor(bead, columns, colorScheme, colorPalette)
// Get custom style for this specific bead
const customStyle =
bead.type === 'heaven'
? customStyles?.heavenBeads
: customStyles?.earthBeads
// Build base props
const baseProps: BeadComponentProps = {
bead,
x: position.x,
y: position.y,
size: beadSize,
shape: beadShape,
color,
hideInactiveBeads,
customStyle,
onClick: onBeadClick,
onMouseEnter: onBeadMouseEnter,
onMouseLeave: onBeadMouseLeave,
onRef: onBeadRef,
}
// Calculate extra props if provided (for animations, etc.)
const extraProps = calculateExtraBeadProps?.(bead, baseProps) || {}
return (
<BeadComponent
key={`bead-pv${bead.placeValue}-${bead.type}-${bead.position}`}
{...baseProps}
{...extraProps}
/>
)
})}
</g>
)
})}
{/* Column numbers */}
{showNumbers && beadConfigs.map((_, colIndex) => {
const placeValue = columns - 1 - colIndex
const columnState = state[placeValue] || { heavenActive: false, earthActive: 0 }
const digit = (columnState.heavenActive ? 5 : 0) + columnState.earthActive
const x = colIndex * rodSpacing + rodSpacing / 2
return (
<text
key={`number-${colIndex}`}
x={x}
y={height - numbersHeight / 2 + 5}
textAnchor="middle"
fontSize={customStyles?.numerals?.fontSize || '16px'}
fontWeight={customStyles?.numerals?.fontWeight || '600'}
fill={customStyles?.numerals?.color || 'rgba(0, 0, 0, 0.8)'}
style={{ pointerEvents: 'none', userSelect: 'none' }}
>
{digit}
</text>
)
})}
{/* Additional content (overlays, numbers, etc.) */}
{children}
</svg>
)
}
export default AbacusSVGRenderer

View File

@@ -0,0 +1,283 @@
import type { Meta, StoryObj } from '@storybook/react'
import { AbacusStatic } from './AbacusStatic'
import { ABACUS_THEMES } from './AbacusThemes'
/**
* AbacusStatic - Server Component compatible static abacus
*
* ## Key Features:
* - ✅ Works in React Server Components (no "use client")
* - ✅ **Identical layout to AbacusReact** - same props = same exact SVG output
* - ✅ No animations, hooks, or client-side JavaScript
* - ✅ Lightweight rendering for static displays
*
* ## Shared Architecture (Zero Duplication!):
* Both AbacusStatic and AbacusReact use the **exact same rendering pipeline**:
*
* ```
* calculateStandardDimensions() → AbacusSVGRenderer → calculateBeadPosition()
* ↓
* ┌───────────────────┴───────────────────┐
* ↓ ↓
* AbacusStaticBead AbacusAnimatedBead
* (Simple SVG) (react-spring)
* ```
*
* - `calculateStandardDimensions()` - Single source of truth for layout (beadSize, gaps, bar position, etc.)
* - `AbacusSVGRenderer` - Shared SVG structure with dependency injection for bead components
* - `calculateBeadPosition()` - Exact positioning formulas used by both variants
* - `AbacusStaticBead` - RSC-compatible simple SVG shapes (this component)
* - `AbacusAnimatedBead` - Client component with animations (AbacusReact)
*
* ## Visual Consistency Guarantee:
* Both AbacusStatic and AbacusReact produce **pixel-perfect identical output** for the same props.
* This ensures previews match interactive versions, PDFs match web displays, etc.
*
* **Architecture benefit:** ~560 lines of duplicate code eliminated. Same props = same dimensions = same positions = same layout.
*
* ## When to Use:
* - React Server Components (Next.js App Router)
* - Static site generation
* - Non-interactive previews
* - PDF generation
* - Server-side rendering without hydration
*/
const meta = {
title: 'AbacusStatic/Server Component Ready',
component: AbacusStatic,
parameters: {
layout: 'centered',
},
tags: ['autodocs'],
} satisfies Meta<typeof AbacusStatic>
export default meta
type Story = StoryObj<typeof meta>
export const Default: Story = {
args: {
value: 123,
columns: 'auto',
},
}
export const DifferentValues: Story = {
render: () => (
<div style={{ display: 'flex', gap: '30px', flexWrap: 'wrap' }}>
{[1, 5, 10, 25, 50, 100, 456, 789].map((value) => (
<div key={value} style={{ textAlign: 'center' }}>
<AbacusStatic value={value} columns="auto" />
<p style={{ marginTop: '10px', color: '#64748b' }}>{value}</p>
</div>
))}
</div>
),
}
export const ColorSchemes: Story = {
render: () => (
<div style={{ display: 'flex', gap: '40px', flexWrap: 'wrap' }}>
<div style={{ textAlign: 'center' }}>
<AbacusStatic value={456} colorScheme="place-value" />
<p style={{ marginTop: '10px', color: '#64748b' }}>Place Value</p>
</div>
<div style={{ textAlign: 'center' }}>
<AbacusStatic value={456} colorScheme="monochrome" />
<p style={{ marginTop: '10px', color: '#64748b' }}>Monochrome</p>
</div>
<div style={{ textAlign: 'center' }}>
<AbacusStatic value={456} colorScheme="heaven-earth" />
<p style={{ marginTop: '10px', color: '#64748b' }}>Heaven-Earth</p>
</div>
<div style={{ textAlign: 'center' }}>
<AbacusStatic value={456} colorScheme="alternating" />
<p style={{ marginTop: '10px', color: '#64748b' }}>Alternating</p>
</div>
</div>
),
}
export const BeadShapes: Story = {
render: () => (
<div style={{ display: 'flex', gap: '40px' }}>
<div style={{ textAlign: 'center' }}>
<AbacusStatic value={42} beadShape="circle" />
<p style={{ marginTop: '10px', color: '#64748b' }}>Circle</p>
</div>
<div style={{ textAlign: 'center' }}>
<AbacusStatic value={42} beadShape="diamond" />
<p style={{ marginTop: '10px', color: '#64748b' }}>Diamond</p>
</div>
<div style={{ textAlign: 'center' }}>
<AbacusStatic value={42} beadShape="square" />
<p style={{ marginTop: '10px', color: '#64748b' }}>Square</p>
</div>
</div>
),
}
export const CompactMode: Story = {
render: () => (
<div style={{ fontSize: '24px', display: 'flex', alignItems: 'center', gap: '15px' }}>
<span>The equation:</span>
<AbacusStatic value={5} columns={1} compact hideInactiveBeads scaleFactor={0.7} />
<span>+</span>
<AbacusStatic value={3} columns={1} compact hideInactiveBeads scaleFactor={0.7} />
<span>=</span>
<AbacusStatic value={8} columns={1} compact hideInactiveBeads scaleFactor={0.7} />
</div>
),
}
export const HideInactiveBeads: Story = {
render: () => (
<div style={{ display: 'flex', gap: '40px' }}>
<div style={{ textAlign: 'center' }}>
<AbacusStatic value={25} hideInactiveBeads={false} />
<p style={{ marginTop: '10px', color: '#64748b' }}>Show All</p>
</div>
<div style={{ textAlign: 'center' }}>
<AbacusStatic value={25} hideInactiveBeads />
<p style={{ marginTop: '10px', color: '#64748b' }}>Hide Inactive</p>
</div>
</div>
),
}
export const WithThemes: Story = {
render: () => (
<div style={{ display: 'flex', gap: '40px', flexWrap: 'wrap' }}>
<div style={{ textAlign: 'center' }}>
<AbacusStatic value={123} customStyles={ABACUS_THEMES.light} />
<p style={{ marginTop: '10px', color: '#64748b' }}>Light</p>
</div>
<div style={{ textAlign: 'center', padding: '20px', background: '#1e293b', borderRadius: '8px' }}>
<AbacusStatic value={123} customStyles={ABACUS_THEMES.dark} />
<p style={{ marginTop: '10px', color: '#cbd5e1' }}>Dark</p>
</div>
<div style={{ textAlign: 'center' }}>
<AbacusStatic value={123} customStyles={ABACUS_THEMES.trophy} />
<p style={{ marginTop: '10px', color: '#64748b' }}>Trophy</p>
</div>
</div>
),
}
export const ColumnHighlightingAndLabels: Story = {
render: () => (
<div style={{ display: 'flex', flexDirection: 'column', gap: '40px' }}>
<div style={{ textAlign: 'center' }}>
<AbacusStatic
value={456}
highlightColumns={[1]}
columnLabels={['ones', 'tens', 'hundreds']}
/>
<p style={{ marginTop: '10px', color: '#64748b' }}>Highlighting tens place</p>
</div>
<div style={{ textAlign: 'center' }}>
<AbacusStatic
value={789}
highlightColumns={[0, 2]}
columnLabels={['ones', 'tens', 'hundreds']}
/>
<p style={{ marginTop: '10px', color: '#64748b' }}>Multiple highlights</p>
</div>
</div>
),
}
export const Scaling: Story = {
render: () => (
<div style={{ display: 'flex', gap: '40px', alignItems: 'flex-end' }}>
<div style={{ textAlign: 'center' }}>
<AbacusStatic value={9} scaleFactor={0.5} />
<p style={{ marginTop: '10px', color: '#64748b' }}>0.5x</p>
</div>
<div style={{ textAlign: 'center' }}>
<AbacusStatic value={9} scaleFactor={1} />
<p style={{ marginTop: '10px', color: '#64748b' }}>1x</p>
</div>
<div style={{ textAlign: 'center' }}>
<AbacusStatic value={9} scaleFactor={1.5} />
<p style={{ marginTop: '10px', color: '#64748b' }}>1.5x</p>
</div>
</div>
),
}
export const ServerComponentExample: Story = {
render: () => (
<div style={{ maxWidth: '700px', padding: '20px', background: '#f8fafc', borderRadius: '8px' }}>
<h3 style={{ marginTop: 0 }}>React Server Component Usage</h3>
<pre
style={{
background: '#1e293b',
color: '#e2e8f0',
padding: '15px',
borderRadius: '6px',
overflow: 'auto',
fontSize: '13px',
}}
>
{`// app/flashcards/page.tsx (Server Component)
import { AbacusStatic } from '@soroban/abacus-react'
export default function FlashcardsPage() {
const numbers = [1, 5, 10, 25, 50, 100]
return (
<div className="grid grid-cols-3 gap-4">
{numbers.map(num => (
<div key={num} className="card">
<AbacusStatic
value={num}
columns="auto"
hideInactiveBeads
compact
/>
<p>{num}</p>
</div>
))}
</div>
)
}
// ✅ No "use client" needed!
// ✅ Rendered on server
// ✅ Zero client JavaScript`}
</pre>
</div>
),
}
export const PreviewCards: Story = {
render: () => (
<div
style={{
display: 'grid',
gridTemplateColumns: 'repeat(auto-fit, minmax(150px, 1fr))',
gap: '20px',
maxWidth: '900px',
}}
>
{[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 25, 50].map((value) => (
<div
key={value}
style={{
padding: '15px',
background: 'white',
border: '2px solid #e2e8f0',
borderRadius: '12px',
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
gap: '10px',
}}
>
<AbacusStatic value={value} columns="auto" scaleFactor={0.8} hideInactiveBeads />
<span style={{ fontSize: '18px', fontWeight: 'bold', color: '#475569' }}>{value}</span>
</div>
))}
</div>
),
}

View File

@@ -0,0 +1,184 @@
/**
* AbacusStatic - Server Component compatible static abacus
*
* Shares layout and rendering with AbacusReact through dependency injection.
* Uses standard dimensions to ensure same props = same exact visual output.
* Reuses: AbacusSVGRenderer for structure, shared dimension/position calculators
* Different: No hooks, no animations, no interactions, simplified bead rendering
*/
import { numberToAbacusState, calculateStandardDimensions } from './AbacusUtils'
import { AbacusSVGRenderer } from './AbacusSVGRenderer'
import { AbacusStaticBead } from './AbacusStaticBead'
import type {
AbacusCustomStyles,
BeadConfig,
ValidPlaceValues
} from './AbacusReact'
export interface AbacusStaticConfig {
value: number | bigint
columns?: number | 'auto'
beadShape?: 'circle' | 'diamond' | 'square'
colorScheme?: 'monochrome' | 'place-value' | 'alternating' | 'heaven-earth'
colorPalette?: 'default' | 'pastel' | 'vibrant' | 'earth-tones'
showNumbers?: boolean | 'always' | 'never'
hideInactiveBeads?: boolean
scaleFactor?: number
frameVisible?: boolean
compact?: boolean
customStyles?: AbacusCustomStyles
highlightColumns?: number[]
columnLabels?: string[]
}
// Shared color logic (matches AbacusReact)
function getBeadColor(
bead: BeadConfig,
totalColumns: number,
colorScheme: string,
colorPalette: string
): string {
const placeValue = bead.placeValue
// Place-value coloring
if (colorScheme === 'place-value') {
const colors: Record<string, string[]> = {
default: [
'#ef4444', // red - ones
'#f59e0b', // amber - tens
'#10b981', // emerald - hundreds
'#3b82f6', // blue - thousands
'#8b5cf6', // purple - ten thousands
'#ec4899', // pink - hundred thousands
'#14b8a6', // teal - millions
'#f97316', // orange - ten millions
'#6366f1', // indigo - hundred millions
'#84cc16', // lime - billions
],
pastel: [
'#fca5a5', '#fcd34d', '#6ee7b7', '#93c5fd', '#c4b5fd',
'#f9a8d4', '#5eead4', '#fdba74', '#a5b4fc', '#bef264',
],
vibrant: [
'#dc2626', '#d97706', '#059669', '#2563eb', '#7c3aed',
'#db2777', '#0d9488', '#ea580c', '#4f46e5', '#65a30d',
],
'earth-tones': [
'#92400e', '#78350f', '#365314', '#1e3a8a', '#4c1d95',
'#831843', '#134e4a', '#7c2d12', '#312e81', '#3f6212',
],
}
const palette = colors[colorPalette] || colors.default
return palette[placeValue % palette.length]
}
// Heaven-earth coloring
if (colorScheme === 'heaven-earth') {
return bead.type === 'heaven' ? '#3b82f6' : '#10b981'
}
// Alternating coloring
if (colorScheme === 'alternating') {
const columnIndex = totalColumns - 1 - placeValue
return columnIndex % 2 === 0 ? '#3b82f6' : '#10b981'
}
// Monochrome (default)
return '#3b82f6'
}
/**
* AbacusStatic - Pure static abacus component (Server Component compatible)
*/
export function AbacusStatic({
value,
columns = 'auto',
beadShape = 'circle',
colorScheme = 'place-value',
colorPalette = 'default',
showNumbers = true,
hideInactiveBeads = false,
scaleFactor = 1,
frameVisible = true,
compact = false,
customStyles,
highlightColumns = [],
columnLabels = [],
}: AbacusStaticConfig) {
// Calculate columns
const valueStr = value.toString().replace('-', '')
const minColumns = Math.max(1, valueStr.length)
const effectiveColumns = columns === 'auto' ? minColumns : Math.max(columns, minColumns)
// Use shared utility to convert value to bead states
const state = numberToAbacusState(value, effectiveColumns)
// Generate bead configs (matching AbacusReact's structure)
const beadConfigs: BeadConfig[][] = []
for (let colIndex = 0; colIndex < effectiveColumns; colIndex++) {
const placeValue = (effectiveColumns - 1 - colIndex) as ValidPlaceValues
const columnState = state[placeValue] || { heavenActive: false, earthActive: 0 }
const beads: BeadConfig[] = []
// Heaven bead
beads.push({
type: 'heaven',
value: 5,
active: columnState.heavenActive,
position: 0,
placeValue,
})
// Earth beads
for (let i = 0; i < 4; i++) {
beads.push({
type: 'earth',
value: 1,
active: i < columnState.earthActive,
position: i,
placeValue,
})
}
beadConfigs.push(beads)
}
// Calculate standard dimensions (same as AbacusReact!)
const dimensions = calculateStandardDimensions({
columns: effectiveColumns,
scaleFactor,
showNumbers: !!showNumbers,
columnLabels,
})
// Compact mode hides frame
const effectiveFrameVisible = compact ? false : frameVisible
// Use shared renderer with static bead component
return (
<AbacusSVGRenderer
value={value}
columns={effectiveColumns}
state={state}
beadConfigs={beadConfigs}
dimensions={dimensions}
scaleFactor={scaleFactor}
beadShape={beadShape}
colorScheme={colorScheme}
colorPalette={colorPalette}
hideInactiveBeads={hideInactiveBeads}
frameVisible={effectiveFrameVisible}
showNumbers={!!showNumbers}
customStyles={customStyles}
highlightColumns={highlightColumns}
columnLabels={columnLabels}
BeadComponent={AbacusStaticBead}
getBeadColor={getBeadColor}
/>
)
}
export default AbacusStatic

View File

@@ -0,0 +1,101 @@
/**
* StaticBead - Pure SVG bead with no animations or interactions
* Used by AbacusStatic for server-side rendering
*/
import type { BeadConfig, BeadStyle } from './AbacusReact'
export interface StaticBeadProps {
bead: BeadConfig
x: number
y: number
size: number
shape: 'diamond' | 'square' | 'circle'
color: string
customStyle?: BeadStyle
hideInactiveBeads?: boolean
}
export function AbacusStaticBead({
bead,
x,
y,
size,
shape,
color,
customStyle,
hideInactiveBeads = false,
}: StaticBeadProps) {
// Don't render inactive beads if hideInactiveBeads is true
if (!bead.active && hideInactiveBeads) {
return null
}
const halfSize = size / 2
const opacity = bead.active ? (customStyle?.opacity ?? 1) : 0.3
const fill = customStyle?.fill || color
const stroke = customStyle?.stroke || '#000'
const strokeWidth = customStyle?.strokeWidth || 0.5
// Calculate offset based on shape (matching AbacusReact positioning)
const getXOffset = () => {
return shape === 'diamond' ? size * 0.7 : halfSize
}
const getYOffset = () => {
return halfSize
}
const transform = `translate(${x - getXOffset()}, ${y - getYOffset()})`
const renderShape = () => {
switch (shape) {
case 'diamond':
return (
<polygon
points={`${size * 0.7},0 ${size * 1.4},${halfSize} ${size * 0.7},${size} 0,${halfSize}`}
fill={fill}
stroke={stroke}
strokeWidth={strokeWidth}
opacity={opacity}
/>
)
case 'square':
return (
<rect
width={size}
height={size}
fill={fill}
stroke={stroke}
strokeWidth={strokeWidth}
rx="1"
opacity={opacity}
/>
)
case 'circle':
default:
return (
<circle
cx={halfSize}
cy={halfSize}
r={halfSize}
fill={fill}
stroke={stroke}
strokeWidth={strokeWidth}
opacity={opacity}
/>
)
}
}
return (
<g
className={`abacus-bead ${bead.active ? 'active' : 'inactive'} ${hideInactiveBeads && !bead.active ? 'hidden-inactive' : ''}`}
data-testid={`bead-place-${bead.placeValue}-${bead.type}${bead.type === 'earth' ? `-pos-${bead.position}` : ''}`}
transform={transform}
style={{ transition: 'opacity 0.2s ease-in-out' }}
>
{renderShape()}
</g>
)
}

View File

@@ -0,0 +1,115 @@
/**
* Pre-defined theme presets for AbacusReact component
* These eliminate the need for manual style object creation
*/
import type { AbacusCustomStyles } from './AbacusReact'
export const ABACUS_THEMES = {
/**
* Light theme - solid white frame with subtle gray accents
* Best for: Clean, minimalist designs on light backgrounds
*/
light: {
columnPosts: {
fill: 'rgb(255, 255, 255)',
stroke: 'rgb(200, 200, 200)',
strokeWidth: 2,
},
reckoningBar: {
fill: 'rgb(255, 255, 255)',
stroke: 'rgb(200, 200, 200)',
strokeWidth: 3,
},
} as AbacusCustomStyles,
/**
* Dark theme - translucent white with subtle glow
* Best for: Dark backgrounds, hero sections, dramatic presentations
*/
dark: {
columnPosts: {
fill: 'rgba(255, 255, 255, 0.3)',
stroke: 'rgba(255, 255, 255, 0.2)',
strokeWidth: 2,
},
reckoningBar: {
fill: 'rgba(255, 255, 255, 0.4)',
stroke: 'rgba(255, 255, 255, 0.25)',
strokeWidth: 3,
},
} as AbacusCustomStyles,
/**
* Trophy/Premium theme - golden frame with warm tones
* Best for: Achievements, rewards, celebration contexts
*/
trophy: {
columnPosts: {
fill: '#fbbf24',
stroke: '#f59e0b',
strokeWidth: 3,
},
reckoningBar: {
fill: '#fbbf24',
stroke: '#f59e0b',
strokeWidth: 4,
},
} as AbacusCustomStyles,
/**
* Translucent theme - subtle, nearly invisible frame
* Best for: Inline displays, minimal UI, focus on beads
*/
translucent: {
columnPosts: {
fill: 'rgba(0, 0, 0, 0.05)',
stroke: 'rgba(0, 0, 0, 0.1)',
strokeWidth: 1,
},
reckoningBar: {
fill: 'rgba(0, 0, 0, 0.1)',
stroke: 'none',
strokeWidth: 0,
},
} as AbacusCustomStyles,
/**
* Solid/High-contrast theme - black frame for maximum visibility
* Best for: Educational contexts, high visibility requirements
*/
solid: {
columnPosts: {
fill: 'rgb(0, 0, 0)',
stroke: 'rgb(0, 0, 0)',
strokeWidth: 2,
},
reckoningBar: {
fill: 'rgb(0, 0, 0)',
stroke: 'none',
strokeWidth: 0,
},
} as AbacusCustomStyles,
/**
* Traditional/Natural theme - brown wooden appearance
* Best for: Traditional soroban aesthetic, cultural contexts
*/
traditional: {
columnPosts: {
fill: '#8B5A2B',
stroke: '#654321',
strokeWidth: 2,
},
reckoningBar: {
fill: '#8B5A2B',
stroke: '#654321',
strokeWidth: 3,
},
} as AbacusCustomStyles,
} as const
/**
* Theme names type for TypeScript autocomplete
*/
export type AbacusThemeName = keyof typeof ABACUS_THEMES

View File

@@ -0,0 +1,562 @@
/**
* Utility functions for working with abacus states and calculations
* These help convert between numbers and bead positions, calculate diffs, etc.
*/
import type { ValidPlaceValues, BeadHighlight } from './AbacusReact'
/**
* Represents the state of beads in a single column
*/
export interface BeadState {
heavenActive: boolean
earthActive: number // 0-4
}
/**
* Represents the complete state of an abacus
* Key is the place value (0 = ones, 1 = tens, etc.)
*/
export interface AbacusState {
[placeValue: number]: BeadState
}
/**
* Convert a number to abacus state representation
* @param value - The number to convert
* @param maxPlaces - Maximum number of place values to include
* @returns AbacusState object representing the bead positions
*/
export function numberToAbacusState(value: number | bigint, maxPlaces: number = 5): AbacusState {
const state: AbacusState = {}
const valueNum = typeof value === 'bigint' ? Number(value) : value
for (let place = 0; place < maxPlaces; place++) {
const placeValueNum = 10 ** place
const digit = Math.floor(valueNum / placeValueNum) % 10
state[place] = {
heavenActive: digit >= 5,
earthActive: digit >= 5 ? digit - 5 : digit,
}
}
return state
}
/**
* Convert abacus state to a number
* @param state - The abacus state to convert
* @returns The numeric value represented by the abacus
*/
export function abacusStateToNumber(state: AbacusState): number {
let total = 0
for (const placeStr in state) {
const place = parseInt(placeStr, 10)
const beadState = state[place]
const digit = (beadState.heavenActive ? 5 : 0) + beadState.earthActive
total += digit * (10 ** place)
}
return total
}
/**
* Bead highlight with place value (internal type for calculations)
*/
export interface PlaceValueBasedBead {
placeValue: ValidPlaceValues
beadType: 'heaven' | 'earth'
position?: 0 | 1 | 2 | 3
}
/**
* Calculate which beads need to change between two abacus states
* @param startState - The starting abacus state
* @param targetState - The target abacus state
* @returns Object with arrays of bead additions and removals
*/
export function calculateBeadChanges(
startState: AbacusState,
targetState: AbacusState
): {
additions: PlaceValueBasedBead[]
removals: PlaceValueBasedBead[]
placeValue: number
} {
const additions: PlaceValueBasedBead[] = []
const removals: PlaceValueBasedBead[] = []
let mainPlaceValue = 0
for (const placeStr in targetState) {
const place = parseInt(placeStr, 10) as ValidPlaceValues
const start = startState[place] || { heavenActive: false, earthActive: 0 }
const target = targetState[place]
// Check heaven bead changes
if (!start.heavenActive && target.heavenActive) {
additions.push({ placeValue: place, beadType: 'heaven' })
mainPlaceValue = place
} else if (start.heavenActive && !target.heavenActive) {
removals.push({ placeValue: place, beadType: 'heaven' })
mainPlaceValue = place
}
// Check earth bead changes
if (target.earthActive > start.earthActive) {
// Adding earth beads
for (let pos = start.earthActive; pos < target.earthActive; pos++) {
additions.push({ placeValue: place, beadType: 'earth', position: pos as 0 | 1 | 2 | 3 })
mainPlaceValue = place
}
} else if (target.earthActive < start.earthActive) {
// Removing earth beads
for (let pos = start.earthActive - 1; pos >= target.earthActive; pos--) {
removals.push({ placeValue: place, beadType: 'earth', position: pos as 0 | 1 | 2 | 3 })
mainPlaceValue = place
}
}
}
return { additions, removals, placeValue: mainPlaceValue }
}
/**
* Result of a bead diff calculation
*/
export interface BeadDiffResult {
placeValue: ValidPlaceValues
beadType: 'heaven' | 'earth'
position?: number
direction: 'activate' | 'deactivate'
order: number // Order of operations for animations
}
/**
* Output of calculateBeadDiff function
*/
export interface BeadDiffOutput {
changes: BeadDiffResult[]
highlights: PlaceValueBasedBead[]
hasChanges: boolean
summary: string
}
/**
* Calculate the diff between two abacus states
* Returns exactly which beads need to move with directions and order
* @param fromState - Starting state
* @param toState - Target state
* @returns BeadDiffOutput with changes, highlights, and summary
*/
export function calculateBeadDiff(fromState: AbacusState, toState: AbacusState): BeadDiffOutput {
const { additions, removals } = calculateBeadChanges(fromState, toState)
const changes: BeadDiffResult[] = []
const highlights: PlaceValueBasedBead[] = []
let order = 0
// Process removals first (pedagogical order: clear before adding)
removals.forEach((removal) => {
changes.push({
placeValue: removal.placeValue,
beadType: removal.beadType,
position: removal.position,
direction: 'deactivate',
order: order++,
})
highlights.push({
placeValue: removal.placeValue,
beadType: removal.beadType,
position: removal.position,
})
})
// Process additions second (pedagogical order: add after clearing)
additions.forEach((addition) => {
changes.push({
placeValue: addition.placeValue,
beadType: addition.beadType,
position: addition.position,
direction: 'activate',
order: order++,
})
highlights.push({
placeValue: addition.placeValue,
beadType: addition.beadType,
position: addition.position,
})
})
// Generate summary
const summary = generateDiffSummary(changes)
return {
changes,
highlights,
hasChanges: changes.length > 0,
summary,
}
}
/**
* Calculate bead diff from numeric values
* Convenience function for when you have numbers instead of states
* @param fromValue - Starting numeric value
* @param toValue - Target numeric value
* @param maxPlaces - Maximum number of place values to consider
* @returns BeadDiffOutput
*/
export function calculateBeadDiffFromValues(
fromValue: number | bigint,
toValue: number | bigint,
maxPlaces: number = 5
): BeadDiffOutput {
const fromState = numberToAbacusState(fromValue, maxPlaces)
const toState = numberToAbacusState(toValue, maxPlaces)
return calculateBeadDiff(fromState, toState)
}
/**
* Validate that an abacus value is within the supported range
* @param value - The value to validate
* @param maxPlaces - Maximum number of place values supported
* @returns Object with isValid boolean and optional error message
*/
export function validateAbacusValue(
value: number | bigint,
maxPlaces: number = 5
): { isValid: boolean; error?: string } {
const valueNum = typeof value === 'bigint' ? Number(value) : value
if (valueNum < 0) {
return { isValid: false, error: 'Negative values are not supported' }
}
const maxValue = 10 ** maxPlaces - 1
if (valueNum > maxValue) {
return { isValid: false, error: `Value exceeds maximum for ${maxPlaces} columns (max: ${maxValue})` }
}
return { isValid: true }
}
/**
* Check if two abacus states are equal
* @param state1 - First state
* @param state2 - Second state
* @returns true if states are equal
*/
export function areStatesEqual(state1: AbacusState, state2: AbacusState): boolean {
const places1 = Object.keys(state1)
.map((k) => parseInt(k, 10))
.sort()
const places2 = Object.keys(state2)
.map((k) => parseInt(k, 10))
.sort()
if (places1.length !== places2.length) return false
for (const place of places1) {
const bead1 = state1[place]
const bead2 = state2[place]
if (!bead2) return false
if (bead1.heavenActive !== bead2.heavenActive) return false
if (bead1.earthActive !== bead2.earthActive) return false
}
return true
}
// Internal helper functions
function generateDiffSummary(changes: BeadDiffResult[]): string {
if (changes.length === 0) {
return 'No changes needed'
}
// Sort by order to respect pedagogical sequence
const sortedChanges = [...changes].sort((a, b) => a.order - b.order)
const deactivations = sortedChanges.filter((c) => c.direction === 'deactivate')
const activations = sortedChanges.filter((c) => c.direction === 'activate')
const parts: string[] = []
// Process deactivations first (pedagogical order)
if (deactivations.length > 0) {
const deactivationsByPlace = groupByPlace(deactivations)
Object.entries(deactivationsByPlace).forEach(([place, beads]) => {
const placeName = getPlaceName(parseInt(place, 10))
const heavenBeads = beads.filter((b) => b.beadType === 'heaven')
const earthBeads = beads.filter((b) => b.beadType === 'earth')
if (heavenBeads.length > 0) {
parts.push(`remove heaven bead in ${placeName}`)
}
if (earthBeads.length > 0) {
const count = earthBeads.length
parts.push(`remove ${count} earth bead${count > 1 ? 's' : ''} in ${placeName}`)
}
})
}
// Process activations second (pedagogical order)
if (activations.length > 0) {
const activationsByPlace = groupByPlace(activations)
Object.entries(activationsByPlace).forEach(([place, beads]) => {
const placeName = getPlaceName(parseInt(place, 10))
const heavenBeads = beads.filter((b) => b.beadType === 'heaven')
const earthBeads = beads.filter((b) => b.beadType === 'earth')
if (heavenBeads.length > 0) {
parts.push(`add heaven bead in ${placeName}`)
}
if (earthBeads.length > 0) {
const count = earthBeads.length
parts.push(`add ${count} earth bead${count > 1 ? 's' : ''} in ${placeName}`)
}
})
}
return parts.join(', then ')
}
function groupByPlace(changes: BeadDiffResult[]): {
[place: string]: BeadDiffResult[]
} {
return changes.reduce(
(groups, change) => {
const place = change.placeValue.toString()
if (!groups[place]) {
groups[place] = []
}
groups[place].push(change)
return groups
},
{} as { [place: string]: BeadDiffResult[] }
)
}
function getPlaceName(place: number): string {
switch (place) {
case 0:
return 'ones column'
case 1:
return 'tens column'
case 2:
return 'hundreds column'
case 3:
return 'thousands column'
default:
return `place ${place} column`
}
}
/**
* Complete layout dimensions for abacus rendering
* Used by both static and dynamic rendering to ensure identical layouts
*/
export interface AbacusLayoutDimensions {
// SVG canvas size
width: number
height: number
// Bead and spacing
beadSize: number
rodSpacing: number // Same as columnSpacing
rodWidth: number
barThickness: number
// Gaps and positioning
heavenEarthGap: number // Gap between heaven and earth sections (where bar sits)
activeGap: number // Gap between active beads and reckoning bar
inactiveGap: number // Gap between inactive beads and active beads/bar
adjacentSpacing: number // Minimal spacing for adjacent beads of same type
// Key Y positions (absolute coordinates)
barY: number // Y position of reckoning bar
heavenY: number // Y position where inactive heaven beads rest
earthY: number // Y position where inactive earth beads rest
// Padding and extras
padding: number
labelHeight: number
numbersHeight: number
// Derived values
totalColumns: number
}
/**
* Calculate standard layout dimensions for abacus rendering
* This ensures both static and dynamic rendering use identical geometry
* Same props = same exact visual output
*
* @param columns - Number of columns in the abacus
* @param scaleFactor - Size multiplier (default: 1)
* @param showNumbers - Whether numbers are shown below columns
* @param columnLabels - Array of column labels (if any)
* @returns Complete layout dimensions object
*/
export function calculateStandardDimensions({
columns,
scaleFactor = 1,
showNumbers = false,
columnLabels = [],
}: {
columns: number
scaleFactor?: number
showNumbers?: boolean
columnLabels?: string[]
}): AbacusLayoutDimensions {
// Standard dimensions - used by both AbacusStatic and AbacusReact
const rodWidth = 3 * scaleFactor
const beadSize = 12 * scaleFactor
const adjacentSpacing = 0.5 * scaleFactor
const columnSpacing = 25 * scaleFactor
const heavenEarthGap = 30 * scaleFactor
const barThickness = 2 * scaleFactor
// Positioning gaps
const activeGap = 1 * scaleFactor
const inactiveGap = 8 * scaleFactor
// Calculate total dimensions
const totalWidth = columns * columnSpacing
const baseHeight = heavenEarthGap + 5 * (beadSize + 4 * scaleFactor) + 10 * scaleFactor
// Extra spacing
const numbersSpace = showNumbers ? 40 * scaleFactor : 0
const labelSpace = columnLabels.length > 0 ? 30 * scaleFactor : 0
const padding = 0 // No padding - keeps layout clean
const totalHeight = baseHeight + numbersSpace + labelSpace
// Key Y positions - bar is at heavenEarthGap from top
const barY = heavenEarthGap + labelSpace
const heavenY = labelSpace + activeGap // Top area for inactive heaven beads
const earthY = barY + barThickness + (4 * beadSize) + activeGap + inactiveGap // Bottom area for inactive earth
return {
width: totalWidth,
height: totalHeight,
beadSize,
rodSpacing: columnSpacing,
rodWidth,
barThickness,
heavenEarthGap,
activeGap,
inactiveGap,
adjacentSpacing,
barY,
heavenY,
earthY,
padding,
labelHeight: labelSpace,
numbersHeight: numbersSpace,
totalColumns: columns,
}
}
/**
* @deprecated Use calculateStandardDimensions instead for full layout info
* This function only returns width/height for backward compatibility
*/
export function calculateAbacusDimensions({
columns,
showNumbers = true,
columnLabels = [],
}: {
columns: number
showNumbers?: boolean
columnLabels?: string[]
}): { width: number; height: number } {
// Redirect to new function for backward compatibility
const dims = calculateStandardDimensions({ columns, scaleFactor: 1, showNumbers, columnLabels })
return { width: dims.width, height: dims.height }
}
/**
* Simplified bead config for position calculation
* (Compatible with BeadConfig from AbacusReact)
*/
export interface BeadPositionConfig {
type: 'heaven' | 'earth'
active: boolean
position: number // 0 for heaven, 0-3 for earth
placeValue: number
}
/**
* Column state needed for earth bead positioning
* (Required to calculate inactive earth bead positions correctly)
*/
export interface ColumnStateForPositioning {
earthActive: number // Number of active earth beads (0-4)
}
/**
* Calculate the x,y position for a bead based on standard layout dimensions
* This ensures both static and dynamic rendering position beads identically
* Uses exact Typst formulas from the original implementation
*
* @param bead - Bead configuration
* @param dimensions - Layout dimensions from calculateStandardDimensions
* @param columnState - Optional column state (required for inactive earth beads)
* @returns Object with x and y coordinates
*/
export function calculateBeadPosition(
bead: BeadPositionConfig,
dimensions: AbacusLayoutDimensions,
columnState?: ColumnStateForPositioning
): { x: number; y: number } {
const { beadSize, rodSpacing, heavenEarthGap, barThickness, activeGap, inactiveGap, adjacentSpacing, totalColumns } = dimensions
// X position based on place value (rightmost = ones place)
const columnIndex = totalColumns - 1 - bead.placeValue
const x = columnIndex * rodSpacing + rodSpacing / 2
// Y position based on bead type and active state
// These formulas match the original Typst implementation exactly
if (bead.type === 'heaven') {
if (bead.active) {
// Active heaven bead: positioned close to reckoning bar (Typst line 175)
const y = heavenEarthGap - beadSize / 2 - activeGap
return { x, y }
} else {
// Inactive heaven bead: positioned away from reckoning bar (Typst line 178)
const y = heavenEarthGap - inactiveGap - beadSize / 2
return { x, y }
}
} else {
// Earth bead positioning (Typst lines 249-261)
const earthActive = columnState?.earthActive ?? 0
if (bead.active) {
// Active beads: positioned near reckoning bar, adjacent beads touch (Typst line 251)
const y = heavenEarthGap + barThickness + activeGap + beadSize / 2 +
bead.position * (beadSize + adjacentSpacing)
return { x, y }
} else {
// Inactive beads: positioned after active beads + gap (Typst lines 254-261)
let y: number
if (earthActive > 0) {
// Position after the last active bead + gap, then adjacent inactive beads touch (Typst line 256)
y = heavenEarthGap + barThickness + activeGap + beadSize / 2 +
(earthActive - 1) * (beadSize + adjacentSpacing) +
beadSize / 2 + inactiveGap + beadSize / 2 +
(bead.position - earthActive) * (beadSize + adjacentSpacing)
} else {
// No active beads: position after reckoning bar + gap, adjacent inactive beads touch (Typst line 259)
y = heavenEarthGap + barThickness + inactiveGap + beadSize / 2 +
bead.position * (beadSize + adjacentSpacing)
}
return { x, y }
}
}
}

View File

@@ -1,5 +1,20 @@
export { default as AbacusReact } from "./AbacusReact";
export type { AbacusConfig, BeadConfig, AbacusDimensions } from "./AbacusReact";
export type {
AbacusConfig,
BeadConfig,
AbacusDimensions,
AbacusCustomStyles,
BeadStyle,
ColumnPostStyle,
ReckoningBarStyle,
NumeralStyle,
ValidPlaceValues,
BeadHighlight,
StepBeadHighlight,
BeadClickEvent,
AbacusCallbacks,
AbacusOverlay,
} from "./AbacusReact";
export {
useAbacusConfig,
@@ -17,3 +32,33 @@ export type {
export { StandaloneBead } from "./StandaloneBead";
export type { StandaloneBeadProps } from "./StandaloneBead";
export { AbacusStatic } from "./AbacusStatic";
export type { AbacusStaticConfig } from "./AbacusStatic";
export { ABACUS_THEMES } from "./AbacusThemes";
export type { AbacusThemeName } from "./AbacusThemes";
export {
numberToAbacusState,
abacusStateToNumber,
calculateBeadChanges,
calculateBeadDiff,
calculateBeadDiffFromValues,
validateAbacusValue,
areStatesEqual,
calculateAbacusDimensions,
calculateStandardDimensions, // NEW: Shared layout calculator
calculateBeadPosition, // NEW: Bead position calculator
} from "./AbacusUtils";
export type {
BeadState,
AbacusState,
BeadDiffResult,
BeadDiffOutput,
PlaceValueBasedBead,
AbacusLayoutDimensions, // NEW: Complete layout dimensions type
BeadPositionConfig, // NEW: Bead config for position calculation
} from "./AbacusUtils";
export { useAbacusDiff, useAbacusState } from "./AbacusHooks";

View File

@@ -0,0 +1,18 @@
/**
* Server Component compatible exports
* This entry point only exports components that work without "use client"
*/
export { AbacusStatic } from './AbacusStatic'
export type { AbacusStaticConfig } from './AbacusStatic'
export { AbacusStaticBead } from './AbacusStaticBead'
export type { StaticBeadProps } from './AbacusStaticBead'
// Re-export shared utilities that are safe for server components
export { numberToAbacusState, calculateAbacusDimensions } from './AbacusUtils'
export type {
AbacusCustomStyles,
BeadConfig,
PlaceState,
ValidPlaceValues,
} from './AbacusReact'

View File

@@ -8,10 +8,12 @@ export default defineConfig(async () => {
plugins: [react()],
build: {
lib: {
entry: resolve(__dirname, "src/index.ts"),
name: "AbacusReact",
entry: {
index: resolve(__dirname, "src/index.ts"),
static: resolve(__dirname, "src/static.ts"),
},
formats: ["es", "cjs"],
fileName: (format) => `index.${format}.js`,
fileName: (format, entryName) => `${entryName}.${format === "es" ? "es" : "cjs"}.js`,
},
sourcemap: true,
rollupOptions: {