Compare commits

...

10 Commits

Author SHA1 Message Date
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
semantic-release-bot
276f6f0744 chore(release): 4.30.0 [skip ci]
## [4.30.0](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.29.0...v4.30.0) (2025-10-20)

### Features

* **levels:** create true horizontal slider with abacus visualizations ([6d734f1](6d734f1d51))
2025-10-20 12:34:34 +00:00
Thomas Hallock
6d734f1d51 feat(levels): create true horizontal slider with abacus visualizations
Replace carousel with actual horizontal slider showing all 21 ranks:
- Continuous scrollable view of 10 Kyu levels + 11 Dan levels
- Each level card displays:
  - Level name, emoji, and metadata
  - Visual abacus showing digit mastery (2-30 columns)
  - Color-coded by progression (green→blue→violet→amber)
- Simplified abacus columns with proper visual structure
- Visual transition marker between Kyu and Dan sections
- Color legend for all four progression stages
- Fully mobile-responsive horizontal scrolling

Also add documentation note about using @soroban/abacus-react for all
abacus visualizations in the codebase.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-20 07:33:36 -05:00
semantic-release-bot
03b9b1228b chore(release): 4.29.0 [skip ci]
## [4.29.0](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.28.0...v4.29.0) (2025-10-20)

### Features

* **levels:** replace kyu grid with interactive slider and abacus visualizations ([10978e8](10978e890b))
2025-10-20 12:30:16 +00:00
Thomas Hallock
10978e890b feat(levels): replace kyu grid with interactive slider and abacus visualizations
Replace cluttered static grid of kyu level cards with an elegant horizontal slider.
Each level now features:
- Interactive slider with left/right navigation buttons
- Keyboard navigation support (arrow keys)
- Visual abacus showing digit mastery progression (2-10 columns)
- Clickable progress indicators
- Smooth transitions and hover effects
- Mobile-responsive design

The abacus visualizations provide an intuitive representation of the student's
progression through each level, showing the increasing number of digit columns
they master from 10th Kyu (2 digits) to 1st Kyu (10 digits).

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-20 07:29:11 -05:00
4 changed files with 331 additions and 509 deletions

View File

@@ -1,3 +1,38 @@
## [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)
### Features
* **levels:** create true horizontal slider with abacus visualizations ([6d734f1](https://github.com/antialias/soroban-abacus-flashcards/commit/6d734f1d51f5ba1367f55923e58bd977413d754e))
## [4.29.0](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.28.0...v4.29.0) (2025-10-20)
### Features
* **levels:** replace kyu grid with interactive slider and abacus visualizations ([10978e8](https://github.com/antialias/soroban-abacus-flashcards/commit/10978e890beee65dea78ddcce52cfe5315d58063))
## [4.28.0](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.27.0...v4.28.0) (2025-10-20)

View File

@@ -121,6 +121,31 @@ className="bg-blue-200 border-gray-300 text-brand-600"
See `.claude/GAME_THEMES.md` for standardized color theme usage in arcade games.
## Abacus Visualizations
**CRITICAL: This project uses @soroban/abacus-react for all abacus visualizations.**
- All abacus displays MUST use components from `@soroban/abacus-react`
- Package location: `packages/abacus-react`
- Main components: `AbacusReact`, `useAbacusConfig`, `useAbacusDisplay`
- DO NOT create custom abacus visualizations
- DO NOT manually draw abacus columns, beads, or bars
**Common Mistakes to Avoid:**
- ❌ Don't create custom abacus components or SVGs
- ❌ Don't manually render abacus beads or columns
- ✅ Always use `AbacusReact` from `@soroban/abacus-react`
- ✅ Use `useAbacusConfig` for abacus configuration
- ✅ Use `useAbacusDisplay` for reading abacus state
**Example Usage:**
```typescript
import { AbacusReact, useAbacusConfig } from '@soroban/abacus-react'
const config = useAbacusConfig({ columns: 5 })
<AbacusReact config={config} initialNumber={123} />
```
## Known Issues
### @soroban/abacus-react TypeScript Module Resolution

View File

@@ -1,179 +1,135 @@
'use client'
import { useState } from 'react'
import { AbacusReact } from '@soroban/abacus-react'
import { PageWithNav } from '@/components/PageWithNav'
import { css } from '../../../styled-system/css'
import { container, grid, stack } from '../../../styled-system/patterns'
import { container, stack } from '../../../styled-system/patterns'
// Kyu level data from the Japan Abacus Federation
const kyuLevels = [
// 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',
duration: '20 min',
passThreshold: '30%',
points: '60/200',
sections: [
{ name: 'Addition', digits: '2-digit', problems: 10, points: 10 },
{ name: 'Subtraction', digits: '2-digit', problems: 10, points: 10 },
],
notes: 'No division at this level',
level: 'Pre-1st Dan',
name: 'Jun-Shodan',
minScore: 90,
emoji: '🧙',
color: 'amber',
digits: 30,
type: 'dan' as const,
},
{
level: '9th Kyu',
emoji: '🧒',
color: 'green',
duration: '20 min',
passThreshold: '60%',
points: '120/200',
sections: [
{ name: 'Addition', digits: '2-digit', problems: 10, points: 10 },
{ name: 'Subtraction', digits: '2-digit', problems: 10, points: 10 },
{ name: 'Multiplication', digits: '1×1', problems: 10, points: 10 },
],
notes: 'Introduces multiplication',
level: '1st Dan',
name: 'Shodan',
minScore: 100,
emoji: '🧙',
color: 'amber',
digits: 30,
type: 'dan' as const,
},
{
level: '8th Kyu',
emoji: '🧒',
color: 'green',
duration: '20 min',
passThreshold: '60%',
points: '120/200',
sections: [
{ name: 'Addition', digits: '3-digit', problems: 10, points: 10 },
{ name: 'Subtraction', digits: '3-digit', problems: 10, points: 10 },
{ name: 'Multiplication', digits: '2×1', problems: 10, points: 10 },
{ name: 'Division', digits: '2÷1', problems: 10, points: 10 },
],
notes: 'Introduces division',
level: '2nd Dan',
name: 'Nidan',
minScore: 120,
emoji: '🧙‍♂️',
color: 'amber',
digits: 30,
type: 'dan' as const,
},
{
level: '7th Kyu',
emoji: '🧒',
color: 'green',
duration: '20 min',
passThreshold: '60%',
points: '120/200',
sections: [
{ name: 'Addition', digits: '4-digit', problems: 10, points: 10 },
{ name: 'Subtraction', digits: '4-digit', problems: 10, points: 10 },
{ name: 'Multiplication', digits: '3×1', problems: 10, points: 10 },
{ name: 'Division', digits: '3÷1', problems: 10, points: 10 },
],
notes: 'All four operations',
level: '3rd Dan',
name: 'Sandan',
minScore: 140,
emoji: '🧙‍♂️',
color: 'amber',
digits: 30,
type: 'dan' as const,
},
{
level: '6th Kyu',
emoji: '🧑',
color: 'blue',
duration: '30 min',
passThreshold: '70%',
points: '210/300',
sections: [
{ name: 'Addition', digits: '5-digit', problems: 10, points: 10 },
{ name: 'Subtraction', digits: '5-digit', problems: 10, points: 10 },
{ name: 'Multiplication', digits: '4×2', problems: 10, points: 10 },
{ name: 'Division', digits: '5÷2', problems: 10, points: 10 },
],
notes: 'Longer exam time',
level: '4th Dan',
name: 'Yondan',
minScore: 160,
emoji: '🧙‍♀️',
color: 'amber',
digits: 30,
type: 'dan' as const,
},
{
level: '5th Kyu',
emoji: '🧑',
color: 'blue',
duration: '30 min',
passThreshold: '70%',
points: '210/300',
sections: [
{ name: 'Addition', digits: '6-digit', problems: 10, points: 10 },
{ name: 'Subtraction', digits: '6-digit', problems: 10, points: 10 },
{ name: 'Multiplication', digits: '5×2', problems: 10, points: 10 },
{ name: 'Division', digits: '6÷2', problems: 10, points: 10 },
],
notes: 'Mid-level proficiency',
level: '5th Dan',
name: 'Godan',
minScore: 180,
emoji: '🧙‍♀️',
color: 'amber',
digits: 30,
type: 'dan' as const,
},
{
level: '4th Kyu',
emoji: '🧑',
color: 'blue',
duration: '30 min',
passThreshold: '70%',
points: '210/300',
sections: [
{ name: 'Addition', digits: '7-digit', problems: 10, points: 10 },
{ name: 'Subtraction', digits: '7-digit', problems: 10, points: 10 },
{ name: 'Multiplication', digits: '6×2', problems: 10, points: 10 },
{ name: 'Division', digits: '7÷2', problems: 10, points: 10 },
],
notes: 'Advanced intermediate',
level: '6th Dan',
name: 'Rokudan',
minScore: 200,
emoji: '🧝',
color: 'amber',
digits: 30,
type: 'dan' as const,
},
{
level: '3rd Kyu',
emoji: '🧔',
color: 'violet',
duration: '30 min',
passThreshold: '80%',
points: '240/300',
sections: [
{ name: 'Addition', digits: '8-digit', problems: 10, points: 10 },
{ name: 'Subtraction', digits: '8-digit', problems: 10, points: 10 },
{ name: 'Multiplication', digits: '7×2', problems: 10, points: 10 },
{ name: 'Division', digits: '8÷2', problems: 10, points: 10 },
],
notes: 'Higher pass threshold (80%)',
level: '7th Dan',
name: 'Nanadan',
minScore: 220,
emoji: '🧝',
color: 'amber',
digits: 30,
type: 'dan' as const,
},
{
level: '2nd Kyu',
emoji: '🧔',
color: 'violet',
duration: '30 min',
passThreshold: '80%',
points: '240/300',
sections: [
{ name: 'Addition', digits: '9-digit', problems: 10, points: 10 },
{ name: 'Subtraction', digits: '9-digit', problems: 10, points: 10 },
{ name: 'Multiplication', digits: '8×2', problems: 10, points: 10 },
{ name: 'Division', digits: '9÷2', problems: 10, points: 10 },
],
notes: 'Near-mastery level',
level: '8th Dan',
name: 'Hachidan',
minScore: 250,
emoji: '🧝‍♂️',
color: 'amber',
digits: 30,
type: 'dan' as const,
},
{
level: '1st Kyu',
emoji: '🧔',
color: 'violet',
duration: '30 min',
passThreshold: '80%',
points: '240/300',
sections: [
{ name: 'Addition', digits: '10-digit', problems: 10, points: 10 },
{ name: 'Subtraction', digits: '10-digit', problems: 10, points: 10 },
{ name: 'Multiplication', digits: '9×2', problems: 10, points: 10 },
{ name: 'Division', digits: '10÷2', problems: 10, points: 10 },
],
notes: 'Highest Kyu level before Dan',
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
// Dan level data - score-based ranking system
const danLevels = [
{ level: 'Pre-1st Dan', name: 'Jun-Shodan', minScore: 90, emoji: '🧙' },
{ level: '1st Dan', name: 'Shodan', minScore: 100, emoji: '🧙' },
{ level: '2nd Dan', name: 'Nidan', minScore: 120, emoji: '🧙‍♂️' },
{ level: '3rd Dan', name: 'Sandan', minScore: 140, emoji: '🧙‍♂️' },
{ level: '4th Dan', name: 'Yondan', minScore: 160, emoji: '🧙‍♀️' },
{ level: '5th Dan', name: 'Godan', minScore: 180, emoji: '🧙‍♀️' },
{ level: '6th Dan', name: 'Rokudan', minScore: 200, emoji: '🧝' },
{ level: '7th Dan', name: 'Nanadan', minScore: 220, emoji: '🧝' },
{ level: '8th Dan', name: 'Hachidan', minScore: 250, emoji: '🧝‍♂️' },
{ level: '9th Dan', name: 'Kudan', minScore: 270, emoji: '🧝‍♀️' },
{ level: '10th Dan', name: 'Judan', minScore: 290, emoji: '👑' },
] as const
export default function LevelsPage() {
const [currentIndex, setCurrentIndex] = useState(0)
const currentLevel = allLevels[currentIndex]
// 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)
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({
@@ -185,7 +141,6 @@ export default function LevelsPage() {
overflow: 'hidden',
})}
>
{/* Background pattern */}
<div
className={css({
position: 'absolute',
@@ -199,7 +154,6 @@ export default function LevelsPage() {
<div className={container({ maxW: '6xl', px: '4', position: 'relative' })}>
<div className={css({ textAlign: 'center', maxW: '5xl', mx: 'auto' })}>
{/* Main headline */}
<h1
className={css({
fontSize: { base: '3xl', md: '5xl', lg: '6xl' },
@@ -214,7 +168,6 @@ export default function LevelsPage() {
Understanding Kyu & Dan Levels
</h1>
{/* Subtitle */}
<p
className={css({
fontSize: { base: 'lg', md: 'xl' },
@@ -225,370 +178,194 @@ export default function LevelsPage() {
lineHeight: '1.6',
})}
>
Learn about the official Japanese soroban ranking system used by the Japan Abacus
Federation
Slide through the complete progression from beginner to master
</p>
{/* Journey progression emojis */}
<div
className={css({
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
gap: '4',
mb: '8',
flexWrap: 'wrap',
})}
>
{[
{ emoji: '🧒', label: '10 Kyu' },
{ emoji: '→', label: '', isArrow: true },
{ emoji: '🧑', label: '5 Kyu' },
{ emoji: '→', label: '', isArrow: true },
{ emoji: '🧔', label: '1 Kyu' },
{ emoji: '→', label: '', isArrow: true },
{ emoji: '🧙', label: 'Dan' },
].map((step, i) => (
<div
key={i}
className={css({
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
gap: '1',
opacity: step.isArrow ? 0.5 : 1,
})}
>
<div
className={css({
fontSize: step.isArrow ? 'xl' : '4xl',
color: step.isArrow ? 'gray.500' : 'yellow.400',
})}
>
{step.emoji}
</div>
{step.label && (
<div className={css({ fontSize: 'xs', color: 'gray.400' })}>{step.label}</div>
)}
</div>
))}
</div>
</div>
</div>
</div>
{/* Main content container */}
<div className={container({ maxW: '7xl', px: '4', py: '12' })}>
{/* Kyu Levels Section */}
{/* 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',
})}
>
Kyu Levels (10th to 1st)
</h2>
<p className={css({ color: 'gray.400', fontSize: 'md', mb: '8' })}>
Progress from beginner to advanced mastery
</p>
</div>
{/* Kyu Level Cards */}
<div className={grid({ columns: { base: 1, md: 2, lg: 3 }, gap: '6' })}>
{kyuLevels.map((kyu, index) => (
<div
key={index}
className={css({
bg: 'rgba(0, 0, 0, 0.4)',
border: '1px solid',
borderColor:
kyu.color === 'green'
? 'green.700'
: kyu.color === 'blue'
? 'blue.700'
: 'violet.700',
rounded: 'xl',
p: '6',
transition: 'all 0.2s',
_hover: {
bg: 'rgba(0, 0, 0, 0.5)',
borderColor:
kyu.color === 'green'
? 'green.500'
: kyu.color === 'blue'
? 'blue.500'
: 'violet.500',
transform: 'translateY(-2px)',
},
})}
>
{/* Card Header */}
<div
className={css({ display: 'flex', alignItems: 'center', gap: '3', mb: '4' })}
>
<div className={css({ fontSize: '3xl' })}>{kyu.emoji}</div>
<div>
<h3
className={css({
fontSize: 'xl',
fontWeight: 'bold',
color:
kyu.color === 'green'
? 'green.400'
: kyu.color === 'blue'
? 'blue.400'
: 'violet.400',
})}
>
{kyu.level}
</h3>
<p className={css({ fontSize: 'sm', color: 'gray.400' })}>{kyu.notes}</p>
</div>
</div>
{/* Exam Details */}
<div className={stack({ gap: '3' })}>
<div
className={css({
display: 'flex',
justifyContent: 'space-between',
fontSize: 'sm',
})}
>
<span className={css({ color: 'gray.400' })}>Duration:</span>
<span className={css({ color: 'white', fontWeight: '500' })}>
{kyu.duration}
</span>
</div>
<div
className={css({
display: 'flex',
justifyContent: 'space-between',
fontSize: 'sm',
})}
>
<span className={css({ color: 'gray.400' })}>Pass Threshold:</span>
<span className={css({ color: 'amber.400', fontWeight: '600' })}>
{kyu.passThreshold}
</span>
</div>
<div
className={css({
display: 'flex',
justifyContent: 'space-between',
fontSize: 'sm',
})}
>
<span className={css({ color: 'gray.400' })}>Points Needed:</span>
<span className={css({ color: 'white', fontWeight: '500' })}>
{kyu.points}
</span>
</div>
</div>
{/* Sections */}
<div
className={css({
mt: '4',
pt: '4',
borderTop: '1px solid',
borderColor: 'gray.700',
})}
>
<div
className={css({
fontSize: 'xs',
color: 'gray.500',
mb: '2',
textTransform: 'uppercase',
letterSpacing: '0.05em',
})}
>
Problem Types
</div>
{kyu.sections.map((section, i) => (
<div
key={i}
className={css({
display: 'flex',
justifyContent: 'space-between',
fontSize: 'sm',
py: '1',
})}
>
<span className={css({ color: 'gray.300' })}>{section.name}</span>
<span className={css({ color: 'gray.400', fontSize: 'xs' })}>
{section.digits}
</span>
</div>
))}
</div>
</div>
))}
</div>
</section>
{/* Dan Levels Section */}
<section className={stack({ gap: '8', mt: '16' })}>
<div className={css({ textAlign: 'center' })}>
<h2
className={css({
fontSize: { base: '2xl', md: '3xl' },
fontWeight: 'bold',
color: 'white',
mb: '4',
})}
>
Dan Levels (Master Ranks)
</h2>
<p className={css({ color: 'gray.400', fontSize: 'md', mb: '8' })}>
Score-based ranking system for master-level practitioners
</p>
</div>
{/* Exam Requirements Box */}
<div
className={css({
bg: 'rgba(251, 191, 36, 0.1)',
border: '1px solid',
borderColor: 'amber.700',
rounded: 'xl',
p: '6',
mb: '6',
})}
>
<h3
className={css({
fontSize: 'lg',
fontWeight: 'bold',
color: 'amber.400',
mb: '3',
})}
>
Dan Exam Requirements
</h3>
<div className={stack({ gap: '2' })}>
<div className={css({ fontSize: 'sm', color: 'gray.300' })}>
<strong>Duration:</strong> 30 minutes
</div>
<div className={css({ fontSize: 'sm', color: 'gray.300' })}>
<strong>Problem Complexity:</strong> 3× that of 1st Kyu
</div>
<div className={css({ fontSize: 'sm', color: 'gray.300', mt: '2' })}>
Addition/Subtraction: 30-digit numbers
</div>
<div className={css({ fontSize: 'sm', color: 'gray.300' })}>
Multiplication: 27×6 digits
</div>
<div className={css({ fontSize: 'sm', color: 'gray.300' })}>
Division: 30÷6 digits
</div>
</div>
</div>
{/* Dan Ladder Visualization */}
{/* Current Level Display */}
<div
className={css({
bg: 'rgba(0, 0, 0, 0.4)',
border: '1px solid',
borderColor: 'amber.700',
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' },
position: 'relative',
})}
>
<div className={stack({ gap: '0' })}>
{danLevels
.slice()
.reverse()
.map((dan, index) => {
const isTop = index === 0
return (
<div
key={index}
className={css({
display: 'flex',
alignItems: 'center',
gap: '4',
p: '3',
borderBottom: !isTop ? '1px solid' : 'none',
borderColor: 'gray.700',
transition: 'all 0.2s',
_hover: {
bg: isTop ? 'rgba(251, 191, 36, 0.1)' : 'rgba(251, 191, 36, 0.05)',
},
})}
>
{/* Emoji */}
<div
className={css({
fontSize: '3xl',
minW: '12',
textAlign: 'center',
})}
>
{dan.emoji}
</div>
{/* Level Info */}
<div className={css({ flex: '1' })}>
<div
className={css({
fontSize: 'lg',
fontWeight: 'bold',
color: isTop ? 'amber.300' : 'amber.400',
})}
>
{dan.level}
</div>
<div className={css({ fontSize: 'sm', color: 'gray.400' })}>
{dan.name}
</div>
</div>
{/* Score */}
<div
className={css({
fontSize: { base: 'xl', md: '2xl' },
fontWeight: 'bold',
color: isTop ? 'amber.300' : 'white',
minW: { base: '20', md: '24' },
textAlign: 'right',
})}
>
{dan.minScore}
<span className={css({ fontSize: 'sm', color: 'gray.400', ml: '1' })}>
pts
</span>
</div>
</div>
)
{/* Level Info */}
<div className={css({ textAlign: 'center', mb: '6' })}>
<div className={css({ fontSize: '5xl', mb: '3' })}>{currentLevel.emoji}</div>
<h2
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',
})}
>
{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>
)}
</div>
{/* Legend */}
{/* Abacus Display */}
<div
className={css({
mt: '6',
pt: '6',
borderTop: '1px solid',
display: 'flex',
justifyContent: 'center',
mb: '6',
p: '6',
bg: 'rgba(0, 0, 0, 0.3)',
rounded: 'lg',
border: '1px solid',
borderColor: 'gray.700',
fontSize: 'sm',
color: 'gray.400',
textAlign: 'center',
overflowX: 'auto',
})}
>
Ranks are awarded based on total score achieved on the Dan-level exam
<AbacusReact
value={0}
columns={currentLevel.digits}
scaleFactor={scaleFactor}
showNumbers={true}
customStyles={{
reckoningBar: { stroke: '#9ca3af', strokeWidth: 3 },
columnPosts: { stroke: '#6b7280' },
numerals: { color: '#d1d5db', fontSize: '14px', fontWeight: 'normal' },
heavenBeads: { fill: '#60a5fa' },
earthBeads: { fill: '#34d399' },
}}
/>
</div>
{/* Digit Count */}
<div className={css({ textAlign: 'center', color: 'gray.400', fontSize: 'sm' })}>
Requires mastery of <strong>{currentLevel.digits}-digit</strong> calculations
</div>
</div>
</section>
{/* Additional Information Section */}
<section className={stack({ gap: '8', mt: '16', pb: '12' })}>
{/* 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',
})}
>
<span>10th Kyu</span>
<span>1st Kyu</span>
<span>10th Dan</span>
</div>
</div>
{/* Legend */}
<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>
{/* Info Section */}
<div
className={css({
bg: 'rgba(0, 0, 0, 0.4)',
@@ -616,26 +393,11 @@ export default function LevelsPage() {
(10th Dan), used internationally for soroban proficiency assessment.
</p>
<p className={css({ color: 'gray.300', lineHeight: '1.6' })}>
The system is designed to gradually increase in difficulty, ensuring students
build a solid foundation before advancing. Each level requires mastery of
increasingly complex calculations, from simple 2-digit operations at 10th Kyu to
30-digit calculations at Dan level.
The system is designed to gradually increase in difficulty. Kyu levels progress
from 2-digit calculations at 10th Kyu to 10-digit calculations at 1st Kyu. Dan
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>

View File

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