Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1e6459f9c1 | ||
|
|
477a0b367e | ||
|
|
8751649233 | ||
|
|
07c783a794 | ||
|
|
ca8cef1c36 |
19
CHANGELOG.md
19
CHANGELOG.md
@@ -1,3 +1,22 @@
|
||||
## [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)
|
||||
|
||||
|
||||
|
||||
@@ -183,8 +183,18 @@ const allLevels = [
|
||||
|
||||
export default function LevelsPage() {
|
||||
const [currentIndex, setCurrentIndex] = useState(0)
|
||||
const [isHovering, setIsHovering] = useState(false)
|
||||
const currentLevel = allLevels[currentIndex]
|
||||
|
||||
// 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)
|
||||
@@ -193,7 +203,7 @@ export default function LevelsPage() {
|
||||
// Animate scale factor with React Spring for smooth transitions
|
||||
const animatedProps = useSpring({
|
||||
scaleFactor,
|
||||
config: { tension: 400, friction: 40 },
|
||||
config: { tension: 350, friction: 45 },
|
||||
})
|
||||
|
||||
// Animate emoji with proper cross-fade (old fades out, new fades in)
|
||||
@@ -202,7 +212,7 @@ export default function LevelsPage() {
|
||||
from: { opacity: 0 },
|
||||
enter: { opacity: 1 },
|
||||
leave: { opacity: 0 },
|
||||
config: { duration: 80 },
|
||||
config: { duration: 120 },
|
||||
})
|
||||
|
||||
// Generate an interesting non-zero number to display on the abacus
|
||||
@@ -319,12 +329,6 @@ export default function LevelsPage() {
|
||||
>
|
||||
{/* 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' })}>
|
||||
Drag or click the beads to explore all levels
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className={css({ position: 'relative', py: '12' })}>
|
||||
{/* Emoji tick marks */}
|
||||
<div
|
||||
@@ -351,10 +355,14 @@ export default function LevelsPage() {
|
||||
{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}
|
||||
@@ -369,6 +377,9 @@ export default function LevelsPage() {
|
||||
min={0}
|
||||
max={allLevels.length - 1}
|
||||
step={1}
|
||||
onMouseMove={handleSliderHover}
|
||||
onMouseEnter={() => setIsHovering(true)}
|
||||
onMouseLeave={() => setIsHovering(false)}
|
||||
className={css({
|
||||
position: 'relative',
|
||||
display: 'flex',
|
||||
@@ -402,7 +413,7 @@ export default function LevelsPage() {
|
||||
h: '128px',
|
||||
bg: 'transparent',
|
||||
cursor: 'grab',
|
||||
transition: 'all 0.1s ease-out',
|
||||
transition: 'transform 0.15s ease-out, left 0.3s ease-out',
|
||||
zIndex: 10,
|
||||
_hover: { transform: 'scale(1.15)' },
|
||||
_focus: {
|
||||
@@ -499,49 +510,6 @@ export default function LevelsPage() {
|
||||
<span>1st Kyu</span>
|
||||
<span>10th Dan</span>
|
||||
</div>
|
||||
|
||||
{/* Level Text - integrated with slider */}
|
||||
<div
|
||||
className={css({
|
||||
textAlign: 'center',
|
||||
mt: '4',
|
||||
mb: '2',
|
||||
})}
|
||||
>
|
||||
<h2
|
||||
className={css({
|
||||
fontSize: { base: 'xl', md: '2xl' },
|
||||
fontWeight: 'bold',
|
||||
color:
|
||||
currentLevel.color === 'green'
|
||||
? 'green.400'
|
||||
: currentLevel.color === 'blue'
|
||||
? 'blue.400'
|
||||
: currentLevel.color === 'violet'
|
||||
? 'violet.400'
|
||||
: 'amber.400',
|
||||
mb: '1',
|
||||
})}
|
||||
>
|
||||
{currentLevel.level}
|
||||
</h2>
|
||||
{'name' in currentLevel && (
|
||||
<div
|
||||
className={css({
|
||||
fontSize: 'sm',
|
||||
color: 'gray.400',
|
||||
mb: '1',
|
||||
})}
|
||||
>
|
||||
{currentLevel.name}
|
||||
</div>
|
||||
)}
|
||||
{'minScore' in currentLevel && (
|
||||
<div className={css({ fontSize: 'xs', color: 'gray.500' })}>
|
||||
Minimum Score: {currentLevel.minScore} points
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Abacus Display */}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "soroban-monorepo",
|
||||
"version": "4.35.1",
|
||||
"version": "4.37.0",
|
||||
"private": true,
|
||||
"description": "Beautiful Soroban Flashcard Generator - Monorepo",
|
||||
"workspaces": [
|
||||
|
||||
Reference in New Issue
Block a user