Compare commits

...

6 Commits

Author SHA1 Message Date
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
semantic-release-bot
6d86281c63 chore(release): 4.28.0 [skip ci]
## [4.28.0](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.27.0...v4.28.0) (2025-10-20)

### Features

* **levels:** add informational footer section ([0b1bff7](0b1bff7eab))
2025-10-20 12:24:15 +00:00
Thomas Hallock
0b1bff7eab feat(levels): add informational footer section
Add comprehensive "About This Ranking System" section at the bottom of the
page explaining the Japan Abacus Federation ranking system and its purpose.

Features:
- Educational context about the JAF ranking system
- Explanation of progressive difficulty structure
- Clear disclaimer about educational vs certification purposes
- Professional styling matching the rest of the page
- Mobile-responsive padding and typography

Phase 6 complete: Final polish with educational context and disclaimers.

All phases complete! The /levels page now provides:
- Complete kyu level information (10th to 1st)
- Dan level ladder visualization (Pre-1st Dan to 10th Dan)
- Exam requirements and scoring details
- Educational context and disclaimers

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-20 07:23:10 -05:00
4 changed files with 344 additions and 484 deletions

View File

@@ -1,3 +1,24 @@
## [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)
### Features
* **levels:** add informational footer section ([0b1bff7](https://github.com/antialias/soroban-abacus-flashcards/commit/0b1bff7eab8f5da84ae309dbda336e168c2fe3fd))
## [4.27.0](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.26.0...v4.27.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

@@ -2,174 +2,185 @@
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 = [
{
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: '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: '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: '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: '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: '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: '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: '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: '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: '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: '10th Kyu', emoji: '🧒', color: 'green', digits: 2 },
{ level: '9th Kyu', emoji: '🧒', color: 'green', digits: 2 },
{ level: '8th Kyu', emoji: '🧒', color: 'green', digits: 3 },
{ level: '7th Kyu', emoji: '🧒', color: 'green', digits: 4 },
{ level: '6th Kyu', emoji: '🧑', color: 'blue', digits: 5 },
{ level: '5th Kyu', emoji: '🧑', color: 'blue', digits: 6 },
{ level: '4th Kyu', emoji: '🧑', color: 'blue', digits: 7 },
{ level: '3rd Kyu', emoji: '🧔', color: 'violet', digits: 8 },
{ level: '2nd Kyu', emoji: '🧔', color: 'violet', digits: 9 },
{ level: '1st Kyu', emoji: '🧔', color: 'violet', digits: 10 },
] as const
// Dan level data - score-based ranking system
// Dan level data - all use 30-digit calculations
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: '👑' },
{ level: 'Pre-1st Dan', name: 'Jun-Shodan', minScore: 90, emoji: '🧙', digits: 30 },
{ level: '1st Dan', name: 'Shodan', minScore: 100, emoji: '🧙', digits: 30 },
{ level: '2nd Dan', name: 'Nidan', minScore: 120, emoji: '🧙‍♂️', digits: 30 },
{ level: '3rd Dan', name: 'Sandan', minScore: 140, emoji: '🧙‍♂️', digits: 30 },
{ level: '4th Dan', name: 'Yondan', minScore: 160, emoji: '🧙‍♀️', digits: 30 },
{ level: '5th Dan', name: 'Godan', minScore: 180, emoji: '🧙‍♀️', digits: 30 },
{ level: '6th Dan', name: 'Rokudan', minScore: 200, emoji: '🧝', digits: 30 },
{ level: '7th Dan', name: 'Nanadan', minScore: 220, emoji: '🧝', digits: 30 },
{ level: '8th Dan', name: 'Hachidan', minScore: 250, emoji: '🧝‍♂️', digits: 30 },
{ level: '9th Dan', name: 'Kudan', minScore: 270, emoji: '🧝‍♀️', digits: 30 },
{ level: '10th Dan', name: 'Judan', minScore: 290, emoji: '👑', digits: 30 },
] as const
// Compact abacus column component
function AbacusColumn({ color }: { color: string }) {
return (
<div
className={css({
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
gap: '0.5',
w: '2',
})}
>
{/* Top bead */}
<div
className={css({
w: '2',
h: '2',
rounded: 'full',
bg:
color === 'green'
? 'green.500'
: color === 'blue'
? 'blue.500'
: color === 'violet'
? 'violet.500'
: 'amber.500',
opacity: 0.7,
})}
/>
{/* Divider */}
<div className={css({ w: '2', h: '0.5', bg: 'gray.600' })} />
{/* Bottom beads */}
{[0, 1, 2, 3].map((i) => (
<div
key={i}
className={css({
w: '2',
h: '2',
rounded: 'full',
bg:
color === 'green'
? 'green.500'
: color === 'blue'
? 'blue.500'
: color === 'violet'
? 'violet.500'
: 'amber.500',
opacity: 0.7,
})}
/>
))}
</div>
)
}
// Level card component for the slider
function LevelCard({
level,
emoji,
color,
digits,
subtitle,
}: {
level: string
emoji: string
color: string
digits: number
subtitle?: string
}) {
// Limit display for very high digit counts
const displayDigits = Math.min(digits, 15)
const showEllipsis = digits > 15
return (
<div
className={css({
minW: { base: '64', md: '80' },
bg: 'rgba(0, 0, 0, 0.4)',
border: '2px solid',
borderColor:
color === 'green'
? 'green.500'
: color === 'blue'
? 'blue.500'
: color === 'violet'
? 'violet.500'
: 'amber.500',
rounded: 'xl',
p: { base: '4', md: '6' },
transition: 'all 0.2s',
_hover: {
transform: 'translateY(-4px)',
boxShadow: '0 8px 16px rgba(0, 0, 0, 0.4)',
},
})}
>
{/* Header */}
<div className={css({ textAlign: 'center', mb: '4' })}>
<div className={css({ fontSize: { base: '3xl', md: '4xl' }, mb: '2' })}>{emoji}</div>
<div
className={css({
fontSize: { base: 'lg', md: 'xl' },
fontWeight: 'bold',
color:
color === 'green'
? 'green.400'
: color === 'blue'
? 'blue.400'
: color === 'violet'
? 'violet.400'
: 'amber.400',
mb: '1',
})}
>
{level}
</div>
{subtitle && <div className={css({ fontSize: 'sm', color: 'gray.400' })}>{subtitle}</div>}
</div>
{/* Abacus Visualization */}
<div
className={css({
display: 'flex',
gap: '1.5',
justifyContent: 'center',
alignItems: 'center',
p: '4',
bg: 'rgba(0, 0, 0, 0.3)',
rounded: 'lg',
minH: '32',
})}
>
{Array.from({ length: displayDigits }).map((_, i) => (
<AbacusColumn key={i} color={color} />
))}
{showEllipsis && (
<div className={css({ color: 'gray.500', fontSize: '2xl', px: '2' })}>...</div>
)}
</div>
{/* Digit count label */}
<div className={css({ textAlign: 'center', mt: '3', fontSize: 'sm', color: 'gray.400' })}>
{digits} {digits === 1 ? 'digit' : 'digits'}
</div>
</div>
)
}
export default function LevelsPage() {
return (
<PageWithNav navTitle="Kyu & Dan Levels" navEmoji="📊">
@@ -185,7 +196,6 @@ export default function LevelsPage() {
overflow: 'hidden',
})}
>
{/* Background pattern */}
<div
className={css({
position: 'absolute',
@@ -199,7 +209,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 +223,6 @@ export default function LevelsPage() {
Understanding Kyu & Dan Levels
</h1>
{/* Subtitle */}
<p
className={css({
fontSize: { base: 'lg', md: 'xl' },
@@ -225,61 +233,15 @@ export default function LevelsPage() {
lineHeight: '1.6',
})}
>
Learn about the official Japanese soroban ranking system used by the Japan Abacus
Federation
Explore the 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 */}
{/* Journey Slider */}
<section className={stack({ gap: '8' })}>
<div className={css({ textAlign: 'center' })}>
<h2
@@ -290,299 +252,151 @@ export default function LevelsPage() {
mb: '4',
})}
>
Kyu Levels (10th to 1st)
The Complete Journey
</h2>
<p className={css({ color: 'gray.400', fontSize: 'md', mb: '8' })}>
Progress from beginner to advanced mastery
Slide through all ranks from 10th Kyu to 10th Dan
</p>
</div>
{/* Kyu Level Cards */}
<div className={grid({ columns: { base: 1, md: 2, lg: 3 }, gap: '6' })}>
{kyuLevels.map((kyu, index) => (
{/* Horizontal Slider */}
<div
className={css({
w: '100%',
overflowX: 'auto',
overflowY: 'hidden',
pb: '4',
})}
>
<div className={css({ display: 'flex', gap: '4', pb: '2' })}>
{/* Kyu Levels */}
{kyuLevels.map((kyu, index) => (
<LevelCard
key={index}
level={kyu.level}
emoji={kyu.emoji}
color={kyu.color}
digits={kyu.digits}
/>
))}
{/* Transition marker */}
<div
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)',
},
minW: '20',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
fontSize: '4xl',
color: 'gray.500',
})}
>
{/* 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>
))}
{/* Dan Levels */}
{danLevels.map((dan, index) => (
<LevelCard
key={index}
level={dan.level}
emoji={dan.emoji}
color="amber"
digits={dan.digits}
subtitle={`${dan.minScore}+ pts`}
/>
))}
</div>
</div>
{/* Legend */}
<div
className={css({
display: 'flex',
flexWrap: 'wrap',
gap: '6',
justifyContent: 'center',
mt: '6',
p: '6',
bg: 'rgba(0, 0, 0, 0.3)',
rounded: 'lg',
})}
>
<div className={css({ display: 'flex', alignItems: 'center', gap: '2' })}>
<div className={css({ w: '4', h: '4', bg: 'green.500', rounded: 'sm' })} />
<span className={css({ fontSize: 'sm', color: 'gray.300' })}>
Beginner (10-7 Kyu)
</span>
</div>
<div className={css({ display: 'flex', alignItems: 'center', gap: '2' })}>
<div className={css({ w: '4', h: '4', bg: 'blue.500', rounded: 'sm' })} />
<span className={css({ fontSize: 'sm', color: 'gray.300' })}>
Intermediate (6-4 Kyu)
</span>
</div>
<div className={css({ display: 'flex', alignItems: 'center', gap: '2' })}>
<div className={css({ w: '4', h: '4', bg: 'violet.500', rounded: 'sm' })} />
<span className={css({ fontSize: 'sm', color: 'gray.300' })}>
Advanced (3-1 Kyu)
</span>
</div>
<div className={css({ display: 'flex', alignItems: 'center', gap: '2' })}>
<div className={css({ w: '4', h: '4', bg: 'amber.500', rounded: 'sm' })} />
<span className={css({ fontSize: 'sm', color: 'gray.300' })}>
Master (Dan ranks)
</span>
</div>
</div>
</section>
{/* Dan Levels Section */}
<section className={stack({ gap: '8', mt: '16' })}>
<div className={css({ textAlign: 'center' })}>
<h2
{/* Additional Information Section */}
<section className={stack({ gap: '8', mt: '16', pb: '12' })}>
<div
className={css({
bg: 'rgba(0, 0, 0, 0.4)',
border: '1px solid',
borderColor: 'gray.700',
rounded: 'xl',
p: { base: '6', md: '8' },
})}
>
<h3
className={css({
fontSize: { base: '2xl', md: '3xl' },
fontSize: { base: 'xl', md: '2xl' },
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
About This Ranking System
</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 */}
<div
className={css({
bg: 'rgba(0, 0, 0, 0.4)',
border: '1px solid',
borderColor: 'amber.700',
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>
)
<div className={stack({ gap: '4' })}>
<p className={css({ color: 'gray.300', lineHeight: '1.6' })}>
This ranking system is based on the official examination structure used by the{' '}
<strong className={css({ color: 'white' })}>Japan Abacus Federation</strong>. It
represents a standardized progression from beginner (10th Kyu) to master level
(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. 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',
})}
</div>
{/* Legend */}
<div
className={css({
mt: '6',
pt: '6',
borderTop: '1px solid',
borderColor: 'gray.700',
fontSize: 'sm',
color: 'gray.400',
textAlign: 'center',
})}
>
Ranks are awarded based on total score achieved on the Dan-level exam
>
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.27.0",
"version": "4.30.0",
"private": true,
"description": "Beautiful Soroban Flashcard Generator - Monorepo",
"workspaces": [