Compare commits

...

8 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
4 changed files with 203 additions and 115 deletions

View File

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

View File

@@ -192,6 +192,50 @@ function getLevelDetailsKey(levelName: string): string | null {
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)
@@ -605,43 +649,83 @@ export default function LevelsPage() {
{currentLevel.type === 'kyu' &&
(() => {
const detailsKey = getLevelDetailsKey(currentLevel.level)
const detailsText = detailsKey
const rawText = detailsKey
? kyuLevelDetails[detailsKey as keyof typeof kyuLevelDetails]
: null
const sections = rawText ? parseKyuDetails(rawText) : []
// Calculate responsive font size based on digits
// More digits = larger abacus = less space for details
const getFontSize = () => {
if (currentLevel.digits <= 3) return 'sm' // 10th-8th Kyu
if (currentLevel.digits <= 6) return 'xs' // 7th-5th Kyu
return '2xs' // 4th-1st Kyu
}
// Use consistent sizing across all levels
const sizing = { fontSize: 'sm', gap: '3', iconSize: 'xl' }
return detailsText ? (
return sections.length > 0 ? (
<div
className={css({
flex: '0 0 auto',
display: 'flex',
flexDirection: 'column',
justifyContent: 'center',
display: 'grid',
gridTemplateColumns: 'repeat(2, 1fr)',
gap: sizing.gap,
pr: '4',
pl: '2',
borderRight: '1px solid',
borderColor: 'gray.600',
maxW: '280px',
maxW: '480px',
alignContent: 'center',
})}
>
<pre
className={css({
fontFamily: 'mono',
fontSize: getFontSize(),
color: 'gray.300',
lineHeight: '1.5',
whiteSpace: 'pre-wrap',
wordWrap: 'break-word',
})}
>
{detailsText}
</pre>
{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
})()}

View File

@@ -1,147 +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': `+ / :
• 2-digit, 5 rows, 10 chars
'10-kyu': `Add/Sub: 2-digit, 5口, 10字
×:
• 3 digits total (20 problems)
×: 実+法 = 3 digits (20 problems)
Exam: 20 min
Pass: ≥60/200 points`,
Time: 20 min; Pass ≥ 60/200.
shuzan.jp`,
'9-kyu': `+ / :
• 2-digit, 5 rows, 10 chars
'9-kyu': `Add/Sub: 2-digit, 5口, 10字
×:
• 3 digits total (20 problems)
×: 実+法 = 3 digits (20)
Exam: 20 min
Pass: ≥120/200 points`,
Time: 20 min; Pass ≥ 120/200. (If only one part clears, it's treated as 10-kyu per federation notes.)
shuzan.jp`,
'8-kyu': `+ / :
• 2-digit, 8 rows, 16 chars
'8-kyu': `Add/Sub: 2-digit, 8口, 16字
×:
• 4 digits total (10 problems)
×: 実+法 = 4 digits (10)
÷:
• 3 digits total (10 problems)
÷: 法+商 = 3 digits (10)
Exam: 20 min | Pass: ≥120/200`,
Time: 20 min; Pass ≥ 120/200.
shuzan.jp`,
'7-kyu': `+ / :
• 2-digit, 10 rows, 20 chars
'7-kyu': `Add/Sub: 2-digit, 10口, 20字
×:
• 4 digits total (10 problems)
×: 実+法 = 4 digits (10)
÷:
• 4 digits total (10 problems)
÷: 法+商 = 4 digits (10)
Exam: 20 min | Pass: ≥120/200`,
Time: 20 min; Pass ≥ 120/200.
shuzan.jp`,
'6-kyu': `+ / :
• 10 rows, 30 chars
'6-kyu': `Add/Sub: 10口, 30字
×:
• 5 digits total (20 problems)
×: 実+法 = 5 digits (20)
÷:
• 4 digits total (20 problems)
÷: 法+商 = 4 digits (20)
Exam: 30 min | Pass: ≥210/300`,
Time: 30 min; Pass ≥ 210/300.
shuzan.jp`,
'5-kyu': `+ / :
• 10 rows, 40 chars
'5-kyu': `Add/Sub: 10口, 40字
×:
• 6 digits total (20 problems)
×: 実+法 = 6 digits (20)
÷:
• 5 digits total (20 problems)
÷: 法+商 = 5 digits (20)
Exam: 30 min | Pass: ≥210/300`,
Time: 30 min; Pass ≥ 210/300.
shuzan.jp`,
'4-kyu': `+ / :
• 10 rows, 50 chars
'4-kyu': `Add/Sub: 10口, 50字
×:
• 7 digits total (20 problems)
×: 実+法 = 7 digits (20)
÷:
• 6 digits total (20 problems)
÷: 法+商 = 6 digits (20)
Exam: 30 min | Pass: ≥210/300`,
Time: 30 min; Pass ≥ 210/300.
shuzan.jp`,
'Pre-3-kyu': `+ / :
• 10 rows, 50-60 chars (10 problems)
'Pre-3-kyu': `Add/Sub: 10口, 50字 ×5題 and 10口, 60字 ×5題 (total 10)
×:
• 7 digits total (20 problems)
×: 実+法 = 7 digits (20)
÷:
• 6 digits total (20 problems)
÷: 法+商 = 6 digits (20)
Exam: 30 min | Pass: ≥240/300`,
Time: 30 min; Pass ≥ 240/300.
shuzan.jp`,
'3-kyu': `+ / :
• 10 rows, 60 chars
'3-kyu': `Add/Sub: 10口, 60字
×:
• 7 digits total (20 problems)
×: 実+法 = 7 digits (20)
÷:
• 6 digits total (20 problems)
÷: 法+商 = 6 digits (20)
Exam: 30 min | Pass: ≥240/300`,
Time: 30 min; Pass ≥ 240/300.
shuzan.jp`,
'Pre-2-kyu': `+ / :
• 10 rows, 70 chars
'Pre-2-kyu': `Add/Sub: 10口, 70字
×:
• 8 digits total (20 problems)
×: 実+法 = 8 digits (20)
÷:
• 7 digits total (20 problems)
÷: 法+商 = 7 digits (20)
Exam: 30 min | Pass: ≥240/300`,
Time: 30 min; Pass ≥ 240/300.
shuzan.jp`,
'2-kyu': `+ / :
• 10 rows, 80 chars
'2-kyu': `Add/Sub: 10口, 80字
×:
• 9 digits total (20 problems)
×: 実+法 = 9 digits (20)
÷:
• 8 digits total (20 problems)
÷: 法+商 = 8 digits (20)
Exam: 30 min | Pass: ≥240/300`,
Time: 30 min; Pass ≥ 240/300.
shuzan.jp`,
'Pre-1-kyu': `+ / :
• 10 rows, 90 chars
'Pre-1-kyu': `Add/Sub: 10口, 90字
×:
• 10 digits total (20 problems)
×: 実+法 = 10 digits (20)
÷:
• 9 digits total (20 problems)
÷: 法+商 = 9 digits (20)
Exam: 30 min | Pass: ≥240/300`,
Time: 30 min; Pass ≥ 240/300.
shuzan.jp`,
'1-kyu': `+ / :
• 10 rows, 100 chars
'1-kyu': `Add/Sub: 10口, 100字
×:
• 11 digits total (20 problems)
×: 実+法 = 11 digits (20)
÷:
• 10 digits total (20 problems)
÷: 法+商 = 10 digits (20)
Exam: 30 min | Pass: ≥240/300`,
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.42.0",
"version": "4.43.2",
"private": true,
"description": "Beautiful Soroban Flashcard Generator - Monorepo",
"workspaces": [