Compare commits

...

6 Commits

Author SHA1 Message Date
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
3 changed files with 119 additions and 43 deletions

View File

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

View File

@@ -1,7 +1,8 @@
'use client'
import { useRef, useState } from 'react'
import { useState } from 'react'
import { useSpring, animated } from '@react-spring/web'
import * as Slider from '@radix-ui/react-slider'
import { AbacusReact } from '@soroban/abacus-react'
import { PageWithNav } from '@/components/PageWithNav'
import { css } from '../../../styled-system/css'
@@ -122,13 +123,12 @@ const allLevels = [
export default function LevelsPage() {
const [currentIndex, setCurrentIndex] = useState(0)
const sliderRef = useRef<HTMLInputElement>(null)
const currentLevel = allLevels[currentIndex]
// Calculate scale factor based on number of columns to fit the page
// Use constrained range to prevent huge size differences between levels
// Min 1.5 (for 30-column Dan levels) to Max 3.0 (for 2-column Kyu levels)
const scaleFactor = Math.max(1.5, Math.min(3.0, 20 / currentLevel.digits))
// 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({
@@ -136,23 +136,6 @@ export default function LevelsPage() {
config: { tension: 280, friction: 60 },
})
// Handle mouse/touch move over slider for hover interaction
const handleSliderHover = (
e: React.MouseEvent<HTMLDivElement> | React.TouchEvent<HTMLDivElement>
) => {
if (!sliderRef.current) return
const rect = sliderRef.current.getBoundingClientRect()
const clientX = 'touches' in e ? e.touches[0].clientX : e.clientX
const x = clientX - rect.left
const percentage = Math.max(0, Math.min(1, x / rect.width))
const newIndex = Math.round(percentage * (allLevels.length - 1))
if (newIndex !== currentIndex) {
setCurrentIndex(newIndex)
}
}
// 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.
@@ -254,7 +237,7 @@ export default function LevelsPage() {
: 'amber.500',
rounded: 'xl',
p: { base: '6', md: '8' },
height: { base: 'auto', md: '900px' },
height: { base: 'auto', md: '700px' },
display: 'flex',
flexDirection: 'column',
})}
@@ -300,35 +283,108 @@ export default function LevelsPage() {
)}
</div>
{/* Range Slider with hover support */}
{/* Abacus-themed Radix Slider */}
<div className={css({ mb: '6', px: { base: '2', md: '8' } })}>
<div className={css({ mb: '3', textAlign: 'center' })}>
<p className={css({ fontSize: 'sm', color: 'gray.400' })}>
Hover, drag, or touch the slider to explore all levels
Drag or click the beads to explore all levels
</p>
</div>
<div
onMouseMove={handleSliderHover}
onTouchMove={handleSliderHover}
className={css({ position: 'relative' })}
>
<input
ref={sliderRef}
type="range"
min="0"
<div className={css({ position: 'relative', py: '6' })}>
<Slider.Root
value={[currentIndex]}
onValueChange={([value]) => setCurrentIndex(value)}
min={0}
max={allLevels.length - 1}
value={currentIndex}
onChange={(e) => setCurrentIndex(Number(e.target.value))}
step={1}
className={css({
w: '100%',
h: '2',
bg: 'gray.700',
rounded: 'full',
outline: 'none',
position: 'relative',
display: 'flex',
alignItems: 'center',
userSelect: 'none',
touchAction: 'none',
w: 'full',
h: '12',
cursor: 'pointer',
})}
/>
>
{/* Decorative beads for all levels */}
{allLevels.map((level, index) => {
const isActive = index === currentIndex
const isDan = 'color' in level && level.color === 'violet'
const beadColor = isActive
? isDan
? 'violet.400'
: 'green.400'
: isDan
? 'rgba(139, 92, 246, 0.4)'
: 'rgba(34, 197, 94, 0.4)'
return (
<div
key={index}
className={css({
position: 'absolute',
top: '50%',
left: `${(index / (allLevels.length - 1)) * 100}%`,
transform: 'translate(-50%, -50%)',
w: isActive ? '16px' : '10px',
h: isActive ? '16px' : '10px',
bg: beadColor,
rounded: 'full',
border: '2px solid',
borderColor: isActive
? isDan
? 'violet.200'
: 'green.200'
: 'rgba(255, 255, 255, 0.3)',
transition: 'all 0.2s',
boxShadow: isActive
? `0 0 12px ${isDan ? 'rgba(139, 92, 246, 0.6)' : 'rgba(34, 197, 94, 0.6)'}`
: 'none',
pointerEvents: 'none',
zIndex: 0,
})}
/>
)
})}
<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: 'block',
w: '20px',
h: '20px',
bg: currentLevel.color === 'violet' ? 'violet.500' : 'green.500',
shadow: '0 0 16px rgba(0, 0, 0, 0.4)',
rounded: 'full',
border: '3px solid',
borderColor: currentLevel.color === 'violet' ? 'violet.200' : 'green.200',
cursor: 'grab',
transition: 'all 0.2s',
zIndex: 10,
_hover: { transform: 'scale(1.15)' },
_focus: {
outline: 'none',
transform: 'scale(1.15)',
boxShadow: `0 0 20px ${currentLevel.color === 'violet' ? 'rgba(139, 92, 246, 0.8)' : 'rgba(34, 197, 94, 0.8)'}`,
},
_active: { cursor: 'grabbing' },
})}
/>
</Slider.Root>
</div>
{/* Level Markers */}
@@ -336,7 +392,7 @@ export default function LevelsPage() {
className={css({
display: 'flex',
justifyContent: 'space-between',
mt: '3',
mt: '1',
fontSize: 'xs',
color: 'gray.500',
})}

View File

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