Compare commits

...

57 Commits

Author SHA1 Message Date
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
7 changed files with 1091 additions and 123 deletions

View File

@@ -1,3 +1,202 @@
## [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)

View File

@@ -1,23 +1,86 @@
'use client'
import { useState } from 'react'
import { AbacusReact } from '@soroban/abacus-react'
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 { PageWithNav } from '@/components/PageWithNav'
import { css } from '../../../styled-system/css'
import { container, 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: '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',
@@ -119,25 +182,167 @@ const allLevels = [
},
] 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'))
const sections: Array<{ icon: string; label: string; value: string }> = []
for (const line of lines) {
if (line.includes('Add/Sub:')) {
// Parse addition/subtraction requirements
const match = line.match(/(\d+)-digit.*?(\d+)口.*?(\d+)字/)
if (match) {
sections.push({
icon: '',
label: 'Add/Sub',
value: `${match[1]}-digit, ${match[2]} rows, ${match[3]} chars`,
})
}
} else if (line.includes('×:')) {
// Parse multiplication requirements
const match = line.match(/(\d+) digits.*?\((\d+)/)
if (match) {
sections.push({
icon: '✖️',
label: 'Multiply',
value: `${match[1]}-digit total (${match[2]} problems)`,
})
}
} else if (line.includes('÷:')) {
// Parse division requirements
const match = line.match(/(\d+) digits.*?\((\d+)/)
if (match) {
sections.push({
icon: '➗',
label: 'Divide',
value: `${match[1]}-digit total (${match[2]} problems)`,
})
}
}
// Skip Time and Pass requirements since we don't have tests implemented
}
return sections
}
export default function LevelsPage() {
const [currentIndex, setCurrentIndex] = useState(0)
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 (isPaneHovered) return // Don't auto-advance when mouse is over the pane
const interval = setInterval(() => {
setCurrentIndex((prev) => {
// Cycle back to 0 when reaching the end
return prev >= allLevels.length - 1 ? 0 : prev + 1
})
}, 3000)
return () => clearInterval(interval)
}, [isPaneHovered])
// 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
// Smaller scale for more columns (Dan levels with 30 columns)
const scaleFactor = Math.min(2.5, 20 / currentLevel.digits)
// 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))
// Generate an interesting non-zero number to display on the abacus
// Use a suffix pattern so rightmost digits stay constant as columns increase
// This prevents beads from shifting: ones always 9, tens always 8, etc.
const digitPattern = '123456789'
// Use BigInt for numbers > 15 digits (Dan levels with 30 columns)
const repeatedPattern = digitPattern.repeat(Math.ceil(currentLevel.digits / digitPattern.length))
const digitString = repeatedPattern.slice(-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 =
currentLevel.digits > 15 ? BigInt(digitString) : Number.parseInt(digitString, 10)
animatedDigits.length > 15
? BigInt(animatedDigits || '0')
: Number.parseInt(animatedDigits || '0', 10)
// Dark theme styles matching the homepage
const darkStyles = {
@@ -178,7 +383,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({
@@ -215,8 +426,10 @@ export default function LevelsPage() {
<section className={stack({ gap: '8' })}>
{/* Current Level Display */}
<div
onMouseEnter={() => setIsPaneHovered(true)}
onMouseLeave={() => setIsPaneHovered(false)}
className={css({
bg: 'rgba(0, 0, 0, 0.4)',
bg: 'transparent',
border: '2px solid',
borderColor:
currentLevel.color === 'green'
@@ -228,120 +441,329 @@ export default function LevelsPage() {
: 'amber.500',
rounded: 'xl',
p: { base: '6', md: '8' },
height: { base: 'auto', md: '700px' },
display: 'flex',
flexDirection: 'column',
})}
>
{/* Level Info */}
<div className={css({ textAlign: 'center', mb: '6' })}>
<div className={css({ fontSize: '5xl', mb: '3' })}>{currentLevel.emoji}</div>
<h2
{/* 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({
fontSize: { base: '2xl', md: '3xl' },
fontWeight: 'bold',
color:
currentLevel.color === 'green'
? 'green.400'
: currentLevel.color === 'blue'
? 'blue.400'
: currentLevel.color === 'violet'
? 'violet.400'
: 'amber.400',
mb: '2',
display: 'flex',
justifyContent: 'space-between',
mt: '1',
fontSize: 'xs',
color: 'gray.500',
})}
>
{currentLevel.level}
</h2>
{'name' in currentLevel && (
<div className={css({ fontSize: 'md', color: 'gray.400', mb: '1' })}>
{currentLevel.name}
</div>
)}
{'minScore' in currentLevel && (
<div className={css({ fontSize: 'sm', color: 'gray.500' })}>
Minimum Score: {currentLevel.minScore} points
</div>
)}
<span>10th Kyu</span>
<span>1st Kyu</span>
<span>10th Dan</span>
</div>
</div>
{/* Abacus Display */}
{/* Abacus Display with Level Details */}
<div
className={css({
display: 'flex',
justifyContent: 'center',
mb: '6',
gap: '4',
p: '6',
bg: 'rgba(0, 0, 0, 0.3)',
rounded: 'lg',
border: '1px solid',
borderColor: 'gray.700',
overflowX: 'auto',
overflow: 'hidden',
flex: 1,
})}
>
<AbacusReact
value={displayValue}
columns={currentLevel.digits}
scaleFactor={scaleFactor}
showNumbers={true}
customStyles={darkStyles}
/>
{/* 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: 'sm', gap: '3', iconSize: 'xl' }
return sections.length > 0 ? (
<div
className={css({
flex: '0 0 auto',
display: 'grid',
gridTemplateColumns: 'repeat(2, 1fr)',
gap: sizing.gap,
pr: '4',
pl: '2',
borderRight: '1px solid',
borderColor: 'gray.600',
maxW: '480px',
alignContent: 'center',
})}
>
{sections.map((section, idx) => (
<div
key={idx}
className={css({
bg: 'rgba(0, 0, 0, 0.4)',
border: '1px solid',
borderColor: 'gray.700',
rounded: 'md',
p: '2',
transition: 'all 0.2s',
_hover: {
borderColor: 'gray.500',
transform: 'translateX(4px)',
},
})}
>
<div
className={css({
display: 'flex',
alignItems: 'center',
gap: '2',
mb: '1',
})}
>
<span className={css({ fontSize: sizing.iconSize })}>
{section.icon}
</span>
<span
className={css({
fontSize: sizing.fontSize,
fontWeight: 'semibold',
color:
currentLevel.color === 'green'
? 'green.300'
: currentLevel.color === 'blue'
? 'blue.300'
: 'violet.300',
})}
>
{section.label}
</span>
</div>
<div
className={css({
fontSize: sizing.fontSize,
color: 'gray.400',
lineHeight: '1.4',
pl: sizing.gap === '3' ? '6' : sizing.gap === '2' ? '5' : '4',
})}
>
{section.value}
</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>
{/* Slider Control */}
<div
className={css({
bg: 'rgba(0, 0, 0, 0.3)',
border: '1px solid',
borderColor: 'gray.700',
rounded: 'xl',
p: '6',
})}
>
<div className={css({ mb: '4', textAlign: 'center' })}>
<h3
className={css({ fontSize: 'lg', fontWeight: 'bold', color: 'white', mb: '2' })}
>
Explore All Levels
</h3>
<p className={css({ fontSize: 'sm', color: 'gray.400' })}>
Drag the slider to see each rank
</p>
</div>
{/* Range Slider */}
<input
type="range"
min="0"
max={allLevels.length - 1}
value={currentIndex}
onChange={(e) => setCurrentIndex(Number(e.target.value))}
className={css({
w: '100%',
h: '2',
bg: 'gray.700',
rounded: 'full',
outline: 'none',
cursor: 'pointer',
})}
/>
{/* Level Markers */}
<div
className={css({
display: 'flex',
justifyContent: 'space-between',
mt: '4',
fontSize: 'xs',
color: 'gray.500',
textAlign: 'center',
color: 'gray.400',
fontSize: 'sm',
})}
>
<span>10th Kyu</span>
<span>1st Kyu</span>
<span>10th Dan</span>
Requires mastery of <strong>{currentLevel.digits}-digit</strong> calculations
</div>
</div>
@@ -359,26 +781,78 @@ export default function LevelsPage() {
borderColor: 'gray.700',
})}
>
<div className={css({ display: 'flex', alignItems: 'center', gap: '2' })}>
<div className={css({ w: '4', h: '4', bg: 'green.500', rounded: 'sm' })} />
<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' })} />
<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' })} />
<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' })} />
<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>

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

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

View File

@@ -1,3 +1,25 @@
# [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)

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";