Compare commits

...

201 Commits

Author SHA1 Message Date
semantic-release-bot
6d1bad142b chore(release): 4.63.4 [skip ci]
## [4.63.4](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.63.3...v4.63.4) (2025-10-21)

### Bug Fixes

* **flashcards:** keep grab point under cursor with proper coordinate conversion ([1869216](1869216d2f))
2025-10-21 16:28:08 +00:00
Thomas Hallock
1869216d2f fix(flashcards): keep grab point under cursor with proper coordinate conversion
Fixed the issue where cards would slip out from under the cursor when rotating.
The grab point now stays perfectly under your cursor throughout the drag.

The problem: Simple delta positioning didn't account for rotation causing the
grab point to rotate away from the cursor position.

The solution: Properly convert between coordinate systems:
1. Calculate rotated grab offset (2D rotation matrix)
2. Determine card center: cursor position - rotated grab offset (viewport space)
3. Convert from viewport coordinates to container coordinates
4. Calculate top-left position from center for CSS translate()

Key changes:
- Pass containerRef down to DraggableCard components
- Get container bounds for viewport→container conversion
- Apply rotation matrix to grab offset
- Position card so rotated grab point aligns with cursor

Now when you grab a card by its edge and drag, the exact point you grabbed
stays glued to your cursor while the card rotates around its center.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-21 11:26:54 -05:00
semantic-release-bot
e4ae3aefef chore(release): 4.63.3 [skip ci]
## [4.63.3](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.63.2...v4.63.3) (2025-10-21)

### Bug Fixes

* **flashcards:** revert to simple delta positioning to prevent card jumping ([d018b69](d018b699c4))
2025-10-21 16:22:59 +00:00
Thomas Hallock
d018b699c4 fix(flashcards): revert to simple delta positioning to prevent card jumping
Reverted the complex rotation-compensated positioning that was causing
cards to jump to the right of the viewport on click.

The issue: The previous attempt to keep the grab point "stuck" to the cursor
while rotating was mixing screen coordinates with container-relative coords,
causing cards to teleport on pointer down.

The solution: Use simple delta positioning (cursor movement from drag start)
and let CSS handle rotation around the card center. While this means the grab
point won't stay perfectly under the cursor as the card rotates, it's much
better than cards jumping unexpectedly.

The rotation still works - cards rotate based on grab point physics - but
the positioning is now stable and predictable.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-21 11:21:49 -05:00
semantic-release-bot
be323bfbc5 chore(release): 4.63.2 [skip ci]
## [4.63.2](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.63.1...v4.63.2) (2025-10-21)

### Bug Fixes

* **flashcards:** correct pivot point to rotate around card center ([50fc3fd](50fc3fdf7f))
2025-10-21 16:19:23 +00:00
Thomas Hallock
50fc3fdf7f fix(flashcards): correct pivot point to rotate around card center
Fixed issue where cards appeared to pivot around the grab point in
viewport space instead of rotating around their own center.

The problem: When dragging, the card would stay "pinned" at the grab
point in screen space, making it rotate around that viewport location.

The solution: As the card rotates, calculate the rotated grab offset
and reposition the card so that:
1. The grab point stays under the cursor
2. The card rotates around its own center
3. The visual result feels like the card is "stuck" to your finger

Implementation:
- Convert rotation angle to radians
- Apply 2D rotation matrix to grab offset vector
- Calculate card center position: cursor - rotated grab offset
- Convert center position to top-left for CSS translate positioning

This creates the natural feeling of grabbing and spinning a physical
card by its edge.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-21 11:18:04 -05:00
semantic-release-bot
e52d907087 chore(release): 4.63.1 [skip ci]
## [4.63.1](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.63.0...v4.63.1) (2025-10-21)

### Bug Fixes

* **flashcards:** increase rotation sensitivity 10x for visible grab point physics ([c0fa926](c0fa926d16))
2025-10-21 16:08:00 +00:00
Thomas Hallock
c0fa926d16 fix(flashcards): increase rotation sensitivity 10x for visible grab point physics
The previous scale factor of 5000 made rotation changes too subtle to see.
Reduced to 500 (10x more sensitive) so card rotation is clearly visible
when dragging from off-center grab points.

Also added detailed rotation logging during drag to help debug:
- Shows current rotation angle
- Shows rotation influence being applied
- Shows cross product value from physics calculation

This will help test and tune the rotation feel.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-21 11:06:48 -05:00
semantic-release-bot
1fd0474cd5 chore(release): 4.63.0 [skip ci]
## [4.63.0](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.62.1...v4.63.0) (2025-10-21)

### Features

* **flashcards:** add grab point physics for realistic rotation ([bf37eb1](bf37eb1928))
2025-10-21 16:05:21 +00:00
Thomas Hallock
bf37eb1928 feat(flashcards): add grab point physics for realistic rotation
Implements physics-based rotation that responds to where the user grabs
the card. When you grab a card off-center and drag it, it rotates
naturally based on the grab point and drag direction.

Features:
- Calculate grab offset from card center on pointer down
- Apply rotation using cross product of grab offset and drag direction
- Rotation clamped to ±45° to prevent excessive spinning
- Final rotation preserved when card is released
- Console logging for grab point coordinates and rotation changes

Physics details:
- Cross product (grabOffset.x * deltaY - grabOffset.y * deltaX) determines
  rotation direction and magnitude
- Grabbing left side + dragging right = clockwise rotation
- Grabbing right side + dragging left = counter-clockwise rotation
- Scale factor of 5000 provides smooth, realistic rotation feel

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-21 11:04:07 -05:00
semantic-release-bot
9f56c9728c chore(release): 4.62.1 [skip ci]
## [4.62.1](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.62.0...v4.62.1) (2025-10-21)

### Bug Fixes

* **flashcards:** improve shadow speed logging with separate throttling ([0f51366](0f51366fd5))
2025-10-21 15:59:26 +00:00
Thomas Hallock
0f51366fd5 fix(flashcards): improve shadow speed logging with separate throttling
Fixed logging issue where speed logs weren't showing during drag:
- Added separate lastLogTimeRef for logging throttle
- Logs now appear every ~200ms during drag (was never logging before)
- Velocity calculation unchanged, only logging throttle fixed

Now you can see speed values in console during drag to verify
shadow responsiveness.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-21 10:58:07 -05:00
semantic-release-bot
fe1e8979c8 chore(release): 4.62.0 [skip ci]
## [4.62.0](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.61.3...v4.62.0) (2025-10-21)

### Features

* **flashcards:** add dynamic shadow based on drag speed ([92148a4](92148a4cf8))
2025-10-21 15:52:28 +00:00
Thomas Hallock
92148a4cf8 feat(flashcards): add dynamic shadow based on drag speed
First physics enhancement - shadow changes based on how fast you drag:
- Tracks drag velocity (distance/time) during pointer move
- Shadow grows larger and darker with faster dragging
- Base: 8px offset, 24px blur, 0.3 opacity
- Fast: 32px offset, 64px blur, 0.6 opacity
- Smooth decay when released

Console logging included:
- [Shadow] logs on drag start/release
- Speed/distance/time logged during drag (throttled to ~100ms)

Test: Drag cards slowly vs fast and watch shadow change

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-21 10:51:08 -05:00
semantic-release-bot
7088a7096a chore(release): 4.61.3 [skip ci]
## [4.61.3](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.61.2...v4.61.3) (2025-10-21)

### Code Refactoring

* **flashcards:** completely rewrite drag-drop with simple approach ([5f0ad14](5f0ad14133))
2025-10-21 15:45:03 +00:00
Thomas Hallock
5f0ad14133 refactor(flashcards): completely rewrite drag-drop with simple approach
Replaced complex react-spring + useDrag implementation with simple
PointerEvents-based drag and drop:

- Uses native onPointerDown/Move/Up events
- Tracks position with useState (no animation library)
- Cards stay exactly where dropped (no physics or snap-back)
- Simple scale-up effect while dragging
- Much more predictable and maintainable

Removed dependencies on:
- @react-spring/web
- @use-gesture/react
- Complex velocity tracking and decay physics
- Transform-origin calculations

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-21 10:43:43 -05:00
semantic-release-bot
73f8f637cd chore(release): 4.61.2 [skip ci]
## [4.61.2](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.61.1...v4.61.2) (2025-10-21)

### Bug Fixes

* **flashcards:** use explicit per-property configs to fix decay physics ([f32480a](f32480a0f9))
2025-10-21 15:41:53 +00:00
Thomas Hallock
f32480a0f9 fix(flashcards): use explicit per-property configs to fix decay physics
Removed conflicting top-level config that was interfering with decay
animations. Now using explicit config objects for each property:
- x, y: decay physics with velocity
- scale, rotation: wobbly spring animations

This should fix the issue where cards were snapping back to pickup
position instead of staying where dropped.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-21 10:40:31 -05:00
semantic-release-bot
11aa44d882 chore(release): 4.61.1 [skip ci]
## [4.61.1](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.61.0...v4.61.1) (2025-10-21)

### Bug Fixes

* **flashcards:** fix position snap-back by using api.set before decay ([30e16c8](30e16c8e5a))
2025-10-21 15:37:33 +00:00
Thomas Hallock
30e16c8e5a fix(flashcards): fix position snap-back by using api.set before decay
Fixed the issue where cards were snapping back to pickup position:
- Use api.set() to immediately snap spring position to dropped location
- Then apply decay animation with momentum from that position
- Removed problematic 'from' property which doesn't work with decay

The bug was that react-spring's 'from' property is ignored when using
decay: true, causing the spring to animate from its current value rather
than the specified position. Using api.set() first ensures the spring
starts from the correct dropped position.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-21 10:36:07 -05:00
semantic-release-bot
86357b3d7a chore(release): 4.61.0 [skip ci]
## [4.61.0](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.60.0...v4.61.0) (2025-10-21)

### Features

* **flashcards:** enable unbounded drag and position persistence ([ad1ad69](ad1ad690f0))
2025-10-21 15:32:18 +00:00
Thomas Hallock
ad1ad690f0 feat(flashcards): enable unbounded drag and position persistence
Made interactive flashcards a fun easter egg by:
- Changed overflow from 'hidden' to 'visible' to allow cards to be
  thrown anywhere on the page, not just within container bounds
- Fixed position persistence: cards now stay exactly where dropped
  instead of snapping back to pickup location
- Updated currentPositionRef immediately on drop before applying
  momentum physics

Cards can now be dragged and tossed freely around the entire page
and will maintain their position after being dropped.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-21 10:31:08 -05:00
semantic-release-bot
53475cf40e chore(release): 4.60.0 [skip ci]
## [4.60.0](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.59.1...v4.60.0) (2025-10-21)

### Features

* **homepage:** significantly increase mobile hero abacus size ([424f41d](424f41d4bf))
2025-10-21 15:29:16 +00:00
Thomas Hallock
424f41d4bf feat(homepage): significantly increase mobile hero abacus size
Increased mobile scale from 2.5 to 3.5 to better utilize available screen
space on mobile devices. Screenshot review showed ample room above and
below the abacus without risk of overlapping title or scroll hint.

Final scales:
- Mobile (base): scale(3.5) - 75% larger than original
- Medium (md): scale(3.5) - 16% larger than original
- Desktop (lg): scale(4.25) - 6% larger than original

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-21 10:27:53 -05:00
semantic-release-bot
4c6939807e chore(release): 4.59.1 [skip ci]
## [4.59.1](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.59.0...v4.59.1) (2025-10-21)

### Bug Fixes

* **homepage:** adjust hero abacus scale for optimal sizing across devices ([86dee31](86dee31c9a))
* **homepage:** reduce mobile abacus scale to prevent scroll hint overlap ([b8235be](b8235be612))
2025-10-21 15:27:10 +00:00
Thomas Hallock
b8235be612 fix(homepage): reduce mobile abacus scale to prevent scroll hint overlap
Reduced mobile scale from 2.8 to 2.5 to ensure the "Scroll to explore"
hint at the bottom is not covered by the abacus.

Final scales:
- Mobile (base): scale(2.5) - 25% larger than original, no overlap
- Medium (md): scale(3.5) - 16% larger than original
- Desktop (lg): scale(4.25) - 6% larger than original

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-21 10:25:50 -05:00
Thomas Hallock
86dee31c9a fix(homepage): adjust hero abacus scale for optimal sizing across devices
Fine-tuned scale values based on visual feedback:

- Mobile (base): scale(2.8) - increased for better mobile visibility (40% larger than original 2)
- Medium screens (md): scale(3.5) - unchanged (16% larger than original 3)
- Desktop (lg): scale(4.25) - reduced 15% for safety (6% larger than original 4)

This balances maximum visual impact on mobile while preventing any
layout issues on larger screens.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-21 10:25:00 -05:00
semantic-release-bot
b401bb5fa4 chore(release): 4.59.0 [skip ci]
## [4.59.0](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.58.2...v4.59.0) (2025-10-21)

### Features

* **homepage:** increase hero abacus size for better visibility ([7666b0a](7666b0aea9))
2025-10-21 15:21:46 +00:00
Thomas Hallock
7666b0aea9 feat(homepage): increase hero abacus size for better visibility
Increase scale values across all breakpoints, especially on mobile:
- Mobile (base): scale(2) → scale(3) (50% increase)
- Medium screens (md): scale(3) → scale(4.5) (50% increase)
- Large screens (lg): scale(4) → scale(6) (50% increase)

The abacus now fills more of the available hero space without
clipping, improving visual impact and usability on all devices.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-21 10:19:55 -05:00
semantic-release-bot
39afa455de chore(release): 4.58.2 [skip ci]
## [4.58.2](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.58.1...v4.58.2) (2025-10-21)

### Code Refactoring

* **navbar:** prevent subtitle wrap and remove abacus emoji ([a58f7b7](a58f7b78b0))
2025-10-21 15:02:58 +00:00
Thomas Hallock
a58f7b78b0 refactor(navbar): prevent subtitle wrap and remove abacus emoji
Two small refinements to navbar branding:
- Added whiteSpace: 'nowrap' to subtitle to prevent text wrapping
- Removed abacus emoji from "Abaci One" branding for cleaner look

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-21 10:01:40 -05:00
semantic-release-bot
1c001e07b7 chore(release): 4.58.1 [skip ci]
## [4.58.1](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.58.0...v4.58.1) (2025-10-21)

### Bug Fixes

* **navbar:** apply glassmorphism to transparent mode, not scrolled mode ([8893675](8893675b36))
2025-10-21 14:58:54 +00:00
Thomas Hallock
8893675b36 fix(navbar): apply glassmorphism to transparent mode, not scrolled mode
Fixed logic - the glassmorphism effect (backdrop blur, subtle backgrounds,
borders, shadows) now applies when the navbar is transparent at the top of
the homepage (isTransparent=true), not when scrolled down.

When scrolled or on other pages, the links are simpler without the extra
glassmorphism styling.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-21 09:57:41 -05:00
semantic-release-bot
4254459238 chore(release): 4.58.0 [skip ci]
## [4.58.0](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.57.10...v4.58.0) (2025-10-21)

### Features

* **navbar:** add glassmorphism effect to nav links when scrolled ([89b9072](89b90723b7))
2025-10-21 14:56:51 +00:00
Thomas Hallock
89b90723b7 feat(navbar): add glassmorphism effect to nav links when scrolled
Enhanced nav links (Create, Guide, Games) with floating glass pill effect
when navbar is in scrolled mode:

- Added backdrop-filter blur(8px) to blur content underneath each link
- Subtle semi-transparent backgrounds (white for inactive, purple for active)
- Added borders with purple/white tints for enhanced definition
- Soft box shadows that glow purple on hover
- Enhanced hover states with stronger purple effects

The links now have better contrast against the dark navbar while maintaining
the ethereal floating illusion. Effects are only applied when scrolled (not
when navbar is transparent on homepage hero).

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-21 09:55:31 -05:00
Thomas Hallock
6e5aec858f revert(navbar): remove bottom fade gradient
Removed the 10px gradient fade at the bottom of the navbar per user request.
Kept the border fix that removes the black line artifact when transparent.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-21 09:53:33 -05:00
semantic-release-bot
5611d148aa chore(release): 4.57.10 [skip ci]
## [4.57.10](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.57.9...v4.57.10) (2025-10-21)

### Bug Fixes

* **navbar:** remove border artifact and add 10px bottom fade ([d5f60ce](d5f60ce9d2))

### Styles

* **navbar:** improve theming to match homepage dark aesthetic ([284fc90](284fc90a53))
2025-10-21 14:53:05 +00:00
Thomas Hallock
d5f60ce9d2 fix(navbar): remove border artifact and add 10px bottom fade
Fixed two visual issues with the navbar:

1. Removed black border line when transparent: Changed borderBottom to
   conditionally be 'none' instead of '1px solid transparent', which was
   showing up as a visible line when the navbar is transparent on homepage

2. Added 10px gradient fade at bottom: Applied linear-gradient that fades
   from the dark background to transparent over the last 10px, creating a
   softer transition instead of a sharp cut-off

The navbar now seamlessly blends into the page content with no visual artifacts.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-21 09:51:48 -05:00
Thomas Hallock
284fc90a53 style(navbar): improve theming to match homepage dark aesthetic
Updated AppNavBar styling to blend with homepage's dark glassmorphism theme:

- Changed navbar background from white to semi-transparent dark (rgba(0,0,0,0.5))
- Added backdrop-filter blur(12px) for glassmorphism effect
- Updated border color to purple accent (rgba(139,92,246,0.2))
- Changed logo/branding text to white/light purple tones
- Updated nav link colors from gray to light gray/purple palette
- Enhanced hover states with purple highlights (rgba(196,181,253))

The navbar now seamlessly integrates with the homepage's dark theme while
maintaining transparency when the hero section is visible.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-21 09:45:43 -05:00
semantic-release-bot
e54ea20dbe chore(release): 4.57.9 [skip ci]
## [4.57.9](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.57.8...v4.57.9) (2025-10-21)

### Bug Fixes

* **homepage:** add overflow hidden to Your Journey section ([415a1fb](415a1fb1fa))
2025-10-21 14:44:30 +00:00
Thomas Hallock
415a1fb1fa fix(homepage): add overflow hidden to Your Journey section
Prevents the level slider from causing horizontal overflow on mobile
devices, which was making the page width wider than intended.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-21 09:43:13 -05:00
semantic-release-bot
60b3a788b3 chore(release): 4.57.8 [skip ci]
## [4.57.8](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.57.7...v4.57.8) (2025-10-21)

### Bug Fixes

* **homepage:** adjust responsive breakpoints to prevent skill card clipping ([62ff067](62ff067bb9))
2025-10-21 14:33:47 +00:00
Thomas Hallock
62ff067bb9 fix(homepage): adjust responsive breakpoints to prevent skill card clipping
Changed layout breakpoints from md (768px) to xl (1280px) to ensure:
- Skills section doesn't get clipped at medium viewport sizes
- Layout only switches to side-by-side when there's sufficient space
- Container min-width is responsive (100% below xl, 1400px at xl+)

This prevents the issue where content was overflowing at intermediate
viewport widths.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-21 09:32:33 -05:00
semantic-release-bot
3d774c8d82 chore(release): 4.57.7 [skip ci]
## [4.57.7](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.57.6...v4.57.7) (2025-10-21)

### Bug Fixes

* **homepage:** restructure layout to center 1400px wide demo section ([61403f2](61403f2f50))
* **homepage:** set min-width 1400px on container and remove max-width ([aa297d4](aa297d4ef7))
2025-10-21 03:07:11 +00:00
Thomas Hallock
61403f2f50 fix(homepage): restructure layout to center 1400px wide demo section
Moved "Learn by Doing" section outside the maxW:7xl container to allow
the demo div to be 1400px wide and properly centered with mx:auto.

Remaining content stays within the 7xl constrained container.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-20 22:06:02 -05:00
Thomas Hallock
aa297d4ef7 fix(homepage): set min-width 1400px on container and remove max-width
Added minW: '1400px' and removed maxW: '1200px' constraint to ensure
the "What You'll Learn" section is properly sized and centered.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-20 22:03:31 -05:00
semantic-release-bot
712d318e7c chore(release): 4.57.6 [skip ci]
## [4.57.6](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.57.5...v4.57.6) (2025-10-21)

### Bug Fixes

* **homepage:** increase skill cards container width to prevent title wrapping ([cd3eb61](cd3eb61cb5))
2025-10-21 02:36:21 +00:00
Thomas Hallock
cd3eb61cb5 fix(homepage): increase skill cards container width to prevent title wrapping
Increased container from 650px to 800px to ensure skill card titles
like "Friends techniques" don't wrap awkwardly across multiple lines.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-20 21:35:05 -05:00
semantic-release-bot
8871050990 chore(release): 4.57.5 [skip ci]
## [4.57.5](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.57.4...v4.57.5) (2025-10-21)

### Bug Fixes

* **homepage:** prevent text overflow in skill cards ([a6ac55b](a6ac55b7b1))
2025-10-21 02:23:27 +00:00
Thomas Hallock
a6ac55b7b1 fix(homepage): prevent text overflow in skill cards
Fixed text being cut off on the right side of skill cards by:
- Adding minWidth: '0' to text container to enable proper flex shrinking
- Adding flexWrap: 'wrap' to title/badge row for better wrapping behavior

This fixes the flexbox issue where text was overflowing the card boundaries.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-20 21:22:17 -05:00
semantic-release-bot
64e2464ec1 chore(release): 4.57.4 [skip ci]
## [4.57.4](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.57.3...v4.57.4) (2025-10-21)

### Bug Fixes

* **homepage:** align container width breakpoint with grid columns ([422bf3d](422bf3d968))
* **homepage:** make MiniAbacus fill container properly ([3b5d147](3b5d14765d))
* **homepage:** widen skill cards container to 650px ([bc1ad3a](bc1ad3a43a))
2025-10-21 00:30:13 +00:00
Thomas Hallock
422bf3d968 fix(homepage): align container width breakpoint with grid columns
Changed width breakpoint from md to lg to match when the grid switches
to 2 columns. This prevents overflow on medium-width screens where the
container was 650px wide but still showing 1-column layout.

Now:
- Below lg: 100% width, 1 column
- At lg+: 650px width, 2 columns

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-20 19:29:05 -05:00
Thomas Hallock
bc1ad3a43a fix(homepage): widen skill cards container to 650px
Increased the "What You'll Learn" container width from 420px to 650px
to give the 2-column grid of skill cards proper breathing room and
prevent cramped layouts.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-20 19:27:58 -05:00
Thomas Hallock
3b5d14765d fix(homepage): make MiniAbacus fill container properly
Fixed skill card abacus sizing by:
- Changing MiniAbacus to use width/height 100% instead of fixed 75px/80px
- Increased scale from 0.6 to 0.75 for better visibility
- Now properly fills the 120px/150px container set on the wrapper

This fixes the clipping issue by making the component hierarchy work correctly:
outer container (120px/150px) -> MiniAbacus (100%) -> scaled AbacusReact

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-20 19:26:45 -05:00
semantic-release-bot
9847f8f461 chore(release): 4.57.3 [skip ci]
## [4.57.3](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.57.2...v4.57.3) (2025-10-21)

### Bug Fixes

* **homepage:** increase abacus container width to 120px/150px ([57c212f](57c212f4f5))
2025-10-21 00:25:47 +00:00
Thomas Hallock
57c212f4f5 fix(homepage): increase abacus container width to 120px/150px
Increase abacus container width from 95px/110px to 120px (mobile) and
150px (desktop) to properly accommodate 3-column abacus visualizations
without clipping.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-20 19:24:37 -05:00
semantic-release-bot
b2f5c19ce3 chore(release): 4.57.2 [skip ci]
## [4.57.2](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.57.1...v4.57.2) (2025-10-21)

### Bug Fixes

* **homepage:** increase skill card abacus container width ([e65e969](e65e96952f))
2025-10-21 00:23:44 +00:00
Thomas Hallock
e65e96952f fix(homepage): increase skill card abacus container width
Remove overflow:hidden and increase the abacus container width from
75px to responsive widths (95px mobile, 110px desktop) to properly
accommodate the abacus visualizations without clipping.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-20 19:22:25 -05:00
semantic-release-bot
556a0eb194 chore(release): 4.57.1 [skip ci]
## [4.57.1](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.57.0...v4.57.1) (2025-10-21)

### Bug Fixes

* **homepage:** add overflow hidden to skill cards ([5070d8d](5070d8d64f))
2025-10-21 00:22:09 +00:00
Thomas Hallock
5070d8d64f fix(homepage): add overflow hidden to skill cards
Add overflow: hidden to skill card containers to properly contain
content within card bounds and prevent visual overflow issues.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-20 19:20:59 -05:00
semantic-release-bot
54cedbe03a chore(release): 4.57.0 [skip ci]
## [4.57.0](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.56.0...v4.57.0) (2025-10-21)

### Features

* **homepage:** make skills section responsive with emojis ([9ec0a71](9ec0a71546))

### Bug Fixes

* **homepage:** prevent skill card overflow ([fa26acf](fa26acfbae))
2025-10-21 00:19:34 +00:00
Thomas Hallock
fa26acfbae fix(homepage): prevent skill card overflow
Change abacus container height to minHeight to prevent content
overflow in skill cards. This allows the container to grow as
needed while maintaining minimum dimensions.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-20 19:18:23 -05:00
Thomas Hallock
9ec0a71546 feat(homepage): make skills section responsive with emojis
Update "What You'll Learn" section to display in a responsive grid:
- One column on mobile/tablet
- Two columns on larger screens (lg breakpoint)
- Increased padding and height on two-column layout
- Added emojis to skill titles for better visual appeal

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-20 19:17:26 -05:00
semantic-release-bot
6448249512 chore(release): 4.56.0 [skip ci]
## [4.56.0](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.55.0...v4.56.0) (2025-10-20)

### Features

* **homepage:** emphasize single-player and observer modes ([a537bc1](a537bc18c3))
2025-10-20 23:52:50 +00:00
Thomas Hallock
a537bc18c3 feat(homepage): emphasize single-player and observer modes
Update arcade section subtitle to highlight both single-player
challenges and multiplayer battles. Also mention the ability to
invite friends to observe games live over the network.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-20 18:51:34 -05:00
semantic-release-bot
33ab7aaaf0 chore(release): 4.55.0 [skip ci]
## [4.55.0](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.54.0...v4.55.0) (2025-10-20)

### Features

* **homepage:** update section title to "The Arcade" ([f47b172](f47b172f66))
2025-10-20 23:46:47 +00:00
Thomas Hallock
f47b172f66 feat(homepage): update section title to "The Arcade"
Replace "Available Now" section with "The Arcade" to better describe
the multiplayer room system. Updated subtitle to explain that users
can create or join rooms to play real-time games with friends over
the network.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-20 18:45:31 -05:00
semantic-release-bot
9d25e1dd35 chore(release): 4.54.0 [skip ci]
## [4.54.0](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.53.0...v4.54.0) (2025-10-20)

### Features

* **homepage:** add interactive levels slider to replace static progression ([8a2d5ae](8a2d5ae319))

### Code Refactoring

* **levels:** extract levels slider into shared component ([015e30b](015e30b085))
2025-10-20 23:31:37 +00:00
Thomas Hallock
015e30b085 refactor(levels): extract levels slider into shared component
Consolidate the complete levels slider UI from /levels page into a reusable
LevelSliderDisplay component. This eliminates code duplication and provides
a single source of truth for the levels progression display.

Changes:
- Created LevelSliderDisplay component (862 lines) with all levels data,
  state management, animations, and UI
- Updated /levels page to use shared component (959 → 117 lines, 87.8% reduction)
- Updated homepage to use shared component (removed duplicate state/data)
- Deleted incomplete LevelsSlider component from previous commit

Component includes:
- Complete allLevels array (21 levels: 10th Kyu through 10th Dan)
- Interactive Radix slider with emoji tick marks
- StandaloneBead thumb with animated emoji transitions
- Level details for Kyu levels (Add/Sub, Multiply, Divide stats)
- Animated abacus display with speed scaling for Dan levels
- Auto-advance functionality (3s interval, pauses on hover)
- Dark theme styling

Both homepage and /levels page now use the same component with identical
behavior and appearance, eliminating maintenance overhead.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-20 18:30:22 -05:00
Thomas Hallock
8a2d5ae319 feat(homepage): add interactive levels slider to replace static progression
Replace the static emoji progression display in "Your Journey" section with
an interactive slider component adapted from /levels page. Features:

- Auto-advancing slider (3s intervals, pauses on hover)
- Interactive bead thumb with animated emoji overlay
- Animated abacus display that changes with level
- Shows 8 key milestone levels (10th Kyu through 10th Dan)
- Emoji tick marks above slider for visual navigation
- Dynamic border colors based on level category

Components:
- Created LevelsSlider component to encapsulate the UI
- Reuses existing React Spring animations from /levels page
- Uses Radix Slider for accessible interaction
- Dark theme abacus styling for consistency

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-20 18:30:22 -05:00
semantic-release-bot
d2ff2c6a29 chore(release): 4.53.0 [skip ci]
## [4.53.0](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.52.2...v4.53.0) (2025-10-20)

### Features

* **homepage:** add interactive draggable flashcards with physics ([0744883](074488349a))

### Code Refactoring

* **homepage:** merge flashcard display with create button section ([3cf4f92](3cf4f92643))
2025-10-20 23:12:50 +00:00
Thomas Hallock
3cf4f92643 refactor(homepage): merge flashcard display with create button section
Combine the interactive flashcards demonstration and the "Create
Flashcards" call-to-action into a single unified section. The card
throwing area, feature highlights, and CTA button are now part of
one cohesive pane instead of two separate sections.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-20 18:11:32 -05:00
Thomas Hallock
074488349a feat(homepage): add interactive draggable flashcards with physics
Add physics-based flashcard component with drag, throw, and momentum:
- Random generation of 8-15 flashcards with abacus visualizations
- Smooth drag interactions with velocity-based rotation
- Decay physics for realistic throwing and sliding
- Transform-origin pivot point based on click position
- Position persistence so cards stay where thrown

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-20 18:11:32 -05:00
semantic-release-bot
30a5587bca chore(release): 4.52.2 [skip ci]
## [4.52.2](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.52.1...v4.52.2) (2025-10-20)

### Bug Fixes

* **homepage:** use actual container dimensions for flashcard positioning ([4082a24](4082a246a3))
2025-10-20 22:44:22 +00:00
Thomas Hallock
4082a246a3 fix(homepage): use actual container dimensions for flashcard positioning
Fix flashcards being positioned outside visible area by using the
container's actual dimensions instead of hardcoded pixel values.

The previous implementation used CONTAINER_WIDTH=800px and
CONTAINER_HEIGHT=500px to position cards, but the actual container
width is 100% which varies by screen size. This caused cards to be
positioned outside the visible area on smaller screens.

Changes:
- Add containerRef to get actual container dimensions
- Calculate card positions based on offsetWidth/offsetHeight
- Remove hardcoded dimension constants
- Ensure cards stay within visible bounds with proper margins

This makes the flashcard positioning responsive to any screen size.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-20 17:43:06 -05:00
semantic-release-bot
9703fed94c chore(release): 4.52.1 [skip ci]
## [4.52.1](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.52.0...v4.52.1) (2025-10-20)

### Bug Fixes

* **homepage:** correct flashcard transform rendering ([5dc636a](5dc636a71c))
2025-10-20 22:40:40 +00:00
Thomas Hallock
5dc636a71c fix(homepage): correct flashcard transform rendering
Fix interactive flashcards not rendering by properly converting
react-spring animated values to CSS transforms. The x, y, rotation,
and scale spring values now use the `to` helper to create proper
CSS transform strings.

Changes:
- Import `to` helper from @react-spring/web
- Convert spring values to CSS transform using translate/rotate/scale
- Set position to absolute with left:0, top:0 as transform origin

This fixes the issue where flashcards were invisible because the
spring values weren't being properly converted to CSS.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-20 17:39:20 -05:00
semantic-release-bot
16d978db9a chore(release): 4.52.0 [skip ci]
## [4.52.0](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.51.0...v4.52.0) (2025-10-20)

### Features

* **homepage:** add interactive draggable flashcards with physics ([e711c52](e711c52757))
2025-10-20 22:39:00 +00:00
Thomas Hallock
e711c52757 feat(homepage): add interactive draggable flashcards with physics
Add a fun, interactive flashcard display to the homepage's flashcard
generator section. Users can drag and throw 8-15 randomly generated
flashcards around with realistic physics-based momentum.

Features:
- Drag and drop flashcards with mouse/touch
- Throw cards with velocity-based physics
- 8-15 randomly generated flashcards (100-999 range)
- Real AbacusReact components for each card
- Client-side rendering to avoid hydration errors

Technical implementation:
- Uses @use-gesture/react for drag gesture handling
- Uses @react-spring/web for smooth physics animations
- Cards generated client-side with useEffect to prevent SSR mismatch
- Each card maintains its own spring-based position and rotation

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-20 17:37:48 -05:00
semantic-release-bot
009162e22c chore(release): 4.51.0 [skip ci]
## [4.51.0](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.50.1...v4.51.0) (2025-10-20)

### Features

* **homepage:** create fancy flashcard display with spread-out cards ([cd30944](cd30944c5e))
2025-10-20 22:33:29 +00:00
Thomas Hallock
cd30944c5e feat(homepage): create fancy flashcard display with spread-out cards
Transform the flashcard generator section into an eye-catching display
featuring five rotated flashcards spread out in a fan pattern, each
showing real AbacusReact components with different numbers.

Features:
- Five flashcards (123, 456, 789, 321, 654) rotated at different angles
- Spread out horizontally for a dynamic, fanned-out effect
- Interactive hover effects - cards lift and scale on hover
- Real AbacusReact components showing actual bead positions
- Feature boxes highlighting capabilities (formats, customization, sizes)
- Blue glow effect on hover for the entire section
- Matches the visual style of other homepage sections

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-20 17:32:15 -05:00
semantic-release-bot
3e58cb5f92 chore(release): 4.50.1 [skip ci]
## [4.50.1](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.50.0...v4.50.1) (2025-10-20)

### Bug Fixes

* **homepage:** set fixed width for learning panel to prevent layout shift ([dc19622](dc19622bbb))
* **homepage:** set fixed width for tutorial panel to prevent layout shift ([aba9f8a](aba9f8a94d))
2025-10-20 22:29:57 +00:00
Thomas Hallock
aba9f8a94d fix(homepage): set fixed width for tutorial panel to prevent layout shift
Set the tutorial panel (left side) to a fixed width of 500px on desktop
to prevent the layout from shifting when switching between tutorials
with different text lengths.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-20 17:28:48 -05:00
Thomas Hallock
dc19622bbb fix(homepage): set fixed width for learning panel to prevent layout shift
Changed the "What You'll Learn" panel from maxW to a fixed width (420px)
on desktop screens to prevent it from shifting when switching between
tutorials with different text lengths.

Also added minW: '0' to the tutorial panel to allow proper flex shrinking.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-20 17:27:58 -05:00
semantic-release-bot
1babfde328 chore(release): 4.50.0 [skip ci]
## [4.50.0](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.49.1...v4.50.0) (2025-10-20)

### Features

* **homepage:** add interactive learning panels with animated mini-tutorials ([76d6f19](76d6f19d51))
2025-10-20 22:23:24 +00:00
Thomas Hallock
76d6f19d51 feat(homepage): add interactive learning panels with animated mini-tutorials
Transform "What You'll Learn" section into an interactive experience where
users can click skill panels to see corresponding tutorials with animated
abacus demonstrations.

Changes:
- Make skill panels clickable to switch between different tutorials
- Replace static emojis with animated MiniAbacus components for each skill
- Create skill-specific tutorials: basic numbers, friends of 5,
  multiplication, and mental calculation
- Add visual indication for selected panel (yellow glow effect)
- Update MiniAbacus component to cycle through custom value sequences
- Default to "Friends techniques" panel (Friends of 5 tutorial)

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-20 17:22:11 -05:00
semantic-release-bot
9ad35e65d3 chore(release): 4.49.1 [skip ci]
## [4.49.1](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.49.0...v4.49.1) (2025-10-20)

### Code Refactoring

* **homepage:** streamline homepage sections ([d362a77](d362a770d6))
2025-10-20 22:19:50 +00:00
Thomas Hallock
d362a770d6 refactor(homepage): streamline homepage sections
Simplified homepage by removing redundant sections and elevating
the flashcard creator to a standalone section.

Changes:
- Remove 'For Kids & Families' section entirely
- Remove 'Interactive Abacus' pane from Additional Tools
- Promote 'Flashcard Creator' to standalone top-level section

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-20 17:18:22 -05:00
semantic-release-bot
095cdda4ca chore(release): 4.49.0 [skip ci]
## [4.49.0](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.48.5...v4.49.0) (2025-10-20)

### Features

* add vibrant gradients and equal heights to game cards ([a1a135a](a1a135a858)), closes [#4](https://github.com/antialias/soroban-abacus-flashcards/issues/4) [#00f2](https://github.com/antialias/soroban-abacus-flashcards/issues/00f2) [#667](https://github.com/antialias/soroban-abacus-flashcards/issues/667) [#764ba2](https://github.com/antialias/soroban-abacus-flashcards/issues/764ba2) [#f093](https://github.com/antialias/soroban-abacus-flashcards/issues/f093) [#f5576](https://github.com/antialias/soroban-abacus-flashcards/issues/f5576) [#43e97](https://github.com/antialias/soroban-abacus-flashcards/issues/43e97) [#38f9d7](https://github.com/antialias/soroban-abacus-flashcards/issues/38f9d7)

### Code Refactoring

* make homepage game cards dynamic from game registry ([7f51652](7f516526fb))
2025-10-20 22:14:22 +00:00
Thomas Hallock
a1a135a858 feat: add vibrant gradients and equal heights to game cards
Updated the game theme system and game cards to have vibrant, eye-catching
gradients and consistent heights:

Theme System Changes:
- Updated all game themes from pastel to vibrant gradients
- Blue: Vibrant cyan (#4facfe to #00f2fe)
- Purple: Vibrant purple (#667eea to #764ba2)
- Pink: Vibrant pink (#f093fb to #f5576c)
- Green: Vibrant green/teal (#43e97b to #38f9d7)
- Plus updated indigo, teal, orange, yellow, red, gray themes

Game Manifest Updates:
- Memory Lightning: now uses purple theme
- Matching Pairs: now uses pink theme
- Complement Race: continues using blue (cyan) theme
- Card Sorting: now uses green theme

Homepage Game Cards:
- Added height: '100%' and flexbox to make all cards equal height
- Cards now stretch uniformly regardless of content length
- Maintains responsive hover effects and text readability overlays

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-20 17:13:09 -05:00
Thomas Hallock
7f516526fb refactor: make homepage game cards dynamic from game registry
Previously game cards were hardcoded. Now they automatically populate
from getAvailableGames() in the game registry. This means:
- Adding a new game to the registry automatically shows it on homepage
- Game metadata (icon, title, description, gradient, chips) comes from
  game manifests
- No manual homepage updates needed when games are added

Changes:
- Import getAvailableGames from game registry
- Replace hardcoded GameCard components with .map() over available games
- Format maxPlayers into player count string (e.g., "1-4 players")

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-20 17:08:55 -05:00
semantic-release-bot
9f706e9dce chore(release): 4.48.5 [skip ci]
## [4.48.5](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.48.4...v4.48.5) (2025-10-20)

### Bug Fixes

* **homepage:** add dark gradient overlay for better text contrast on game cards ([6410b21](6410b21f82))
2025-10-20 22:03:34 +00:00
Thomas Hallock
6410b21f82 fix(homepage): add dark gradient overlay for better text contrast on game cards
Added layered approach for better text readability:
- Vibrant gradient background layer (bottom)
- Dark gradient overlay from transparent (top 10%) to semi-dark (bottom 50%)
- Content with relative z-index positioning

This keeps the colorful gradients visible while ensuring all text
(titles, descriptions, tags) is clearly readable on all four cards.

Gradient goes from barely visible at top to moderately dark at bottom,
creating a natural fade that doesn't overwhelm the vibrant colors.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-20 17:02:24 -05:00
semantic-release-bot
3dc9f48d12 chore(release): 4.48.4 [skip ci]
## [4.48.4](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.48.3...v4.48.4) (2025-10-20)

### Bug Fixes

* **homepage:** improve text contrast on game cards with text shadows ([b6410c7](b6410c7c22))
2025-10-20 21:49:42 +00:00
Thomas Hallock
b6410c7c22 fix(homepage): improve text contrast on game cards with text shadows
Added text shadows to all game card text elements for better readability
on bright gradient backgrounds:
- Icon emoji: subtle shadow
- Title: strong shadow for maximum contrast
- Description: medium shadow (also increased opacity to 0.95)
- Player count: medium shadow (increased opacity to 0.85)
- Tags: medium shadow

This ensures all text is clearly readable on all four gradient backgrounds
(purple, pink, cyan, and green).

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-20 16:48:35 -05:00
semantic-release-bot
b54aaf1a67 chore(release): 4.48.3 [skip ci]
## [4.48.3](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.48.2...v4.48.3) (2025-10-20)

### Bug Fixes

* **homepage:** display gradient backgrounds on all game cards ([c6dc210](c6dc210bf8))

### Documentation

* **z-index:** add comprehensive z-index and stacking context documentation ([c89aea7](c89aea7444))
2025-10-20 21:41:12 +00:00
Thomas Hallock
c6dc210bf8 fix(homepage): display gradient backgrounds on all game cards
Fixed issue where only the Memory Lightning card showed its gradient
background while other cards appeared dark. The problem was that Panda
CSS's css() function doesn't properly handle raw CSS gradient strings.

Solution: Moved gradient from Panda css() to inline style prop, allowing
all four game cards to display their vibrant gradients:
- Memory Lightning: purple/violet
- Matching Pairs: pink/red
- Complement Race: blue/cyan
- Card Sorting: green/cyan

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-20 16:39:56 -05:00
Thomas Hallock
c89aea7444 docs(z-index): add comprehensive z-index and stacking context documentation
Created .claude/Z_INDEX_MANAGEMENT.md with:
- Complete z-index layering hierarchy (0-20000+)
- Stacking context rules and examples
- Current z-index audit of all 100+ hardcoded values
- Guidelines for choosing z-index values
- Migration plan to use constants file consistently
- Debugging checklist for layering issues
- Documentation of recent nav bar z-index fix

Updated .claude/CLAUDE.md to reference z-index documentation.

This provides a single source of truth for reasoning about visual
layering and prevents future z-index conflicts like the tutorial
tooltip issue.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-20 16:24:18 -05:00
semantic-release-bot
3564bd51dc chore(release): 4.48.2 [skip ci]
## [4.48.2](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.48.1...v4.48.2) (2025-10-20)

### Bug Fixes

* **nav:** ensure nav bar appears above tutorial tooltips ([cc31564](cc315645de))

### Styles

* **hero:** unify background with rest of homepage ([035d831](035d8312c7))
2025-10-20 21:00:17 +00:00
Thomas Hallock
cc315645de fix(nav): ensure nav bar appears above tutorial tooltips
Increased navigation bar z-index from 30 to 1000 to ensure it remains
above tutorial tooltips (which use z-index 50 and 100) when scrolling.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-20 15:58:58 -05:00
Thomas Hallock
035d8312c7 style(hero): unify background with rest of homepage
Replace purple gradient with solid gray.900 background to eliminate
sharp transition between hero section and page content.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-20 15:52:33 -05:00
semantic-release-bot
5f9b2dfe2b chore(release): 4.48.1 [skip ci]
## [4.48.1](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.48.0...v4.48.1) (2025-10-20)

### Bug Fixes

* **hero:** prevent SSR hydration mismatch for subtitle ([1bfde8f](1bfde8fb25))
2025-10-20 19:31:27 +00:00
Thomas Hallock
1bfde8fb25 fix(hero): prevent SSR hydration mismatch for subtitle
Add isSubtitleLoaded flag to hide subtitle until client-side random
selection completes, preventing flash of wrong subtitle during hydration.

Changes:
- Add isSubtitleLoaded state to HomeHeroContext
- Set flag to true after subtitle is selected on client
- Add opacity transition to subtitle in HeroAbacus
- Matches loading pattern used for abacus value

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-20 14:30:21 -05:00
semantic-release-bot
48647e4fb5 chore(release): 4.48.0 [skip ci]
## [4.48.0](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.47.2...v4.48.0) (2025-10-20)

### Features

* **hero:** persist random subtitle per-session ([318f946](318f9469a0))
2025-10-20 19:20:03 +00:00
Thomas Hallock
318f9469a0 feat(hero): persist random subtitle per-session
Store the randomly selected subtitle index in sessionStorage so it
remains consistent throughout the user's session. This eliminates
subtitle flashing on page reloads and provides a more stable UX.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-20 14:18:31 -05:00
semantic-release-bot
4bace36561 chore(release): 4.47.2 [skip ci]
## [4.47.2](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.47.1...v4.47.2) (2025-10-20)

### Bug Fixes

* **nav:** prevent thrashing by using fixed position always ([eff44b3](eff44b3ad1))
* **nav:** remove unnecessary borders from transparent nav ([8c2ddca](8c2ddca28d))
2025-10-20 19:16:22 +00:00
Thomas Hallock
8c2ddca28d fix(nav): remove unnecessary borders from transparent nav
Remove borders and extra padding around navigation elements when in
transparent mode. The borders were appearing as black and cluttering
the clean transparent overlay look. Now navigation elements show only
white text without any borders.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-20 14:14:59 -05:00
Thomas Hallock
eff44b3ad1 fix(nav): prevent thrashing by using fixed position always
The thrashing was caused by a layout shift feedback loop: switching
between position sticky (takes up space) and position fixed (overlays)
caused content to shift, triggering IntersectionObserver again.

Fix: Always use position fixed so nav state changes are purely visual
(transparency, borders, colors) without any layout shifts.

Also removed unnecessary hysteresis code since the root cause is fixed.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-20 14:13:54 -05:00
semantic-release-bot
f81b88ae30 chore(release): 4.47.1 [skip ci]
## [4.47.1](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.47.0...v4.47.1) (2025-10-20)

### Bug Fixes

* **hero:** prevent nav thrashing with hysteresis ([71b1b93](71b1b933b5))
2025-10-20 19:11:07 +00:00
Thomas Hallock
71b1b933b5 fix(hero): prevent nav thrashing with hysteresis
Add hysteresis to IntersectionObserver to prevent rapid toggling
between transparent and opaque nav bar states when scrolling near
the threshold. Now uses different thresholds for hiding (< 10%) vs
showing (> 30%), creating a 20% buffer zone.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-20 14:09:51 -05:00
semantic-release-bot
92d50673e5 chore(release): 4.47.0 [skip ci]
## [4.47.0](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.46.2...v4.47.0) (2025-10-20)

### Features

* **nav:** add transparent nav bar with borders when hero visible ([463841e](463841e191))

### Bug Fixes

* **hero:** use Number.isNaN instead of global isNaN ([c229faf](c229faffac))

### Styles

* **hero-abacus:** add purple bead colors for dark theme ([721dfe4](721dfe426d))
* **hero:** adjust spacing between title, subtitle, and abacus ([3a3706c](3a3706cc6f))
2025-10-20 19:04:17 +00:00
Thomas Hallock
c229faffac fix(hero): use Number.isNaN instead of global isNaN
Replace unsafe global isNaN with Number.isNaN to fix linting warning
and follow best practices for type coercion checking.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-20 14:02:59 -05:00
Thomas Hallock
463841e191 feat(nav): add transparent nav bar with borders when hero visible
When the hero section is visible on the homepage, the navigation bar
now becomes transparent with a fixed position overlay, featuring
contrasting white borders around nav elements for visibility. The nav
links change to white text for better contrast against the dark hero
background.

When scrolling past the hero, the nav returns to its normal white
background with sticky positioning.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-20 14:02:52 -05:00
Thomas Hallock
3a3706cc6f style(hero): adjust spacing between title, subtitle, and abacus
Reduce gap between title and subtitle from 4 to 2, and add margin
below subtitle to increase space before the abacus for better visual
hierarchy.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-20 14:02:43 -05:00
Thomas Hallock
721dfe426d style(hero-abacus): add purple bead colors for dark theme
Previously only styled column posts and reckoning bar, leaving
bright default bead colors that clashed with dark background.

Added:
- Heaven beads: light purple rgba(196, 181, 253, 0.8)
- Earth beads: medium purple rgba(167, 139, 250, 0.7)
- Both with violet strokes for definition

Beads now blend harmoniously with the dark purple gradient background.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-20 13:05:11 -05:00
semantic-release-bot
9dba75c9d9 chore(release): 4.46.2 [skip ci]
## [4.46.2](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.46.1...v4.46.2) (2025-10-20)

### Bug Fixes

* **types:** properly type HomeHeroContext in AppNavBar ([f9a7cb7](f9a7cb7f05))
2025-10-20 18:03:17 +00:00
Thomas Hallock
f9a7cb7f05 fix(types): properly type HomeHeroContext in AppNavBar
TypeScript error: Context type was incorrectly inferred when using
fallback React.createContext(null), causing type mismatch.

Solution: Add explicit HomeHeroContextValue type and cast both the
dynamically loaded context and the fallback to this type.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-20 13:01:59 -05:00
semantic-release-bot
ae7463d917 chore(release): 4.46.1 [skip ci]
## [4.46.1](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.46.0...v4.46.1) (2025-10-20)

### Bug Fixes

* **hero-abacus:** restructure layout to prevent visual overlap ([02b6c70](02b6c70b7a))
2025-10-20 17:59:13 +00:00
Thomas Hallock
02b6c70b7a fix(hero-abacus): restructure layout to prevent visual overlap
Problem: Title, subtitle, and scaled abacus were stacking too tightly,
creating visual overlap and a messy appearance.

Solution: Reorganize with space-between flexbox layout:
- Group title + subtitle together at top with compact spacing
- Give abacus its own centered flex container with generous padding
- Separate scroll hint at bottom
- Use vertical padding and flex: 1 to ensure proper spacing

This creates clear visual separation between all sections.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-20 12:58:05 -05:00
semantic-release-bot
4b72e0c561 chore(release): 4.46.0 [skip ci]
## [4.46.0](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.45.0...v4.46.0) (2025-10-20)

### Features

* **homepage:** add full-page hero abacus with scroll-based nav transition ([d8ec642](d8ec64280e))

### Bug Fixes

* **homepage:** improve hero abacus sizing and layout ([230f1dc](230f1dcd86))

### Styles

* **hero-abacus:** apply dark theme to match homepage styling ([e0b6a2e](e0b6a2e88b))
2025-10-20 17:57:21 +00:00
Thomas Hallock
e0b6a2e88b style(hero-abacus): apply dark theme to match homepage styling
Add dark mode custom styles for column posts and reckoning bar:
- Semi-transparent white fills (0.3-0.4 opacity)
- Subtle stroke borders (0.2-0.25 opacity)
- Matches styling used in MiniAbacus component

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-20 12:56:13 -05:00
Thomas Hallock
230f1dcd86 fix(homepage): improve hero abacus sizing and layout
Improvements:
- Increase hero abacus size: 2x→3x→4x scale for mobile/tablet/desktop
- Add better spacing between subtitle and abacus (mb: 16)
- Add z-index layering to prevent subtitle/abacus overlap
- Fix nav layout issue by adding spacer div when branding is hidden
- Remove emoji from hero title (redundant with actual abacus)

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-20 12:55:01 -05:00
Thomas Hallock
d8ec64280e feat(homepage): add full-page hero abacus with scroll-based nav transition
Implement an immersive homepage experience with a large, interactive
4-column abacus that dominates the initial viewport, creating a
"play with this" first impression. The hero smoothly transitions
to reveal the Abaci One branding in the navigation when scrolled.

**New Components:**
- `HeroAbacus`: Full-viewport interactive abacus with title/subtitle
  - Auto-cycles through random 4-digit numbers
  - Responsive scaling (1.5x mobile, 2x tablet, 2.5x desktop)
  - Intersection Observer to track visibility

- `HomeHeroContext`: Shared state for subtitle and scroll visibility
  - SSR-safe random subtitle selection (client-side only)
  - Prevents hydration mismatch warnings
  - Shares abacus value across hero/nav

**Navigation Updates:**
- AppNavBar conditionally shows/hides branding based on hero visibility
- Smooth fadeIn animation when branding appears after scroll
- Uses same random subtitle from context (consistent across page)
- Optional context access without breaking other pages

**Mobile Support:**
- Responsive abacus scaling for all screen sizes
- Touch-friendly interactive abacus
- Smooth animations work on all devices

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-20 12:55:01 -05:00
semantic-release-bot
1d4419364a chore(release): 4.45.0 [skip ci]
## [4.45.0](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.44.3...v4.45.0) (2025-10-20)

### Features

* **branding:** rebrand navigation from 'Soroban Generator' to 'Abaci One' ([cce8980](cce8980e17))
2025-10-20 17:39:58 +00:00
Thomas Hallock
cce8980e17 feat(branding): rebrand navigation from 'Soroban Generator' to 'Abaci One'
Changes:
- Add new subtitle data file with 75 three-word rhyming options
- Update AppNavBar to display "🧮 Abaci One" with random subtitle
- Implement Radix UI tooltip showing subtitle description on hover
- Use useMemo for performance (subtitle won't change on re-renders)
- Clean, minimal design with italic subtitle and help cursor

Implementation:
- Created `/src/data/abaciOneSubtitles.ts` with subtitle data structure
- Updated AppNavBar imports to include Radix Tooltip and subtitle util
- Wrapped navigation in Tooltip.Provider with 200ms delay
- Logo displays vertically with brand name and subtitle
- Tooltip shows description like "blaze through bead races" on hover

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-20 12:38:38 -05:00
semantic-release-bot
4bcce2a8db chore(release): 4.44.3 [skip ci]
## [4.44.3](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.44.2...v4.44.3) (2025-10-20)

### Bug Fixes

* **levels:** reduce operator box sizes and remove divider line ([29d20a6](29d20a6c07))
* **levels:** use uniform padding on operator box grid ([2818fd1](2818fd15ca))
2025-10-20 16:40:18 +00:00
Thomas Hallock
2818fd15ca fix(levels): use uniform padding on operator box grid
- Replaced separate pl, pr, py with uniform p: '2'
- Ensures equal padding on all sides (left, right, top, bottom)

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-20 11:39:09 -05:00
Thomas Hallock
29d20a6c07 fix(levels): reduce operator box sizes and remove divider line
- Reduced box dimensions from 200x180px to 170x150px for better fit
- Reduced grid gap from '3' to '2' for tighter layout
- Reduced box padding from '4' to '3' and gap from '2' to '1.5'
- Reduced maxW from 480px to 400px
- Changed my (margins) to py (padding) for more control
- Removed borderRight divider line between operator boxes and abacus

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-20 11:38:14 -05:00
semantic-release-bot
be2c3f63b0 chore(release): 4.44.2 [skip ci]
## [4.44.2](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.44.1...v4.44.2) (2025-10-20)

### Bug Fixes

* **levels:** match top/bottom margins to left padding on kyu detail boxes ([aa0bdcf](aa0bdcf686))
2025-10-20 16:37:17 +00:00
Thomas Hallock
aa0bdcf686 fix(levels): match top/bottom margins to left padding on kyu detail boxes
- Reduced my (vertical margins) from '6' to '2' to match pl (left padding)
- Ensures consistent spacing on all sides of the detail box grid

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-20 11:36:11 -05:00
semantic-release-bot
baea602000 chore(release): 4.44.1 [skip ci]
## [4.44.1](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.44.0...v4.44.1) (2025-10-20)

### Bug Fixes

* **levels:** add fixed dimensions and margins to kyu detail boxes ([05dd0b3](05dd0b30d3))
2025-10-20 16:35:46 +00:00
Thomas Hallock
05dd0b30d3 fix(levels): add fixed dimensions and margins to kyu detail boxes
- Set fixed width (200px) and height (180px) for operator boxes to prevent shifting
- Add vertical margins (my: 6) to grid container for better spacing
- Ensures boxes stay in consistent positions when sliding through levels
- Large enough to accommodate longest content (11 digits)

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-20 11:34:32 -05:00
semantic-release-bot
4febf5905b chore(release): 4.44.0 [skip ci]
## [4.44.0](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.43.2...v4.44.0) (2025-10-20)

### Features

* **levels:** redesign kyu details with larger operators and prominent digits ([6739d59](6739d59f2b))
2025-10-20 16:31:02 +00:00
Thomas Hallock
6739d59f2b feat(levels): redesign kyu details with larger operators and prominent digits
- Increase operator icon size from xl to 4xl for better visibility
- Center-align card layout with operator icon at top
- Extract and prominently display digit count in 2xl bold text
- Change hover effect from slide to scale for centered design
- Increase base font size from sm to md

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-20 11:29:47 -05:00
semantic-release-bot
cb20019c16 chore(release): 4.43.2 [skip ci]
## [4.43.2](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.43.1...v4.43.2) (2025-10-20)

### Code Refactoring

* **levels:** remove Time and Pass sections from kyu details ([d90b5d5](d90b5d5532))
2025-10-20 16:29:27 +00:00
Thomas Hallock
d90b5d5532 refactor(levels): remove Time and Pass sections from kyu details
Remove the Time and Pass requirement cards since we don't have actual tests
implemented. Now only showing Add/Sub, Multiply, and Divide requirements.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-20 11:28:19 -05:00
semantic-release-bot
7028db0263 chore(release): 4.43.1 [skip ci]
## [4.43.1](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.43.0...v4.43.1) (2025-10-20)

### Bug Fixes

* **levels:** use two-column grid for kyu details to prevent clipping ([fa3b73c](fa3b73c691))
2025-10-20 16:28:08 +00:00
Thomas Hallock
fa3b73c691 fix(levels): use two-column grid for kyu details to prevent clipping
Change from single-column flex to two-column grid layout to avoid vertical
overflow. Increased max width to 480px to accommodate the wider layout.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-20 11:27:01 -05:00
semantic-release-bot
fd4d25c2d1 chore(release): 4.43.0 [skip ci]
## [4.43.0](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.42.1...v4.43.0) (2025-10-20)

### Features

* **levels:** add structured kyu exam details with card UI ([6501b07](6501b073b1))
2025-10-20 16:26:37 +00:00
Thomas Hallock
6501b073b1 feat(levels): add structured kyu exam details with card UI
Parse kyu level requirements into structured sections with icons and color-coded
labels. Display in clean card layout with hover effects. Maintain consistent
font sizing across all levels.

Features:
- Parse Japanese exam data into structured sections (Add/Sub, Multiply, Divide, Time, Pass)
- Icon-based visual hierarchy (, ✖️, , ⏱️, )
- Color-coded labels matching level colors
- Card UI with hover effects
- Consistent sizing for better readability

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-20 11:25:34 -05:00
semantic-release-bot
9b4d9c21df chore(release): 4.42.1 [skip ci]
## [4.42.1](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.42.0...v4.42.1) (2025-10-20)

### Code Refactoring

* **levels:** store kyu data verbatim, add formatting layer ([53d23f1](53d23f19bc))
2025-10-20 16:18:49 +00:00
Thomas Hallock
53d23f19bc refactor(levels): store kyu data verbatim, add formatting layer
Store level details exactly as provided from shuzan.jp (with Japanese
characters), then translate and format creatively in the display layer.

Changes:
- Restore verbatim data with 口, 字, 実+法, 法+商, 題
- Add formatKyuDetails() helper to translate on display
- Translate: 口→rows, 字→chars, 実+法→total, 法+商→total, 題→sets
- Use operator symbols: + / −, ×, ÷
- Maintain separation between data source and presentation

This approach keeps the original data intact while allowing creative
freedom in how we display it.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-20 11:17:43 -05:00
semantic-release-bot
6c14012b97 chore(release): 4.42.0 [skip ci]
## [4.42.0](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.41.0...v4.42.0) (2025-10-20)

### Features

* **levels:** add kyu level details display with English translations ([c650ffa](c650ffa193))
2025-10-20 16:16:03 +00:00
Thomas Hallock
c650ffa193 feat(levels): add kyu level details display with English translations
Add comprehensive exam requirements for each Kyu level displayed on
the left side of the abacus pane:

- Create kyuLevelDetails data file with English translations
- Display level details only for Kyu levels (hidden for Dan)
- Implement responsive font sizing based on abacus size
- Center abacus for Dan levels, right-align for Kyu
- Format details in clean, readable layout with bullet points

Details include:
- Addition/Subtraction requirements (rows, characters)
- Multiplication/Division requirements (digit counts, problem counts)
- Exam time limits and passing scores

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-20 11:14:50 -05:00
semantic-release-bot
28834e8a3e chore(release): 4.41.0 [skip ci]
## [4.41.0](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.40.1...v4.41.0) (2025-10-20)

### Features

* **levels:** right-align abacus display ([8681b17](8681b17340))
2025-10-20 16:10:19 +00:00
Thomas Hallock
8681b17340 feat(levels): right-align abacus display
Change abacus display container from center to right-aligned layout for
better visual balance on the levels page.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-20 11:09:14 -05:00
semantic-release-bot
d52cc608eb chore(release): 4.40.1 [skip ci]
## [4.40.1](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.40.0...v4.40.1) (2025-10-20)

### Bug Fixes

* **levels:** increase animation speed to 10ms for 10th Dan ([6f89d9e](6f89d9e274))
2025-10-20 16:00:14 +00:00
Thomas Hallock
6f89d9e274 fix(levels): increase animation speed to 10ms for 10th Dan
Update animation speed progression to be more dramatic, reaching 10ms
(0.01 seconds) at 10th Dan instead of 50ms. Speed progression now runs
from 1st Dan to 10th Dan (not from Pre-1st Dan).

Speed progression:
- Kyu levels: 500ms (constant)
- Pre-1st Dan: 500ms
- 1st Dan: 500ms
- 10th Dan: 10ms
- Linear interpolation between 1st and 10th Dan

This creates an extremely fast blur effect at the highest mastery level,
better representing the extraordinary calculation speed expected at 10th Dan.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-20 10:58:59 -05:00
semantic-release-bot
a8cc2bc0f0 chore(release): 4.40.0 [skip ci]
## [4.40.0](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.39.1...v4.40.0) (2025-10-20)

### Features

* **levels:** progressive animation speed for Dan levels ([9dff3e7](9dff3e7b7b))
2025-10-20 15:58:36 +00:00
Thomas Hallock
9dff3e7b7b feat(levels): progressive animation speed for Dan levels
Increase abacus animation speed as Dan levels advance, creating a visual
representation of increasing mastery and speed. Animation interval changes
from 500ms at Kyu/Pre-1st Dan to 50ms at 10th Dan.

Changes:
- Kyu levels (10th-1st): constant 500ms animation interval
- Dan levels: linear interpolation from 500ms to 50ms
- Pre-1st Dan (index 10): 500ms
- 10th Dan (index 20): 50ms
- Effect now depends on currentIndex to update interval dynamically
- Add getAnimationInterval() helper for calculating speed

This creates a dramatic visual effect where the abacus becomes a blur of
movement at the highest mastery levels.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-20 10:57:31 -05:00
semantic-release-bot
92fedb698d chore(release): 4.39.1 [skip ci]
## [4.39.1](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.39.0...v4.39.1) (2025-10-20)

### Bug Fixes

* **levels:** improve slider tick spacing to use full width ([1e90d6c](1e90d6c620))
2025-10-20 15:54:04 +00:00
Thomas Hallock
1e90d6c620 fix(levels): improve slider tick spacing to use full width
Remove horizontal padding (90px) from tick marks container to allow emoji
tick marks to spread across the full width of the slider. This provides
better visual distribution of level indicators.

Changes:
- Change px from '90px' to '0' on tick marks container
- Tick marks now use space-between across full slider width

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-20 10:52:56 -05:00
semantic-release-bot
5fcb7925eb chore(release): 4.39.0 [skip ci]
## [4.39.0](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.38.1...v4.39.0) (2025-10-20)

### Features

* **levels:** add auto-advance slider with hover pause ([41eaed2](41eaed24fc))
2025-10-20 15:49:37 +00:00
Thomas Hallock
41eaed24fc feat(levels): add auto-advance slider with hover pause
Add automatic slider progression that advances one level every 3 seconds,
cycling back to the start when reaching the end. The auto-advance pauses
when the user's mouse is over the pane containing the slider and abacus,
allowing for manual exploration without interruption.

Changes:
- Add isPaneHovered state to track mouse position
- Add auto-advance effect with 3-second interval
- Pause auto-advance when isPaneHovered is true
- Add onMouseEnter/onMouseLeave handlers to pane container
- Cycle position back to 0 when reaching the last level

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-20 10:48:22 -05:00
semantic-release-bot
de5f36481b chore(release): 4.38.1 [skip ci]
## [4.38.1](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.38.0...v4.38.1) (2025-10-20)

### Bug Fixes

* **levels:** adjust slider text positioning to prevent emoji overlap ([e5ffe39](e5ffe3927e))
2025-10-20 15:46:10 +00:00
Thomas Hallock
e5ffe3927e fix(levels): adjust slider text positioning to prevent emoji overlap
Move level text further down (bottom: -80px) to prevent it from overlapping
with emoji characters. Previous positioning (-60px) was too close for Dan
level emojis, causing the text to cover the characters' chins.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-20 10:45:09 -05:00
semantic-release-bot
7c47fcdc54 chore(release): 4.38.0 [skip ci]
## [4.38.0](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.37.0...v4.38.0) (2025-10-20)

### Features

* **levels:** add animated calculation effect to abacus display ([4f4c735](4f4c73577a))
2025-10-20 15:42:45 +00:00
Thomas Hallock
4f4c73577a feat(levels): add animated calculation effect to abacus display
Add visual animation to the levels page abacus that simulates ongoing
calculations by randomly changing 1-3 adjacent columns every 0.5 seconds.
This creates a more dynamic, engaging visual that shows the abacus "working"
as users explore different skill levels.

Changes:
- Add animatedDigits state to track current digit values
- Initialize random digits when level changes
- Add interval effect to update 1-3 adjacent columns every 500ms
- Use animated digits instead of static pattern for AbacusReact display
- Adjacent column grouping simulates realistic calculation movements

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-20 10:40:42 -05:00
semantic-release-bot
1e6459f9c1 chore(release): 4.37.0 [skip ci]
## [4.37.0](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.36.0...v4.37.0) (2025-10-20)

### Features

* **levels:** add hover tracking to slider for real-time level preview ([477a0b3](477a0b367e))
2025-10-20 15:35:35 +00:00
Thomas Hallock
477a0b367e feat(levels): add hover tracking to slider for real-time level preview
Added mouse hover functionality to the slider so it responds as you move
your mouse across the track:
- Calculates hover position based on mouse X coordinate
- Updates slider value in real-time as you hover
- Tracks hover state with isHovering flag
- Slider thumb follows your mouse smoothly with CSS transitions

Now you can explore levels by simply hovering across the slider track,
in addition to clicking emoji tick marks or dragging the thumb.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-20 10:34:25 -05:00
semantic-release-bot
8751649233 chore(release): 4.36.0 [skip ci]
## [4.36.0](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.35.1...v4.36.0) (2025-10-20)

### Features

* **levels:** make emoji tick marks clickable and remove redundant UI ([07c783a](07c783a794))

### Bug Fixes

* **levels:** add smooth CSS transitions for slider thumb movement ([ca8cef1](ca8cef1c36))
2025-10-20 15:32:54 +00:00
Thomas Hallock
07c783a794 feat(levels): make emoji tick marks clickable and remove redundant UI
- Added click handlers to emoji tick marks so you can click any emoji to jump to that level
- Added hover effects (opacity 0.6) and pointer cursor to tick marks
- Enabled pointer events on tick marks (parent has pointerEvents: 'none')
- Removed redundant "Drag or click the beads" instruction text
- Removed duplicated level info text below slider (info now only shows on slider thumb)

This simplifies the UI and makes the slider more interactive - you can now
click, drag, or hover over any emoji to explore different levels.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-20 10:31:40 -05:00
Thomas Hallock
ca8cef1c36 fix(levels): add smooth CSS transitions for slider thumb movement
Fixed slider transitions to be smooth but responsive:
- Balanced animation speeds (scale: tension 350/friction 45, emoji: 120ms)
- Added 0.3s CSS transition specifically for thumb position (left property)
- Removed complex React Spring state management that wasn't triggering re-renders
- Simplified to basic state management with CSS handling the smoothness

This gives a nice gliding effect when clicking between levels while
keeping the interface snappy and responsive.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-20 10:31:40 -05:00
semantic-release-bot
0d47664f9f chore(release): 4.35.1 [skip ci]
## [4.35.1](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.35.0...v4.35.1) (2025-10-20)

### Features

* **abacus-react:** export StandaloneBead component wired to AbacusDisplayContext ([0146ce1](0146ce1e67))

### Performance Improvements

* **levels:** speed up slider animations for more responsive feel ([1e5467f](1e5467fad4))
2025-10-20 15:25:06 +00:00
Thomas Hallock
1e5467fad4 perf(levels): speed up slider animations for more responsive feel
Reduced animation timing across the board to make the slider feel snappier:
- Scale factor animation: increased tension (280→400), reduced friction (60→40)
- Emoji cross-fade: reduced duration from 150ms to 80ms
- Thumb hover: reduced transition from 0.2s to 0.1s with ease-out

This addresses user feedback that the slider felt sluggish.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-20 10:23:46 -05:00
semantic-release-bot
44f8b27fa1 chore(abacus-react): release v2.1.0 [skip ci]
# [2.1.0](https://github.com/antialias/soroban-abacus-flashcards/compare/abacus-react-v2.0.0...abacus-react-v2.1.0) (2025-10-20)

### Bug Fixes

* **levels:** add fixed height to entire level display pane ([200b26c](200b26c2cd))
* **levels:** increase container height to prevent abacus clipping ([cd5c15a](cd5c15aeb2))
* **levels:** only animate abacus, not container with background/border ([c80477d](c80477d248))
* **levels:** reduce Dan scale and container height to prevent clipping ([563136f](563136fb79))
* **levels:** reduce max scale factor to allow more compact container ([ead9ee9](ead9ee9589))
* **levels:** reduce scale factor variation to minimize margin differences ([abb647c](abb647ce40))
* **levels:** revert delayed column change, keep overflow hidden ([22f00f5](22f00f59f5))
* **levels:** stabilize slider position and prevent abacus clipping ([09004dc](09004dc2c0))

### Features

* **abacus-react:** export StandaloneBead component wired to AbacusDisplayContext ([0146ce1](0146ce1e67))
* **levels:** add hover interaction and smooth React Spring transitions ([fd2b633](fd2b6338a8))
* **levels:** redesign slider with abacus-themed beads ([f3dce84](f3dce84532))
* **levels:** replace slider thumb with diamond-shaped abacus beads ([0fbde53](0fbde53039))
2025-10-20 14:31:30 +00:00
Thomas Hallock
0146ce1e67 feat(abacus-react): export StandaloneBead component wired to AbacusDisplayContext
feat(levels): use StandaloneBead for slider thumb and decorative tick beads

fix(levels): make slider background transparent to prevent abacus clipping

Created StandaloneBead component that integrates with the abacus style context,
replacing hardcoded SVG diamonds with proper context-aware bead rendering.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-20 09:28:41 -05:00
semantic-release-bot
acfb0dac0a chore(release): 4.35.0 [skip ci]
## [4.35.0](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.34.0...v4.35.0) (2025-10-20)

### Features

* **levels:** replace slider thumb with diamond-shaped abacus beads ([0fbde53](0fbde53039))
2025-10-20 14:21:41 +00:00
Thomas Hallock
0fbde53039 feat(levels): replace slider thumb with diamond-shaped abacus beads
Replaced circular slider elements with proper diamond-shaped beads that
match the authentic abacus bead style:

**Slider Thumb (Drag Handle)**:
- Diamond SVG polygon matching AbacusReact bead geometry
- 28x28px size for easy grabbing
- Two-layer design: outer diamond + inner highlight for depth
- Black stroke (0.8px) matching abacus bead styling
- Color-coded: violet for Dan levels, green for Kyu levels
- Maintains grab/grabbing cursor states

**Decorative Tick Beads**:
- All 40 level markers now use diamond shapes instead of circles
- Sized 8px (inactive) to 14px (active)
- Same color scheme and styling as main beads
- Proper stroke colors matching active/inactive states
- Drop-shadow filter for active bead glow effect

This creates a cohesive visual language connecting the interactive
slider to the abacus display, making the page feel more integrated
and true to the abacus aesthetic.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-20 09:20:33 -05:00
semantic-release-bot
90bbe6fbb7 chore(release): 4.34.0 [skip ci]
## [4.34.0](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.33.8...v4.34.0) (2025-10-20)

### Features

* **levels:** redesign slider with abacus-themed beads ([f3dce84](f3dce84532))

### Code Refactoring

* **levels:** convert to Radix UI Slider with abacus theme ([a03e73c](a03e73c849))
2025-10-20 14:16:55 +00:00
Thomas Hallock
a03e73c849 refactor(levels): convert to Radix UI Slider with abacus theme
Replaced custom HTML input slider with Radix UI Slider for better
accessibility and consistency with the rest of the application.

Key changes:
- Integrated @radix-ui/react-slider for proper a11y support
- Maintained abacus-themed visual design with bead ticks
- Larger interactive area (h: '12' / 48px) for easier interaction
- Color-coded beads (green for Kyu, violet for Dan)
- Interactive thumb styled as a prominent bead with grab cursor
- Decorative beads for all 40 levels with pointer-events: none
- Smooth transitions and hover effects on thumb
- Removed custom hover handler in favor of Radix's built-in interaction

This provides a more robust and accessible slider while maintaining
the unique abacus aesthetic.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-20 09:15:46 -05:00
Thomas Hallock
f3dce84532 feat(levels): redesign slider with abacus-themed beads
Replaced standard slider with custom abacus-themed design:
- Added bead-like circular ticks for all 40 levels
- Color-coded beads (green for Kyu, violet for Dan)
- Active bead glows and scales up (16px) with shadow effect
- Inactive beads are semi-transparent (12px)
- Increased hit target with vertical padding (py: '6')
- Added horizontal "reckoning bar" as the track
- Smooth transitions on bead state changes

This creates a more forgiving hover/drag experience and prevents
confusion from minor cursor deviations while interacting.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-20 09:13:26 -05:00
semantic-release-bot
3b6284ae18 chore(release): 4.33.8 [skip ci]
## [4.33.8](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.33.7...v4.33.8) (2025-10-20)

### Bug Fixes

* **levels:** reduce Dan scale and container height to prevent clipping ([563136f](563136fb79))
* **levels:** reduce max scale factor to allow more compact container ([ead9ee9](ead9ee9589))
2025-10-20 14:13:05 +00:00
Thomas Hallock
563136fb79 fix(levels): reduce Dan scale and container height to prevent clipping
Changed minimum scale factor from 1.5 to 1.2 for 30-column Dan abacuses
to prevent leftmost/rightmost columns from being clipped. Also reduced
container height from 900px to 700px to provide better visual balance
without excessive whitespace around the largest Kyu abacus.

Scale factor range is now 1.2 to 2.0, creating a 1.67x size difference.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-20 09:11:59 -05:00
Thomas Hallock
ead9ee9589 fix(levels): reduce max scale factor to allow more compact container
Changed maximum scale factor from 3.0 to 2.0 for small Kyu abacuses.
This allows for a more compact fixed-height container while still
providing appropriate visual scaling across all levels.

Scale factor range is now 1.5 to 2.0, creating a 1.33x size difference
instead of the previous 2.0x difference.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-20 09:10:20 -05:00
semantic-release-bot
a12ae969be chore(release): 4.33.7 [skip ci]
## [4.33.7](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.33.6...v4.33.7) (2025-10-20)

### Bug Fixes

* **levels:** reduce scale factor variation to minimize margin differences ([abb647c](abb647ce40))
2025-10-20 14:08:00 +00:00
Thomas Hallock
abb647ce40 fix(levels): reduce scale factor variation to minimize margin differences
Changed scale factor formula to use a constrained range (1.5 to 3.0)
instead of the previous unbounded formula that allowed much wider variation.

Previous formula: Math.min(2.5, 20 / digits)
- 2 columns (10 Kyu): 2.5
- 30 columns (Dan): 0.67
- Ratio: ~3.7x difference

New formula: Math.max(1.5, Math.min(3.0, 20 / digits))
- 2 columns (10 Kyu): 3.0
- 30 columns (Dan): 1.5
- Ratio: 2.0x difference

This reduces the excessive margin around Dan level abacuses while still
providing appropriate scaling for different column counts.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-20 09:06:46 -05:00
semantic-release-bot
d6c28f7ede chore(release): 4.33.6 [skip ci]
## [4.33.6](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.33.5...v4.33.6) (2025-10-20)

### Bug Fixes

* **levels:** increase container height to prevent abacus clipping ([cd5c15a](cd5c15aeb2))
2025-10-20 14:04:50 +00:00
Thomas Hallock
cd5c15aeb2 fix(levels): increase container height to prevent abacus clipping
Increased the main level display container height from 700px to 900px
to provide more vertical space for the abacus display.

This prevents the top and bottom of the abacus from being clipped,
especially for levels with larger column counts.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-20 09:03:41 -05:00
semantic-release-bot
ccaad3abc8 chore(release): 4.33.5 [skip ci]
## [4.33.5](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.33.4...v4.33.5) (2025-10-20)

### Bug Fixes

* **levels:** revert delayed column change, keep overflow hidden ([22f00f5](22f00f59f5))
2025-10-20 14:03:23 +00:00
Thomas Hallock
22f00f59f5 fix(levels): revert delayed column change, keep overflow hidden
Reverted the delayed column change approach (using displayedIndex state
and onRest callback) which was causing bugs. Went back to the simpler
direct approach where currentIndex immediately drives all changes.

Changes:
- Removed displayedIndex state and displayedLevel variable
- Removed onRest callback from React Spring configuration
- All UI elements now use currentLevel directly
- Kept overflow: 'hidden' on abacus container

The simpler approach provides better UX with immediate feedback while
React Spring still provides smooth scale transitions.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-20 09:02:19 -05:00
semantic-release-bot
a85815fdf9 chore(release): 4.33.4 [skip ci]
## [4.33.4](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.33.3...v4.33.4) (2025-10-20)

### Bug Fixes

* **levels:** stabilize slider position and prevent abacus clipping ([09004dc](09004dc2c0))
2025-10-20 13:58:06 +00:00
Thomas Hallock
09004dc2c0 fix(levels): stabilize slider position and prevent abacus clipping
Fixed three layout issues with the levels page slider interaction:

1. Fixed height for level info section (160px with flexbox centering)
   - Prevents slider from shifting vertically when switching between Kyu
     and Dan levels (Dan levels have extra text for name + minScore)
   - Keeps slider position stable during hover interaction

2. Changed abacus container overflow from 'auto' to 'visible'
   - Prevents abacus from being clipped at container boundaries
   - Allows full abacus display without scrollbars

3. Reduced spacing between sections for better layout balance

These changes ensure the slider stays perfectly under the mouse cursor
during hover interaction while the abacus smoothly animates.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-20 08:56:57 -05:00
semantic-release-bot
22c8a57a16 chore(release): 4.33.3 [skip ci]
## [4.33.3](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.33.2...v4.33.3) (2025-10-20)

### Code Refactoring

* **levels:** move slider into level display pane above abacus ([2d8bb4a](2d8bb4ab88))
2025-10-20 13:51:02 +00:00
Thomas Hallock
2d8bb4ab88 refactor(levels): move slider into level display pane above abacus
Relocate the slider control from a separate container below the level
display to inside the level pane itself, positioned above the abacus.

Changes:
- Move slider markup into main level display pane
- Position between level info and abacus display
- Remove separate slider container that was below
- Adjust spacing for better visual hierarchy

Improves UX by keeping the control close to the content it affects.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-20 08:49:59 -05:00
semantic-release-bot
8e345cfb4c chore(release): 4.33.2 [skip ci]
## [4.33.2](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.33.1...v4.33.2) (2025-10-20)

### Bug Fixes

* **levels:** add fixed height to entire level display pane ([200b26c](200b26c2cd))
2025-10-20 13:46:27 +00:00
Thomas Hallock
200b26c2cd fix(levels): add fixed height to entire level display pane
Fix slider hover interaction by making the entire level display pane
(including level info, abacus, and digit count) a fixed height. This
prevents the slider from moving when hovering over it.

Changes:
- Add fixed height of 700px on desktop (auto on mobile) to level pane
- Convert container to flexbox with column direction
- Make abacus display area flex: 1 to fill remaining space
- Slider now stays perfectly still under mouse during hover

This makes the hover interaction much more usable - you can now smoothly
move your mouse across the slider without it jumping away.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-20 08:45:17 -05:00
semantic-release-bot
66e38af457 chore(release): 4.33.1 [skip ci]
## [4.33.1](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.33.0...v4.33.1) (2025-10-20)

### Bug Fixes

* **levels:** only animate abacus, not container with background/border ([c80477d](c80477d248))
2025-10-20 13:44:30 +00:00
Thomas Hallock
c80477d248 fix(levels): only animate abacus, not container with background/border
Fix React Spring animation to only affect the abacus itself, not the
container with background and border. Also keep container height fixed.

Changes:
- Move animation from container div to inner wrapper around AbacusReact
- Add minHeight to container to prevent height changes
- Add alignItems: 'center' to vertically center the abacus
- Container background/border now stays fixed while abacus animates

This provides a cleaner, more polished animation where only the abacus
scales smoothly while the container remains stable.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-20 08:43:24 -05:00
semantic-release-bot
9a688c1574 chore(release): 4.33.0 [skip ci]
## [4.33.0](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.32.1...v4.33.0) (2025-10-20)

### Features

* **abacus-react:** add BigInt support for 30-digit Dan level abacuses ([0ab4cc2](0ab4cc2880))
* **levels:** add hover interaction and smooth React Spring transitions ([fd2b633](fd2b6338a8))
2025-10-20 13:43:10 +00:00
Thomas Hallock
fd2b6338a8 feat(levels): add hover interaction and smooth React Spring transitions
Add hover/touch support and smooth animations to the levels page slider
for an enhanced interactive experience.

Changes:
- Add hover/touch move handlers to slider for instant level preview
- Integrate React Spring for smooth scale transitions between levels
- Animate abacus container with smooth scale transformations
- Support both mouse and touch events for mobile compatibility
- Update UI text to mention hover, drag, and touch interactions

The slider now responds immediately to mouse/touch position, making it
easy to explore different levels by simply hovering over the slider.
React Spring provides smooth transitions when switching between levels
with different abacus sizes.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-20 08:41:58 -05:00
semantic-release-bot
2cfde18414 chore(abacus-react): release v2.0.0 [skip ci]
# [2.0.0](https://github.com/antialias/soroban-abacus-flashcards/compare/abacus-react-v1.8.0...abacus-react-v2.0.0) (2025-10-20)

### Bug Fixes

* add dark color for abacus numerals ([73ff32c](73ff32c243)), closes [#1f2937](https://github.com/antialias/soroban-abacus-flashcards/issues/1f2937)
* add POST handler for join requests API endpoint ([d3e5cdf](d3e5cdfc54))
* add Typst to Docker image for flashcard generation ([d9a7694](d9a7694031))
* allow join with pending invitation for restricted rooms ([85b2cf9](85b2cf9816))
* allow password retry when joining via share link ([e469363](e469363699))
* **api:** add 'math-sprint' to settings endpoint validation ([d790e5e](d790e5e278)), closes [#1](https://github.com/antialias/soroban-abacus-flashcards/issues/1)
* **api:** include members and memberPlayers in room creation response ([8320d9e](8320d9e730))
* **arcade-rooms:** navigate to invite link after room creation ([1922b21](1922b2122b))
* **arcade:** add defensive checks and update test fixtures ([a93d981](a93d981d1a))
* **arcade:** add host-only game selection with clear messaging ([22df1b0](22df1b0b66))
* **arcade:** add host-only game selection with clear messaging ([c0680ca](c0680cad0f))
* **arcade:** add Number Guesser to game config helpers ([7d1a351](7d1a351ed6))
* **arcade:** allow room creator to rejoin restricted/approval rooms ([654ba19](654ba19ccc))
* **arcade:** delete old session when room game changes ([98a3a25](98a3a2573d))
* **arcade:** implement settings persistence for matching game ([08fe432](08fe4326a6))
* **arcade:** only notify room creator of join requests ([bc571e3](bc571e3d0d))
* **arcade:** preserve game settings when returning to game selection ([0ee7739](0ee7739091))
* **arcade:** preserve gameConfig when switching games ([2273c71](2273c71a87))
* **arcade:** prevent empty update in settings API when only gameConfig changes ([ffb626f](ffb626f403))
* **arcade:** prevent gameConfig from being overwritten when switching games ([a89d3a9](a89d3a9701))
* **arcade:** prevent server-side loading of React components ([784793b](784793ba24))
* **arcade:** read nested gameConfig correctly when creating sessions ([94ef392](94ef39234d))
* **arcade:** remove broken query param from game URLs ([87631af](87631af678))
* **arcade:** remove legacy master-organizer placeholder ([76d207e](76d207e2e5))
* **arcade:** resolve TypeScript errors in game config helpers ([04c9944](04c9944f2e))
* **build:** resolve Docker build failures preventing deployment ([7801dbb](7801dbb25f))
* **card-sorting:** center AbacusReact SVGs in card tiles ([26edec1](26edec1bbf))
* **card-sorting:** faithfully port UI/UX from Python original ([c92076f](c92076f232)), closes [#2c5f76](https://github.com/antialias/soroban-abacus-flashcards/issues/2c5f76) [#1976d2](https://github.com/antialias/soroban-abacus-flashcards/issues/1976d2)
* **card-sorting:** increase card tile sizes to contain abacuses ([d2a3b7a](d2a3b7ae2e))
* **card-sorting:** increase SVG size to fill card containers ([cf9d893](cf9d893f3f))
* **card-sorting:** match game selector background to other games ([db62519](db62519f9b)), closes [#ccfbf1](https://github.com/antialias/soroban-abacus-flashcards/issues/ccfbf1) [#99f6e4](https://github.com/antialias/soroban-abacus-flashcards/issues/99f6e4)
* **card-sorting:** match Python card layout with flex wrap ([9679d68](9679d68154))
* **card-sorting:** position slots flow horizontally with wrap ([e14ffe4](e14ffe44d6))
* **card-sorting:** use blue gradient matching other game cards ([bdb84f5](bdb84f5d90))
* clear hover state in CLEAR_MISMATCH for clean turn transitions ([43f7c92](43f7c92f6d))
* clear hover state on turn changes and game transitions ([6fd425c](6fd425ce85))
* **complement-race:** add missing AI commentary cooldown updates ([357aa30](357aa30618))
* **complement-race:** add missing useEffect import ([3054130](30541304dd))
* **complement-race:** add missing useRef import ([d43829a](d43829ad48))
* **complement-race:** add pressure decay system and improve logging ([66992e8](66992e8770))
* **complement-race:** balance AI speeds to match original implementation ([054f0c0](054f0c0d23))
* **complement-race:** clear input state on question transitions ([5872030](587203056a))
* **complement-race:** correct passenger boarding to use multiplayer fields ([7ed1b94](7ed1b94b8f))
* **complement-race:** counter-flip AI speech bubbles to make text readable ([07d5607](07d5607218))
* **complement-race:** flip AI racers to face right in practice mode ([ebfff1a](ebfff1a62f))
* **complement-race:** flip player avatar to face right in practice mode ([fa6b3b6](fa6b3b69d5))
* **complement-race:** implement client-side momentum with continuous decay for smooth train movement ([ea19ff9](ea19ff918b))
* **complement-race:** improve AI speech bubble positioning ([6e436db](6e436db5e7))
* **complement-race:** reduce initial momentum from 50 to 10 to prevent train sailing past first station ([5f146b0](5f146b0daf))
* **complement-race:** remove dual game loop conflict preventing route progression ([84d42e2](84d42e22ac))
* **complement-race:** resolve TypeScript errors in state adapter ([59abcca](59abcca4c4))
* **complement-race:** restore smooth train movement with client-side game loop ([46a80cb](46a80cbcc8))
* **complement-race:** show new passengers when route changes ([ec1c8ed](ec1c8ed263))
* **complement-race:** track physical car indices to prevent boarding issues ([53bbae8](53bbae84af))
* **complement-race:** track previous position to detect route threshold crossing ([a6c20aa](a6c20aab3b))
* **complement-race:** train now moves in sprint mode ([54b46e7](54b46e771e))
* **complement-race:** update passenger display when state changes ([5116364](511636400c))
* **complement-race:** use active local players pattern from navbar ([71cdc34](71cdc342c9))
* **complement-race:** use local player emoji instead of first active player ([76eb051](76eb0517c2))
* correct AbacusReact API usage and add structural styling ([247377f](247377fca3)), closes [#fbbf24](https://github.com/antialias/soroban-abacus-flashcards/issues/fbbf24) [#a78](https://github.com/antialias/soroban-abacus-flashcards/issues/a78)
* create arcade sessions on room join to enable config changes ([c29501f](c29501f666))
* **db:** add 'math-sprint' to database schema enums ([7b112a9](7b112a98ba)), closes [#1](https://github.com/antialias/soroban-abacus-flashcards/issues/1)
* **deployment:** pass git info to Docker build for deployment info modal ([4b04e43](4b04e43ff8))
* **docker:** add packages/templates for Typst flashcard generation ([1417722](1417722438))
* **docker:** add qpdf for PDF linearization and validation ([c92ff39](c92ff3971c))
* **docker:** bypass PEP 668 externally-managed-environment error ([bb59c61](bb59c61638))
* **docker:** copy core package with Python scripts to production image ([33e9ad2](33e9ad2f79))
* **docker:** include Panda CSS styled-system in production image ([57fabff](57fabffe60))
* **docker:** install py3-pip for Python dependency installation ([0f55909](0f55909533))
* **docker:** install Python dependencies for flashcard generation ([c9b7e92](c9b7e92f39))
* **docker:** remove reference to deleted @soroban/client package ([2953ef8](2953ef8917))
* exclude dist from TypeScript compilation and add missing type import ([b7f1d5a](b7f1d5a569))
* hide hover avatar for current user's own player ([dba42b5](dba42b5925))
* **homepage:** adjust mini abacus container height ([c4066d6](c4066d6879))
* **homepage:** correct positioning of progression arrows in Your Journey section ([3fff9ef](3fff9ef140))
* **homepage:** fix MiniAbacus runtime error and improve sizing ([1fa0df8](1fa0df85f7))
* **homepage:** improve text contrast in Your Journey section ([24d1200](24d120004d))
* **homepage:** use correct AbacusReact API and fix clipping/styling issues ([1432afd](1432afd6e6))
* **homepage:** use direct conditionals for mini abacus padding ([38ef16a](38ef16a8f9))
* **homepage:** use explicit RGBA colors for Your Journey text ([9c51cc9](9c51cc94ee))
* **homepage:** use inline styles for journey level colors ([5d85e89](5d85e898d6)), closes [#4ade80](https://github.com/antialias/soroban-abacus-flashcards/issues/4ade80) [#60a5](https://github.com/antialias/soroban-abacus-flashcards/issues/60a5) [#a78](https://github.com/antialias/soroban-abacus-flashcards/issues/a78) [#fbbf24](https://github.com/antialias/soroban-abacus-flashcards/issues/fbbf24)
* **homepage:** use inline styles for Your Journey text contrast ([8e51390](8e51390018)), closes [#e5e7](https://github.com/antialias/soroban-abacus-flashcards/issues/e5e7) [#e5e7](https://github.com/antialias/soroban-abacus-flashcards/issues/e5e7) [#9ca3](https://github.com/antialias/soroban-abacus-flashcards/issues/9ca3) [#d1d5](https://github.com/antialias/soroban-abacus-flashcards/issues/d1d5)
* **home:** use Panda CSS token() for dynamic colors and center arrows properly ([d52ba63](d52ba6373a))
* improve authorization error handling and add missing decline invitation endpoint ([97669ad](97669ad084))
* improve join request approval error handling with actionable messages ([57bf846](57bf8460c8))
* improve kicked modal message for retired room ejections ([f865ce1](f865ce16ec))
* join user socket channel to receive approval notifications ([7d08fdd](7d08fdd906))
* **levels:** use correct AbacusReact API with direct props ([892b377](892b377eb3))
* **levels:** use correct dark mode styling from homepage + docs update ([c38767f](c38767f4d3))
* **matching:** add settings persistence to matching game ([00dcb87](00dcb872b7))
* **matching:** apply turn indicators to arcade version too ([e6f96a8](e6f96a8b99))
* **matching:** make MemoryGrid generic to support different card types ([dcda826](dcda826b9a))
* **matching:** only apply turn indicator when game is active ([cb4c061](cb4c061d11))
* **matching:** replace mismatch banner with card shake animation ([804096f](804096fd8a))
* **matching:** use UUID instead of numeric index for scores ([5036cb0](5036cb00b6))
* **math-sprint:** remove unused import and autoFocus attribute ([51593eb](51593eb44f))
* **memory-quiz:** fix playMode persistence by updating validator ([de0efd5](de0efd5932))
* **memory-quiz:** persist playMode setting across game switches ([487ca7f](487ca7fba6))
* **memory-quiz:** prevent duplicate card processing from optimistic updates ([51676fc](51676fc15f))
* **memory-quiz:** prevent input lag during rapid typing in room mode ([b45139b](b45139b588))
* **memory-quiz:** scope game settings by game name for proper persistence ([3dfe54f](3dfe54f1cb))
* **memory-quiz:** synchronize card display across all players in multiplayer ([472f201](472f201088))
* **migrations:** add migration 0009 for display_password column ([040d749](040d7495a0))
* **moderation:** don't show pending invitation for users already in room ([fae5920](fae5920e2f))
* **moderation:** improve access mode settings UX ([dd9e657](dd9e657db8))
* move invitations into nav and filter out current/banned rooms ([cfaf82b](cfaf82b2cc))
* **nav:** add delay to hamburger menu hover to prevent premature closing ([95cd72e](95cd72e9bf))
* **nav:** add z-index to turn labels to prevent avatar overlap ([7c294da](7c294dafff))
* **nav:** close hamburger menu when nested dropdown closes and mouse not hovering ([7d65212](7d652126d0))
* **nav:** enable tooltips for local players during gameplay ([5499700](54997007b8))
* **nav:** improve readability of turn label text ([bbd1da0](bbd1da02b5))
* **nav:** improve text contrast in room info pane ([3e691cb](3e691cb06d))
* **nav:** navigate to /arcade/room (not /arcade/rooms/{id}) ([1c55f36](1c55f3630c))
* **nav:** navigate to room after creation from (+) menu ([21e6e33](21e6e33173))
* **nav:** prevent hamburger menu from closing when toggling Style dropdown ([a898fbc](a898fbc187))
* **nav:** prevent style dropdown from closing hamburger menu ([560a052](560a05266e))
* **nav:** prevent turn label text from being obscured ([c4b00dd](c4b00dd679))
* **nav:** properly prevent nested style dropdown from closing hamburger menu ([c5b6a82](c5b6a82ca4))
* **nav:** remove animation/enlargement from network player turn indicator ([53079ed](53079ede13))
* **nav:** remove blue gradient background from network players ([2881aff](2881affecc))
* **nav:** remove opacity reduction from local players ([5215af8](5215af801f))
* **nav:** remove play arrow badge from turn indicators ([80cfc10](80cfc10f78))
* **nav:** update types for registry games with nullable gameName ([a51e539](a51e539d02))
* **number-guesser:** add turn indicators, error feedback, and fix player ordering ([9f62623](9f62623684))
* pixel-perfect alignment across all nav elements ([fa78a2c](fa78a2c001))
* **player-config:** correct label positioning in player settings dialog ([554cc40](554cc4063b))
* populate session activePlayers from room members on join ([2d00939](2d00939f1b))
* prevent duplicate arcade sessions per room ([4cc3de5](4cc3de5f43))
* remove duplicate ModerationNotifications causing double toasts ([c6886a0](c6886a0e59))
* replace isLocked with accessMode and add bcryptjs ([a74b96b](a74b96bb6f))
* replace last remaining isLoading with isPending in CreateRoomModal ([85d13cc](85d13cc552))
* replace native alerts with inline confirmations in ModerationPanel ([ebe123e](ebe123ed7e))
* reset join request toast state when moderation event cleared ([6beb58a](6beb58a7b8))
* resolve Memory Quiz room-based multiplayer validation issues ([2ffeade](2ffeade437))
* resolve TypeScript errors in MemoryGrid and StandardGameLayout ([cabbc82](cabbc82195))
* **room-data:** update query cache when gameConfig changes ([7cea297](7cea297095))
* **rooms:** add real-time ownership transfer updates via WebSocket ([c00cfa3](c00cfa3de0))
* **room:** update GAME_TYPE_TO_NAME mapping for memory-quiz ([4afa171](4afa171af2))
* set color on abacus container div for numeral visibility ([cd47960](cd4796024e)), closes [#1f2937](https://github.com/antialias/soroban-abacus-flashcards/issues/1f2937)
* show initial value and improve numeral contrast ([1b57f6d](1b57f6ddec)), closes [#fbbf24](https://github.com/antialias/soroban-abacus-flashcards/issues/fbbf24)
* simplify abacus pane with light background ([30f48ab](30f48ab897))
* **socket-io:** update import path for socket-server module ([1a64dec](1a64decf5a))
* stack game title dropdown ABOVE room pane, not inside it ([7bc815f](7bc815fd7d))
* **toast:** scope animations to prevent affecting other UI elements ([245ed8a](245ed8a625))
* **tutorial:** correct column index calculation for variable column counts ([bf1ced4](bf1ced43f8))
* **tutorial:** filter bead highlights when using fewer columns ([4d906ec](4d906ec20e))
* **tutorial:** reduce tooltip z-index to scroll under nav bar ([47640f3](47640f3486))
* **tutorial:** resolve React hydration error in TutorialPlayer ([c883d9e](c883d9e4c1))
* **tutorial:** resolve TypeScript errors in TutorialPlayer ([88f57ce](88f57ce6df))
* **tutorial:** use correct customStyles API for dark mode frame styling ([fdc882c](fdc882cb04))
* update locked room terminology and allow existing members ([1ddf985](1ddf985938))
* use app-wide abacus config and remove instruction text ([0a50c73](0a50c733b0))
* use color instead of fill for numeral styling ([ea10c16](ea10c16811))
* use defaultValue for interactive abacus control ([06aca98](06aca986ac))
* use useCreateRoom hook instead of nonexistent createRoom from useRoomData ([f7d63b3](f7d63b30ac))

### Code Refactoring

* **db:** remove database schema coupling for game names ([e135d92](e135d92abb)), closes [#1](https://github.com/antialias/soroban-abacus-flashcards/issues/1)

### Features

* **abacus-react:** add BigInt support for 30-digit Dan level abacuses ([0ab4cc2](0ab4cc2880))
* add API routes for moderation and invitations ([79a8518](79a8518557))
* add backend library functions for room moderation ([84f3c4b](84f3c4bcfd))
* add common UI components ([cd3115a](cd3115aa6d))
* add database schema for room moderation and invitations ([97d1604](97d16041df))
* add drizzle migration for room_game_configs table ([3bae00b](3bae00b9a9))
* add fun automatic player naming system ([249257c](249257c6c7))
* add invitation system UI components ([fd3a2d1](fd3a2d1f76))
* add moderation panel with unban & invite feature ([a2d0169](a2d0169f80))
* add name generator button and abacus emoji ([07212e4](07212e4df0))
* add player count to stacked room info ([540f6b7](540f6b76d0))
* add prominent join request approval notifications for room moderators ([036da6d](036da6de66))
* add real-time socket updates for moderation events ([86ceba3](86ceba3df3))
* add room access modes and ownership transfer ([6ff21c4](6ff21c4f1d))
* add room creation and join flow UI ([7f95032](7f95032253))
* add socket listener and polling for approval notifications ([35b4a72](35b4a72c8b))
* add waiting state for approval requests in JoinRoomModal ([f9b0429](f9b0429a2e))
* adjust tier probabilities for more abacus flavor ([49219e3](49219e34cd))
* **arcade:** add Card Sorting Challenge game scaffolding ([df37260](df37260e26))
* **arcade:** add Change Game functionality for room hosts ([ee39241](ee39241e3c))
* **arcade:** add game selection screen with navigation to room page ([4124f1c](4124f1cc08))
* **arcade:** add Math Sprint game implementation ([e5be09e](e5be09ef5f))
* **arcade:** add modular game SDK and registry system ([de30bec](de30bec479))
* **arcade:** add Number Guesser demo game with plugin architecture ([0e3c058](0e3c058707))
* **arcade:** broadcast game selection changes to all room members ([b99e754](b99e754395))
* **arcade:** migrate matching pairs - phases 1-4 and 7 complete ([2a3af97](2a3af973f7))
* **arcade:** migrate memory-quiz to modular game system ([f48c37a](f48c37accc))
* **arcade:** register Math Sprint in game system ([0c05a7c](0c05a7c6bb)), closes [#2](https://github.com/antialias/soroban-abacus-flashcards/issues/2) [#3](https://github.com/antialias/soroban-abacus-flashcards/issues/3)
* **card-sorting:** add spectator mode UX enhancements ([4ab093a](4ab093a9d8))
* **card-sorting:** add UI components and fix AbacusReact props ([d249ec0](d249ec0e5f))
* **card-sorting:** implement Provider with arcade session integration ([7f6fea9](7f6fea91f6))
* **complement-race:** add infinite win condition for Steam Sprint mode ([d8fdfee](d8fdfeef74))
* **complement-race:** add mini app navigation bar ([ed0ef2d](ed0ef2d3b8))
* **complement-race:** enable adaptive AI difficulty in arcade ([55010d2](55010d2bcd))
* **complement-race:** implement state adapter for multiplayer support ([13882bd](13882bda32))
* **complement-race:** restore AI opponents in practice and survival modes ([325e07d](325e07de59))
* **homepage:** add animated mini abacus to "Read and set numbers" card ([e028e34](e028e342ad))
* **homepage:** add more visual embellishments to learning cards ([4ec1b95](4ec1b952f2))
* **homepage:** enhance "What You'll Learn" with visual cards ([d142342](d1423420e6))
* **home:** redesign home page to showcase complete platform ([ee6c4f2](ee6c4f2f4f))
* implement approval request flow for share links ([4a6b3ca](4a6b3cabe5))
* implement avatar-themed name generation with probabilistic mixing ([76a8472](76a8472f12))
* implement proper retired room behavior with member expulsion ([a2d5368](a2d53680f2))
* improve arcade nav player grouping and add room join code display ([8e9980d](8e9980dc82))
* improve room creation UX and add password support for share links ([dcbb507](dcbb5072d8))
* integrate moderation system into arcade pages ([087652f](087652f9e7))
* **levels:** add Dan levels ladder visualization ([c18012c](c18012cb50))
* **levels:** add dark mode styling and responsive scaling to abacus ([92e1e62](92e1e62132))
* **levels:** add informational footer section ([0b1bff7](0b1bff7eab))
* **levels:** add Kyu & Dan levels page with homepage link ([39b1e7d](39b1e7de16))
* **levels:** add kyu level data and cards ([6463a3b](6463a3b2f6))
* **levels:** create true horizontal slider with abacus visualizations ([6d734f1](6d734f1d51))
* **levels:** implement interactive slider for exploring kyu & dan ranks ([eb3b100](eb3b100056))
* **levels:** replace kyu grid with interactive slider and abacus visualizations ([10978e8](10978e890b))
* make home page abacus interactive with audio ([9a53d7e](9a53d7e5db))
* **matching:** use nav avatars as turn indicators ([7263828](7263828ed4))
* **math-sprint:** add game manifest ([1eefcc8](1eefcc89a5))
* **memory-quiz:** add multiplayer support with redesigned scoreboards ([1cf4469](1cf44696c2))
* **memory-quiz:** persist game settings per-game across sessions ([05a8e0a](05a8e0a842))
* **memory-quiz:** show player emojis on cards to indicate who found them ([05bd11a](05bd11a133))
* **moderation:** add inline feedback and persistent password display ([86e3d41](86e3d41996))
* **moderation:** improve password input with copy button ([2580e47](2580e474d0))
* **nav:** add prominent turn indicator arrow badge ([f574558](f574558dff))
* **nav:** add pulsing indicator for offline network players ([64fb30e](64fb30e7ec))
* **nav:** add turn indicators to network players ([623314b](623314bd38))
* **nav:** add turn label text under current player avatars ([52a66d5](52a66d5f68))
* **nav:** center game context with hamburger menu for utilities ([a35a7d5](a35a7d56df))
* **nav:** combine room info and network players in single pane ([d5473ab](d5473ab66a))
* **nav:** unify room dropdown with join code and game menu ([f7b83f8](f7b83f8c14))
* prevent invitations to retired rooms ([a7c3c1f](a7c3c1f4cd))
* redesign home page with component showcase ([29af265](29af265958))
* redesign homepage with educational vision and interactive demo ([2f09cb5](2f09cb5539))
* redesign room info as compact inline badge with click-to-copy ([6b3a440](6b3a440369))
* replace access mode dropdown with visual button grid ([e5d0672](e5d0672059))
* **tutorial:** add dark mode styling for coaching bar and abacus frame ([7e2f580](7e2f580877))
* **tutorial:** add dark theme and column control props ([d42f9b2](d42f9b2d9a))
* **tutorial:** add fill color support for dark mode column posts and reckoning bar ([2eb3ff3](2eb3ff3406))
* **tutorial:** add hideNavigation prop to TutorialPlayer ([79ea52a](79ea52af80))
* **tutorial:** add hideTooltip prop and improve dark mode coaching bar ([1ee25b3](1ee25b3dd2))
* **tutorial:** add silentErrors prop to suppress error messages ([8835e1c](8835e1c57a))

### Reverts

* **nav:** restore original room creation/join behavior ([710e93c](710e93c997))

### BREAKING CHANGES

* **db:** Database schemas now accept any string for game names
* Added DELETE /api/arcade/rooms/:roomId/invite endpoint for declining invitations

Authorization Error Handling:
- ModerationPanel: Parse and display API error messages (kick, ban, unban, invite, data loading)
- PendingInvitations: Parse and display API error messages (decline, fetch)
- All moderation actions now show specific auth errors like "Only the host can kick users"

New Endpoint:
- DELETE /api/arcade/rooms/:roomId/invite: Allow users to decline their pending invitations
  * Validates invitation exists and is pending
  * Only invited user can decline their own invitation
  * Returns proper error messages for auth failures

Bug Fix:
- Fixed invitations/pending/route.ts ban check query (removed reference to non-existent unbannedAt field)
- Ban records are deleted when unbanned, so any existing ban is active

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-20 13:34:47 +00:00
Thomas Hallock
0ab4cc2880 feat(abacus-react): add BigInt support for 30-digit Dan level abacuses
Add BigInt support to AbacusReact to handle 30-digit numbers without
precision loss (JavaScript's Number.MAX_SAFE_INTEGER is ~16 digits).

Changes:
- Update AbacusReact types to accept `value?: number | bigint`
- Modify useAbacusPlaceStates hook to use string-based digit parsing
- Add conditional BigInt arithmetic for >15 digits (maxPlaceValue > 14)
- Update levels page to pass BigInt for Dan levels (30 columns)
- Fix games page Date comparison (unrelated TypeScript error)

The implementation automatically detects when BigInt is needed based on
the number of digits, maintaining backward compatibility.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-20 08:33:31 -05:00
semantic-release-bot
6b7c455315 chore(release): 4.32.1 [skip ci]
## [4.32.1](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.32.0...v4.32.1) (2025-10-20)

### Bug Fixes

* **levels:** use correct dark mode styling from homepage + docs update ([c38767f](c38767f4d3))
2025-10-20 13:00:25 +00:00
Thomas Hallock
c38767f4d3 fix(levels): use correct dark mode styling from homepage + docs update
Fixed abacus styling issues by matching homepage implementation:
- Use `fill` (not just `stroke`) for columnPosts and reckoningBar
- Changed from all zeros to interesting display value (123456...)
- Removed incorrect color customization causing mixed bead styles
- Now uses exact same darkStyles pattern as homepage MiniAbacus

Documentation update:
- Added MANDATORY section: "Read the Docs Before Customizing"
- Emphasized always reading packages/abacus-react/README.md
- Added references to homepage and storybook examples
- Included concrete example of correct darkStyles usage
- Key point: columnPosts and reckoningBar need `fill` property

This ensures columns and reckoning bar are now visible on dark backgrounds
and provides guidance to prevent similar issues in the future.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-20 07:59:08 -05:00
semantic-release-bot
321d9aea10 chore(release): 4.32.0 [skip ci]
## [4.32.0](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.31.1...v4.32.0) (2025-10-20)

### Features

* **levels:** add dark mode styling and responsive scaling to abacus ([92e1e62](92e1e62132))
2025-10-20 12:50:34 +00:00
Thomas Hallock
92e1e62132 feat(levels): add dark mode styling and responsive scaling to abacus
Improvements to the levels page abacus display:
- Added showNumbers={true} to show place value numbers
- Styled for dark background with light gray columns and reckoning bar
- Colored beads (blue heaven, green earth) for better visibility
- Dynamic scaling: large (2.5x) for Kyu levels, smaller for Dan levels
- Added horizontal overflow for very wide Dan level abacuses (30 columns)
- Formula: scaleFactor = Math.min(2.5, 20 / digits)

The abacus now fits gracefully at all levels and is clearly visible
on the dark page background.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-20 07:49:28 -05:00
semantic-release-bot
84d980bb24 chore(release): 4.31.1 [skip ci]
## [4.31.1](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.31.0...v4.31.1) (2025-10-20)

### Bug Fixes

* **levels:** use correct AbacusReact API with direct props ([892b377](892b377eb3))
2025-10-20 12:47:01 +00:00
Thomas Hallock
892b377eb3 fix(levels): use correct AbacusReact API with direct props
Fixed abacus not rendering by using the correct API:
- Removed non-existent useAbacusConfig import
- Changed from config object to direct props (value, columns, scaleFactor)
- Added scaleFactor=1.5 for better visibility

The abacus now properly displays with the appropriate number of columns
based on the selected kyu/dan level.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-20 07:45:53 -05:00
semantic-release-bot
bc21095fa1 chore(release): 4.31.0 [skip ci]
## [4.31.0](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.30.0...v4.31.0) (2025-10-20)

### Features

* **levels:** implement interactive slider for exploring kyu & dan ranks ([eb3b100](eb3b100056))
2025-10-20 12:41:45 +00:00
Thomas Hallock
eb3b100056 feat(levels): implement interactive slider for exploring kyu & dan ranks
Replace static grid layout with an interactive range slider that allows
users to explore all 21 kyu and dan levels dynamically. The slider updates
a single AbacusReact component showing the appropriate number of columns
(2-30 digits) based on the selected rank.

Features:
- HTML range input slider from 10th Kyu to 10th Dan
- Dynamic abacus visualization using @soroban/abacus-react
- Real-time updates of level metadata (emoji, name, min score)
- Color-coded borders matching progression levels
- Reference markers for key ranks

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-20 07:40:19 -05:00
25 changed files with 4172 additions and 1004 deletions

View File

@@ -1,3 +1,692 @@
## [4.63.4](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.63.3...v4.63.4) (2025-10-21)
### Bug Fixes
* **flashcards:** keep grab point under cursor with proper coordinate conversion ([1869216](https://github.com/antialias/soroban-abacus-flashcards/commit/1869216d2fda77303c0b79d4f613c6dcdaf5324b))
## [4.63.3](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.63.2...v4.63.3) (2025-10-21)
### Bug Fixes
* **flashcards:** revert to simple delta positioning to prevent card jumping ([d018b69](https://github.com/antialias/soroban-abacus-flashcards/commit/d018b699c46aea90e9cdc3309e797ff2d7447ecf))
## [4.63.2](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.63.1...v4.63.2) (2025-10-21)
### Bug Fixes
* **flashcards:** correct pivot point to rotate around card center ([50fc3fd](https://github.com/antialias/soroban-abacus-flashcards/commit/50fc3fdf7f2c9b7412f6d7d890f5e0d52cb86a9b))
## [4.63.1](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.63.0...v4.63.1) (2025-10-21)
### Bug Fixes
* **flashcards:** increase rotation sensitivity 10x for visible grab point physics ([c0fa926](https://github.com/antialias/soroban-abacus-flashcards/commit/c0fa926d16d02c1bfe880b7f0056a760e8461b3b))
## [4.63.0](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.62.1...v4.63.0) (2025-10-21)
### Features
* **flashcards:** add grab point physics for realistic rotation ([bf37eb1](https://github.com/antialias/soroban-abacus-flashcards/commit/bf37eb1928de8d07673234e2faa1fa6268c45686))
## [4.62.1](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.62.0...v4.62.1) (2025-10-21)
### Bug Fixes
* **flashcards:** improve shadow speed logging with separate throttling ([0f51366](https://github.com/antialias/soroban-abacus-flashcards/commit/0f51366fd56540e691df4931b6350c03043484f1))
## [4.62.0](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.61.3...v4.62.0) (2025-10-21)
### Features
* **flashcards:** add dynamic shadow based on drag speed ([92148a4](https://github.com/antialias/soroban-abacus-flashcards/commit/92148a4cf87e828ba2e5ec1740fb51d9667c1d73))
## [4.61.3](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.61.2...v4.61.3) (2025-10-21)
### Code Refactoring
* **flashcards:** completely rewrite drag-drop with simple approach ([5f0ad14](https://github.com/antialias/soroban-abacus-flashcards/commit/5f0ad14133340d073e861f5721cb48e1abab03ff))
## [4.61.2](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.61.1...v4.61.2) (2025-10-21)
### Bug Fixes
* **flashcards:** use explicit per-property configs to fix decay physics ([f32480a](https://github.com/antialias/soroban-abacus-flashcards/commit/f32480a0f9153285341e5a28078840abc0590873))
## [4.61.1](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.61.0...v4.61.1) (2025-10-21)
### Bug Fixes
* **flashcards:** fix position snap-back by using api.set before decay ([30e16c8](https://github.com/antialias/soroban-abacus-flashcards/commit/30e16c8e5ac3bb25f2d54cf715dc6fb45adc4fcc))
## [4.61.0](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.60.0...v4.61.0) (2025-10-21)
### Features
* **flashcards:** enable unbounded drag and position persistence ([ad1ad69](https://github.com/antialias/soroban-abacus-flashcards/commit/ad1ad690f014257b5a3c3f599e794205a11d286f))
## [4.60.0](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.59.1...v4.60.0) (2025-10-21)
### Features
* **homepage:** significantly increase mobile hero abacus size ([424f41d](https://github.com/antialias/soroban-abacus-flashcards/commit/424f41d4bfc1ddea068f8c110b495ebd5c0bb455))
## [4.59.1](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.59.0...v4.59.1) (2025-10-21)
### Bug Fixes
* **homepage:** adjust hero abacus scale for optimal sizing across devices ([86dee31](https://github.com/antialias/soroban-abacus-flashcards/commit/86dee31c9a51ca0712f1b4181a4899d25374d403))
* **homepage:** reduce mobile abacus scale to prevent scroll hint overlap ([b8235be](https://github.com/antialias/soroban-abacus-flashcards/commit/b8235be612c3f1dbd0da2b6cd1a935001b7dac9b))
## [4.59.0](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.58.2...v4.59.0) (2025-10-21)
### Features
* **homepage:** increase hero abacus size for better visibility ([7666b0a](https://github.com/antialias/soroban-abacus-flashcards/commit/7666b0aea949f2432a4d0f4648c1a366af3ea6d2))
## [4.58.2](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.58.1...v4.58.2) (2025-10-21)
### Code Refactoring
* **navbar:** prevent subtitle wrap and remove abacus emoji ([a58f7b7](https://github.com/antialias/soroban-abacus-flashcards/commit/a58f7b78b0020c85da523c36fdf6d70ad069736a))
## [4.58.1](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.58.0...v4.58.1) (2025-10-21)
### Bug Fixes
* **navbar:** apply glassmorphism to transparent mode, not scrolled mode ([8893675](https://github.com/antialias/soroban-abacus-flashcards/commit/8893675b36b1c1534c6fe7e57fa7e0cc55f198d6))
## [4.58.0](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.57.10...v4.58.0) (2025-10-21)
### Features
* **navbar:** add glassmorphism effect to nav links when scrolled ([89b9072](https://github.com/antialias/soroban-abacus-flashcards/commit/89b90723b7a3fc9ed12da3ba8718fccb6ce0760f))
## [4.57.10](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.57.9...v4.57.10) (2025-10-21)
### Bug Fixes
* **navbar:** remove border artifact and add 10px bottom fade ([d5f60ce](https://github.com/antialias/soroban-abacus-flashcards/commit/d5f60ce9d2fbc2a870b3bb96f5365a0e04e0afc4))
### Styles
* **navbar:** improve theming to match homepage dark aesthetic ([284fc90](https://github.com/antialias/soroban-abacus-flashcards/commit/284fc90a53f5f4868a3e41421760ebc813be12b5))
## [4.57.9](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.57.8...v4.57.9) (2025-10-21)
### Bug Fixes
* **homepage:** add overflow hidden to Your Journey section ([415a1fb](https://github.com/antialias/soroban-abacus-flashcards/commit/415a1fb1faa263c9d69b4e781ce22da235ca2b66))
## [4.57.8](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.57.7...v4.57.8) (2025-10-21)
### Bug Fixes
* **homepage:** adjust responsive breakpoints to prevent skill card clipping ([62ff067](https://github.com/antialias/soroban-abacus-flashcards/commit/62ff067bb956b17a9b3569eadc2a32abd24c27b8))
## [4.57.7](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.57.6...v4.57.7) (2025-10-21)
### Bug Fixes
* **homepage:** restructure layout to center 1400px wide demo section ([61403f2](https://github.com/antialias/soroban-abacus-flashcards/commit/61403f2f506557b57716a298d4dc481d7853552f))
* **homepage:** set min-width 1400px on container and remove max-width ([aa297d4](https://github.com/antialias/soroban-abacus-flashcards/commit/aa297d4ef7559473a147934766bfa3868552f58d))
## [4.57.6](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.57.5...v4.57.6) (2025-10-21)
### Bug Fixes
* **homepage:** increase skill cards container width to prevent title wrapping ([cd3eb61](https://github.com/antialias/soroban-abacus-flashcards/commit/cd3eb61cb59e6faef37fbf609f37f7e2dc302e72))
## [4.57.5](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.57.4...v4.57.5) (2025-10-21)
### Bug Fixes
* **homepage:** prevent text overflow in skill cards ([a6ac55b](https://github.com/antialias/soroban-abacus-flashcards/commit/a6ac55b7b161e0dd33a4dd5acc0df647b2a513aa))
## [4.57.4](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.57.3...v4.57.4) (2025-10-21)
### Bug Fixes
* **homepage:** align container width breakpoint with grid columns ([422bf3d](https://github.com/antialias/soroban-abacus-flashcards/commit/422bf3d968b67e4683ac7ea7e487a84513f992f9))
* **homepage:** make MiniAbacus fill container properly ([3b5d147](https://github.com/antialias/soroban-abacus-flashcards/commit/3b5d14765dfb2d61a76f66ba3ae09695ce88bb6d))
* **homepage:** widen skill cards container to 650px ([bc1ad3a](https://github.com/antialias/soroban-abacus-flashcards/commit/bc1ad3a43a79570e1f9c61d5118d14ac4c201d71))
## [4.57.3](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.57.2...v4.57.3) (2025-10-21)
### Bug Fixes
* **homepage:** increase abacus container width to 120px/150px ([57c212f](https://github.com/antialias/soroban-abacus-flashcards/commit/57c212f4f5be591f712e1c5610e1f323e56e15dd))
## [4.57.2](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.57.1...v4.57.2) (2025-10-21)
### Bug Fixes
* **homepage:** increase skill card abacus container width ([e65e969](https://github.com/antialias/soroban-abacus-flashcards/commit/e65e96952f4e631722c73fc56d088fa3ff1ba858))
## [4.57.1](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.57.0...v4.57.1) (2025-10-21)
### Bug Fixes
* **homepage:** add overflow hidden to skill cards ([5070d8d](https://github.com/antialias/soroban-abacus-flashcards/commit/5070d8d64f7f58887ff7259bee9ce5166c4f8af8))
## [4.57.0](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.56.0...v4.57.0) (2025-10-21)
### Features
* **homepage:** make skills section responsive with emojis ([9ec0a71](https://github.com/antialias/soroban-abacus-flashcards/commit/9ec0a71546ee483233ed7866dae97345bf2384d7))
### Bug Fixes
* **homepage:** prevent skill card overflow ([fa26acf](https://github.com/antialias/soroban-abacus-flashcards/commit/fa26acfbaef1a04bb225956b2f684cd5023b56fa))
## [4.56.0](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.55.0...v4.56.0) (2025-10-20)
### Features
* **homepage:** emphasize single-player and observer modes ([a537bc1](https://github.com/antialias/soroban-abacus-flashcards/commit/a537bc18c34d94ca931e483ea01e497d6f5d4e5b))
## [4.55.0](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.54.0...v4.55.0) (2025-10-20)
### Features
* **homepage:** update section title to "The Arcade" ([f47b172](https://github.com/antialias/soroban-abacus-flashcards/commit/f47b172f66bee0017c11d8f129f5b83f2ef3dcd9))
## [4.54.0](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.53.0...v4.54.0) (2025-10-20)
### Features
* **homepage:** add interactive levels slider to replace static progression ([8a2d5ae](https://github.com/antialias/soroban-abacus-flashcards/commit/8a2d5ae319af8fd66010dd5538e4b82f7fb35d40))
### Code Refactoring
* **levels:** extract levels slider into shared component ([015e30b](https://github.com/antialias/soroban-abacus-flashcards/commit/015e30b085ad2ef798ffd6f7f6716269e3256651))
## [4.53.0](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.52.2...v4.53.0) (2025-10-20)
### Features
* **homepage:** add interactive draggable flashcards with physics ([0744883](https://github.com/antialias/soroban-abacus-flashcards/commit/074488349a3ec480548223c313006aa1e9e64e5c))
### Code Refactoring
* **homepage:** merge flashcard display with create button section ([3cf4f92](https://github.com/antialias/soroban-abacus-flashcards/commit/3cf4f92643306f055188ede508557515ef5efe98))
## [4.52.2](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.52.1...v4.52.2) (2025-10-20)
### Bug Fixes
* **homepage:** use actual container dimensions for flashcard positioning ([4082a24](https://github.com/antialias/soroban-abacus-flashcards/commit/4082a246a33ea67617b762d5b7490a8c9af0ad49))
## [4.52.1](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.52.0...v4.52.1) (2025-10-20)
### Bug Fixes
* **homepage:** correct flashcard transform rendering ([5dc636a](https://github.com/antialias/soroban-abacus-flashcards/commit/5dc636a71c15db28c029fd4f60e4a6c95620f953))
## [4.52.0](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.51.0...v4.52.0) (2025-10-20)
### Features
* **homepage:** add interactive draggable flashcards with physics ([e711c52](https://github.com/antialias/soroban-abacus-flashcards/commit/e711c527574412de2f9d451c7985c4f8667d269a))
## [4.51.0](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.50.1...v4.51.0) (2025-10-20)
### Features
* **homepage:** create fancy flashcard display with spread-out cards ([cd30944](https://github.com/antialias/soroban-abacus-flashcards/commit/cd30944c5e067f84d00dfdf41c37580acc589548))
## [4.50.1](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.50.0...v4.50.1) (2025-10-20)
### Bug Fixes
* **homepage:** set fixed width for learning panel to prevent layout shift ([dc19622](https://github.com/antialias/soroban-abacus-flashcards/commit/dc19622bbba2fead8cd9c0b2bda3a38abba0bd41))
* **homepage:** set fixed width for tutorial panel to prevent layout shift ([aba9f8a](https://github.com/antialias/soroban-abacus-flashcards/commit/aba9f8a94d50590cf94b6cd87f85b497e89045e7))
## [4.50.0](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.49.1...v4.50.0) (2025-10-20)
### Features
* **homepage:** add interactive learning panels with animated mini-tutorials ([76d6f19](https://github.com/antialias/soroban-abacus-flashcards/commit/76d6f19d51fe4b9594998ae4e0a8823aff389854))
## [4.49.1](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.49.0...v4.49.1) (2025-10-20)
### Code Refactoring
* **homepage:** streamline homepage sections ([d362a77](https://github.com/antialias/soroban-abacus-flashcards/commit/d362a770d63405efee5ef8a896d34e783dd11de2))
## [4.49.0](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.48.5...v4.49.0) (2025-10-20)
### Features
* add vibrant gradients and equal heights to game cards ([a1a135a](https://github.com/antialias/soroban-abacus-flashcards/commit/a1a135a8586e314c9d695bec6c4e58ec24e5c9cb)), closes [#4](https://github.com/antialias/soroban-abacus-flashcards/issues/4) [#00f2](https://github.com/antialias/soroban-abacus-flashcards/issues/00f2) [#667](https://github.com/antialias/soroban-abacus-flashcards/issues/667) [#764ba2](https://github.com/antialias/soroban-abacus-flashcards/issues/764ba2) [#f093](https://github.com/antialias/soroban-abacus-flashcards/issues/f093) [#f5576](https://github.com/antialias/soroban-abacus-flashcards/issues/f5576) [#43e97](https://github.com/antialias/soroban-abacus-flashcards/issues/43e97) [#38f9d7](https://github.com/antialias/soroban-abacus-flashcards/issues/38f9d7)
### Code Refactoring
* make homepage game cards dynamic from game registry ([7f51652](https://github.com/antialias/soroban-abacus-flashcards/commit/7f516526fb5f5b60c1782db5c8c3e29f05caafa7))
## [4.48.5](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.48.4...v4.48.5) (2025-10-20)
### Bug Fixes
* **homepage:** add dark gradient overlay for better text contrast on game cards ([6410b21](https://github.com/antialias/soroban-abacus-flashcards/commit/6410b21f829810af27e42d188295630bd67d6b6b))
## [4.48.4](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.48.3...v4.48.4) (2025-10-20)
### Bug Fixes
* **homepage:** improve text contrast on game cards with text shadows ([b6410c7](https://github.com/antialias/soroban-abacus-flashcards/commit/b6410c7c225f01f42d095ca270b8da7903cbfbb0))
## [4.48.3](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.48.2...v4.48.3) (2025-10-20)
### Bug Fixes
* **homepage:** display gradient backgrounds on all game cards ([c6dc210](https://github.com/antialias/soroban-abacus-flashcards/commit/c6dc210bf8e3a5b4d7d6e53f2a7427d335c65322))
### Documentation
* **z-index:** add comprehensive z-index and stacking context documentation ([c89aea7](https://github.com/antialias/soroban-abacus-flashcards/commit/c89aea744478696b6f812fe53311a2dba210540f))
## [4.48.2](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.48.1...v4.48.2) (2025-10-20)
### Bug Fixes
* **nav:** ensure nav bar appears above tutorial tooltips ([cc31564](https://github.com/antialias/soroban-abacus-flashcards/commit/cc315645de30218d1b034da3e130458fe2961a69))
### Styles
* **hero:** unify background with rest of homepage ([035d831](https://github.com/antialias/soroban-abacus-flashcards/commit/035d8312c707cbf5b0e2a725d7b1d8ff406f842d))
## [4.48.1](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.48.0...v4.48.1) (2025-10-20)
### Bug Fixes
* **hero:** prevent SSR hydration mismatch for subtitle ([1bfde8f](https://github.com/antialias/soroban-abacus-flashcards/commit/1bfde8fb251b227ccd2528bfe1c47acffd79fa49))
## [4.48.0](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.47.2...v4.48.0) (2025-10-20)
### Features
* **hero:** persist random subtitle per-session ([318f946](https://github.com/antialias/soroban-abacus-flashcards/commit/318f9469a0805c200c55ce4024a95fd7b8dbe6a2))
## [4.47.2](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.47.1...v4.47.2) (2025-10-20)
### Bug Fixes
* **nav:** prevent thrashing by using fixed position always ([eff44b3](https://github.com/antialias/soroban-abacus-flashcards/commit/eff44b3ad1ea0535c6965ad58012f9275cb143ec))
* **nav:** remove unnecessary borders from transparent nav ([8c2ddca](https://github.com/antialias/soroban-abacus-flashcards/commit/8c2ddca28dbdd7743227eed4d19a9a8f662a72b5))
## [4.47.1](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.47.0...v4.47.1) (2025-10-20)
### Bug Fixes
* **hero:** prevent nav thrashing with hysteresis ([71b1b93](https://github.com/antialias/soroban-abacus-flashcards/commit/71b1b933b598c0a6a8aef1bc9f8c598c1871b2eb))
## [4.47.0](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.46.2...v4.47.0) (2025-10-20)
### Features
* **nav:** add transparent nav bar with borders when hero visible ([463841e](https://github.com/antialias/soroban-abacus-flashcards/commit/463841e1910f4ddb9af662f036e4efb867836a83))
### Bug Fixes
* **hero:** use Number.isNaN instead of global isNaN ([c229faf](https://github.com/antialias/soroban-abacus-flashcards/commit/c229faffac525f3eebeb12155cb5ca4dff744472))
### Styles
* **hero-abacus:** add purple bead colors for dark theme ([721dfe4](https://github.com/antialias/soroban-abacus-flashcards/commit/721dfe426db4fe259f6cdeac587d008339df769b))
* **hero:** adjust spacing between title, subtitle, and abacus ([3a3706c](https://github.com/antialias/soroban-abacus-flashcards/commit/3a3706cc6fb694c7762f065f4ab4996bb8608dc4))
## [4.46.2](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.46.1...v4.46.2) (2025-10-20)
### Bug Fixes
* **types:** properly type HomeHeroContext in AppNavBar ([f9a7cb7](https://github.com/antialias/soroban-abacus-flashcards/commit/f9a7cb7f05dfddf291d89212a77ba1c11c00c9c7))
## [4.46.1](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.46.0...v4.46.1) (2025-10-20)
### Bug Fixes
* **hero-abacus:** restructure layout to prevent visual overlap ([02b6c70](https://github.com/antialias/soroban-abacus-flashcards/commit/02b6c70b7a52f7de2954e5e0efddbed64d419d6c))
## [4.46.0](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.45.0...v4.46.0) (2025-10-20)
### Features
* **homepage:** add full-page hero abacus with scroll-based nav transition ([d8ec642](https://github.com/antialias/soroban-abacus-flashcards/commit/d8ec64280ec0c2f44f2fd9c72a93a882481f650b))
### Bug Fixes
* **homepage:** improve hero abacus sizing and layout ([230f1dc](https://github.com/antialias/soroban-abacus-flashcards/commit/230f1dcd866e5b3625e19f7400f5eae478fe7d0c))
### Styles
* **hero-abacus:** apply dark theme to match homepage styling ([e0b6a2e](https://github.com/antialias/soroban-abacus-flashcards/commit/e0b6a2e88b3ebbaae41ed54f23f9e514604d2262))
## [4.45.0](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.44.3...v4.45.0) (2025-10-20)
### Features
* **branding:** rebrand navigation from 'Soroban Generator' to 'Abaci One' ([cce8980](https://github.com/antialias/soroban-abacus-flashcards/commit/cce8980e177da1b3c344e46561d928ed98b86f6c))
## [4.44.3](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.44.2...v4.44.3) (2025-10-20)
### Bug Fixes
* **levels:** reduce operator box sizes and remove divider line ([29d20a6](https://github.com/antialias/soroban-abacus-flashcards/commit/29d20a6c0741e7427f2bb64bc9c3e950b1a3238a))
* **levels:** use uniform padding on operator box grid ([2818fd1](https://github.com/antialias/soroban-abacus-flashcards/commit/2818fd15cacac78de6d86ba769b9b2a02800ed1e))
## [4.44.2](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.44.1...v4.44.2) (2025-10-20)
### Bug Fixes
* **levels:** match top/bottom margins to left padding on kyu detail boxes ([aa0bdcf](https://github.com/antialias/soroban-abacus-flashcards/commit/aa0bdcf686adcbfd1a145cf67121181d1f1194d9))
## [4.44.1](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.44.0...v4.44.1) (2025-10-20)
### Bug Fixes
* **levels:** add fixed dimensions and margins to kyu detail boxes ([05dd0b3](https://github.com/antialias/soroban-abacus-flashcards/commit/05dd0b30d3c397b82b7b7cc93a5ea575f3aada6d))
## [4.44.0](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.43.2...v4.44.0) (2025-10-20)
### Features
* **levels:** redesign kyu details with larger operators and prominent digits ([6739d59](https://github.com/antialias/soroban-abacus-flashcards/commit/6739d59f2b6189a98570e23e04c20d86d774ccce))
## [4.43.2](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.43.1...v4.43.2) (2025-10-20)
### Code Refactoring
* **levels:** remove Time and Pass sections from kyu details ([d90b5d5](https://github.com/antialias/soroban-abacus-flashcards/commit/d90b5d55322e75dd28b95376614663a506c829d4))
## [4.43.1](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.43.0...v4.43.1) (2025-10-20)
### Bug Fixes
* **levels:** use two-column grid for kyu details to prevent clipping ([fa3b73c](https://github.com/antialias/soroban-abacus-flashcards/commit/fa3b73c69169b4694201ffa19ae3f8b5a68dfe32))
## [4.43.0](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.42.1...v4.43.0) (2025-10-20)
### Features
* **levels:** add structured kyu exam details with card UI ([6501b07](https://github.com/antialias/soroban-abacus-flashcards/commit/6501b073b100a00982cff1ca3140921e74f31a9c))
## [4.42.1](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.42.0...v4.42.1) (2025-10-20)
### Code Refactoring
* **levels:** store kyu data verbatim, add formatting layer ([53d23f1](https://github.com/antialias/soroban-abacus-flashcards/commit/53d23f19bc06459462afb76ed94d9b99d583a32d))
## [4.42.0](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.41.0...v4.42.0) (2025-10-20)
### Features
* **levels:** add kyu level details display with English translations ([c650ffa](https://github.com/antialias/soroban-abacus-flashcards/commit/c650ffa1935fe370d37190b2843c0deecdcce8e7))
## [4.41.0](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.40.1...v4.41.0) (2025-10-20)
### Features
* **levels:** right-align abacus display ([8681b17](https://github.com/antialias/soroban-abacus-flashcards/commit/8681b17340e757cf04d17f884a780a251645bb33))
## [4.40.1](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.40.0...v4.40.1) (2025-10-20)
### Bug Fixes
* **levels:** increase animation speed to 10ms for 10th Dan ([6f89d9e](https://github.com/antialias/soroban-abacus-flashcards/commit/6f89d9e274082908fc090a9c0ba310f2cb06f014))
## [4.40.0](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.39.1...v4.40.0) (2025-10-20)
### Features
* **levels:** progressive animation speed for Dan levels ([9dff3e7](https://github.com/antialias/soroban-abacus-flashcards/commit/9dff3e7b7b1ca46ea7f19a48135124b80c5182c0))
## [4.39.1](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.39.0...v4.39.1) (2025-10-20)
### Bug Fixes
* **levels:** improve slider tick spacing to use full width ([1e90d6c](https://github.com/antialias/soroban-abacus-flashcards/commit/1e90d6c6207f29084a8dc96ccfbb1013a1a62271))
## [4.39.0](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.38.1...v4.39.0) (2025-10-20)
### Features
* **levels:** add auto-advance slider with hover pause ([41eaed2](https://github.com/antialias/soroban-abacus-flashcards/commit/41eaed24fce510bab7fd03fa2e39e829b33a7346))
## [4.38.1](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.38.0...v4.38.1) (2025-10-20)
### Bug Fixes
* **levels:** adjust slider text positioning to prevent emoji overlap ([e5ffe39](https://github.com/antialias/soroban-abacus-flashcards/commit/e5ffe3927edfb1baea7ddd216507e081f50e5d2c))
## [4.38.0](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.37.0...v4.38.0) (2025-10-20)
### Features
* **levels:** add animated calculation effect to abacus display ([4f4c735](https://github.com/antialias/soroban-abacus-flashcards/commit/4f4c73577a944518c093b3208a85482909fe3064))
## [4.37.0](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.36.0...v4.37.0) (2025-10-20)
### Features
* **levels:** add hover tracking to slider for real-time level preview ([477a0b3](https://github.com/antialias/soroban-abacus-flashcards/commit/477a0b367e32749b865b5a5405846e86d5bcef6a))
## [4.36.0](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.35.1...v4.36.0) (2025-10-20)
### Features
* **levels:** make emoji tick marks clickable and remove redundant UI ([07c783a](https://github.com/antialias/soroban-abacus-flashcards/commit/07c783a79454f50e7302b19684be6d2e5930154d))
### Bug Fixes
* **levels:** add smooth CSS transitions for slider thumb movement ([ca8cef1](https://github.com/antialias/soroban-abacus-flashcards/commit/ca8cef1c36efeb1c8c214c74f8bd383f9295be3b))
## [4.35.1](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.35.0...v4.35.1) (2025-10-20)
### Features
* **abacus-react:** export StandaloneBead component wired to AbacusDisplayContext ([0146ce1](https://github.com/antialias/soroban-abacus-flashcards/commit/0146ce1e67da27a24cbaa8338ba6a1a6befd6bd3))
### Performance Improvements
* **levels:** speed up slider animations for more responsive feel ([1e5467f](https://github.com/antialias/soroban-abacus-flashcards/commit/1e5467fad4e27b832300c49b4f73547dc47598b0))
## [4.35.0](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.34.0...v4.35.0) (2025-10-20)
### Features
* **levels:** replace slider thumb with diamond-shaped abacus beads ([0fbde53](https://github.com/antialias/soroban-abacus-flashcards/commit/0fbde53039d3ea000c6a3be492b733479e7bf47c))
## [4.34.0](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.33.8...v4.34.0) (2025-10-20)
### Features
* **levels:** redesign slider with abacus-themed beads ([f3dce84](https://github.com/antialias/soroban-abacus-flashcards/commit/f3dce84532fa706e4ec9551facde2055a060ee13))
### Code Refactoring
* **levels:** convert to Radix UI Slider with abacus theme ([a03e73c](https://github.com/antialias/soroban-abacus-flashcards/commit/a03e73c849c5da4337f26a74b8f12b617c66068e))
## [4.33.8](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.33.7...v4.33.8) (2025-10-20)
### Bug Fixes
* **levels:** reduce Dan scale and container height to prevent clipping ([563136f](https://github.com/antialias/soroban-abacus-flashcards/commit/563136fb79fa10b2af3a119bf0f861e3b0812b2e))
* **levels:** reduce max scale factor to allow more compact container ([ead9ee9](https://github.com/antialias/soroban-abacus-flashcards/commit/ead9ee9589aa4d7376e9385da5da53a6b444858a))
## [4.33.7](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.33.6...v4.33.7) (2025-10-20)
### Bug Fixes
* **levels:** reduce scale factor variation to minimize margin differences ([abb647c](https://github.com/antialias/soroban-abacus-flashcards/commit/abb647ce40b8f9d0c8268ab18c139324ae3195c5))
## [4.33.6](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.33.5...v4.33.6) (2025-10-20)
### Bug Fixes
* **levels:** increase container height to prevent abacus clipping ([cd5c15a](https://github.com/antialias/soroban-abacus-flashcards/commit/cd5c15aeb260c568fe7ad9b6a4f51c4d6498b2b8))
## [4.33.5](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.33.4...v4.33.5) (2025-10-20)
### Bug Fixes
* **levels:** revert delayed column change, keep overflow hidden ([22f00f5](https://github.com/antialias/soroban-abacus-flashcards/commit/22f00f59f5facc36a846408dcd196ec54ea676b1))
## [4.33.4](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.33.3...v4.33.4) (2025-10-20)
### Bug Fixes
* **levels:** stabilize slider position and prevent abacus clipping ([09004dc](https://github.com/antialias/soroban-abacus-flashcards/commit/09004dc2c055031ee2f71c964ceee6f7b1d42ecd))
## [4.33.3](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.33.2...v4.33.3) (2025-10-20)
### Code Refactoring
* **levels:** move slider into level display pane above abacus ([2d8bb4a](https://github.com/antialias/soroban-abacus-flashcards/commit/2d8bb4ab8804f399d1ccc8a18feff9f09eca8029))
## [4.33.2](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.33.1...v4.33.2) (2025-10-20)
### Bug Fixes
* **levels:** add fixed height to entire level display pane ([200b26c](https://github.com/antialias/soroban-abacus-flashcards/commit/200b26c2cd35d1d637ede9dcfc3dbbc7f3f19320))
## [4.33.1](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.33.0...v4.33.1) (2025-10-20)
### Bug Fixes
* **levels:** only animate abacus, not container with background/border ([c80477d](https://github.com/antialias/soroban-abacus-flashcards/commit/c80477d24877ddada5f3f4405abbf05e1d753b5d))
## [4.33.0](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.32.1...v4.33.0) (2025-10-20)
### Features
* **abacus-react:** add BigInt support for 30-digit Dan level abacuses ([0ab4cc2](https://github.com/antialias/soroban-abacus-flashcards/commit/0ab4cc288066b75a6ea4371f65098db5c0fc8847))
* **levels:** add hover interaction and smooth React Spring transitions ([fd2b633](https://github.com/antialias/soroban-abacus-flashcards/commit/fd2b6338a84c3bbc683eff216a8da3b155749f0f))
## [4.32.1](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.32.0...v4.32.1) (2025-10-20)
### Bug Fixes
* **levels:** use correct dark mode styling from homepage + docs update ([c38767f](https://github.com/antialias/soroban-abacus-flashcards/commit/c38767f4d399fa2caa5cd4e0185689d0207fbdaf))
## [4.32.0](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.31.1...v4.32.0) (2025-10-20)
### Features
* **levels:** add dark mode styling and responsive scaling to abacus ([92e1e62](https://github.com/antialias/soroban-abacus-flashcards/commit/92e1e621321039206f65b3605f5797bbdc6beafc))
## [4.31.1](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.31.0...v4.31.1) (2025-10-20)
### Bug Fixes
* **levels:** use correct AbacusReact API with direct props ([892b377](https://github.com/antialias/soroban-abacus-flashcards/commit/892b377eb3bbd555dd2566bf58e946e9faa7b9f6))
## [4.31.0](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.30.0...v4.31.0) (2025-10-20)
### Features
* **levels:** implement interactive slider for exploring kyu & dan ranks ([eb3b100](https://github.com/antialias/soroban-abacus-flashcards/commit/eb3b1000563536d4143ba1f4ec04e59e8dd2e608))
## [4.30.0](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.29.0...v4.30.0) (2025-10-20)

View File

@@ -138,12 +138,43 @@ See `.claude/GAME_THEMES.md` for standardized color theme usage in arcade games.
- ✅ Use `useAbacusConfig` for abacus configuration
- ✅ Use `useAbacusDisplay` for reading abacus state
**MANDATORY: Read the Docs Before Customizing**
**ALWAYS read the full README documentation before customizing or styling AbacusReact:**
- Location: `packages/abacus-react/README.md`
- Check homepage implementation: `src/app/page.tsx` (MiniAbacus component)
- Check storybook examples: `src/stories/AbacusReact.*.stories.tsx`
**Key Documentation Points:**
1. **Custom Styles**: Use `fill` (not just `stroke`) for columnPosts and reckoningBar
2. **Props**: Use direct props like `value`, `columns`, `scaleFactor` (not config objects)
3. **Example from Homepage:**
```typescript
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,
},
}
<AbacusReact
value={123}
columns={3}
customStyles={darkStyles}
/>
```
**Example Usage:**
```typescript
import { AbacusReact, useAbacusConfig } from '@soroban/abacus-react'
import { AbacusReact } from '@soroban/abacus-react'
const config = useAbacusConfig({ columns: 5 })
<AbacusReact config={config} initialNumber={123} />
<AbacusReact value={123} columns={5} scaleFactor={1.5} showNumbers={true} />
```
## Known Issues
@@ -192,3 +223,48 @@ Three places must handle settings correctly:
3. **Validator** (`{Game}Validator.ts`) - `getInitialState()` must accept ALL settings
If a setting doesn't persist, check all three locations.
## Z-Index and Stacking Context Management
When working with z-index values or encountering layering issues, refer to:
- **`.claude/Z_INDEX_MANAGEMENT.md`** - Complete z-index documentation
- Z-index layering hierarchy (0-20000+)
- Stacking context rules and gotchas
- Current z-index audit of all components
- Guidelines for choosing z-index values
- Migration plan to use constants file
- Debugging checklist for layering issues
**Quick Reference:**
**ALWAYS use the constants file:**
```typescript
import { Z_INDEX } from '@/constants/zIndex'
// ✅ Good
zIndex: Z_INDEX.NAV_BAR
zIndex: Z_INDEX.MODAL
zIndex: Z_INDEX.TOOLTIP
// ❌ Bad - magic numbers!
zIndex: 100
zIndex: 10000
zIndex: 500
```
**Layering hierarchy:**
- Base content: 0-99
- Navigation/UI chrome: 100-999
- Overlays/dropdowns/tooltips: 1000-9999
- Modals/dialogs: 10000-19999
- Toasts: 20000+
**Critical reminder about stacking contexts:**
Z-index values are only compared within the same stacking context! Elements with `position + zIndex`, `opacity < 1`, `transform`, or `filter` create new stacking contexts where child z-indexes are relative, not global.
Before setting a z-index, always check:
1. What stacking context is this element in?
2. Am I comparing against siblings or global elements?
3. Does my parent create a stacking context?

View File

@@ -0,0 +1,392 @@
# Z-Index & Stacking Context Management
## Overview
This document tracks z-index values and stacking contexts across the application to prevent layering conflicts and make reasoning about visual hierarchy easy.
## The Z-Index Constants System
**Location:** `src/constants/zIndex.ts`
All z-index values should be defined in this file and imported where needed:
```typescript
import { Z_INDEX } from '../constants/zIndex'
// Use it like this:
zIndex: Z_INDEX.NAV_BAR
zIndex: Z_INDEX.MODAL
zIndex: Z_INDEX.GAME_NAV.HAMBURGER_MENU
```
## Z-Index Layering Hierarchy
From lowest to highest:
| Layer | Range | Purpose | Examples |
|-------|-------|---------|----------|
| **Base Content** | 0-99 | Default page content, game elements | Background elements, game tracks, cards |
| **Navigation & UI Chrome** | 100-999 | Fixed navigation, sticky headers | AppNavBar, page headers |
| **Overlays & Dropdowns** | 1000-9999 | Tooltips, popovers, dropdowns, tutorial tooltips | Tutorial tooltips (50-100), ConfigForm (50), dropdowns (999-1000) |
| **Modals & Dialogs** | 10000-19999 | Modal dialogs, confirmation dialogs | Modal backdrop (10000), Modal content (10001) |
| **Top-Level Overlays** | 20000+ | Toasts, critical notifications | Toast notifications (20000) |
## Stacking Context Rules
### What Creates a Stacking Context?
These CSS properties create new stacking contexts (z-index values are relative within them):
1. `position: fixed` or `position: sticky` with z-index
2. `position: absolute` or `position: relative` with z-index
3. `opacity` < 1
4. `transform` (any value)
5. `filter` (any value except none)
6. `isolation: isolate`
### Key Insight
**Z-index values are only compared within the same stacking context!**
If Element A creates a stacking context with `z-index: 1` and Element B is outside that context with `z-index: 999`, Element B will be on top regardless of child z-indexes inside Element A.
### Example
```tsx
// Parent creates stacking context
<div style={{ position: 'relative', zIndex: 1 }}>
{/* This child's z-index is relative to parent, not global! */}
<div style={{ position: 'absolute', zIndex: 999999 }}>
I'm still under elements with zIndex: 2 outside my parent!
</div>
</div>
<div style={{ position: 'relative', zIndex: 2 }}>
I'm on top of the z-index: 999999 element above!
</div>
```
## Current Z-Index Audit (2025-10-20)
### ✅ Using Z_INDEX Constants (Good!)
| Component | Value | Source |
|-----------|-------|--------|
| AppNavBar (Panda section) | `Z_INDEX.NAV_BAR` (100) | `src/components/AppNavBar.tsx:464` |
| AppNavBar hamburger | `Z_INDEX.GAME_NAV.HAMBURGER_MENU` (9999) | `src/components/AppNavBar.tsx:165` |
| AbacusDisplayDropdown | `Z_INDEX.GAME_NAV.HAMBURGER_NESTED_DROPDOWN` (10000) | `src/components/AbacusDisplayDropdown.tsx:99` |
### ⚠️ Hardcoded Z-Index Values (Need Migration)
#### Critical Navigation Issues
| Component | Line | Value | Issue | Fix |
|-----------|------|-------|-------|-----|
| **AppNavBar (fixed section)** | 587 | `1000` | ❌ Should use `Z_INDEX.NAV_BAR` (100), but increased to 1000 to fix tutorial tooltip overlap | Define `TUTORIAL_TOOLTIP` in constants, set nav to proper layer |
| AppNavBar (badge) | 645 | `50` | Should use constant | Add `Z_INDEX.BADGE` |
#### Tutorial System
| Component | Line | Value | Purpose |
|-----------|------|-------|---------|
| TutorialPlayer | 643 | `50` | Tooltip container |
| Tutorial shared/EditorComponents | 569, 590 | `50` | Tooltip button |
| Tutorial shared/EditorComponents | 612 | `100` | Dropdown content (must be above tooltip) |
| Tutorial decomposition CSS | 73 | `50` | Legacy CSS |
| TutorialEditor | 65, 812, 2339 | `1000`, `10` | Various overlays |
#### Modals & Overlays
| Component | Line | Value | Purpose |
|-----------|------|-------|---------|
| Modal (common) | 59 | `10000` | Modal backdrop |
| ModerationPanel | 1994, 2009 | `10001`, `10002` | Moderation overlays |
| ToastContext | 171 | `10001` | Toast notifications (should be 20000!) |
| Join page | 35 | `10000` | Join page overlay |
| EmojiPicker | 636 | `10000` | Emoji picker modal |
#### Dropdowns & Popovers
| Component | Line | Value | Purpose |
|-----------|------|-------|---------|
| FormatSelectField | 115 | `999` | Dropdown |
| DeploymentInfoModal | 37, 55 | `9998`, `9999` | Info modal layers |
| RoomInfo | 338, 562 | `9999`, `10000` | Room tooltips |
| GameTitleMenu | 119 | `9999` | Game menu |
| PlayerTooltip | 69 | `9999` | Player tooltip |
#### Game Elements
| Component | Line | Value | Purpose |
|-----------|------|-------|---------|
| Complement Race Game | Multiple | `0`, `1` | Base game layers |
| Complement Race Track | 118, 140, 151 | `10`, `5`, `20` | Track, AI racers, player |
| Complement Race HUD | 51, 106, 119, 137, 168 | `10`, `1000` | HUD elements |
| GameCountdown | 58 | `1000` | Countdown overlay |
| RouteCelebration | 31 | `9999` | Celebration overlay |
| Matching GameCard | 203, 229, 243, 272, 386 | `9`, `10`, `-1`, `8`, `1` | Card layers |
| Matching PlayerStatusBar | 154, 181, 202 | `10`, `10`, `5` | Status bars |
#### Misc UI
| Component | Line | Value | Purpose |
|-----------|------|-------|---------|
| HeroAbacus | 89, 127, 163 | `10` | Hero section layers |
| ChampionArena | 425, 514, 554, 614 | `10`, `1`, `1`, `10` | Arena layers |
| NetworkPlayerIndicator | 118, 145, 169, 192, 275 | `-1`, `2`, `1`, `2`, `10` | Player avatars |
| ConfigurationForm | 521, 502 | `50` | Config overlays |
## The Recent Bug: Tutorial Tooltips Over Nav Bar
**Problem:** Tutorial tooltips (z-index: 50, 100) were appearing over the navigation bar.
**Root Cause:**
- Nav bar was using `Z_INDEX.NAV_BAR` = 100 in one place
- But also hardcoded `zIndex: 30` in the fixed positioning section (line 587)
- Tutorial tooltips use hardcoded `zIndex: 50` and `zIndex: 100`
- Since 50 and 100 > 30, tooltips appeared on top
**Temporary Fix:** Increased nav bar's hardcoded value from 30 to 1000
**Proper Fix Needed:**
1. Define tutorial tooltip z-indexes in constants file
2. Update nav bar to consistently use `Z_INDEX.NAV_BAR`
3. Ensure NAV_BAR > TUTORIAL_TOOLTIP in the hierarchy
4. Consider: Should tutorial tooltips be in the 1000-9999 range (overlays) rather than 50-100?
## Guidelines for Choosing Z-Index Values
### 1. **Always Import and Use Z_INDEX Constants**
```typescript
// ✅ Good
import { Z_INDEX } from '../constants/zIndex'
zIndex: Z_INDEX.NAV_BAR
// ❌ Bad
zIndex: 100 // Magic number!
```
### 2. **Add New Values to Constants File First**
Before using a new z-index value, add it to `src/constants/zIndex.ts`:
```typescript
export const Z_INDEX = {
// ... existing values ...
TUTORIAL: {
TOOLTIP: 500, // Tutorial tooltips (overlays layer)
DROPDOWN: 600, // Tutorial dropdown (above tooltip)
},
} as const
```
### 3. **Choose the Right Layer**
Ask yourself:
- Is this base content? → Use 0-99
- Is this navigation/UI chrome? → Use 100-999
- Is this a dropdown/tooltip/overlay? → Use 1000-9999
- Is this a modal dialog? → Use 10000-19999
- Is this a toast notification? → Use 20000+
### 4. **Understand Your Stacking Context**
Before setting z-index, ask:
- What is my parent's stacking context?
- Am I comparing against siblings or global elements?
- Does my element create a new stacking context?
### 5. **Document Special Cases**
If you must deviate from the constants, document why:
```typescript
// HACK: Needs to be above tutorial tooltips (50) but below modals (10000)
// TODO: Migrate to Z_INDEX.TUTORIAL.TOOLTIP system
zIndex: 100
```
## Migration Plan
### Phase 1: Update Constants File ✅ TODO
Add missing constants to `src/constants/zIndex.ts`:
```typescript
export const Z_INDEX = {
// Base content layer (0-99)
BASE: 0,
CONTENT: 1,
HERO_SECTION: 10, // Hero abacus components
// Game content layers (0-99)
GAME_CONTENT: {
TRACK: 0,
CONTROLS: 1,
RACER_AI: 5,
RACER_PLAYER: 10,
RACER_FLAG: 20,
HUD: 50,
},
// Navigation and UI chrome (100-999)
NAV_BAR: 1000, // ⚠️ Currently needs to be 1000 due to tutorial tooltips
STICKY_HEADER: 100,
BADGE: 50,
// Overlays and dropdowns (1000-9999)
TUTORIAL: {
TOOLTIP: 500, // Tutorial tooltips
DROPDOWN: 600, // Tutorial dropdowns (must be > tooltip)
EDITOR: 700, // Tutorial editor
},
DROPDOWN: 1000,
TOOLTIP: 1000,
POPOVER: 1000,
CONFIG_FORM: 1000,
PLAYER_TOOLTIP: 1000,
GAME_COUNTDOWN: 1000,
// High overlays (9000-9999)
CELEBRATION: 9000,
INFO_MODAL: 9998,
// Modal and dialog layers (10000-19999)
MODAL_BACKDROP: 10000,
MODAL: 10001,
MODERATION_PANEL: 10001,
EMOJI_PICKER: 10000,
// Top-level overlays (20000+)
TOAST: 20000,
// Special navigation layers for game pages
GAME_NAV: {
HAMBURGER_MENU: 9999,
HAMBURGER_NESTED_DROPDOWN: 10000,
},
} as const
```
### Phase 2: Migrate High-Priority Components
Priority order:
1. **Navigation components** (AppNavBar, etc.) - most critical for user experience
2. **Tutorial system** (TutorialPlayer, tooltips) - currently conflicting
3. **Modals and overlays** - ensure they're always on top
4. **Game HUDs** - ensure proper layering
5. **Everything else**
### Phase 3: Add Linting Rule
Consider adding an ESLint rule to prevent raw z-index numbers:
```javascript
// Warn when zIndex is used with a number literal
'no-magic-numbers': ['warn', {
ignore: [0, 1, -1],
ignoreArrayIndexes: true,
enforceConst: true,
}]
```
## Debugging Z-Index Issues
### Checklist
When elements aren't layering correctly:
1. **Check the value**
- [ ] What z-index does each element have?
- [ ] Are they using constants or magic numbers?
2. **Check the stacking context**
- [ ] What are the parent elements?
- [ ] Do any parents create stacking contexts? (position + z-index, opacity, transform, etc.)
- [ ] Are we comparing siblings or elements in different contexts?
3. **Verify the DOM hierarchy**
- [ ] Use browser DevTools to inspect the DOM tree
- [ ] Check the "Layers" panel in Chrome DevTools
- [ ] Look for transforms, opacity, filters on parent elements
4. **Test the fix**
- [ ] Does the fix work in all scenarios?
- [ ] Did we introduce new conflicts?
- [ ] Should we update the constants file?
### DevTools Tips
**Chrome DevTools:**
1. Open DevTools → More Tools → Layers
2. Select an element and see its stacking context
3. View the 3D layer composition
**Firefox DevTools:**
1. Inspector → Layout → scroll to "Z-index"
2. Shows the stacking context parent
## Examples
### Good: Using Constants
```typescript
import { Z_INDEX } from '@/constants/zIndex'
export function MyTooltip() {
return (
<div className={css({
position: 'absolute',
zIndex: Z_INDEX.TOOLTIP, // ✅ Clear and maintainable
})}>
Tooltip content
</div>
)
}
```
### Bad: Magic Numbers
```typescript
export function MyTooltip() {
return (
<div className={css({
position: 'absolute',
zIndex: 500, // ❌ Where did 500 come from? How does it relate to other elements?
})}>
Tooltip content
</div>
)
}
```
### Good: Documenting Stacking Context
```typescript
// Creates a new stacking context for card contents
<div className={css({
position: 'relative',
zIndex: Z_INDEX.BASE,
transform: 'translateZ(0)', // ⚠️ Creates stacking context!
})}>
{/* Child z-indexes are relative to this context */}
<div className={css({
position: 'absolute',
zIndex: Z_INDEX.CONTENT, // Relative to parent, not global
})}>
Card face
</div>
</div>
```
## Resources
- [MDN: CSS Stacking Context](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_positioned_layout/Understanding_z-index/Stacking_context)
- [What The Heck, z-index??](https://www.joshwcomeau.com/css/stacking-contexts/) by Josh Comeau
- [Z-Index Playground](https://thirumanikandan.com/posts/learn-z-index-using-a-visualization-tool)
## Last Updated
2025-10-20 - Initial audit and documentation created

View File

@@ -103,7 +103,8 @@
"Bash(node -e:*)",
"Bash(do gh run list --limit 1 --workflow=\"Build and Deploy\" --json conclusion,status,databaseId --jq '.[0] | \"\"\\(.status) - \\(.conclusion // \"\"running\"\") - Run \\(.databaseId)\"\"')",
"Bash(do ssh nas.home.network '/usr/local/bin/docker inspect soroban-abacus-flashcards --format=\"\"{{index .Config.Labels \\\"\"org.opencontainers.image.revision\\\"\"}}\"\"')",
"Bash(git rev-parse HEAD)"
"Bash(git rev-parse HEAD)",
"Bash(gh run watch --exit-status 18662351595)"
],
"deny": [],
"ask": []

View File

@@ -53,6 +53,7 @@
"@tanstack/react-form": "^0.19.0",
"@tanstack/react-query": "^5.90.2",
"@types/jsdom": "^21.1.7",
"@use-gesture/react": "^10.3.1",
"bcryptjs": "^2.4.3",
"better-sqlite3": "^12.4.1",
"drizzle-orm": "^0.44.6",

View File

@@ -16,7 +16,11 @@ function GamesPageContent() {
const router = useRouter()
// Get all players sorted by creation time
const allPlayers = getAllPlayers().sort((a, b) => a.createdAt - b.createdAt)
const allPlayers = getAllPlayers().sort((a, b) => {
const aTime = a.createdAt instanceof Date ? a.createdAt.getTime() : a.createdAt
const bTime = b.createdAt instanceof Date ? b.createdAt.getTime() : b.createdAt
return aTime - bTime
})
return (
<div

View File

@@ -1,190 +1,14 @@
'use client'
import { PageWithNav } from '@/components/PageWithNav'
import { LevelSliderDisplay } from '@/components/LevelSliderDisplay'
import { css } from '../../../styled-system/css'
import { container, stack } from '../../../styled-system/patterns'
// Kyu level data from the Japan Abacus Federation
const kyuLevels = [
{ level: '10th Kyu', emoji: '🧒', color: 'green', digits: 2 },
{ level: '9th Kyu', emoji: '🧒', color: 'green', digits: 2 },
{ level: '8th Kyu', emoji: '🧒', color: 'green', digits: 3 },
{ level: '7th Kyu', emoji: '🧒', color: 'green', digits: 4 },
{ level: '6th Kyu', emoji: '🧑', color: 'blue', digits: 5 },
{ level: '5th Kyu', emoji: '🧑', color: 'blue', digits: 6 },
{ level: '4th Kyu', emoji: '🧑', color: 'blue', digits: 7 },
{ level: '3rd Kyu', emoji: '🧔', color: 'violet', digits: 8 },
{ level: '2nd Kyu', emoji: '🧔', color: 'violet', digits: 9 },
{ level: '1st Kyu', emoji: '🧔', color: 'violet', digits: 10 },
] as const
// Dan level data - all use 30-digit calculations
const danLevels = [
{ level: 'Pre-1st Dan', name: 'Jun-Shodan', minScore: 90, emoji: '🧙', digits: 30 },
{ level: '1st Dan', name: 'Shodan', minScore: 100, emoji: '🧙', digits: 30 },
{ level: '2nd Dan', name: 'Nidan', minScore: 120, emoji: '🧙‍♂️', digits: 30 },
{ level: '3rd Dan', name: 'Sandan', minScore: 140, emoji: '🧙‍♂️', digits: 30 },
{ level: '4th Dan', name: 'Yondan', minScore: 160, emoji: '🧙‍♀️', digits: 30 },
{ level: '5th Dan', name: 'Godan', minScore: 180, emoji: '🧙‍♀️', digits: 30 },
{ level: '6th Dan', name: 'Rokudan', minScore: 200, emoji: '🧝', digits: 30 },
{ level: '7th Dan', name: 'Nanadan', minScore: 220, emoji: '🧝', digits: 30 },
{ level: '8th Dan', name: 'Hachidan', minScore: 250, emoji: '🧝‍♂️', digits: 30 },
{ level: '9th Dan', name: 'Kudan', minScore: 270, emoji: '🧝‍♀️', digits: 30 },
{ level: '10th Dan', name: 'Judan', minScore: 290, emoji: '👑', digits: 30 },
] as const
// Compact abacus column component
function AbacusColumn({ color }: { color: string }) {
return (
<div
className={css({
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
gap: '0.5',
w: '2',
})}
>
{/* Top bead */}
<div
className={css({
w: '2',
h: '2',
rounded: 'full',
bg:
color === 'green'
? 'green.500'
: color === 'blue'
? 'blue.500'
: color === 'violet'
? 'violet.500'
: 'amber.500',
opacity: 0.7,
})}
/>
{/* Divider */}
<div className={css({ w: '2', h: '0.5', bg: 'gray.600' })} />
{/* Bottom beads */}
{[0, 1, 2, 3].map((i) => (
<div
key={i}
className={css({
w: '2',
h: '2',
rounded: 'full',
bg:
color === 'green'
? 'green.500'
: color === 'blue'
? 'blue.500'
: color === 'violet'
? 'violet.500'
: 'amber.500',
opacity: 0.7,
})}
/>
))}
</div>
)
}
// Level card component for the slider
function LevelCard({
level,
emoji,
color,
digits,
subtitle,
}: {
level: string
emoji: string
color: string
digits: number
subtitle?: string
}) {
// Limit display for very high digit counts
const displayDigits = Math.min(digits, 15)
const showEllipsis = digits > 15
return (
<div
className={css({
minW: { base: '64', md: '80' },
bg: 'rgba(0, 0, 0, 0.4)',
border: '2px solid',
borderColor:
color === 'green'
? 'green.500'
: color === 'blue'
? 'blue.500'
: color === 'violet'
? 'violet.500'
: 'amber.500',
rounded: 'xl',
p: { base: '4', md: '6' },
transition: 'all 0.2s',
_hover: {
transform: 'translateY(-4px)',
boxShadow: '0 8px 16px rgba(0, 0, 0, 0.4)',
},
})}
>
{/* Header */}
<div className={css({ textAlign: 'center', mb: '4' })}>
<div className={css({ fontSize: { base: '3xl', md: '4xl' }, mb: '2' })}>{emoji}</div>
<div
className={css({
fontSize: { base: 'lg', md: 'xl' },
fontWeight: 'bold',
color:
color === 'green'
? 'green.400'
: color === 'blue'
? 'blue.400'
: color === 'violet'
? 'violet.400'
: 'amber.400',
mb: '1',
})}
>
{level}
</div>
{subtitle && <div className={css({ fontSize: 'sm', color: 'gray.400' })}>{subtitle}</div>}
</div>
{/* Abacus Visualization */}
<div
className={css({
display: 'flex',
gap: '1.5',
justifyContent: 'center',
alignItems: 'center',
p: '4',
bg: 'rgba(0, 0, 0, 0.3)',
rounded: 'lg',
minH: '32',
})}
>
{Array.from({ length: displayDigits }).map((_, i) => (
<AbacusColumn key={i} color={color} />
))}
{showEllipsis && (
<div className={css({ color: 'gray.500', fontSize: '2xl', px: '2' })}>...</div>
)}
</div>
{/* Digit count label */}
<div className={css({ textAlign: 'center', mt: '3', fontSize: 'sm', color: 'gray.400' })}>
{digits} {digits === 1 ? 'digit' : 'digits'}
</div>
</div>
)
}
export default function LevelsPage() {
return (
<PageWithNav navTitle="Kyu & Dan Levels" navEmoji="📊">
<div className={css({ bg: 'gray.900', minHeight: '100vh' })}>
<div className={css({ bg: 'gray.900', minHeight: '100vh', pb: '12' })}>
{/* Hero Section */}
<div
className={css({
@@ -207,7 +31,13 @@ export default function LevelsPage() {
})}
/>
<div className={container({ maxW: '6xl', px: '4', position: 'relative' })}>
<div
className={container({
maxW: '6xl',
px: '4',
position: 'relative',
})}
>
<div className={css({ textAlign: 'center', maxW: '5xl', mx: 'auto' })}>
<h1
className={css({
@@ -233,123 +63,18 @@ export default function LevelsPage() {
lineHeight: '1.6',
})}
>
Explore the progression from beginner to master
Slide through the complete progression from beginner to master
</p>
</div>
</div>
</div>
{/* Main content container */}
<div className={container({ maxW: '7xl', px: '4', py: '12' })}>
{/* Journey Slider */}
{/* Main content */}
<div className={container({ maxW: '6xl', px: '4', py: '12' })}>
<section className={stack({ gap: '8' })}>
<div className={css({ textAlign: 'center' })}>
<h2
className={css({
fontSize: { base: '2xl', md: '3xl' },
fontWeight: 'bold',
color: 'white',
mb: '4',
})}
>
The Complete Journey
</h2>
<p className={css({ color: 'gray.400', fontSize: 'md', mb: '8' })}>
Slide through all ranks from 10th Kyu to 10th Dan
</p>
</div>
<LevelSliderDisplay />
{/* Horizontal Slider */}
<div
className={css({
w: '100%',
overflowX: 'auto',
overflowY: 'hidden',
pb: '4',
})}
>
<div className={css({ display: 'flex', gap: '4', pb: '2' })}>
{/* Kyu Levels */}
{kyuLevels.map((kyu, index) => (
<LevelCard
key={index}
level={kyu.level}
emoji={kyu.emoji}
color={kyu.color}
digits={kyu.digits}
/>
))}
{/* Transition marker */}
<div
className={css({
minW: '20',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
fontSize: '4xl',
color: 'gray.500',
})}
>
</div>
{/* Dan Levels */}
{danLevels.map((dan, index) => (
<LevelCard
key={index}
level={dan.level}
emoji={dan.emoji}
color="amber"
digits={dan.digits}
subtitle={`${dan.minScore}+ pts`}
/>
))}
</div>
</div>
{/* Legend */}
<div
className={css({
display: 'flex',
flexWrap: 'wrap',
gap: '6',
justifyContent: 'center',
mt: '6',
p: '6',
bg: 'rgba(0, 0, 0, 0.3)',
rounded: 'lg',
})}
>
<div className={css({ display: 'flex', alignItems: 'center', gap: '2' })}>
<div className={css({ w: '4', h: '4', bg: 'green.500', rounded: 'sm' })} />
<span className={css({ fontSize: 'sm', color: 'gray.300' })}>
Beginner (10-7 Kyu)
</span>
</div>
<div className={css({ display: 'flex', alignItems: 'center', gap: '2' })}>
<div className={css({ w: '4', h: '4', bg: 'blue.500', rounded: 'sm' })} />
<span className={css({ fontSize: 'sm', color: 'gray.300' })}>
Intermediate (6-4 Kyu)
</span>
</div>
<div className={css({ display: 'flex', alignItems: 'center', gap: '2' })}>
<div className={css({ w: '4', h: '4', bg: 'violet.500', rounded: 'sm' })} />
<span className={css({ fontSize: 'sm', color: 'gray.300' })}>
Advanced (3-1 Kyu)
</span>
</div>
<div className={css({ display: 'flex', alignItems: 'center', gap: '2' })}>
<div className={css({ w: '4', h: '4', bg: 'amber.500', rounded: 'sm' })} />
<span className={css({ fontSize: 'sm', color: 'gray.300' })}>
Master (Dan ranks)
</span>
</div>
</div>
</section>
{/* Additional Information Section */}
<section className={stack({ gap: '8', mt: '16', pb: '12' })}>
{/* Info Section */}
<div
className={css({
bg: 'rgba(0, 0, 0, 0.4)',
@@ -382,21 +107,6 @@ export default function LevelsPage() {
levels all require mastery of 30-digit calculations, with ranks awarded based on
exam scores.
</p>
<div
className={css({
mt: '4',
pt: '4',
borderTop: '1px solid',
borderColor: 'gray.700',
fontSize: 'sm',
color: 'gray.400',
fontStyle: 'italic',
})}
>
Note: This page provides information about the official Japanese ranking system
for educational purposes. This application does not administer official
examinations or certifications.
</div>
</div>
</div>
</section>

File diff suppressed because it is too large Load Diff

View File

@@ -24,7 +24,7 @@ const manifest: GameManifest = {
maxPlayers: 1, // Single player only
difficulty: 'Intermediate',
chips: ['🧠 Pattern Recognition', '🎯 Solo Challenge', '📊 Smart Scoring'],
...getGameTheme('teal'),
...getGameTheme('green'),
available: true,
}

View File

@@ -23,7 +23,7 @@ const manifest: GameManifest = {
maxPlayers: 4,
difficulty: 'Intermediate',
chips: ['👥 Multiplayer', '🎯 Strategic', '🏆 Competitive'],
...getGameTheme('purple'),
...getGameTheme('pink'),
available: true,
}

View File

@@ -23,7 +23,7 @@ const manifest: GameManifest = {
maxPlayers: 8,
difficulty: 'Intermediate',
chips: ['👥 Multiplayer', '🧠 Memory', '🧮 Soroban'],
...getGameTheme('blue'),
...getGameTheme('purple'),
available: true,
}

View File

@@ -1,15 +1,41 @@
'use client'
import * as DropdownMenu from '@radix-ui/react-dropdown-menu'
import * as Tooltip from '@radix-ui/react-tooltip'
import Link from 'next/link'
import { usePathname, useRouter } from 'next/navigation'
import React, { useState } from 'react'
import React, { useContext, useMemo, useState } from 'react'
import { css } from '../../styled-system/css'
import { container, hstack } from '../../styled-system/patterns'
import { Z_INDEX } from '../constants/zIndex'
import { useFullscreen } from '../contexts/FullscreenContext'
import { getRandomSubtitle } from '../data/abaciOneSubtitles'
import { AbacusDisplayDropdown } from './AbacusDisplayDropdown'
// Import HomeHeroContext for optional usage
import type { Subtitle } from '../data/abaciOneSubtitles'
type HomeHeroContextValue = {
subtitle: Subtitle
isHeroVisible: boolean
} | null
// HomeHeroContext - imported dynamically to avoid circular deps
let HomeHeroContextModule: any = null
try {
HomeHeroContextModule = require('../contexts/HomeHeroContext')
} catch {
// Context not available
}
const HomeHeroContext: React.Context<HomeHeroContextValue> =
HomeHeroContextModule?.HomeHeroContext || React.createContext<HomeHeroContextValue>(null)
// Use HomeHeroContext without requiring it
function useOptionalHomeHero(): HomeHeroContextValue {
return useContext(HomeHeroContext)
}
interface AppNavBarProps {
variant?: 'full' | 'minimal'
navSlot?: React.ReactNode
@@ -514,6 +540,17 @@ export function AppNavBar({ variant = 'full', navSlot }: AppNavBarProps) {
const isArcadePage = pathname?.startsWith('/arcade')
const { isFullscreen, toggleFullscreen, exitFullscreen } = useFullscreen()
// Try to get home hero context (if on homepage)
const homeHero = useOptionalHomeHero()
// Select a random subtitle once on mount (performance: won't change on re-renders)
// Use homeHero subtitle if available, otherwise generate one
const fallbackSubtitle = useMemo(() => getRandomSubtitle(), [])
const subtitle = homeHero?.subtitle || fallbackSubtitle
// Show branding unless we're on homepage with visible hero
const showBranding = !homeHero || !homeHero.isHeroVisible
// Auto-detect variant based on context
const actualVariant = variant === 'full' && (isGamePage || isArcadePage) ? 'minimal' : variant
@@ -532,54 +569,137 @@ export function AppNavBar({ variant = 'full', navSlot }: AppNavBarProps) {
)
}
// Check if we should use transparent styling (when hero is visible)
const isTransparent = homeHero?.isHeroVisible
return (
<header
className={css({
bg: 'white',
shadow: 'sm',
borderBottom: '1px solid',
borderColor: 'gray.200',
position: 'sticky',
top: 0,
zIndex: 30,
})}
>
<div className={container({ maxW: '7xl', px: '4', py: '3' })}>
<div className={hstack({ justify: 'space-between', alignItems: 'center' })}>
{/* Logo */}
<Link
href="/"
className={css({
fontSize: 'xl',
fontWeight: 'bold',
color: 'brand.800',
textDecoration: 'none',
_hover: { color: 'brand.900' },
})}
>
🧮 Soroban Generator
</Link>
<Tooltip.Provider delayDuration={200}>
<header
className={css({
bg: isTransparent ? 'transparent' : 'rgba(0, 0, 0, 0.5)',
backdropFilter: isTransparent ? 'none' : 'blur(12px)',
shadow: isTransparent ? 'none' : 'lg',
borderBottom: isTransparent ? 'none' : '1px solid',
borderColor: isTransparent ? 'transparent' : 'rgba(139, 92, 246, 0.2)',
position: 'fixed',
top: 0,
left: 0,
right: 0,
zIndex: 1000,
transition: 'all 0.3s ease',
})}
>
<div className={container({ maxW: '7xl', px: '4', py: '3' })}>
<div className={hstack({ justify: 'space-between', alignItems: 'center' })}>
{/* Logo - conditionally shown based on hero visibility */}
{showBranding ? (
<Link
href="/"
className={css({
display: 'flex',
flexDirection: 'column',
gap: '0',
textDecoration: 'none',
_hover: { '& > .brand-name': { color: 'rgba(196, 181, 253, 1)' } },
opacity: 0,
animation: 'fadeIn 0.3s ease-out forwards',
})}
>
<span
className={css({
fontSize: 'xl',
fontWeight: 'bold',
color: 'rgba(255, 255, 255, 0.95)',
})}
>
Abaci One
</span>
<Tooltip.Root>
<Tooltip.Trigger asChild>
<span
className={css({
fontSize: 'xs',
fontWeight: 'medium',
color: 'rgba(196, 181, 253, 0.8)',
fontStyle: 'italic',
cursor: 'help',
whiteSpace: 'nowrap',
_hover: { color: 'rgba(196, 181, 253, 1)' },
})}
>
{subtitle.text}
</span>
</Tooltip.Trigger>
<Tooltip.Portal>
<Tooltip.Content
side="bottom"
align="start"
sideOffset={4}
className={css({
bg: 'gray.900',
color: 'white',
px: '3',
py: '2',
rounded: 'md',
fontSize: 'sm',
maxW: '250px',
shadow: 'lg',
zIndex: 50,
})}
>
{subtitle.description}
<Tooltip.Arrow
className={css({
fill: 'gray.900',
})}
/>
</Tooltip.Content>
</Tooltip.Portal>
</Tooltip.Root>
</Link>
) : (
<div />
)}
<div className={hstack({ gap: '6', alignItems: 'center' })}>
{/* Navigation Links */}
<nav className={hstack({ gap: '4' })}>
<NavLink href="/create" currentPath={pathname}>
Create
</NavLink>
<NavLink href="/guide" currentPath={pathname}>
Guide
</NavLink>
<NavLink href="/games" currentPath={pathname}>
Games
</NavLink>
</nav>
<div className={hstack({ gap: '6', alignItems: 'center' })}>
{/* Navigation Links */}
<nav className={hstack({ gap: '4' })}>
<NavLink href="/create" currentPath={pathname} isTransparent={isTransparent}>
Create
</NavLink>
<NavLink href="/guide" currentPath={pathname} isTransparent={isTransparent}>
Guide
</NavLink>
<NavLink href="/games" currentPath={pathname} isTransparent={isTransparent}>
Games
</NavLink>
</nav>
{/* Abacus Style Dropdown */}
<AbacusDisplayDropdown isFullscreen={false} />
{/* Abacus Style Dropdown */}
<AbacusDisplayDropdown isFullscreen={false} />
</div>
</div>
</div>
</div>
</header>
</header>
{/* Keyframes for fade-in animation */}
<style
dangerouslySetInnerHTML={{
__html: `
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(-5px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
`,
}}
/>
</Tooltip.Provider>
)
}
@@ -587,16 +707,21 @@ function NavLink({
href,
currentPath,
children,
isTransparent,
}: {
href: string
currentPath: string | null
children: React.ReactNode
isTransparent?: boolean
}) {
const isActive = currentPath === href || (href !== '/' && currentPath?.startsWith(href))
return (
<Link
href={href}
style={{
backdropFilter: isTransparent ? 'blur(8px)' : 'none',
}}
className={css({
px: { base: '4', md: '3' },
py: { base: '3', md: '2' },
@@ -604,17 +729,38 @@ function NavLink({
minW: { base: '44px', md: 'auto' },
fontSize: 'sm',
fontWeight: 'medium',
color: isActive ? 'brand.700' : 'gray.600',
bg: isActive ? 'brand.50' : 'transparent',
color: isTransparent
? isActive
? 'white'
: 'rgba(255, 255, 255, 0.8)'
: isActive
? 'rgba(196, 181, 253, 1)'
: 'rgba(209, 213, 219, 0.9)',
bg: isTransparent
? isActive
? 'rgba(255, 255, 255, 0.2)'
: 'rgba(255, 255, 255, 0.08)'
: isActive
? 'rgba(139, 92, 246, 0.2)'
: 'transparent',
border: isTransparent ? '1px solid' : 'none',
borderColor: isTransparent
? isActive
? 'rgba(255, 255, 255, 0.3)'
: 'rgba(255, 255, 255, 0.15)'
: 'transparent',
rounded: 'lg',
transition: 'all',
textDecoration: 'none',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
boxShadow: isTransparent ? '0 2px 8px rgba(0, 0, 0, 0.2)' : 'none',
_hover: {
color: isActive ? 'brand.800' : 'gray.900',
bg: isActive ? 'brand.100' : 'gray.50',
color: isTransparent ? 'white' : 'rgba(196, 181, 253, 1)',
bg: isTransparent ? 'rgba(255, 255, 255, 0.25)' : 'rgba(139, 92, 246, 0.25)',
borderColor: isTransparent ? 'rgba(255, 255, 255, 0.4)' : 'transparent',
boxShadow: isTransparent ? '0 4px 12px rgba(0, 0, 0, 0.3)' : 'none',
},
})}
>

View File

@@ -0,0 +1,189 @@
'use client'
import { useEffect, useRef } from 'react'
import { AbacusReact, useAbacusConfig } from '@soroban/abacus-react'
import { css } from '../../styled-system/css'
import { useHomeHero } from '../contexts/HomeHeroContext'
export function HeroAbacus() {
const {
subtitle,
abacusValue,
setAbacusValue,
setIsHeroVisible,
isAbacusLoaded,
isSubtitleLoaded,
} = useHomeHero()
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,
},
}
// Detect when hero scrolls out of view
useEffect(() => {
if (!heroRef.current) return
const observer = new IntersectionObserver(
([entry]) => {
// Hero is visible if more than 20% is in viewport
setIsHeroVisible(entry.intersectionRatio > 0.2)
},
{
threshold: [0, 0.2, 0.5, 1],
}
)
observer.observe(heroRef.current)
return () => observer.disconnect()
}, [setIsHeroVisible])
return (
<div
ref={heroRef}
className={css({
height: '100vh',
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'space-between',
bg: 'gray.900',
position: 'relative',
overflow: 'hidden',
px: '4',
py: '12',
})}
>
{/* Background pattern */}
<div
className={css({
position: 'absolute',
inset: 0,
opacity: 0.1,
backgroundImage:
'radial-gradient(circle at 2px 2px, rgba(255, 255, 255, 0.15) 1px, transparent 0)',
backgroundSize: '40px 40px',
})}
/>
{/* Title and Subtitle Section - DIRECT CHILD */}
<div
className={css({
position: 'relative',
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
gap: '2',
zIndex: 10,
})}
>
<h1
className={css({
fontSize: { base: '4xl', md: '6xl', lg: '7xl' },
fontWeight: 'bold',
background: 'linear-gradient(135deg, #fbbf24 0%, #f59e0b 50%, #fbbf24 100%)',
backgroundClip: 'text',
color: 'transparent',
})}
>
Abaci One
</h1>
<p
className={css({
fontSize: { base: 'xl', md: '2xl' },
fontWeight: 'medium',
color: 'purple.300',
fontStyle: 'italic',
marginBottom: '8',
opacity: isSubtitleLoaded ? 1 : 0,
transition: 'opacity 0.5s ease-in-out',
})}
>
{subtitle.text}
</p>
</div>
{/* Large Interactive Abacus - DIRECT CHILD */}
<div
className={css({
position: 'relative',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
flex: '1',
width: '100%',
zIndex: 10,
opacity: isAbacusLoaded ? 1 : 0,
transition: 'opacity 0.5s ease-in-out',
})}
>
<div
className={css({
transform: { base: 'scale(3.5)', md: 'scale(3.5)', lg: 'scale(4.25)' },
transformOrigin: 'center center',
transition: 'all 0.5s cubic-bezier(0.4, 0, 0.2, 1)',
})}
>
<AbacusReact
value={abacusValue}
columns={4}
beadShape={appConfig.beadShape}
showNumbers={true}
interactive={true}
animated={true}
customStyles={structuralStyles}
onValueChange={setAbacusValue}
/>
</div>
</div>
{/* Subtle hint to scroll - DIRECT CHILD */}
<div
className={css({
position: 'relative',
fontSize: 'sm',
color: 'gray.400',
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
gap: '2',
animation: 'bounce 2s ease-in-out infinite',
zIndex: 10,
})}
>
<span>Scroll to explore</span>
<span></span>
</div>
{/* Keyframes for bounce animation */}
<style
dangerouslySetInnerHTML={{
__html: `
@keyframes bounce {
0%, 100% {
transform: translateY(0);
opacity: 0.7;
}
50% {
transform: translateY(-10px);
opacity: 1;
}
}
`,
}}
/>
</div>
)
}

View File

@@ -0,0 +1,347 @@
'use client'
import { AbacusReact } from '@soroban/abacus-react'
import { useEffect, useRef, useState } from 'react'
import { css } from '../../styled-system/css'
interface Flashcard {
id: number
number: number
initialX: number
initialY: number
initialRotation: number
zIndex: number
}
/**
* InteractiveFlashcards - A fun flashcard display where you can drag cards around
* Cards stay where you drop them - simple and intuitive
*/
export function InteractiveFlashcards() {
const containerRef = useRef<HTMLDivElement>(null)
const [cards, setCards] = useState<Flashcard[]>([])
useEffect(() => {
if (!containerRef.current) return
// Double rAF pattern - ensures layout is fully complete
const frameId1 = requestAnimationFrame(() => {
const frameId2 = requestAnimationFrame(() => {
if (!containerRef.current) return
const containerWidth = containerRef.current.offsetWidth
const containerHeight = containerRef.current.offsetHeight
// Only generate cards once we have proper dimensions
if (containerWidth < 100 || containerHeight < 100) {
return
}
const count = Math.floor(Math.random() * 8) + 8 // 8-15 cards
const generated: Flashcard[] = []
// Position cards within the actual container bounds
const cardWidth = 120 // approximate card width
const cardHeight = 200 // approximate card height
for (let i = 0; i < count; i++) {
const card = {
id: i,
number: Math.floor(Math.random() * 900) + 100, // 100-999
initialX: Math.random() * (containerWidth - cardWidth - 40) + 20,
initialY: Math.random() * (containerHeight - cardHeight - 40) + 20,
initialRotation: Math.random() * 40 - 20, // -20 to 20 degrees
zIndex: i,
}
generated.push(card)
}
setCards(generated)
})
})
return () => {
// Note: can't cancel nested rAF properly, but component cleanup will prevent state updates
}
}, [])
return (
<div
ref={containerRef}
className={css({
position: 'relative',
width: '100%',
maxW: '1200px',
mx: 'auto',
height: { base: '400px', md: '500px' },
overflow: 'visible',
bg: 'rgba(0, 0, 0, 0.3)',
rounded: 'xl',
border: '1px solid rgba(255, 255, 255, 0.1)',
})}
>
{cards.map((card) => (
<DraggableCard key={card.id} card={card} containerRef={containerRef} />
))}
</div>
)
}
interface DraggableCardProps {
card: Flashcard
containerRef: React.RefObject<HTMLDivElement>
}
function DraggableCard({ card, containerRef }: DraggableCardProps) {
// Track position - starts at initial, updates when dragged
const [position, setPosition] = useState({ x: card.initialX, y: card.initialY })
const [rotation, setRotation] = useState(card.initialRotation) // Now dynamic!
const [zIndex, setZIndex] = useState(card.zIndex)
const [isDragging, setIsDragging] = useState(false)
const [dragSpeed, setDragSpeed] = useState(0) // Speed for dynamic shadow
// Track drag state
const dragStartRef = useRef<{ x: number; y: number; cardX: number; cardY: number } | null>(null)
const grabOffsetRef = useRef<{ x: number; y: number }>({ x: 0, y: 0 }) // Offset from card center where grabbed
const baseRotationRef = useRef(card.initialRotation) // Starting rotation
const lastMoveTimeRef = useRef<number>(0)
const lastMovePositionRef = useRef<{ x: number; y: number }>({ x: 0, y: 0 })
const lastLogTimeRef = useRef<number>(0) // Separate throttling for logging
const cardRef = useRef<HTMLDivElement>(null) // Reference to card element
const handlePointerDown = (e: React.PointerEvent) => {
setIsDragging(true)
setZIndex(1000) // Bring to front
setDragSpeed(0)
// Capture the pointer
e.currentTarget.setPointerCapture(e.pointerId)
// Record where the drag started (pointer position and card position)
dragStartRef.current = {
x: e.clientX,
y: e.clientY,
cardX: position.x,
cardY: position.y,
}
// Calculate grab offset from card center
if (cardRef.current) {
const rect = cardRef.current.getBoundingClientRect()
const cardCenterX = rect.left + rect.width / 2
const cardCenterY = rect.top + rect.height / 2
grabOffsetRef.current = {
x: e.clientX - cardCenterX,
y: e.clientY - cardCenterY,
}
console.log(
`[GrabPoint] Grabbed at offset: (${grabOffsetRef.current.x.toFixed(0)}, ${grabOffsetRef.current.y.toFixed(0)})px from center`
)
}
// Store the current rotation as the base for this drag
baseRotationRef.current = rotation
// Initialize velocity tracking
const now = Date.now()
lastMoveTimeRef.current = now
lastMovePositionRef.current = { x: e.clientX, y: e.clientY }
lastLogTimeRef.current = now
console.log('[Shadow] Drag started, speed reset to 0')
}
const handlePointerMove = (e: React.PointerEvent) => {
if (!isDragging || !dragStartRef.current) return
// Calculate how far the pointer has moved since drag started
const deltaX = e.clientX - dragStartRef.current.x
const deltaY = e.clientY - dragStartRef.current.y
// Calculate velocity for dynamic shadow
const now = Date.now()
const timeDelta = now - lastMoveTimeRef.current
if (timeDelta > 0) {
// Distance moved since last frame
const distX = e.clientX - lastMovePositionRef.current.x
const distY = e.clientY - lastMovePositionRef.current.y
const distance = Math.sqrt(distX * distX + distY * distY)
// Speed in pixels per millisecond, then convert to reasonable scale
const speed = distance / timeDelta
const scaledSpeed = Math.min(speed * 100, 100) // Cap at 100 for reasonable shadow size
setDragSpeed(scaledSpeed)
// Log occasionally (every ~200ms) to avoid console spam
const timeSinceLastLog = now - lastLogTimeRef.current
if (timeSinceLastLog > 200) {
console.log(
`[Shadow] Speed: ${scaledSpeed.toFixed(1)}, distance: ${distance.toFixed(0)}px, timeDelta: ${timeDelta}ms`
)
lastLogTimeRef.current = now
}
lastMoveTimeRef.current = now
lastMovePositionRef.current = { x: e.clientX, y: e.clientY }
}
// Calculate rotation based on grab point physics
// Cross product of grab offset and drag direction determines rotation
// If grabbed on left and dragged right → clockwise rotation
// If grabbed on right and dragged left → counter-clockwise rotation
const crossProduct = grabOffsetRef.current.x * deltaY - grabOffsetRef.current.y * deltaX
const rotationInfluence = crossProduct / 500 // Reduced scale factor for more visible rotation
const newRotation = baseRotationRef.current + rotationInfluence
// Clamp rotation to prevent excessive spinning
const clampedRotation = Math.max(-45, Math.min(45, newRotation))
setRotation(clampedRotation)
// Log rotation changes occasionally (same throttle as shadow logging)
const timeSinceLastLog = now - lastLogTimeRef.current
if (timeSinceLastLog > 200) {
console.log(
`[GrabPoint] Rotation: ${clampedRotation.toFixed(1)}° (influence: ${rotationInfluence.toFixed(1)}°, cross: ${crossProduct.toFixed(0)})`
)
}
// Update card position - keep grab point under cursor while rotating
// Calculate the rotated grab offset
const rotationRad = (clampedRotation * Math.PI) / 180
const cosRot = Math.cos(rotationRad)
const sinRot = Math.sin(rotationRad)
// Rotate the grab offset by the current rotation angle
const rotatedGrabX = grabOffsetRef.current.x * cosRot - grabOffsetRef.current.y * sinRot
const rotatedGrabY = grabOffsetRef.current.x * sinRot + grabOffsetRef.current.y * cosRot
// Get container bounds for coordinate conversion
if (!containerRef.current || !cardRef.current) {
// Fallback to simple delta if refs not ready
setPosition({
x: dragStartRef.current.cardX + deltaX,
y: dragStartRef.current.cardY + deltaY,
})
return
}
const containerRect = containerRef.current.getBoundingClientRect()
const cardRect = cardRef.current.getBoundingClientRect()
// Current cursor position in viewport space
const cursorViewportX = e.clientX
const cursorViewportY = e.clientY
// Card center should be at: cursor - rotated grab offset (viewport space)
const cardCenterViewportX = cursorViewportX - rotatedGrabX
const cardCenterViewportY = cursorViewportY - rotatedGrabY
// Convert card center from viewport space to container space
const cardCenterContainerX = cardCenterViewportX - containerRect.left
const cardCenterContainerY = cardCenterViewportY - containerRect.top
// position.x/y represents translate() which positions the top-left corner
// So we need: top-left = center - (width/2, height/2)
setPosition({
x: cardCenterContainerX - cardRect.width / 2,
y: cardCenterContainerY - cardRect.height / 2,
})
}
const handlePointerUp = (e: React.PointerEvent) => {
setIsDragging(false)
dragStartRef.current = null
console.log('[Shadow] Drag released, speed decaying to 0')
console.log(
`[GrabPoint] Final rotation: ${rotation.toFixed(1)}° (base was ${baseRotationRef.current.toFixed(1)}°)`
)
// Gradually decay speed back to 0 for smooth shadow transition
const decayInterval = setInterval(() => {
setDragSpeed((prev) => {
const newSpeed = prev * 0.8 // Decay by 20% each frame
if (newSpeed < 1) {
clearInterval(decayInterval)
return 0
}
return newSpeed
})
}, 50) // Update every 50ms
// Release the pointer capture
e.currentTarget.releasePointerCapture(e.pointerId)
}
// Calculate dynamic shadow based on drag speed
// Base shadow: 0 8px 24px rgba(0, 0, 0, 0.3)
// Fast drag: 0 32px 64px rgba(0, 0, 0, 0.6)
const shadowY = 8 + (dragSpeed / 100) * 24 // 8px to 32px
const shadowBlur = 24 + (dragSpeed / 100) * 40 // 24px to 64px
const shadowOpacity = 0.3 + (dragSpeed / 100) * 0.3 // 0.3 to 0.6
const boxShadow = `0 ${shadowY}px ${shadowBlur}px rgba(0, 0, 0, ${shadowOpacity})`
return (
<div
ref={cardRef}
onPointerDown={handlePointerDown}
onPointerMove={handlePointerMove}
onPointerUp={handlePointerUp}
style={{
position: 'absolute',
left: 0,
top: 0,
transform: `translate(${position.x}px, ${position.y}px) rotate(${rotation}deg) scale(${isDragging ? 1.05 : 1})`,
zIndex,
touchAction: 'none',
cursor: isDragging ? 'grabbing' : 'grab',
transition: isDragging ? 'none' : 'transform 0.2s ease-out',
}}
className={css({
userSelect: 'none',
})}
>
<div
style={{
boxShadow, // Dynamic shadow based on drag speed
}}
className={css({
bg: 'white',
rounded: 'lg',
p: '4',
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
gap: '2',
minW: '120px',
border: '2px solid rgba(0, 0, 0, 0.1)',
transition: 'box-shadow 0.1s', // Quick transition for responsive feel
})}
>
{/* Abacus visualization */}
<div
className={css({
transform: 'scale(0.6)',
transformOrigin: 'center',
})}
>
<AbacusReact value={card.number} columns={3} beadShape="circle" />
</div>
{/* Number display */}
<div
className={css({
fontSize: 'xl',
fontWeight: 'bold',
color: 'gray.800',
fontFamily: 'mono',
})}
>
{card.number}
</div>
</div>
</div>
)
}

View File

@@ -0,0 +1,862 @@
'use client'
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 { css } from '../../styled-system/css'
import { stack } from '../../styled-system/patterns'
import { kyuLevelDetails } from '@/data/kyuLevelDetails'
// Combine all levels into one array for the slider
const allLevels = [
{
level: '10th Kyu',
emoji: '🧒',
color: 'green',
digits: 2,
type: 'kyu' as const,
},
{
level: '9th Kyu',
emoji: '🧒',
color: 'green',
digits: 2,
type: 'kyu' as const,
},
{
level: '8th Kyu',
emoji: '🧒',
color: 'green',
digits: 3,
type: 'kyu' as const,
},
{
level: '7th Kyu',
emoji: '🧒',
color: 'green',
digits: 4,
type: 'kyu' as const,
},
{
level: '6th Kyu',
emoji: '🧑',
color: 'blue',
digits: 5,
type: 'kyu' as const,
},
{
level: '5th Kyu',
emoji: '🧑',
color: 'blue',
digits: 6,
type: 'kyu' as const,
},
{
level: '4th Kyu',
emoji: '🧑',
color: 'blue',
digits: 7,
type: 'kyu' as const,
},
{
level: '3rd Kyu',
emoji: '🧔',
color: 'violet',
digits: 8,
type: 'kyu' as const,
},
{
level: '2nd Kyu',
emoji: '🧔',
color: 'violet',
digits: 9,
type: 'kyu' as const,
},
{
level: '1st Kyu',
emoji: '🧔',
color: 'violet',
digits: 10,
type: 'kyu' as const,
},
{
level: 'Pre-1st Dan',
name: 'Jun-Shodan',
minScore: 90,
emoji: '🧙',
color: 'amber',
digits: 30,
type: 'dan' as const,
},
{
level: '1st Dan',
name: 'Shodan',
minScore: 100,
emoji: '🧙',
color: 'amber',
digits: 30,
type: 'dan' as const,
},
{
level: '2nd Dan',
name: 'Nidan',
minScore: 120,
emoji: '🧙‍♂️',
color: 'amber',
digits: 30,
type: 'dan' as const,
},
{
level: '3rd Dan',
name: 'Sandan',
minScore: 140,
emoji: '🧙‍♂️',
color: 'amber',
digits: 30,
type: 'dan' as const,
},
{
level: '4th Dan',
name: 'Yondan',
minScore: 160,
emoji: '🧙‍♀️',
color: 'amber',
digits: 30,
type: 'dan' as const,
},
{
level: '5th Dan',
name: 'Godan',
minScore: 180,
emoji: '🧙‍♀️',
color: 'amber',
digits: 30,
type: 'dan' as const,
},
{
level: '6th Dan',
name: 'Rokudan',
minScore: 200,
emoji: '🧝',
color: 'amber',
digits: 30,
type: 'dan' as const,
},
{
level: '7th Dan',
name: 'Nanadan',
minScore: 220,
emoji: '🧝',
color: 'amber',
digits: 30,
type: 'dan' as const,
},
{
level: '8th Dan',
name: 'Hachidan',
minScore: 250,
emoji: '🧝‍♂️',
color: 'amber',
digits: 30,
type: 'dan' as const,
},
{
level: '9th Dan',
name: 'Kudan',
minScore: 270,
emoji: '🧝‍♀️',
color: 'amber',
digits: 30,
type: 'dan' as const,
},
{
level: '10th Dan',
name: 'Judan',
minScore: 290,
emoji: '👑',
color: 'amber',
digits: 30,
type: 'dan' as const,
},
] as const
// Helper function to map level names to kyuLevelDetails keys
function getLevelDetailsKey(levelName: string): string | null {
// Convert "10th Kyu" → "10-kyu", "3rd Kyu" → "3-kyu", etc.
const match = levelName.match(/^(\d+)(?:st|nd|rd|th)\s+Kyu$/)
if (match) {
return `${match[1]}-kyu`
}
return null
}
// Parse and format kyu level details into structured sections with icons
function parseKyuDetails(rawText: string) {
const lines = rawText.split('\n').filter((line) => line.trim() && !line.includes('shuzan.jp'))
// Always return sections in consistent order: Add/Sub, Multiply, Divide
const sections: Array<{
type: 'addSub' | 'multiply' | 'divide'
icon: string
label: string
digits: string | null
rows: string | null
chars: string | null
problems: string | null
}> = [
{
type: 'addSub',
icon: '',
label: 'Add/Sub',
digits: null,
rows: null,
chars: null,
problems: null,
},
{
type: 'multiply',
icon: '✖️',
label: 'Multiply',
digits: null,
rows: null,
chars: null,
problems: null,
},
{
type: 'divide',
icon: '➗',
label: 'Divide',
digits: null,
rows: null,
chars: null,
problems: null,
},
]
for (const line of lines) {
if (line.includes('Add/Sub:')) {
const match = line.match(/(\d+)-digit.*?(\d+)口.*?(\d+)字/)
if (match) {
sections[0].digits = match[1]
sections[0].rows = match[2]
sections[0].chars = match[3]
}
} else if (line.includes('×:')) {
const match = line.match(/(\d+) digits.*?\((\d+)/)
if (match) {
sections[1].digits = match[1]
sections[1].problems = match[2]
}
} else if (line.includes('÷:')) {
const match = line.match(/(\d+) digits.*?\((\d+)/)
if (match) {
sections[2].digits = match[1]
sections[2].problems = match[2]
}
}
}
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,
},
}
interface LevelSliderDisplayProps {
initialIndex?: number
autoAdvanceEnabled?: boolean
autoAdvanceInterval?: number
showLegend?: boolean
}
export function LevelSliderDisplay({
initialIndex = 0,
autoAdvanceEnabled = true,
autoAdvanceInterval = 3000,
showLegend = true,
}: LevelSliderDisplayProps) {
const [currentIndex, setCurrentIndex] = useState(initialIndex)
const [isHovering, setIsHovering] = useState(false)
const [isPaneHovered, setIsPaneHovered] = useState(false)
const currentLevel = allLevels[currentIndex]
// State for animated abacus digits
const [animatedDigits, setAnimatedDigits] = useState<string>('')
// Initialize animated digits when level changes
useEffect(() => {
const generateRandomDigits = (numDigits: number) => {
return Array.from({ length: numDigits }, () => Math.floor(Math.random() * 10)).join('')
}
setAnimatedDigits(generateRandomDigits(currentLevel.digits))
}, [currentLevel.digits])
// Animate abacus calculations - speed increases with Dan level
useEffect(() => {
// Calculate animation speed based on level
// Kyu levels: 500ms
// Pre-1st Dan: 500ms
// 1st-10th Dan: interpolate from 500ms to 10ms
const getAnimationInterval = () => {
if (currentIndex < 11) {
// Kyu levels and Pre-1st Dan: constant 500ms
return 500
}
// 1st Dan through 10th Dan: speed up from 500ms to 10ms
// Index 11 (1st Dan) → 500ms
// Index 20 (10th Dan) → 10ms
const danProgress = (currentIndex - 11) / 9 // 0.0 to 1.0
return 500 - danProgress * 490 // 500ms down to 10ms
}
const intervalMs = getAnimationInterval()
const interval = setInterval(() => {
setAnimatedDigits((prev) => {
const digits = prev.split('').map(Number)
const numColumns = digits.length
// Pick 1-3 adjacent columns to change (grouping effect)
const groupSize = Math.floor(Math.random() * 3) + 1
const startCol = Math.floor(Math.random() * (numColumns - groupSize + 1))
// Change the selected columns
for (let i = startCol; i < startCol + groupSize && i < numColumns; i++) {
digits[i] = Math.floor(Math.random() * 10)
}
return digits.join('')
})
}, intervalMs)
return () => clearInterval(interval)
}, [currentIndex])
// Auto-advance slider position every 3 seconds (unless pane is hovered)
useEffect(() => {
if (!autoAdvanceEnabled || isPaneHovered) return
const interval = setInterval(() => {
setCurrentIndex((prev) => {
// Cycle back to 0 when reaching the end
return prev >= allLevels.length - 1 ? 0 : prev + 1
})
}, autoAdvanceInterval)
return () => clearInterval(interval)
}, [autoAdvanceEnabled, isPaneHovered, autoAdvanceInterval])
// Handle hover on slider track
const handleSliderHover = (e: React.MouseEvent<HTMLSpanElement>) => {
const rect = e.currentTarget.getBoundingClientRect()
const x = e.clientX - rect.left
const percentage = x / rect.width
const index = Math.round(percentage * (allLevels.length - 1))
setCurrentIndex(Math.max(0, Math.min(allLevels.length - 1, index)))
}
// Calculate scale factor based on number of columns to fit the page
// Use constrained range to prevent huge size differences between levels
// Min 1.2 (for 30-column Dan levels) to Max 2.0 (for 2-column Kyu levels)
const scaleFactor = Math.max(1.2, Math.min(2.0, 20 / currentLevel.digits))
// Animate scale factor with React Spring for smooth transitions
const animatedProps = useSpring({
scaleFactor,
config: { tension: 350, friction: 45 },
})
// Animate emoji with proper cross-fade (old fades out, new fades in)
const emojiTransitions = useTransition(currentLevel.emoji, {
keys: currentIndex,
from: { opacity: 0 },
enter: { opacity: 1 },
leave: { opacity: 0 },
config: { duration: 120 },
})
// Convert animated digits to a number/BigInt for the abacus display
// Use BigInt for large numbers to get full 30-digit precision
const displayValue =
animatedDigits.length > 15
? BigInt(animatedDigits || '0')
: Number.parseInt(animatedDigits || '0', 10)
return (
<div className={stack({ gap: '8' })}>
{/* Current Level Display */}
<div
onMouseEnter={() => setIsPaneHovered(true)}
onMouseLeave={() => setIsPaneHovered(false)}
className={css({
bg: 'transparent',
border: '2px solid',
borderColor:
currentLevel.color === 'green'
? 'green.500'
: currentLevel.color === 'blue'
? 'blue.500'
: currentLevel.color === 'violet'
? 'violet.500'
: 'amber.500',
rounded: 'xl',
p: { base: '6', md: '8' },
height: { base: 'auto', md: '700px' },
display: 'flex',
flexDirection: 'column',
})}
>
{/* Abacus-themed Radix Slider */}
<div className={css({ mb: '6', px: { base: '2', md: '8' } })}>
<div className={css({ position: 'relative', py: '12' })}>
{/* Emoji tick marks */}
<div
className={css({
position: 'absolute',
top: '0',
left: '0',
right: '0',
h: 'full',
display: 'flex',
alignItems: 'center',
pointerEvents: 'none',
px: '0', // Use full width for tick spacing
})}
>
<div
className={css({
position: 'relative',
w: 'full',
display: 'flex',
justifyContent: 'space-between',
})}
>
{allLevels.map((level, index) => (
<div
key={index}
onClick={() => setCurrentIndex(index)}
className={css({
fontSize: '4xl',
opacity: index === currentIndex ? '1' : '0.3',
transition: 'all 0.2s',
cursor: 'pointer',
pointerEvents: 'auto',
_hover: { opacity: index === currentIndex ? '1' : '0.6' },
})}
>
{level.emoji}
</div>
))}
</div>
</div>
<Slider.Root
value={[currentIndex]}
onValueChange={([value]) => setCurrentIndex(value)}
min={0}
max={allLevels.length - 1}
step={1}
onMouseMove={handleSliderHover}
onMouseEnter={() => setIsHovering(true)}
onMouseLeave={() => setIsHovering(false)}
className={css({
position: 'relative',
display: 'flex',
alignItems: 'center',
userSelect: 'none',
touchAction: 'none',
w: 'full',
h: '32',
cursor: 'pointer',
})}
>
<Slider.Track
className={css({
bg: 'rgba(255, 255, 255, 0.2)',
position: 'relative',
flexGrow: 1,
rounded: 'full',
h: '3px',
})}
>
<Slider.Range className={css({ display: 'none' })} />
</Slider.Track>
<Slider.Thumb
className={css({
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
position: 'relative',
w: '180px',
h: '128px',
bg: 'transparent',
cursor: 'grab',
transition: 'transform 0.15s ease-out, left 0.3s ease-out',
zIndex: 10,
_hover: { transform: 'scale(1.15)' },
_focus: {
outline: 'none',
transform: 'scale(1.15)',
},
_active: { cursor: 'grabbing' },
})}
>
<div className={css({ opacity: 0.75 })}>
<StandaloneBead
size={128}
color={currentLevel.color === 'violet' ? '#8b5cf6' : '#22c55e'}
animated={false}
/>
</div>
{emojiTransitions((style, emoji) => (
<animated.div
style={style}
className={css({
position: 'absolute',
fontSize: '9xl',
pointerEvents: 'none',
top: '50%',
left: '50%',
transform: 'translate(-50%, -50%)',
})}
>
{emoji}
</animated.div>
))}
{/* Level text as part of the bead display */}
<div
className={css({
position: 'absolute',
bottom: '-80px',
left: '50%',
transform: 'translateX(-50%)',
textAlign: 'center',
pointerEvents: 'none',
whiteSpace: 'nowrap',
})}
>
<h2
className={css({
fontSize: '2xl',
fontWeight: 'bold',
color:
currentLevel.color === 'green'
? 'green.400'
: currentLevel.color === 'blue'
? 'blue.400'
: currentLevel.color === 'violet'
? 'violet.400'
: 'amber.400',
mb: '0.5',
})}
>
{currentLevel.level}
</h2>
{'name' in currentLevel && (
<div
className={css({
fontSize: 'md',
color: 'gray.300',
mb: '0.5',
})}
>
{currentLevel.name}
</div>
)}
{'minScore' in currentLevel && (
<div className={css({ fontSize: 'sm', color: 'gray.400' })}>
Min: {currentLevel.minScore}pts
</div>
)}
</div>
</Slider.Thumb>
</Slider.Root>
</div>
{/* Level Markers */}
<div
className={css({
display: 'flex',
justifyContent: 'space-between',
mt: '1',
fontSize: 'xs',
color: 'gray.500',
})}
>
<span>10th Kyu</span>
<span>1st Kyu</span>
<span>10th Dan</span>
</div>
</div>
{/* Abacus Display with Level Details */}
<div
className={css({
display: 'flex',
gap: '4',
p: '6',
bg: 'rgba(0, 0, 0, 0.3)',
rounded: 'lg',
border: '1px solid',
borderColor: 'gray.700',
overflow: 'hidden',
flex: 1,
})}
>
{/* Level Details (only for Kyu levels) */}
{currentLevel.type === 'kyu' &&
(() => {
const detailsKey = getLevelDetailsKey(currentLevel.level)
const rawText = detailsKey
? kyuLevelDetails[detailsKey as keyof typeof kyuLevelDetails]
: null
const sections = rawText ? parseKyuDetails(rawText) : []
// Use consistent sizing across all levels
const sizing = { fontSize: 'md', gap: '3', iconSize: '4xl' }
return sections.length > 0 ? (
<div
className={css({
flex: '0 0 auto',
display: 'grid',
gridTemplateColumns: 'repeat(2, 1fr)',
gap: '2',
p: '2',
maxW: '400px',
alignContent: 'center',
})}
>
{sections.map((section, idx) => {
const hasData = section.digits !== null
const levelColor =
currentLevel.color === 'green'
? 'green.300'
: currentLevel.color === 'blue'
? 'blue.300'
: 'violet.300'
return (
<div
key={idx}
className={css({
bg: hasData ? 'rgba(0, 0, 0, 0.4)' : 'rgba(0, 0, 0, 0.2)',
border: '1px solid',
borderColor: hasData ? 'gray.700' : 'gray.800',
rounded: 'md',
p: '3',
transition: 'all 0.2s',
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
gap: '1.5',
opacity: hasData ? 1 : 0.3,
width: '170px',
height: '150px',
_hover: hasData
? {
borderColor: 'gray.500',
transform: 'scale(1.05)',
}
: {},
})}
>
<span className={css({ fontSize: sizing.iconSize, lineHeight: '1' })}>
{section.icon}
</span>
{hasData && section.digits && (
<>
<div
className={css({
fontSize: '2xl',
fontWeight: 'bold',
color: levelColor,
})}
>
{section.digits} digits
</div>
{(section.rows || section.chars) && (
<div
className={css({
fontSize: 'sm',
color: 'gray.400',
textAlign: 'center',
})}
>
{section.rows && `${section.rows} rows`}
{section.rows && section.chars && ' • '}
{section.chars && `${section.chars} chars`}
</div>
)}
{section.problems && (
<div
className={css({
fontSize: 'sm',
color: 'gray.400',
textAlign: 'center',
})}
>
{section.problems} problems
</div>
)}
</>
)}
<div
className={css({
fontSize: 'xs',
color: hasData ? 'gray.500' : 'gray.700',
textAlign: 'center',
fontWeight: hasData ? 'normal' : 'bold',
})}
>
{section.label}
</div>
</div>
)
})}
</div>
) : null
})()}
{/* Abacus (right-aligned for Kyu, centered for Dan) */}
<div
className={css({
display: 'flex',
justifyContent: currentLevel.type === 'kyu' ? 'flex-end' : 'center',
alignItems: 'center',
flex: 1,
})}
>
<animated.div
style={{
transform: animatedProps.scaleFactor.to((s) => `scale(${s / scaleFactor})`),
}}
>
<AbacusReact
value={displayValue}
columns={currentLevel.digits}
scaleFactor={scaleFactor}
showNumbers={true}
customStyles={darkStyles}
/>
</animated.div>
</div>
</div>
{/* Digit Count */}
<div
className={css({
textAlign: 'center',
color: 'gray.400',
fontSize: 'sm',
})}
>
Requires mastery of <strong>{currentLevel.digits}-digit</strong> calculations
</div>
</div>
{/* Legend */}
{showLegend && (
<div
className={css({
display: 'flex',
flexWrap: 'wrap',
gap: '6',
justifyContent: 'center',
p: '6',
bg: 'rgba(0, 0, 0, 0.3)',
rounded: 'lg',
border: '1px solid',
borderColor: 'gray.700',
})}
>
<div
className={css({
display: 'flex',
alignItems: 'center',
gap: '2',
})}
>
<div
className={css({
w: '4',
h: '4',
bg: 'green.500',
rounded: 'sm',
})}
/>
<span className={css({ fontSize: 'sm', color: 'gray.300' })}>Beginner (10-7 Kyu)</span>
</div>
<div
className={css({
display: 'flex',
alignItems: 'center',
gap: '2',
})}
>
<div
className={css({
w: '4',
h: '4',
bg: 'blue.500',
rounded: 'sm',
})}
/>
<span className={css({ fontSize: 'sm', color: 'gray.300' })}>
Intermediate (6-4 Kyu)
</span>
</div>
<div
className={css({
display: 'flex',
alignItems: 'center',
gap: '2',
})}
>
<div
className={css({
w: '4',
h: '4',
bg: 'violet.500',
rounded: 'sm',
})}
/>
<span className={css({ fontSize: 'sm', color: 'gray.300' })}>Advanced (3-1 Kyu)</span>
</div>
<div
className={css({
display: 'flex',
alignItems: 'center',
gap: '2',
})}
>
<div
className={css({
w: '4',
h: '4',
bg: 'amber.500',
rounded: 'sm',
})}
/>
<span className={css({ fontSize: 'sm', color: 'gray.300' })}>Master (Dan ranks)</span>
</div>
</div>
)}
</div>
)
}

View File

@@ -0,0 +1,127 @@
'use client'
import type React from 'react'
import { createContext, useContext, useEffect, useMemo, useRef, useState } from 'react'
import type { Subtitle } from '../data/abaciOneSubtitles'
import { subtitles } from '../data/abaciOneSubtitles'
interface HomeHeroContextValue {
subtitle: Subtitle
abacusValue: number
setAbacusValue: (value: number) => void
isHeroVisible: boolean
setIsHeroVisible: (visible: boolean) => void
isAbacusLoaded: boolean
isSubtitleLoaded: boolean
}
const HomeHeroContext = createContext<HomeHeroContextValue | null>(null)
export { HomeHeroContext }
export function HomeHeroProvider({ children }: { children: React.ReactNode }) {
// Use first subtitle for SSR, then select random one on client mount
const [subtitle, setSubtitle] = useState<Subtitle>(subtitles[0])
const [isSubtitleLoaded, setIsSubtitleLoaded] = useState(false)
// Select random subtitle only on client side, persist per-session
useEffect(() => {
// Check if we have a stored subtitle index for this session
const storedIndex = sessionStorage.getItem('heroSubtitleIndex')
if (storedIndex !== null) {
// Use the stored subtitle index
const index = parseInt(storedIndex, 10)
if (!Number.isNaN(index) && index >= 0 && index < subtitles.length) {
setSubtitle(subtitles[index])
setIsSubtitleLoaded(true)
return
}
}
// Generate a new random index and store it
const randomIndex = Math.floor(Math.random() * subtitles.length)
sessionStorage.setItem('heroSubtitleIndex', randomIndex.toString())
setSubtitle(subtitles[randomIndex])
setIsSubtitleLoaded(true)
}, [])
// Shared abacus value - always start at 0 for SSR/hydration consistency
const [abacusValue, setAbacusValue] = useState(0)
const [isAbacusLoaded, setIsAbacusLoaded] = useState(false)
const isLoadingFromStorage = useRef(false)
// Load from sessionStorage after mount (client-only, no hydration mismatch)
useEffect(() => {
console.log('[HeroAbacus] Loading from sessionStorage...')
isLoadingFromStorage.current = true // Block saves during load
const saved = sessionStorage.getItem('heroAbacusValue')
console.log('[HeroAbacus] Saved value from storage:', saved)
if (saved) {
const parsedValue = parseInt(saved, 10)
console.log('[HeroAbacus] Parsed value:', parsedValue)
if (!Number.isNaN(parsedValue)) {
console.log('[HeroAbacus] Setting abacus value to:', parsedValue)
setAbacusValue(parsedValue)
}
} else {
console.log('[HeroAbacus] No saved value found, staying at 0')
}
// Use setTimeout to ensure the value has been set before we allow saves
setTimeout(() => {
isLoadingFromStorage.current = false
setIsAbacusLoaded(true)
console.log('[HeroAbacus] Load complete, allowing saves now and fading in')
}, 0)
}, [])
// Persist value to sessionStorage when it changes (but skip during load)
useEffect(() => {
console.log(
'[HeroAbacus] Save effect triggered. Value:',
abacusValue,
'isLoadingFromStorage:',
isLoadingFromStorage.current
)
if (!isLoadingFromStorage.current) {
console.log('[HeroAbacus] Saving to sessionStorage:', abacusValue)
sessionStorage.setItem('heroAbacusValue', abacusValue.toString())
console.log(
'[HeroAbacus] Saved successfully. Storage now contains:',
sessionStorage.getItem('heroAbacusValue')
)
} else {
console.log('[HeroAbacus] Skipping save (currently loading from storage)')
}
}, [abacusValue])
// Track hero visibility for nav branding
const [isHeroVisible, setIsHeroVisible] = useState(true)
const value = useMemo(
() => ({
subtitle,
abacusValue,
setAbacusValue,
isHeroVisible,
setIsHeroVisible,
isAbacusLoaded,
isSubtitleLoaded,
}),
[subtitle, abacusValue, isHeroVisible, isAbacusLoaded, isSubtitleLoaded]
)
return <HomeHeroContext.Provider value={value}>{children}</HomeHeroContext.Provider>
}
export function useHomeHero() {
const context = useContext(HomeHeroContext)
if (!context) {
throw new Error('useHomeHero must be used within HomeHeroProvider')
}
return context
}

View File

@@ -0,0 +1,97 @@
/**
* Abaci One subtitle options with descriptions
* Three-word rhyming subtitles for the main app navigation
*/
export interface Subtitle {
text: string
description: string
}
export const subtitles: Subtitle[] = [
{ text: 'Speed Bead Lead', description: 'blaze through bead races' },
{ text: 'Rod Mod Nod', description: 'tweak rod technique, approval earned' },
{ text: 'Grid Kid Lid', description: 'lock in neat grid habits' },
{ text: 'Count Mount Amount', description: 'stack up that number sense' },
{ text: 'Stack Track Tack', description: 'line up beads, lock in sums' },
{ text: 'Quick Flick Trick', description: 'rapid-fire bead tactics' },
{ text: 'Flash Dash Math', description: 'fly through numeric challenges' },
{ text: 'Slide Glide Pride', description: 'smooth soroban strokes' },
{ text: 'Shift Sift Gift', description: 'sort beads, reveal talent' },
{ text: 'Beat Seat Meet', description: 'compete head-to-head' },
{ text: 'Brain Train Gain', description: 'mental math muscle building' },
{ text: 'Flow Show Pro', description: 'demonstrate soroban mastery' },
{ text: 'Fast Blast Past', description: 'surpass speed limits' },
{ text: 'Snap Tap Map', description: 'chart your calculation path' },
{ text: 'Row Grow Know', description: 'advance through structured drills' },
{ text: 'Drill Skill Thrill', description: 'practice that excites' },
{ text: 'Think Link Sync', description: 'connect mind and beads' },
{ text: 'Boost Joust Roost', description: 'power up, compete, settle in' },
{ text: 'Add Grad Rad', description: 'level up addition awesomely' },
{ text: 'Sum Fun Run', description: 'enjoy the arithmetic sprint' },
{ text: 'Track Stack Pack', description: 'organize solutions systematically' },
{ text: 'Beat Neat Feat', description: 'clean victories, impressive wins' },
{ text: 'Math Path Wrath', description: 'dominate numeric challenges' },
{ text: 'Spark Mark Arc', description: 'ignite progress, track growth' },
{ text: 'Race Pace Ace', description: 'speed up, master it' },
{ text: 'Flex Hex Reflex', description: 'adapt calculations instantly' },
{ text: 'Glide Pride Stride', description: 'smooth confident progress' },
{ text: 'Flash Dash Smash', description: 'speed through, crush totals' },
{ text: 'Stack Attack Jack', description: 'aggressive bead strategies' },
{ text: 'Quick Pick Click', description: 'rapid bead selection' },
{ text: 'Snap Map Tap', description: 'visualize and execute' },
{ text: 'Mind Find Grind', description: 'discover mental endurance' },
{ text: 'Flip Skip Rip', description: 'fast transitions, tear through' },
{ text: 'Blend Trend Send', description: 'mix methods, share progress' },
{ text: 'Power Tower Hour', description: 'build skills intensively' },
{ text: 'Launch Staunch Haunch', description: 'start strong, stay firm' },
{ text: 'Rush Crush Hush', description: 'speed quietly dominates' },
{ text: 'Swipe Stripe Hype', description: 'sleek moves, excitement' },
{ text: 'Train Gain Sustain', description: 'build lasting ability' },
{ text: 'Frame Claim Flame', description: 'structure your fire' },
{ text: 'Streak Peak Tweak', description: 'hot runs, optimize performance' },
{ text: 'Edge Pledge Wedge', description: 'commit to precision' },
{ text: 'Pace Grace Space', description: 'rhythm, elegance, room to grow' },
{ text: 'Link Think Brink', description: 'connect at breakthrough edge' },
{ text: 'Quest Test Best', description: 'challenge yourself to excel' },
{ text: 'Drive Thrive Arrive', description: 'push hard, succeed, reach goals' },
{ text: 'Smart Start Chart', description: 'begin wisely, track progress' },
{ text: 'Boost Coast Toast', description: 'accelerate, cruise, celebrate' },
{ text: 'Spark Dark Embark', description: 'ignite before dawn journeys' },
{ text: 'Blaze Graze Amaze', description: 'burn through, touch lightly, wow' },
{ text: 'Shift Drift Gift', description: 'adapt smoothly, reveal talent' },
{ text: 'Zone Hone Own', description: 'focus, refine, claim mastery' },
{ text: 'Vault Halt Exalt', description: 'leap high, pause, celebrate' },
{ text: 'Peak Seek Streak', description: 'find heights, maintain momentum' },
{ text: 'Glow Show Grow', description: 'shine, display, expand' },
{ text: 'Scope Hope Rope', description: 'survey possibilities, climb up' },
{ text: 'Core Score More', description: 'fundamentals yield better results' },
{ text: 'Rank Bank Thank', description: 'earn status, save wins, appreciate' },
{ text: 'Merge Surge Verge', description: 'combine forces, power up, edge closer' },
{ text: 'Bold Gold Hold', description: 'brave attempts, prize rewards, maintain' },
{ text: 'Rise Prize Wise', description: 'ascend, win, learn' },
{ text: 'Move Groove Prove', description: 'act, find rhythm, demonstrate' },
{ text: 'Trust Thrust Adjust', description: 'believe, push, refine' },
{ text: 'Beam Dream Team', description: 'radiate, aspire, collaborate' },
{ text: 'Spin Win Grin', description: 'rotate beads, succeed, smile' },
{ text: 'String Ring Bring', description: 'connect, cycle, deliver' },
{ text: 'Clear Gear Steer', description: 'focus, equip, direct' },
{ text: 'Path Math Aftermath', description: 'route, calculate, results' },
{ text: 'Play Slay Day', description: 'engage, dominate, own it' },
{ text: 'Code Mode Road', description: 'pattern, style, journey' },
{ text: 'Craft Draft Shaft', description: 'build, sketch, core structure' },
{ text: 'Light Might Fight', description: 'illuminate, empower, compete' },
{ text: 'Stream Dream Extreme', description: 'flow, envision, push limits' },
{ text: 'Claim Frame Aim', description: 'assert, structure, target' },
{ text: 'Chart Smart Start', description: 'map, intelligent, begin' },
{ text: 'Bright Flight Height', description: 'brilliant, soar, elevation' },
]
/**
* Get a random subtitle from the list
* Uses current timestamp as seed for variety across sessions
*/
export function getRandomSubtitle(): Subtitle {
const index = Math.floor(Math.random() * subtitles.length)
return subtitles[index]
}

View File

@@ -0,0 +1,123 @@
/**
* Detailed requirements for each Kyu level in the Soroban certification system
* Source: shuzan.jp
*
* Note: Stored verbatim from source. Display formatting/translation happens in the UI layer.
*/
export const kyuLevelDetails = {
'10-kyu': `Add/Sub: 2-digit, 5口, 10字
×: 実+法 = 3 digits (20 problems)
Time: 20 min; Pass ≥ 60/200.
shuzan.jp`,
'9-kyu': `Add/Sub: 2-digit, 5口, 10字
×: 実+法 = 3 digits (20)
Time: 20 min; Pass ≥ 120/200. (If only one part clears, it's treated as 10-kyu per federation notes.)
shuzan.jp`,
'8-kyu': `Add/Sub: 2-digit, 8口, 16字
×: 実+法 = 4 digits (10)
÷: 法+商 = 3 digits (10)
Time: 20 min; Pass ≥ 120/200.
shuzan.jp`,
'7-kyu': `Add/Sub: 2-digit, 10口, 20字
×: 実+法 = 4 digits (10)
÷: 法+商 = 4 digits (10)
Time: 20 min; Pass ≥ 120/200.
shuzan.jp`,
'6-kyu': `Add/Sub: 10口, 30字
×: 実+法 = 5 digits (20)
÷: 法+商 = 4 digits (20)
Time: 30 min; Pass ≥ 210/300.
shuzan.jp`,
'5-kyu': `Add/Sub: 10口, 40字
×: 実+法 = 6 digits (20)
÷: 法+商 = 5 digits (20)
Time: 30 min; Pass ≥ 210/300.
shuzan.jp`,
'4-kyu': `Add/Sub: 10口, 50字
×: 実+法 = 7 digits (20)
÷: 法+商 = 6 digits (20)
Time: 30 min; Pass ≥ 210/300.
shuzan.jp`,
'Pre-3-kyu': `Add/Sub: 10口, 50字 ×5題 and 10口, 60字 ×5題 (total 10)
×: 実+法 = 7 digits (20)
÷: 法+商 = 6 digits (20)
Time: 30 min; Pass ≥ 240/300.
shuzan.jp`,
'3-kyu': `Add/Sub: 10口, 60字
×: 実+法 = 7 digits (20)
÷: 法+商 = 6 digits (20)
Time: 30 min; Pass ≥ 240/300.
shuzan.jp`,
'Pre-2-kyu': `Add/Sub: 10口, 70字
×: 実+法 = 8 digits (20)
÷: 法+商 = 7 digits (20)
Time: 30 min; Pass ≥ 240/300.
shuzan.jp`,
'2-kyu': `Add/Sub: 10口, 80字
×: 実+法 = 9 digits (20)
÷: 法+商 = 8 digits (20)
Time: 30 min; Pass ≥ 240/300.
shuzan.jp`,
'Pre-1-kyu': `Add/Sub: 10口, 90字
×: 実+法 = 10 digits (20)
÷: 法+商 = 9 digits (20)
Time: 30 min; Pass ≥ 240/300.
shuzan.jp`,
'1-kyu': `Add/Sub: 10口, 100字
×: 実+法 = 11 digits (20)
÷: 法+商 = 10 digits (20)
Time: 30 min; Pass ≥ 240/300.
shuzan.jp`,
} as const
export type KyuLevel = keyof typeof kyuLevelDetails

View File

@@ -14,59 +14,59 @@ export interface GameTheme {
}
/**
* Standard theme presets
* These use Panda CSS's color system and provide consistent styling
* Standard theme presets with vibrant gradients
* Updated for eye-catching game cards on the homepage
*/
export const GAME_THEMES = {
blue: {
color: 'blue',
gradient: 'linear-gradient(135deg, #dbeafe, #bfdbfe)', // blue.100 to blue.200
borderColor: '#bfdbfe', // blue.200
gradient: 'linear-gradient(135deg, #4facfe 0%, #00f2fe 100%)', // Vibrant cyan
borderColor: '#00f2fe',
},
purple: {
color: 'purple',
gradient: 'linear-gradient(135deg, #e9d5ff, #ddd6fe)', // purple.100 to purple.200
borderColor: '#ddd6fe', // purple.200
gradient: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)', // Vibrant purple
borderColor: '#764ba2',
},
green: {
color: 'green',
gradient: 'linear-gradient(135deg, #d1fae5, #a7f3d0)', // green.100 to green.200
borderColor: '#a7f3d0', // green.200
gradient: 'linear-gradient(135deg, #43e97b 0%, #38f9d7 100%)', // Vibrant green/teal
borderColor: '#38f9d7',
},
teal: {
color: 'teal',
gradient: 'linear-gradient(135deg, #ccfbf1, #99f6e4)', // teal.100 to teal.200
borderColor: '#99f6e4', // teal.200
gradient: 'linear-gradient(135deg, #11998e 0%, #38ef7d 100%)', // Vibrant teal
borderColor: '#38ef7d',
},
indigo: {
color: 'indigo',
gradient: 'linear-gradient(135deg, #e0e7ff, #c7d2fe)', // indigo.100 to indigo.200
borderColor: '#c7d2fe', // indigo.200
gradient: 'linear-gradient(135deg, #5f72bd 0%, #9b23ea 100%)', // Vibrant indigo
borderColor: '#9b23ea',
},
pink: {
color: 'pink',
gradient: 'linear-gradient(135deg, #fce7f3, #fbcfe8)', // pink.100 to pink.200
borderColor: '#fbcfe8', // pink.200
gradient: 'linear-gradient(135deg, #f093fb 0%, #f5576c 100%)', // Vibrant pink
borderColor: '#f5576c',
},
orange: {
color: 'orange',
gradient: 'linear-gradient(135deg, #ffedd5, #fed7aa)', // orange.100 to orange.200
borderColor: '#fed7aa', // orange.200
gradient: 'linear-gradient(135deg, #fa709a 0%, #fee140 100%)', // Vibrant orange/coral
borderColor: '#fee140',
},
yellow: {
color: 'yellow',
gradient: 'linear-gradient(135deg, #fef3c7, #fde68a)', // yellow.100 to yellow.200
borderColor: '#fde68a', // yellow.200
gradient: 'linear-gradient(135deg, #ffd89b 0%, #19547b 100%)', // Vibrant yellow/blue
borderColor: '#ffd89b',
},
red: {
color: 'red',
gradient: 'linear-gradient(135deg, #fee2e2, #fecaca)', // red.100 to red.200
borderColor: '#fecaca', // red.200
gradient: 'linear-gradient(135deg, #f85032 0%, #e73827 100%)', // Vibrant red
borderColor: '#e73827',
},
gray: {
color: 'gray',
gradient: 'linear-gradient(135deg, #f3f4f6, #e5e7eb)', // gray.100 to gray.200
borderColor: '#e5e7eb', // gray.200
gradient: 'linear-gradient(135deg, #868f96 0%, #596164 100%)', // Vibrant gray
borderColor: '#596164',
},
} as const satisfies Record<string, GameTheme>

View File

@@ -1,6 +1,6 @@
{
"name": "soroban-monorepo",
"version": "4.30.0",
"version": "4.63.4",
"private": true,
"description": "Beautiful Soroban Flashcard Generator - Monorepo",
"workspaces": [

View File

@@ -1,3 +1,305 @@
# [2.1.0](https://github.com/antialias/soroban-abacus-flashcards/compare/abacus-react-v2.0.0...abacus-react-v2.1.0) (2025-10-20)
### Bug Fixes
* **levels:** add fixed height to entire level display pane ([200b26c](https://github.com/antialias/soroban-abacus-flashcards/commit/200b26c2cd35d1d637ede9dcfc3dbbc7f3f19320))
* **levels:** increase container height to prevent abacus clipping ([cd5c15a](https://github.com/antialias/soroban-abacus-flashcards/commit/cd5c15aeb260c568fe7ad9b6a4f51c4d6498b2b8))
* **levels:** only animate abacus, not container with background/border ([c80477d](https://github.com/antialias/soroban-abacus-flashcards/commit/c80477d24877ddada5f3f4405abbf05e1d753b5d))
* **levels:** reduce Dan scale and container height to prevent clipping ([563136f](https://github.com/antialias/soroban-abacus-flashcards/commit/563136fb79fa10b2af3a119bf0f861e3b0812b2e))
* **levels:** reduce max scale factor to allow more compact container ([ead9ee9](https://github.com/antialias/soroban-abacus-flashcards/commit/ead9ee9589aa4d7376e9385da5da53a6b444858a))
* **levels:** reduce scale factor variation to minimize margin differences ([abb647c](https://github.com/antialias/soroban-abacus-flashcards/commit/abb647ce40b8f9d0c8268ab18c139324ae3195c5))
* **levels:** revert delayed column change, keep overflow hidden ([22f00f5](https://github.com/antialias/soroban-abacus-flashcards/commit/22f00f59f5facc36a846408dcd196ec54ea676b1))
* **levels:** stabilize slider position and prevent abacus clipping ([09004dc](https://github.com/antialias/soroban-abacus-flashcards/commit/09004dc2c055031ee2f71c964ceee6f7b1d42ecd))
### Features
* **abacus-react:** export StandaloneBead component wired to AbacusDisplayContext ([0146ce1](https://github.com/antialias/soroban-abacus-flashcards/commit/0146ce1e67da27a24cbaa8338ba6a1a6befd6bd3))
* **levels:** add hover interaction and smooth React Spring transitions ([fd2b633](https://github.com/antialias/soroban-abacus-flashcards/commit/fd2b6338a84c3bbc683eff216a8da3b155749f0f))
* **levels:** redesign slider with abacus-themed beads ([f3dce84](https://github.com/antialias/soroban-abacus-flashcards/commit/f3dce84532fa706e4ec9551facde2055a060ee13))
* **levels:** replace slider thumb with diamond-shaped abacus beads ([0fbde53](https://github.com/antialias/soroban-abacus-flashcards/commit/0fbde53039d3ea000c6a3be492b733479e7bf47c))
# [2.0.0](https://github.com/antialias/soroban-abacus-flashcards/compare/abacus-react-v1.8.0...abacus-react-v2.0.0) (2025-10-20)
### Bug Fixes
* add dark color for abacus numerals ([73ff32c](https://github.com/antialias/soroban-abacus-flashcards/commit/73ff32c2432beb62710e57aa8b3b4793eca43fda)), closes [#1f2937](https://github.com/antialias/soroban-abacus-flashcards/issues/1f2937)
* add POST handler for join requests API endpoint ([d3e5cdf](https://github.com/antialias/soroban-abacus-flashcards/commit/d3e5cdfc54f2749f27c6f8b8db854a8d0b6029f8))
* add Typst to Docker image for flashcard generation ([d9a7694](https://github.com/antialias/soroban-abacus-flashcards/commit/d9a769403187bf70fb069be7ffe77417a62271a5))
* allow join with pending invitation for restricted rooms ([85b2cf9](https://github.com/antialias/soroban-abacus-flashcards/commit/85b2cf98167ccf632ab634a94eb436e1eb584614))
* allow password retry when joining via share link ([e469363](https://github.com/antialias/soroban-abacus-flashcards/commit/e469363699071610a35e0b5c507d0e15e29daa44))
* **api:** add 'math-sprint' to settings endpoint validation ([d790e5e](https://github.com/antialias/soroban-abacus-flashcards/commit/d790e5e278f81686077dbe3ef4adca49574ae434)), closes [#1](https://github.com/antialias/soroban-abacus-flashcards/issues/1)
* **api:** include members and memberPlayers in room creation response ([8320d9e](https://github.com/antialias/soroban-abacus-flashcards/commit/8320d9e730e2b9964e509847dfa504a78b721b5a))
* **arcade-rooms:** navigate to invite link after room creation ([1922b21](https://github.com/antialias/soroban-abacus-flashcards/commit/1922b2122bb1bc4aeada7526d8c46aa89024bb00))
* **arcade:** add defensive checks and update test fixtures ([a93d981](https://github.com/antialias/soroban-abacus-flashcards/commit/a93d981d1ab3abed019b28cebe87525191313cc7))
* **arcade:** add host-only game selection with clear messaging ([22df1b0](https://github.com/antialias/soroban-abacus-flashcards/commit/22df1b0b661efe69fac1a6bd716531c904757412))
* **arcade:** add host-only game selection with clear messaging ([c0680ca](https://github.com/antialias/soroban-abacus-flashcards/commit/c0680cad0fa26af0933e93a06c50317bf443cc7d))
* **arcade:** add Number Guesser to game config helpers ([7d1a351](https://github.com/antialias/soroban-abacus-flashcards/commit/7d1a351ed6a1442ae34f6b75d46039bfa77a921b))
* **arcade:** allow room creator to rejoin restricted/approval rooms ([654ba19](https://github.com/antialias/soroban-abacus-flashcards/commit/654ba19ccca595d34ad205c036c18afb99a494c7))
* **arcade:** delete old session when room game changes ([98a3a25](https://github.com/antialias/soroban-abacus-flashcards/commit/98a3a2573db51899c41ba02796895d676c4e16ef))
* **arcade:** implement settings persistence for matching game ([08fe432](https://github.com/antialias/soroban-abacus-flashcards/commit/08fe4326a6a7c484b9058a241f4ff79b3fb5125f))
* **arcade:** only notify room creator of join requests ([bc571e3](https://github.com/antialias/soroban-abacus-flashcards/commit/bc571e3d0d11fe4142680132d551e25ca626d950))
* **arcade:** preserve game settings when returning to game selection ([0ee7739](https://github.com/antialias/soroban-abacus-flashcards/commit/0ee7739091d60580d2f98cfe288b8586b03348f3))
* **arcade:** preserve gameConfig when switching games ([2273c71](https://github.com/antialias/soroban-abacus-flashcards/commit/2273c71a872a5122d0b2023835fe30640106048e))
* **arcade:** prevent empty update in settings API when only gameConfig changes ([ffb626f](https://github.com/antialias/soroban-abacus-flashcards/commit/ffb626f4038fd32d0f40dba8d83ae4d881d698d0))
* **arcade:** prevent gameConfig from being overwritten when switching games ([a89d3a9](https://github.com/antialias/soroban-abacus-flashcards/commit/a89d3a970137471e2652de992c45370dbb97416d))
* **arcade:** prevent server-side loading of React components ([784793b](https://github.com/antialias/soroban-abacus-flashcards/commit/784793ba244731edf45391da44588a978b137abe))
* **arcade:** read nested gameConfig correctly when creating sessions ([94ef392](https://github.com/antialias/soroban-abacus-flashcards/commit/94ef39234d362b82e032cb69d3561b9fcb436eaf))
* **arcade:** remove broken query param from game URLs ([87631af](https://github.com/antialias/soroban-abacus-flashcards/commit/87631af6788bd7b42e671374e55ec0ad8435900c))
* **arcade:** remove legacy master-organizer placeholder ([76d207e](https://github.com/antialias/soroban-abacus-flashcards/commit/76d207e2e5244f84bc0d76fe3d753034f1991228))
* **arcade:** resolve TypeScript errors in game config helpers ([04c9944](https://github.com/antialias/soroban-abacus-flashcards/commit/04c9944f2ed1025f5a4ece61761889edd08cc60d))
* **build:** resolve Docker build failures preventing deployment ([7801dbb](https://github.com/antialias/soroban-abacus-flashcards/commit/7801dbb25fb0a33429c70f11294264f7238ce7a4))
* **card-sorting:** center AbacusReact SVGs in card tiles ([26edec1](https://github.com/antialias/soroban-abacus-flashcards/commit/26edec1bbf038264405ec9d161edcd18f67a6fc6))
* **card-sorting:** faithfully port UI/UX from Python original ([c92076f](https://github.com/antialias/soroban-abacus-flashcards/commit/c92076f232930aa12d9a0230fa745b73b5cc04d9)), closes [#2c5f76](https://github.com/antialias/soroban-abacus-flashcards/issues/2c5f76) [#1976d2](https://github.com/antialias/soroban-abacus-flashcards/issues/1976d2)
* **card-sorting:** increase card tile sizes to contain abacuses ([d2a3b7a](https://github.com/antialias/soroban-abacus-flashcards/commit/d2a3b7ae2e3f6819b8d9ace32be22f04f748d1bc))
* **card-sorting:** increase SVG size to fill card containers ([cf9d893](https://github.com/antialias/soroban-abacus-flashcards/commit/cf9d893f3fdbef6e91cd0ba283d602b9215569f1))
* **card-sorting:** match game selector background to other games ([db62519](https://github.com/antialias/soroban-abacus-flashcards/commit/db62519f9beb0b4bc6120e1fd5ec251cfde5c3c1)), closes [#ccfbf1](https://github.com/antialias/soroban-abacus-flashcards/issues/ccfbf1) [#99f6e4](https://github.com/antialias/soroban-abacus-flashcards/issues/99f6e4)
* **card-sorting:** match Python card layout with flex wrap ([9679d68](https://github.com/antialias/soroban-abacus-flashcards/commit/9679d68154ac8b6a2f905ec7d17a34b39bc00237))
* **card-sorting:** position slots flow horizontally with wrap ([e14ffe4](https://github.com/antialias/soroban-abacus-flashcards/commit/e14ffe44d66d0c97bc0cc4e0c255698e88ce723a))
* **card-sorting:** use blue gradient matching other game cards ([bdb84f5](https://github.com/antialias/soroban-abacus-flashcards/commit/bdb84f5d909542060fa886a83a5af62c4a785a98))
* clear hover state in CLEAR_MISMATCH for clean turn transitions ([43f7c92](https://github.com/antialias/soroban-abacus-flashcards/commit/43f7c92f6d61616e18439c995dc4a4848e233520))
* clear hover state on turn changes and game transitions ([6fd425c](https://github.com/antialias/soroban-abacus-flashcards/commit/6fd425ce85ddbf5e0125f757dc9886915fb6f749))
* **complement-race:** add missing AI commentary cooldown updates ([357aa30](https://github.com/antialias/soroban-abacus-flashcards/commit/357aa30618f80d659ae515f94b7b9254bb458910))
* **complement-race:** add missing useEffect import ([3054130](https://github.com/antialias/soroban-abacus-flashcards/commit/30541304dd0f0801860dd62967f7f7cae717bcdd))
* **complement-race:** add missing useRef import ([d43829a](https://github.com/antialias/soroban-abacus-flashcards/commit/d43829ad48f7ee879a46879f5e6ac1256db1f564))
* **complement-race:** add pressure decay system and improve logging ([66992e8](https://github.com/antialias/soroban-abacus-flashcards/commit/66992e877065a42d00379ef8fae0a6e252b0ffcb))
* **complement-race:** balance AI speeds to match original implementation ([054f0c0](https://github.com/antialias/soroban-abacus-flashcards/commit/054f0c0d235dc2b0042a0f6af48840d23a4c5ff8))
* **complement-race:** clear input state on question transitions ([5872030](https://github.com/antialias/soroban-abacus-flashcards/commit/587203056a1e1692348805eb0de909d81d16e158))
* **complement-race:** correct passenger boarding to use multiplayer fields ([7ed1b94](https://github.com/antialias/soroban-abacus-flashcards/commit/7ed1b94b8fa620cb4f64ba43e160ef511704f3ce))
* **complement-race:** counter-flip AI speech bubbles to make text readable ([07d5607](https://github.com/antialias/soroban-abacus-flashcards/commit/07d5607218aee03e813eceff5d161a7838d66bcb))
* **complement-race:** flip AI racers to face right in practice mode ([ebfff1a](https://github.com/antialias/soroban-abacus-flashcards/commit/ebfff1a62fd104d531a8158345c8c012ec8a55d3))
* **complement-race:** flip player avatar to face right in practice mode ([fa6b3b6](https://github.com/antialias/soroban-abacus-flashcards/commit/fa6b3b69d5a4a7eb70f8c18fc8c122c54c4d504a))
* **complement-race:** implement client-side momentum with continuous decay for smooth train movement ([ea19ff9](https://github.com/antialias/soroban-abacus-flashcards/commit/ea19ff918bc70ad3eb0339e18dbd32195f34816e))
* **complement-race:** improve AI speech bubble positioning ([6e436db](https://github.com/antialias/soroban-abacus-flashcards/commit/6e436db5e709d944ebffed6936ea1f8e4bd2e19e))
* **complement-race:** reduce initial momentum from 50 to 10 to prevent train sailing past first station ([5f146b0](https://github.com/antialias/soroban-abacus-flashcards/commit/5f146b0daf74d54e1c7b9a57d3a2f37e73849ff2))
* **complement-race:** remove dual game loop conflict preventing route progression ([84d42e2](https://github.com/antialias/soroban-abacus-flashcards/commit/84d42e22ac0cdd25e87e45dc698029ad7ed78559))
* **complement-race:** resolve TypeScript errors in state adapter ([59abcca](https://github.com/antialias/soroban-abacus-flashcards/commit/59abcca4c4192ca28944fa1fa366791d557c1c27))
* **complement-race:** restore smooth train movement with client-side game loop ([46a80cb](https://github.com/antialias/soroban-abacus-flashcards/commit/46a80cbcc8ec39224d4edaf540da25611d48fbdd))
* **complement-race:** show new passengers when route changes ([ec1c8ed](https://github.com/antialias/soroban-abacus-flashcards/commit/ec1c8ed263844f56477c1f709041339b42b48f4e))
* **complement-race:** track physical car indices to prevent boarding issues ([53bbae8](https://github.com/antialias/soroban-abacus-flashcards/commit/53bbae84af7317d5e12109db2054cc70ca5bea27))
* **complement-race:** track previous position to detect route threshold crossing ([a6c20aa](https://github.com/antialias/soroban-abacus-flashcards/commit/a6c20aab3b245d9893808d188d16a35ab80cfca9))
* **complement-race:** train now moves in sprint mode ([54b46e7](https://github.com/antialias/soroban-abacus-flashcards/commit/54b46e771e654721e7fabb1f45ecd45daf8e447f))
* **complement-race:** update passenger display when state changes ([5116364](https://github.com/antialias/soroban-abacus-flashcards/commit/511636400c19776b58c6bddf8f7c9cc398a05236))
* **complement-race:** use active local players pattern from navbar ([71cdc34](https://github.com/antialias/soroban-abacus-flashcards/commit/71cdc342c97ca53b5e7e4202d4d344199e8ddd98))
* **complement-race:** use local player emoji instead of first active player ([76eb051](https://github.com/antialias/soroban-abacus-flashcards/commit/76eb0517c202d1b9160b49dec0b99ff4972daff2))
* correct AbacusReact API usage and add structural styling ([247377f](https://github.com/antialias/soroban-abacus-flashcards/commit/247377fca35ee3433e02ad594ecc1c4f391f0143)), closes [#fbbf24](https://github.com/antialias/soroban-abacus-flashcards/issues/fbbf24) [#a78](https://github.com/antialias/soroban-abacus-flashcards/issues/a78)
* create arcade sessions on room join to enable config changes ([c29501f](https://github.com/antialias/soroban-abacus-flashcards/commit/c29501f6663cb6063f2ddef8b3fdb14c31927639))
* **db:** add 'math-sprint' to database schema enums ([7b112a9](https://github.com/antialias/soroban-abacus-flashcards/commit/7b112a98babe782d4c254ef18a0295e7cbf8fefa)), closes [#1](https://github.com/antialias/soroban-abacus-flashcards/issues/1)
* **deployment:** pass git info to Docker build for deployment info modal ([4b04e43](https://github.com/antialias/soroban-abacus-flashcards/commit/4b04e43ff8c9e9f239d7f5e306aab338b535296f))
* **docker:** add packages/templates for Typst flashcard generation ([1417722](https://github.com/antialias/soroban-abacus-flashcards/commit/14177224380b8c37413123bee344c9b762055a15))
* **docker:** add qpdf for PDF linearization and validation ([c92ff39](https://github.com/antialias/soroban-abacus-flashcards/commit/c92ff3971c853e4e55ccd632ff3ee292fcce8315))
* **docker:** bypass PEP 668 externally-managed-environment error ([bb59c61](https://github.com/antialias/soroban-abacus-flashcards/commit/bb59c61638e60b0678043e954e044d9390f88e7f))
* **docker:** copy core package with Python scripts to production image ([33e9ad2](https://github.com/antialias/soroban-abacus-flashcards/commit/33e9ad2f79b591f1c5ee57a6691e1bcf48420859))
* **docker:** include Panda CSS styled-system in production image ([57fabff](https://github.com/antialias/soroban-abacus-flashcards/commit/57fabffe605d953b4a4d7e05032401cbf1ab2d14))
* **docker:** install py3-pip for Python dependency installation ([0f55909](https://github.com/antialias/soroban-abacus-flashcards/commit/0f55909533414bdc07f113b93bb8bfa21367959b))
* **docker:** install Python dependencies for flashcard generation ([c9b7e92](https://github.com/antialias/soroban-abacus-flashcards/commit/c9b7e92f39ee7aa7f13606c2836763144df102e7))
* **docker:** remove reference to deleted @soroban/client package ([2953ef8](https://github.com/antialias/soroban-abacus-flashcards/commit/2953ef8917f7b13f6eb562eb7d58d14179a718da))
* exclude dist from TypeScript compilation and add missing type import ([b7f1d5a](https://github.com/antialias/soroban-abacus-flashcards/commit/b7f1d5a5696888bb4fbf6d5da14ca333de0f0167))
* hide hover avatar for current user's own player ([dba42b5](https://github.com/antialias/soroban-abacus-flashcards/commit/dba42b59257f2422ec8f31a46c222393fcc157d4))
* **homepage:** adjust mini abacus container height ([c4066d6](https://github.com/antialias/soroban-abacus-flashcards/commit/c4066d687925bbe7737ebfeefdada7365ff97c6c))
* **homepage:** correct positioning of progression arrows in Your Journey section ([3fff9ef](https://github.com/antialias/soroban-abacus-flashcards/commit/3fff9ef140bf1f462042f8319ed6c5e2a376e4ba))
* **homepage:** fix MiniAbacus runtime error and improve sizing ([1fa0df8](https://github.com/antialias/soroban-abacus-flashcards/commit/1fa0df85f7d3988cbc61701d89476419ccf0a13c))
* **homepage:** improve text contrast in Your Journey section ([24d1200](https://github.com/antialias/soroban-abacus-flashcards/commit/24d120004dccecc1ce2f08c1b73eec902868fb23))
* **homepage:** use correct AbacusReact API and fix clipping/styling issues ([1432afd](https://github.com/antialias/soroban-abacus-flashcards/commit/1432afd6e6bd547bd0da76dbeea1c2b71244826f))
* **homepage:** use direct conditionals for mini abacus padding ([38ef16a](https://github.com/antialias/soroban-abacus-flashcards/commit/38ef16a8f91f8ab4ad0d717b0321e2002636fafb))
* **homepage:** use explicit RGBA colors for Your Journey text ([9c51cc9](https://github.com/antialias/soroban-abacus-flashcards/commit/9c51cc94eec4efcab9c0b9d1190f5b79c0c7d365))
* **homepage:** use inline styles for journey level colors ([5d85e89](https://github.com/antialias/soroban-abacus-flashcards/commit/5d85e898d65d44d8d09bee952fad44b5a9c0cd20)), closes [#4ade80](https://github.com/antialias/soroban-abacus-flashcards/issues/4ade80) [#60a5](https://github.com/antialias/soroban-abacus-flashcards/issues/60a5) [#a78](https://github.com/antialias/soroban-abacus-flashcards/issues/a78) [#fbbf24](https://github.com/antialias/soroban-abacus-flashcards/issues/fbbf24)
* **homepage:** use inline styles for Your Journey text contrast ([8e51390](https://github.com/antialias/soroban-abacus-flashcards/commit/8e5139001818d7013e1b2654ac707f7429316d58)), closes [#e5e7](https://github.com/antialias/soroban-abacus-flashcards/issues/e5e7) [#e5e7](https://github.com/antialias/soroban-abacus-flashcards/issues/e5e7) [#9ca3](https://github.com/antialias/soroban-abacus-flashcards/issues/9ca3) [#d1d5](https://github.com/antialias/soroban-abacus-flashcards/issues/d1d5)
* **home:** use Panda CSS token() for dynamic colors and center arrows properly ([d52ba63](https://github.com/antialias/soroban-abacus-flashcards/commit/d52ba6373a4577655dc1e5f5ff4926af7f7d96c3))
* improve authorization error handling and add missing decline invitation endpoint ([97669ad](https://github.com/antialias/soroban-abacus-flashcards/commit/97669ad084b077cbf6f33b570710016ba666cdb6))
* improve join request approval error handling with actionable messages ([57bf846](https://github.com/antialias/soroban-abacus-flashcards/commit/57bf8460c8ecff374355bfb93f4b06dfbb148273))
* improve kicked modal message for retired room ejections ([f865ce1](https://github.com/antialias/soroban-abacus-flashcards/commit/f865ce16ecf7648e41549795c8137f4fc33e34ac))
* join user socket channel to receive approval notifications ([7d08fdd](https://github.com/antialias/soroban-abacus-flashcards/commit/7d08fdd90643920857eda09998ac01afbae74154))
* **levels:** use correct AbacusReact API with direct props ([892b377](https://github.com/antialias/soroban-abacus-flashcards/commit/892b377eb3bbd555dd2566bf58e946e9faa7b9f6))
* **levels:** use correct dark mode styling from homepage + docs update ([c38767f](https://github.com/antialias/soroban-abacus-flashcards/commit/c38767f4d399fa2caa5cd4e0185689d0207fbdaf))
* **matching:** add settings persistence to matching game ([00dcb87](https://github.com/antialias/soroban-abacus-flashcards/commit/00dcb872b7e70bdb7de301b56fe42195e6ee923f))
* **matching:** apply turn indicators to arcade version too ([e6f96a8](https://github.com/antialias/soroban-abacus-flashcards/commit/e6f96a8b992c15f868ac5b1c1ac36b32caf433ed))
* **matching:** make MemoryGrid generic to support different card types ([dcda826](https://github.com/antialias/soroban-abacus-flashcards/commit/dcda826b9a7cab6614638f8661f288e9fa010324))
* **matching:** only apply turn indicator when game is active ([cb4c061](https://github.com/antialias/soroban-abacus-flashcards/commit/cb4c061d11433799a0091f4a958371ff7cef7a00))
* **matching:** replace mismatch banner with card shake animation ([804096f](https://github.com/antialias/soroban-abacus-flashcards/commit/804096fd8a0709750114ab01a1015f9b5fc28b63))
* **matching:** use UUID instead of numeric index for scores ([5036cb0](https://github.com/antialias/soroban-abacus-flashcards/commit/5036cb00b6eff91cfa52b5babb7e5a91ff7e18b3))
* **math-sprint:** remove unused import and autoFocus attribute ([51593eb](https://github.com/antialias/soroban-abacus-flashcards/commit/51593eb44f93e369d6a773ee80e5f5cf50f3be67))
* **memory-quiz:** fix playMode persistence by updating validator ([de0efd5](https://github.com/antialias/soroban-abacus-flashcards/commit/de0efd59321ec779cddb900724035884290419b7))
* **memory-quiz:** persist playMode setting across game switches ([487ca7f](https://github.com/antialias/soroban-abacus-flashcards/commit/487ca7fba62e370c85bc3779ca8a96eb2c2cc3e3))
* **memory-quiz:** prevent duplicate card processing from optimistic updates ([51676fc](https://github.com/antialias/soroban-abacus-flashcards/commit/51676fc15f5bc15cdb43393d3e66f7c5a0667868))
* **memory-quiz:** prevent input lag during rapid typing in room mode ([b45139b](https://github.com/antialias/soroban-abacus-flashcards/commit/b45139b588d0ab6df4d6c1003c1b65b634e2b041))
* **memory-quiz:** scope game settings by game name for proper persistence ([3dfe54f](https://github.com/antialias/soroban-abacus-flashcards/commit/3dfe54f1cb89bd636e763e1c5acb03776f97c011))
* **memory-quiz:** synchronize card display across all players in multiplayer ([472f201](https://github.com/antialias/soroban-abacus-flashcards/commit/472f201088d82f92030273fadaf8a8e488820d6c))
* **migrations:** add migration 0009 for display_password column ([040d749](https://github.com/antialias/soroban-abacus-flashcards/commit/040d7495a0801076b252d2574023f5323540db1a))
* **moderation:** don't show pending invitation for users already in room ([fae5920](https://github.com/antialias/soroban-abacus-flashcards/commit/fae5920e2fda910f8db724103a837537b1063ac7))
* **moderation:** improve access mode settings UX ([dd9e657](https://github.com/antialias/soroban-abacus-flashcards/commit/dd9e657db85752b32ff91ae1b33a0bf7a7628e07))
* move invitations into nav and filter out current/banned rooms ([cfaf82b](https://github.com/antialias/soroban-abacus-flashcards/commit/cfaf82b2cc93c6da1dcceb5aea8d0bd2c7b14cea))
* **nav:** add delay to hamburger menu hover to prevent premature closing ([95cd72e](https://github.com/antialias/soroban-abacus-flashcards/commit/95cd72e9bf410c7772999a17fae88719dd6e404e))
* **nav:** add z-index to turn labels to prevent avatar overlap ([7c294da](https://github.com/antialias/soroban-abacus-flashcards/commit/7c294dafff4e4d70831e12897aec06092cd3ff3f))
* **nav:** close hamburger menu when nested dropdown closes and mouse not hovering ([7d65212](https://github.com/antialias/soroban-abacus-flashcards/commit/7d652126d04239d1971b8aa302137295a3dde90b))
* **nav:** enable tooltips for local players during gameplay ([5499700](https://github.com/antialias/soroban-abacus-flashcards/commit/54997007b8485c4d7c605d5b6179cedef3fdc9c7))
* **nav:** improve readability of turn label text ([bbd1da0](https://github.com/antialias/soroban-abacus-flashcards/commit/bbd1da02b5e94d625844d9301c617da98d01868a))
* **nav:** improve text contrast in room info pane ([3e691cb](https://github.com/antialias/soroban-abacus-flashcards/commit/3e691cb06d64e32f65146ffd690fa1c25e9b487d))
* **nav:** navigate to /arcade/room (not /arcade/rooms/{id}) ([1c55f36](https://github.com/antialias/soroban-abacus-flashcards/commit/1c55f3630cb5f07b685555e41baa5a49314f15a3))
* **nav:** navigate to room after creation from (+) menu ([21e6e33](https://github.com/antialias/soroban-abacus-flashcards/commit/21e6e33173e7939102a7e6d6a7bd5168a97a49d6))
* **nav:** prevent hamburger menu from closing when toggling Style dropdown ([a898fbc](https://github.com/antialias/soroban-abacus-flashcards/commit/a898fbc187bf5286f63719420e0e98654ef25bb3))
* **nav:** prevent style dropdown from closing hamburger menu ([560a052](https://github.com/antialias/soroban-abacus-flashcards/commit/560a05266e15b51089cfd127b4ebe0990f04e64d))
* **nav:** prevent turn label text from being obscured ([c4b00dd](https://github.com/antialias/soroban-abacus-flashcards/commit/c4b00dd679aa2f87ae6b84a37e5b6d0d38113606))
* **nav:** properly prevent nested style dropdown from closing hamburger menu ([c5b6a82](https://github.com/antialias/soroban-abacus-flashcards/commit/c5b6a82ca42e0f9b381f2086e825e3ce36d738a9))
* **nav:** remove animation/enlargement from network player turn indicator ([53079ed](https://github.com/antialias/soroban-abacus-flashcards/commit/53079ede13ce9734cd3e702f27e6cb8f7fff626e))
* **nav:** remove blue gradient background from network players ([2881aff](https://github.com/antialias/soroban-abacus-flashcards/commit/2881affecca16afefdeb05aecdbb3648cba05691))
* **nav:** remove opacity reduction from local players ([5215af8](https://github.com/antialias/soroban-abacus-flashcards/commit/5215af801fc7e7412dc1c84e6abf1231f7670bfb))
* **nav:** remove play arrow badge from turn indicators ([80cfc10](https://github.com/antialias/soroban-abacus-flashcards/commit/80cfc10f7887533b20d663f320696917e1856899))
* **nav:** update types for registry games with nullable gameName ([a51e539](https://github.com/antialias/soroban-abacus-flashcards/commit/a51e539d023681daf639ec104e79079c8ceec98e))
* **number-guesser:** add turn indicators, error feedback, and fix player ordering ([9f62623](https://github.com/antialias/soroban-abacus-flashcards/commit/9f626236845493ef68e1b3626e80efa35637b449))
* pixel-perfect alignment across all nav elements ([fa78a2c](https://github.com/antialias/soroban-abacus-flashcards/commit/fa78a2c001f3530b0a0929411e6e0addbc0abda0))
* **player-config:** correct label positioning in player settings dialog ([554cc40](https://github.com/antialias/soroban-abacus-flashcards/commit/554cc4063bc756c9c9cd1adf0c1964d3f2f6151b))
* populate session activePlayers from room members on join ([2d00939](https://github.com/antialias/soroban-abacus-flashcards/commit/2d00939f1b59a10d271f82098c1b88acb2245ce1))
* prevent duplicate arcade sessions per room ([4cc3de5](https://github.com/antialias/soroban-abacus-flashcards/commit/4cc3de5f43711bb2ffe9b10052108b27bba6889c))
* remove duplicate ModerationNotifications causing double toasts ([c6886a0](https://github.com/antialias/soroban-abacus-flashcards/commit/c6886a0e59b3cbf051a828e0157495101cd8c823))
* replace isLocked with accessMode and add bcryptjs ([a74b96b](https://github.com/antialias/soroban-abacus-flashcards/commit/a74b96bb6fe331d27f3d27b8f77a3ce32b254bce))
* replace last remaining isLoading with isPending in CreateRoomModal ([85d13cc](https://github.com/antialias/soroban-abacus-flashcards/commit/85d13cc552cfe2e825f8ea20c7db00d666599134))
* replace native alerts with inline confirmations in ModerationPanel ([ebe123e](https://github.com/antialias/soroban-abacus-flashcards/commit/ebe123ed7edf24fbc7b8765ed709455a8513d6d5))
* reset join request toast state when moderation event cleared ([6beb58a](https://github.com/antialias/soroban-abacus-flashcards/commit/6beb58a7b8f8e1841c71729a3517ab459e924aa9))
* resolve Memory Quiz room-based multiplayer validation issues ([2ffeade](https://github.com/antialias/soroban-abacus-flashcards/commit/2ffeade43710b5f3fff9991cc84763bbdbf97010))
* resolve TypeScript errors in MemoryGrid and StandardGameLayout ([cabbc82](https://github.com/antialias/soroban-abacus-flashcards/commit/cabbc821955d70f118630dc21a9fcbb6d340f278))
* **room-data:** update query cache when gameConfig changes ([7cea297](https://github.com/antialias/soroban-abacus-flashcards/commit/7cea297095b78d74f5b77ca83489ec1be684a486))
* **rooms:** add real-time ownership transfer updates via WebSocket ([c00cfa3](https://github.com/antialias/soroban-abacus-flashcards/commit/c00cfa3de011720f3399fa340182b347f7e0d456))
* **room:** update GAME_TYPE_TO_NAME mapping for memory-quiz ([4afa171](https://github.com/antialias/soroban-abacus-flashcards/commit/4afa171af212902120599b3d68f58cfbdf7820b0))
* set color on abacus container div for numeral visibility ([cd47960](https://github.com/antialias/soroban-abacus-flashcards/commit/cd4796024e41f731ae5d83c82f6582e19d6eaf99)), closes [#1f2937](https://github.com/antialias/soroban-abacus-flashcards/issues/1f2937)
* show initial value and improve numeral contrast ([1b57f6d](https://github.com/antialias/soroban-abacus-flashcards/commit/1b57f6ddecf3a118f2e4fadd1a91be1256f5a034)), closes [#fbbf24](https://github.com/antialias/soroban-abacus-flashcards/issues/fbbf24)
* simplify abacus pane with light background ([30f48ab](https://github.com/antialias/soroban-abacus-flashcards/commit/30f48ab8976976688e089b07ece7fdae6d7ada79))
* **socket-io:** update import path for socket-server module ([1a64dec](https://github.com/antialias/soroban-abacus-flashcards/commit/1a64decf5afe67c16e1aec283262ffa6132dcd83))
* stack game title dropdown ABOVE room pane, not inside it ([7bc815f](https://github.com/antialias/soroban-abacus-flashcards/commit/7bc815fd7dbbb1489a17782b2df0c3fe508dd574))
* **toast:** scope animations to prevent affecting other UI elements ([245ed8a](https://github.com/antialias/soroban-abacus-flashcards/commit/245ed8a625ba848f8cd79d51bfd88600cd77f0b9))
* **tutorial:** correct column index calculation for variable column counts ([bf1ced4](https://github.com/antialias/soroban-abacus-flashcards/commit/bf1ced43f801938b05f01548eea5fe771de1b58f))
* **tutorial:** filter bead highlights when using fewer columns ([4d906ec](https://github.com/antialias/soroban-abacus-flashcards/commit/4d906ec20e90a9b0b3838f9b8428e0c68992f381))
* **tutorial:** reduce tooltip z-index to scroll under nav bar ([47640f3](https://github.com/antialias/soroban-abacus-flashcards/commit/47640f3486c6d4a7107d59bdcce043f76fabbb1d))
* **tutorial:** resolve React hydration error in TutorialPlayer ([c883d9e](https://github.com/antialias/soroban-abacus-flashcards/commit/c883d9e4c1b3a2f52c9d41e3ddce7418399f2649))
* **tutorial:** resolve TypeScript errors in TutorialPlayer ([88f57ce](https://github.com/antialias/soroban-abacus-flashcards/commit/88f57ce6df125142d6ea7feec60c475926bd4929))
* **tutorial:** use correct customStyles API for dark mode frame styling ([fdc882c](https://github.com/antialias/soroban-abacus-flashcards/commit/fdc882cb046e3d8835fbca59841e9af5329bcc52))
* update locked room terminology and allow existing members ([1ddf985](https://github.com/antialias/soroban-abacus-flashcards/commit/1ddf985938d9542fe26e44da58234f3d4e3c9543))
* use app-wide abacus config and remove instruction text ([0a50c73](https://github.com/antialias/soroban-abacus-flashcards/commit/0a50c733b089c7c341f0fdef47da78d1c61a3cb5))
* use color instead of fill for numeral styling ([ea10c16](https://github.com/antialias/soroban-abacus-flashcards/commit/ea10c16811eb969b9963417079c330ae9ff295ba))
* use defaultValue for interactive abacus control ([06aca98](https://github.com/antialias/soroban-abacus-flashcards/commit/06aca986ace4d76b70f2fd2f5e57f66758185b38))
* use useCreateRoom hook instead of nonexistent createRoom from useRoomData ([f7d63b3](https://github.com/antialias/soroban-abacus-flashcards/commit/f7d63b30ac498b63797ae8683a0beb435a1c97b3))
### Code Refactoring
* **db:** remove database schema coupling for game names ([e135d92](https://github.com/antialias/soroban-abacus-flashcards/commit/e135d92abb4d27f646c1fbeff6524a729d107426)), closes [#1](https://github.com/antialias/soroban-abacus-flashcards/issues/1)
### Features
* **abacus-react:** add BigInt support for 30-digit Dan level abacuses ([0ab4cc2](https://github.com/antialias/soroban-abacus-flashcards/commit/0ab4cc288066b75a6ea4371f65098db5c0fc8847))
* add API routes for moderation and invitations ([79a8518](https://github.com/antialias/soroban-abacus-flashcards/commit/79a85185575571cf628d655b0558f8246d2b02c7))
* add backend library functions for room moderation ([84f3c4b](https://github.com/antialias/soroban-abacus-flashcards/commit/84f3c4bcfd258cb2e54b89b55d5162af57b74fe5))
* add common UI components ([cd3115a](https://github.com/antialias/soroban-abacus-flashcards/commit/cd3115aa6d6bb8cf227b3d15d055f27dc5377a00))
* add database schema for room moderation and invitations ([97d1604](https://github.com/antialias/soroban-abacus-flashcards/commit/97d16041dfe33bd817df2472323962dc4e94f8ee))
* add drizzle migration for room_game_configs table ([3bae00b](https://github.com/antialias/soroban-abacus-flashcards/commit/3bae00b9a9dc925039a02fe07d036a2fc5e0fb79))
* add fun automatic player naming system ([249257c](https://github.com/antialias/soroban-abacus-flashcards/commit/249257c6c77d503b48479065664c96c5de36a234))
* add invitation system UI components ([fd3a2d1](https://github.com/antialias/soroban-abacus-flashcards/commit/fd3a2d1f76eb12473bb2b5a33453362d6889d7b0))
* add moderation panel with unban & invite feature ([a2d0169](https://github.com/antialias/soroban-abacus-flashcards/commit/a2d0169f8063f00726ce2769bd5db270cfa82f4d))
* add name generator button and abacus emoji ([07212e4](https://github.com/antialias/soroban-abacus-flashcards/commit/07212e4df0c7fd4b8cccf935c48b14164df6961d))
* add player count to stacked room info ([540f6b7](https://github.com/antialias/soroban-abacus-flashcards/commit/540f6b76d05f561baa581d67070ab43134d8b5f6))
* add prominent join request approval notifications for room moderators ([036da6d](https://github.com/antialias/soroban-abacus-flashcards/commit/036da6de66ca7d3f459c55df657b04a9e88d9cd3))
* add real-time socket updates for moderation events ([86ceba3](https://github.com/antialias/soroban-abacus-flashcards/commit/86ceba3df3d39812d63fbf5d03fc37d8c3a75027))
* add room access modes and ownership transfer ([6ff21c4](https://github.com/antialias/soroban-abacus-flashcards/commit/6ff21c4f1dd0dd1db14257612809b4d40512689a))
* add room creation and join flow UI ([7f95032](https://github.com/antialias/soroban-abacus-flashcards/commit/7f950322530e8deb2e330d0d2147d1a20fa1e642))
* add socket listener and polling for approval notifications ([35b4a72](https://github.com/antialias/soroban-abacus-flashcards/commit/35b4a72c8b2f80a74b5d2fe02b048d4ec4d1d6f2))
* add waiting state for approval requests in JoinRoomModal ([f9b0429](https://github.com/antialias/soroban-abacus-flashcards/commit/f9b0429a2e2d22944acba66009dd87a9d9eb28c2))
* adjust tier probabilities for more abacus flavor ([49219e3](https://github.com/antialias/soroban-abacus-flashcards/commit/49219e34cde32736155a11929d10581e783cba69))
* **arcade:** add Card Sorting Challenge game scaffolding ([df37260](https://github.com/antialias/soroban-abacus-flashcards/commit/df37260e26bbb146493e0834e093afd98fa3f2a4))
* **arcade:** add Change Game functionality for room hosts ([ee39241](https://github.com/antialias/soroban-abacus-flashcards/commit/ee39241e3c9e04202592497d9987eafcb89c00c9))
* **arcade:** add game selection screen with navigation to room page ([4124f1c](https://github.com/antialias/soroban-abacus-flashcards/commit/4124f1cc081f5cb9d6f450f3c2e0cca8a247deba))
* **arcade:** add Math Sprint game implementation ([e5be09e](https://github.com/antialias/soroban-abacus-flashcards/commit/e5be09ef5f170c7544557f75b9eca17bb2069246))
* **arcade:** add modular game SDK and registry system ([de30bec](https://github.com/antialias/soroban-abacus-flashcards/commit/de30bec47923565fe5d1d5a6f719f3fc4e9d1509))
* **arcade:** add Number Guesser demo game with plugin architecture ([0e3c058](https://github.com/antialias/soroban-abacus-flashcards/commit/0e3c0587073a69574a50f05c467f2499296012bf))
* **arcade:** broadcast game selection changes to all room members ([b99e754](https://github.com/antialias/soroban-abacus-flashcards/commit/b99e7543952bb0d47f42e79dc4226b3c1280a0ee))
* **arcade:** migrate matching pairs - phases 1-4 and 7 complete ([2a3af97](https://github.com/antialias/soroban-abacus-flashcards/commit/2a3af973f70ff07de30b38bbe1cdc549a971846f))
* **arcade:** migrate memory-quiz to modular game system ([f48c37a](https://github.com/antialias/soroban-abacus-flashcards/commit/f48c37accccb88e790c7a1b438fd0566e7120e11))
* **arcade:** register Math Sprint in game system ([0c05a7c](https://github.com/antialias/soroban-abacus-flashcards/commit/0c05a7c6bbc8d6f6e1f92e15e691d7e1aba0d8f7)), closes [#2](https://github.com/antialias/soroban-abacus-flashcards/issues/2) [#3](https://github.com/antialias/soroban-abacus-flashcards/issues/3)
* **card-sorting:** add spectator mode UX enhancements ([4ab093a](https://github.com/antialias/soroban-abacus-flashcards/commit/4ab093a9d8ba5b290da44aaa6aa71ad7d7149b32))
* **card-sorting:** add UI components and fix AbacusReact props ([d249ec0](https://github.com/antialias/soroban-abacus-flashcards/commit/d249ec0e5ff4610f55f35f762d726e0c98ac366c))
* **card-sorting:** implement Provider with arcade session integration ([7f6fea9](https://github.com/antialias/soroban-abacus-flashcards/commit/7f6fea91f6dcc69a173eea86bcefc9921f1c1664))
* **complement-race:** add infinite win condition for Steam Sprint mode ([d8fdfee](https://github.com/antialias/soroban-abacus-flashcards/commit/d8fdfeef74a5d3bb9684254af1c9d64d264b46ad))
* **complement-race:** add mini app navigation bar ([ed0ef2d](https://github.com/antialias/soroban-abacus-flashcards/commit/ed0ef2d3b87324470d06b3246652967544caec26))
* **complement-race:** enable adaptive AI difficulty in arcade ([55010d2](https://github.com/antialias/soroban-abacus-flashcards/commit/55010d2bcd953718d8fea428b1f7f613a193779c))
* **complement-race:** implement state adapter for multiplayer support ([13882bd](https://github.com/antialias/soroban-abacus-flashcards/commit/13882bda3258d68a817473d7d830381f02553043))
* **complement-race:** restore AI opponents in practice and survival modes ([325e07d](https://github.com/antialias/soroban-abacus-flashcards/commit/325e07de5929169aa333ef16f7bca5b41eeb1622))
* **homepage:** add animated mini abacus to "Read and set numbers" card ([e028e34](https://github.com/antialias/soroban-abacus-flashcards/commit/e028e342ad4bc01491e05a4ba074628155926fd8))
* **homepage:** add more visual embellishments to learning cards ([4ec1b95](https://github.com/antialias/soroban-abacus-flashcards/commit/4ec1b952f202d50f6db287c41732ec65ca17c142))
* **homepage:** enhance "What You'll Learn" with visual cards ([d142342](https://github.com/antialias/soroban-abacus-flashcards/commit/d1423420e653b26b2f89d9d17ae5d597807d6979))
* **home:** redesign home page to showcase complete platform ([ee6c4f2](https://github.com/antialias/soroban-abacus-flashcards/commit/ee6c4f2f4f39e3b30f59c54866c3857c218fb80f))
* implement approval request flow for share links ([4a6b3ca](https://github.com/antialias/soroban-abacus-flashcards/commit/4a6b3cabe5c6aa42f4fa00ed09f9b3713f097539))
* implement avatar-themed name generation with probabilistic mixing ([76a8472](https://github.com/antialias/soroban-abacus-flashcards/commit/76a8472f12d251071b97f2288f62f0b358576232))
* implement proper retired room behavior with member expulsion ([a2d5368](https://github.com/antialias/soroban-abacus-flashcards/commit/a2d53680f27db04b2cd09973e62a76c5a7d4ce06))
* improve arcade nav player grouping and add room join code display ([8e9980d](https://github.com/antialias/soroban-abacus-flashcards/commit/8e9980dc82ba2ab4ce973fc2c7ed259a20af9b19))
* improve room creation UX and add password support for share links ([dcbb507](https://github.com/antialias/soroban-abacus-flashcards/commit/dcbb5072d8e0a12838fe70e3faa85f94cd63b0c1))
* integrate moderation system into arcade pages ([087652f](https://github.com/antialias/soroban-abacus-flashcards/commit/087652f9e7091a93c02906162275ef88ec5e44c6))
* **levels:** add Dan levels ladder visualization ([c18012c](https://github.com/antialias/soroban-abacus-flashcards/commit/c18012cb505a1f2a86ebed7579b379a4d7d97f2c))
* **levels:** add dark mode styling and responsive scaling to abacus ([92e1e62](https://github.com/antialias/soroban-abacus-flashcards/commit/92e1e621321039206f65b3605f5797bbdc6beafc))
* **levels:** add informational footer section ([0b1bff7](https://github.com/antialias/soroban-abacus-flashcards/commit/0b1bff7eab8f5da84ae309dbda336e168c2fe3fd))
* **levels:** add Kyu & Dan levels page with homepage link ([39b1e7d](https://github.com/antialias/soroban-abacus-flashcards/commit/39b1e7de16f15412c91cf648c714e31e2de7a6bc))
* **levels:** add kyu level data and cards ([6463a3b](https://github.com/antialias/soroban-abacus-flashcards/commit/6463a3b2f6371ebebac1048197fb44178997d2ef))
* **levels:** create true horizontal slider with abacus visualizations ([6d734f1](https://github.com/antialias/soroban-abacus-flashcards/commit/6d734f1d51f5ba1367f55923e58bd977413d754e))
* **levels:** implement interactive slider for exploring kyu & dan ranks ([eb3b100](https://github.com/antialias/soroban-abacus-flashcards/commit/eb3b1000563536d4143ba1f4ec04e59e8dd2e608))
* **levels:** replace kyu grid with interactive slider and abacus visualizations ([10978e8](https://github.com/antialias/soroban-abacus-flashcards/commit/10978e890beee65dea78ddcce52cfe5315d58063))
* make home page abacus interactive with audio ([9a53d7e](https://github.com/antialias/soroban-abacus-flashcards/commit/9a53d7e5db18853aca4e2e0c7abc799217feaecf))
* **matching:** use nav avatars as turn indicators ([7263828](https://github.com/antialias/soroban-abacus-flashcards/commit/7263828ed494a6487999c8436af53618715b3864))
* **math-sprint:** add game manifest ([1eefcc8](https://github.com/antialias/soroban-abacus-flashcards/commit/1eefcc89a58b79f928932a7425d6b88fb45a5526))
* **memory-quiz:** add multiplayer support with redesigned scoreboards ([1cf4469](https://github.com/antialias/soroban-abacus-flashcards/commit/1cf44696c26473ce4ab2fc2039ff42f08c20edb6))
* **memory-quiz:** persist game settings per-game across sessions ([05a8e0a](https://github.com/antialias/soroban-abacus-flashcards/commit/05a8e0a84272c6c45a4014413ee00726eb88b76a))
* **memory-quiz:** show player emojis on cards to indicate who found them ([05bd11a](https://github.com/antialias/soroban-abacus-flashcards/commit/05bd11a133706c9ed8c09c744da7ca8955fa979a))
* **moderation:** add inline feedback and persistent password display ([86e3d41](https://github.com/antialias/soroban-abacus-flashcards/commit/86e3d4199628f95048b9265c9de0adfdc2934f93))
* **moderation:** improve password input with copy button ([2580e47](https://github.com/antialias/soroban-abacus-flashcards/commit/2580e474d08bf91477339e998b2c70962a633f41))
* **nav:** add prominent turn indicator arrow badge ([f574558](https://github.com/antialias/soroban-abacus-flashcards/commit/f574558dffe22a1ecf06ee44d37b5eb1a20011b9))
* **nav:** add pulsing indicator for offline network players ([64fb30e](https://github.com/antialias/soroban-abacus-flashcards/commit/64fb30e7eca7b485687329b9dd3d9e90ac507e2d))
* **nav:** add turn indicators to network players ([623314b](https://github.com/antialias/soroban-abacus-flashcards/commit/623314bd383a54c57ca93ad9f2d8620cf89412e9))
* **nav:** add turn label text under current player avatars ([52a66d5](https://github.com/antialias/soroban-abacus-flashcards/commit/52a66d5f6869e760f2a8914a6d39d21d47cfb7f4))
* **nav:** center game context with hamburger menu for utilities ([a35a7d5](https://github.com/antialias/soroban-abacus-flashcards/commit/a35a7d56df945a5b15c1ddfa8b489c0d292e71c4))
* **nav:** combine room info and network players in single pane ([d5473ab](https://github.com/antialias/soroban-abacus-flashcards/commit/d5473ab66a7c8c78f10cef9bb7084fc311520b2c))
* **nav:** unify room dropdown with join code and game menu ([f7b83f8](https://github.com/antialias/soroban-abacus-flashcards/commit/f7b83f8c149b532321251abed98a94874196b2f5))
* prevent invitations to retired rooms ([a7c3c1f](https://github.com/antialias/soroban-abacus-flashcards/commit/a7c3c1f4cd802985c8f040bc1cdf3ea4482a2fce))
* redesign home page with component showcase ([29af265](https://github.com/antialias/soroban-abacus-flashcards/commit/29af265958f9fdab0253b92e153c01575840454d))
* redesign homepage with educational vision and interactive demo ([2f09cb5](https://github.com/antialias/soroban-abacus-flashcards/commit/2f09cb5539f2bb0b8c77359c6f774c3742313e1e))
* redesign room info as compact inline badge with click-to-copy ([6b3a440](https://github.com/antialias/soroban-abacus-flashcards/commit/6b3a4403695cc2f32df684005784a11f054827ff))
* replace access mode dropdown with visual button grid ([e5d0672](https://github.com/antialias/soroban-abacus-flashcards/commit/e5d067205989d7c3105998dcd7d67fd0408f332c))
* **tutorial:** add dark mode styling for coaching bar and abacus frame ([7e2f580](https://github.com/antialias/soroban-abacus-flashcards/commit/7e2f580877af9d21409f427778fa3569c950fcf5))
* **tutorial:** add dark theme and column control props ([d42f9b2](https://github.com/antialias/soroban-abacus-flashcards/commit/d42f9b2d9ad630826c55b753dc581c469e8f9083))
* **tutorial:** add fill color support for dark mode column posts and reckoning bar ([2eb3ff3](https://github.com/antialias/soroban-abacus-flashcards/commit/2eb3ff340613301df20bf14f5b461371a27d7f05))
* **tutorial:** add hideNavigation prop to TutorialPlayer ([79ea52a](https://github.com/antialias/soroban-abacus-flashcards/commit/79ea52af80c8cbb482bbdd87f77caf32ada737ee))
* **tutorial:** add hideTooltip prop and improve dark mode coaching bar ([1ee25b3](https://github.com/antialias/soroban-abacus-flashcards/commit/1ee25b3dd2f0ee9dd7ed571ba818b7ca5a247f85))
* **tutorial:** add silentErrors prop to suppress error messages ([8835e1c](https://github.com/antialias/soroban-abacus-flashcards/commit/8835e1c57ab8adcecefe0db082360dd98fbfaac7))
### Reverts
* **nav:** restore original room creation/join behavior ([710e93c](https://github.com/antialias/soroban-abacus-flashcards/commit/710e93c9972339885b8f4ca02ecd2c1cdf65c040))
### BREAKING CHANGES
* **db:** Database schemas now accept any string for game names
* Added DELETE /api/arcade/rooms/:roomId/invite endpoint for declining invitations
Authorization Error Handling:
- ModerationPanel: Parse and display API error messages (kick, ban, unban, invite, data loading)
- PendingInvitations: Parse and display API error messages (decline, fetch)
- All moderation actions now show specific auth errors like "Only the host can kick users"
New Endpoint:
- DELETE /api/arcade/rooms/:roomId/invite: Allow users to decline their pending invitations
* Validates invitation exists and is pending
* Only invited user can decline their own invitation
* Returns proper error messages for auth failures
Bug Fix:
- Fixed invitations/pending/route.ts ban check query (removed reference to non-existent unbannedAt field)
- Ban records are deleted when unbanned, so any existing ban is active
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
# [1.8.0](https://github.com/antialias/soroban-abacus-flashcards/compare/abacus-react-v1.7.0...abacus-react-v1.8.0) (2025-10-10)

View File

@@ -213,7 +213,7 @@ export interface AbacusCallbacks {
value: number,
event: React.MouseEvent,
) => void;
onValueChange?: (newValue: number) => void;
onValueChange?: (newValue: number | bigint) => void;
onBeadRef?: (bead: BeadConfig, element: SVGElement | null) => void;
// Legacy callback for backward compatibility
onClick?: (bead: BeadConfig) => void;
@@ -240,7 +240,7 @@ export interface AbacusOverlay {
export interface AbacusConfig {
// Basic configuration
value?: number;
value?: number | bigint;
columns?: number | "auto";
showEmptyColumns?: boolean;
hideInactiveBeads?: boolean;
@@ -271,7 +271,7 @@ export interface AbacusConfig {
// Legacy callbacks for backward compatibility
onClick?: (bead: BeadConfig) => void;
onValueChange?: (newValue: number) => void;
onValueChange?: (newValue: number | bigint) => void;
}
export interface AbacusDimensions {
@@ -474,18 +474,23 @@ export function useAbacusState(
// NEW: Native place-value state management hook (eliminates the column index nightmare!)
export function useAbacusPlaceStates(
controlledValue: number = 0,
controlledValue: number | bigint = 0,
maxPlaceValue: ValidPlaceValues = 4,
) {
// Initialize state from value using place values as keys - NO MORE ARRAY INDICES!
const initializeFromValue = useCallback(
(value: number): PlaceStatesMap => {
(value: number | bigint): PlaceStatesMap => {
const states = new Map<ValidPlaceValues, PlaceState>();
// Convert to string to handle both number and bigint
const valueStr = value.toString();
const digits = valueStr.split('').map(Number);
// Always create ALL place values from 0 to maxPlaceValue (to match columns)
for (let place = 0; place <= maxPlaceValue; place++) {
const placeValueNum = Math.pow(10, place);
const digit = Math.floor(value / placeValueNum) % 10;
// Get digit from right: place 0 = rightmost, place 1 = second from right, etc.
const digitIndex = digits.length - 1 - place;
const digit = digitIndex >= 0 ? digits[digitIndex] : 0;
states.set(place as ValidPlaceValues, {
placeValue: place as ValidPlaceValues,
@@ -504,18 +509,32 @@ export function useAbacusPlaceStates(
);
// Calculate current value from place states - NO MORE INDEX MATH!
// Use BigInt for numbers that exceed safe integer range (>15 digits)
const value = useMemo(() => {
let total = 0;
placeStates.forEach((state) => {
const placeValueNum = Math.pow(10, state.placeValue);
const digitValue = (state.heavenActive ? 5 : 0) + state.earthActive;
total += digitValue * placeValueNum;
});
return total;
}, [placeStates]);
// Check if we need BigInt (maxPlaceValue > 14 means >15 digits)
const useBigInt = maxPlaceValue > 14;
if (useBigInt) {
let total = 0n;
placeStates.forEach((state) => {
const placeValueNum = 10n ** BigInt(state.placeValue);
const digitValue = BigInt((state.heavenActive ? 5 : 0) + state.earthActive);
total += digitValue * placeValueNum;
});
return total;
} else {
let total = 0;
placeStates.forEach((state) => {
const placeValueNum = Math.pow(10, state.placeValue);
const digitValue = (state.heavenActive ? 5 : 0) + state.earthActive;
total += digitValue * placeValueNum;
});
return total;
}
}, [placeStates, maxPlaceValue]);
const setValue = useCallback(
(newValue: number) => {
(newValue: number | bigint) => {
setPlaceStates(initializeFromValue(newValue));
},
[initializeFromValue],
@@ -531,6 +550,37 @@ export function useAbacusPlaceStates(
}
}, [controlledValue, initializeFromValue, value]);
// Clean up place states when maxPlaceValue decreases (columns decrease)
// This prevents stale place values from causing out-of-bounds access
React.useEffect(() => {
setPlaceStates((prev) => {
const newStates = new Map(prev);
let hasChanges = false;
// Remove any place values greater than maxPlaceValue
for (const placeValue of newStates.keys()) {
if (placeValue > maxPlaceValue) {
newStates.delete(placeValue);
hasChanges = true;
}
}
// Add missing place values up to maxPlaceValue
for (let place = 0; place <= maxPlaceValue; place++) {
if (!newStates.has(place as ValidPlaceValues)) {
newStates.set(place as ValidPlaceValues, {
placeValue: place as ValidPlaceValues,
heavenActive: false,
earthActive: 0,
});
hasChanges = true;
}
}
return hasChanges ? newStates : prev;
});
}, [maxPlaceValue]);
const getPlaceState = useCallback(
(placeValue: ValidPlaceValues): PlaceState => {
return (
@@ -1058,13 +1108,15 @@ function calculateBeadStates(
// NEW: Native place-value bead state calculation (eliminates array index math!)
function calculateBeadStatesFromPlaces(
placeStates: PlaceStatesMap,
maxPlaceValue: ValidPlaceValues,
): BeadConfig[][] {
const columnsList: BeadConfig[][] = [];
// Convert Map to sorted array by place value (ascending order for correct visual layout)
const sortedPlaces = Array.from(placeStates.entries()).sort(
([a], [b]) => a - b,
);
// Filter to only include place values that are within the current column count
const sortedPlaces = Array.from(placeStates.entries())
.filter(([placeValue]) => placeValue <= maxPlaceValue)
.sort(([a], [b]) => a - b);
for (const [placeValue, placeState] of sortedPlaces) {
const beads: BeadConfig[] = [];
@@ -1114,17 +1166,26 @@ function calculateValueFromColumnStates(
}
// NEW: Native place-value calculation (eliminates the array index nightmare!)
function calculateValueFromPlaceStates(placeStates: PlaceStatesMap): number {
let value = 0;
function calculateValueFromPlaceStates(placeStates: PlaceStatesMap): number | bigint {
// Determine if we need BigInt based on the largest place value
const maxPlace = Math.max(...Array.from(placeStates.keys()));
const useBigInt = maxPlace > 14; // >15 digits
// Direct place value iteration - NO MORE ARRAY INDEX MATH!
for (const [placeValue, placeState] of placeStates) {
const digitValue =
(placeState.heavenActive ? 5 : 0) + placeState.earthActive;
value += digitValue * Math.pow(10, placeValue); // Direct place value - no conversion!
if (useBigInt) {
let value = 0n;
for (const [placeValue, placeState] of placeStates) {
const digitValue = BigInt((placeState.heavenActive ? 5 : 0) + placeState.earthActive);
value += digitValue * (10n ** BigInt(placeValue));
}
return value;
} else {
let value = 0;
for (const [placeValue, placeState] of placeStates) {
const digitValue = (placeState.heavenActive ? 5 : 0) + placeState.earthActive;
value += digitValue * Math.pow(10, placeValue);
}
return value;
}
return value;
}
// Components
@@ -1588,8 +1649,8 @@ export const AbacusReact: React.FC<AbacusConfig> = ({
// Use new place-value bead calculation!
const beadStates = useMemo(
() => calculateBeadStatesFromPlaces(placeStates),
[placeStates],
() => calculateBeadStatesFromPlaces(placeStates, maxPlaceValue),
[placeStates, maxPlaceValue],
);
// Layout calculations using exact Typst positioning
@@ -1939,6 +2000,7 @@ export const AbacusReact: React.FC<AbacusConfig> = ({
{/* Background glow effects - rendered behind everything */}
{Array.from({ length: effectiveColumns }, (_, colIndex) => {
const placeValue = effectiveColumns - 1 - colIndex;
const columnStyles = customStyles?.columns?.[colIndex];
const backgroundGlow = columnStyles?.backgroundGlow;
@@ -1952,7 +2014,7 @@ export const AbacusReact: React.FC<AbacusConfig> = ({
return (
<rect
key={`background-glow-${colIndex}`}
key={`background-glow-pv${placeValue}`}
x={x - glowWidth / 2}
y={-(backgroundGlow.spread || 0) / 2}
width={glowWidth}
@@ -1970,6 +2032,7 @@ export const AbacusReact: React.FC<AbacusConfig> = ({
{/* Rods - positioned as rectangles like in Typst */}
{Array.from({ length: effectiveColumns }, (_, colIndex) => {
const placeValue = effectiveColumns - 1 - colIndex;
const x =
colIndex * dimensions.rodSpacing + dimensions.rodSpacing / 2;
@@ -2001,7 +2064,7 @@ export const AbacusReact: React.FC<AbacusConfig> = ({
return (
<rect
key={`rod-${colIndex}`}
key={`rod-pv${placeValue}`}
x={x - dimensions.rodWidth / 2}
y={rodStartY}
width={dimensions.rodWidth}
@@ -2056,6 +2119,15 @@ export const AbacusReact: React.FC<AbacusConfig> = ({
} else {
// Earth bead positioning - exact Typst formulas (lines 249-261)
const columnState = columnStates[colIndex];
if (!columnState) {
throw new Error(
`Invalid abacus state: columnState is undefined for column index ${colIndex}. ` +
`effectiveColumns=${effectiveColumns}, columnStates.length=${columnStates.length}, ` +
`beadStates.length=${beadStates.length}, placeValue=${bead.placeValue}. ` +
`This indicates a mismatch between the number of columns and the bead states. ` +
`Please report this issue with the abacus configuration that triggered it.`
);
}
const earthActive = columnState.earthActive;
if (bead.active) {
@@ -2134,7 +2206,7 @@ export const AbacusReact: React.FC<AbacusConfig> = ({
return (
<Bead
key={`bead-${colIndex}-${bead.type}-${beadIndex}`}
key={`bead-pv${bead.placeValue}-${bead.type}-${bead.type === "earth" ? bead.position : 0}`}
bead={bead}
x={x}
y={y}
@@ -2208,6 +2280,7 @@ export const AbacusReact: React.FC<AbacusConfig> = ({
{/* Background rectangles for place values - in SVG */}
{finalConfig.showNumbers &&
placeValues.map((value, columnIndex) => {
const placeValue = effectiveColumns - 1 - columnIndex;
const x =
columnIndex * dimensions.rodSpacing + dimensions.rodSpacing / 2;
// Position background rectangles to match the text positioning
@@ -2220,7 +2293,7 @@ export const AbacusReact: React.FC<AbacusConfig> = ({
return (
<rect
key={`place-bg-${columnIndex}`}
key={`place-bg-pv${placeValue}`}
x={x - 12 * finalConfig.scaleFactor}
y={y - 12 * finalConfig.scaleFactor}
width={24 * finalConfig.scaleFactor}
@@ -2248,6 +2321,7 @@ export const AbacusReact: React.FC<AbacusConfig> = ({
{/* NumberFlow place value displays - inside SVG using foreignObject */}
{finalConfig.showNumbers &&
placeValues.map((value, columnIndex) => {
const placeValue = effectiveColumns - 1 - columnIndex;
const x =
columnIndex * dimensions.rodSpacing + dimensions.rodSpacing / 2;
// Position numbers within the allocated numbers space (below the baseHeight)
@@ -2259,7 +2333,7 @@ export const AbacusReact: React.FC<AbacusConfig> = ({
return (
<foreignObject
key={`place-number-${columnIndex}`}
key={`place-number-pv${placeValue}`}
x={x - 12 * finalConfig.scaleFactor}
y={y - 8 * finalConfig.scaleFactor}
width={24 * finalConfig.scaleFactor}
@@ -2320,6 +2394,12 @@ export const AbacusReact: React.FC<AbacusConfig> = ({
if (targetBeadType === "heaven") {
const columnState = columnStates[targetColumn];
if (!columnState) {
console.error(
`Invalid abacus overlay: columnState is undefined for overlay targeting column ${targetColumn}`
);
return;
}
y = columnState.heavenActive
? dimensions.heavenEarthGap -
dimensions.beadSize / 2 -
@@ -2332,6 +2412,12 @@ export const AbacusReact: React.FC<AbacusConfig> = ({
targetBeadPosition !== undefined
) {
const columnState = columnStates[targetColumn];
if (!columnState) {
console.error(
`Invalid abacus overlay: columnState is undefined for overlay targeting column ${targetColumn}`
);
return;
}
const earthActive = columnState.earthActive;
const isActive = targetBeadPosition < earthActive;
@@ -2403,6 +2489,7 @@ export const AbacusReact: React.FC<AbacusConfig> = ({
{/* Column interaction areas - rendered last to be on top of all other elements */}
{Array.from({ length: effectiveColumns }, (_, colIndex) => {
const placeValue = effectiveColumns - 1 - colIndex;
const x =
colIndex * dimensions.rodSpacing + dimensions.rodSpacing / 2;
const columnStyles = customStyles?.columns?.[colIndex];
@@ -2413,7 +2500,7 @@ export const AbacusReact: React.FC<AbacusConfig> = ({
return (
<rect
key={`column-interaction-${colIndex}`}
key={`column-interaction-pv${placeValue}`}
x={x - backgroundWidth / 2}
y={0}
width={backgroundWidth}

View File

@@ -0,0 +1,147 @@
"use client";
import React from "react";
import { useSpring, animated } from "@react-spring/web";
import {
useAbacusConfig,
getDefaultAbacusConfig,
type BeadShape,
} from "./AbacusContext";
export interface StandaloneBeadProps {
/** Size of the bead in pixels */
size?: number;
/** Override the shape from context (diamond, circle, square) */
shape?: BeadShape;
/** Override the color from context */
color?: string;
/** Enable animation */
animated?: boolean;
/** Custom className */
className?: string;
/** Custom style */
style?: React.CSSProperties;
/** Active state for the bead */
active?: boolean;
}
/**
* Standalone Bead component that respects the AbacusDisplayContext.
* This component renders a single abacus bead with styling from the context.
*
* Usage:
* ```tsx
* import { StandaloneBead, AbacusDisplayProvider } from '@soroban/abacus-react';
*
* <AbacusDisplayProvider>
* <StandaloneBead size={28} color="#8b5cf6" />
* </AbacusDisplayProvider>
* ```
*/
export const StandaloneBead: React.FC<StandaloneBeadProps> = ({
size = 28,
shape: shapeProp,
color: colorProp,
animated: animatedProp,
className,
style,
active = true,
}) => {
// Try to use context config, fallback to defaults if no context
let contextConfig;
try {
contextConfig = useAbacusConfig();
} catch {
// No context provider, use defaults
contextConfig = getDefaultAbacusConfig();
}
// Use props if provided, otherwise fall back to context config
const shape = shapeProp ?? contextConfig.beadShape;
const enableAnimation = animatedProp ?? contextConfig.animated;
const color = colorProp ?? "#000000";
const [springs, api] = useSpring(() => ({
scale: 1,
config: { tension: 300, friction: 20 },
}));
React.useEffect(() => {
if (enableAnimation) {
api.start({ scale: 1 });
}
}, [enableAnimation, api]);
const renderShape = () => {
const halfSize = size / 2;
const actualColor = active ? color : "rgb(211, 211, 211)";
switch (shape) {
case "diamond":
return (
<polygon
points={`${size * 0.7},0 ${size * 1.4},${halfSize} ${size * 0.7},${size} 0,${halfSize}`}
fill={actualColor}
stroke="#000"
strokeWidth="0.5"
/>
);
case "square":
return (
<rect
width={size}
height={size}
fill={actualColor}
stroke="#000"
strokeWidth="0.5"
rx="1"
/>
);
case "circle":
default:
return (
<circle
cx={halfSize}
cy={halfSize}
r={halfSize}
fill={actualColor}
stroke="#000"
strokeWidth="0.5"
/>
);
}
};
const getXOffset = () => {
return shape === "diamond" ? size * 0.7 : size / 2;
};
const getYOffset = () => {
return size / 2;
};
const AnimatedG = animated.g;
return (
<svg
width={size * (shape === "diamond" ? 1.4 : 1)}
height={size}
className={className}
style={style}
>
<AnimatedG
transform={`translate(0, 0)`}
style={
enableAnimation
? {
transform: springs.scale.to((s) => `scale(${s})`),
transformOrigin: "center",
}
: undefined
}
>
{renderShape()}
</AnimatedG>
</svg>
);
};

View File

@@ -14,3 +14,6 @@ export type {
AbacusDisplayConfig,
AbacusDisplayContextType,
} from "./AbacusContext";
export { StandaloneBead } from "./StandaloneBead";
export type { StandaloneBeadProps } from "./StandaloneBead";

3
pnpm-lock.yaml generated
View File

@@ -134,6 +134,9 @@ importers:
'@types/jsdom':
specifier: ^21.1.7
version: 21.1.7
'@use-gesture/react':
specifier: ^10.3.1
version: 10.3.1(react@18.3.1)
bcryptjs:
specifier: ^2.4.3
version: 2.4.3