Compare commits

...

25 Commits

Author SHA1 Message Date
semantic-release-bot
89fb670f93 chore(release): 4.20.4 [skip ci]
## [4.20.4](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.20.3...v4.20.4) (2025-10-19)

### Bug Fixes

* **homepage:** use inline styles for Your Journey text contrast ([8e51390](8e51390018)), closes [#e5e7](https://github.com/antialias/soroban-abacus-flashcards/issues/e5e7) [#e5e7](https://github.com/antialias/soroban-abacus-flashcards/issues/e5e7) [#9ca3](https://github.com/antialias/soroban-abacus-flashcards/issues/9ca3) [#d1d5](https://github.com/antialias/soroban-abacus-flashcards/issues/d1d5)
2025-10-19 19:07:08 +00:00
Thomas Hallock
8e51390018 fix(homepage): use inline styles for Your Journey text contrast
Switched from Panda CSS to direct inline styles with hex colors:
- Labels (Beginner/Intermediate/Advanced/Master): #e5e7eb (light gray)
- Subtitle: #e5e7eb (light gray)
- Arrows: #9ca3af (medium gray)
- Footer: #d1d5db (light gray)

This bypasses any CSS framework issues and applies colors directly.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-19 14:06:02 -05:00
semantic-release-bot
e7e54619ae chore(release): 4.20.3 [skip ci]
## [4.20.3](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.20.2...v4.20.3) (2025-10-19)

### Bug Fixes

* **homepage:** use explicit RGBA colors for Your Journey text ([9c51cc9](9c51cc94ee))
2025-10-19 18:58:39 +00:00
Thomas Hallock
9c51cc94ee fix(homepage): use explicit RGBA colors for Your Journey text
Switched from Panda CSS gray tokens to explicit RGBA values for better compatibility:
- Text: rgba(229, 231, 235, 1) - light gray for maximum readability
- Subtitle: rgba(209, 213, 219, 1) - slightly darker light gray
- Arrows: rgba(156, 163, 175, 1) - medium gray

This ensures proper rendering regardless of theme token configuration.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-19 13:57:30 -05:00
semantic-release-bot
df674426c5 chore(release): 4.20.2 [skip ci]
## [4.20.2](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.20.1...v4.20.2) (2025-10-19)

### Bug Fixes

* **homepage:** improve text contrast in Your Journey section ([24d1200](24d120004d))
* **tutorial:** resolve TypeScript errors in TutorialPlayer ([88f57ce](88f57ce6df))
2025-10-19 18:57:20 +00:00
Thomas Hallock
24d120004d fix(homepage): improve text contrast in Your Journey section
Changed gray text colors to lighter values for better readability on dark background:
- Subtitle text: gray.400 → gray.200
- Stage labels: gray.400 → gray.200
- Navigation arrows: gray.600 → gray.400
- Footer text: gray.500 → gray.300

This addresses readability concerns while maintaining visual hierarchy.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-19 13:56:04 -05:00
Thomas Hallock
88f57ce6df fix(tutorial): resolve TypeScript errors in TutorialPlayer
- Remove references to non-existent highlight.columnIndex property
- Remove references to removed currentStep.errorMessages property
- Use placeValue directly for highlight filtering and calculations
- Add generic error message for incorrect bead clicks

All changes maintain existing functionality while fixing type safety issues.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-19 13:54:56 -05:00
semantic-release-bot
3a5dc0f1c8 chore(release): 4.20.1 [skip ci]
## [4.20.1](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.20.0...v4.20.1) (2025-10-19)

### Bug Fixes

* **homepage:** correct positioning of progression arrows in Your Journey section ([3fff9ef](3fff9ef140))

### Code Refactoring

* **homepage:** move What You'll Learn above tutorial ([ca1c6d8](ca1c6d8602))
2025-10-19 18:51:24 +00:00
Thomas Hallock
3fff9ef140 fix(homepage): correct positioning of progression arrows in Your Journey section
Added position: 'relative' to parent containers to properly anchor the absolutely positioned arrow elements between progression levels. This ensures the arrows display correctly between stages.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-19 13:50:23 -05:00
Thomas Hallock
ca1c6d8602 refactor(homepage): move What You'll Learn above tutorial
Repositioned the learning objectives section to appear before the interactive tutorial for better visual hierarchy and user flow.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-19 13:49:21 -05:00
semantic-release-bot
e6bcf20807 chore(release): 4.20.0 [skip ci]
## [4.20.0](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.19.0...v4.20.0) (2025-10-19)

### Features

* **tutorial:** add hideTooltip prop and improve dark mode coaching bar ([1ee25b3](1ee25b3dd2))
2025-10-19 18:47:56 +00:00
Thomas Hallock
1ee25b3dd2 feat(tutorial): add hideTooltip prop and improve dark mode coaching bar
- Added hideTooltip prop to TutorialPlayer to optionally hide guidance panels
- Enhanced coaching bar text for dark mode (brighter yellow with glow effect)
- Applied hideTooltip to homepage tutorial for cleaner presentation
- Updated dark mode header background for better integration

These changes are specific to the homepage dark theme instance while preserving default behavior for all other uses of the tutorial system.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-19 13:46:54 -05:00
semantic-release-bot
468bdebe3a chore(release): 4.19.0 [skip ci]
## [4.19.0](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.18.1...v4.19.0) (2025-10-19)

### Features

* **tutorial:** add fill color support for dark mode column posts and reckoning bar ([2eb3ff3](2eb3ff3406))
2025-10-19 18:38:20 +00:00
Thomas Hallock
2eb3ff3406 feat(tutorial): add fill color support for dark mode column posts and reckoning bar
Added fill property to ColumnPostStyle and ReckoningBarStyle interfaces in abacus-react to enable high-contrast colors in dark mode. Updated TutorialPlayer to set fill colors for column posts (30% white) and reckoning bar (40% white) when in dark theme mode.

This improves visibility of the abacus frame elements in dark mode on the homepage tutorial.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-19 13:37:10 -05:00
semantic-release-bot
efbe99a9e2 chore(release): 4.18.1 [skip ci]
## [4.18.1](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.18.0...v4.18.1) (2025-10-19)

### Bug Fixes

* **tutorial:** use correct customStyles API for dark mode frame styling ([fdc882c](fdc882cb04))
2025-10-19 18:30:53 +00:00
Thomas Hallock
fdc882cb04 fix(tutorial): use correct customStyles API for dark mode frame styling
Fixed the dark mode styling to use the correct AbacusReact customStyles API:

Previous (incorrect):
- Used nested `frame` object that doesn't exist in the API
- `frame.column`, `frame.reckoningBar`, `frame.border`

Corrected (per AbacusReact.tsx interface):
- `columnPosts` - Global styling for all column dividers
- `reckoningBar` - Horizontal middle bar styling

Changes:
- Column dividers: rgba(255, 255, 255, 0.2) with 2px stroke
- Reckoning bar: rgba(255, 255, 255, 0.25) with 3px stroke

These properties are at the root level of customStyles, not nested
under a `frame` object. The styling will now properly apply to the
abacus frame elements in dark mode.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-19 13:29:45 -05:00
semantic-release-bot
a7778c648d chore(release): 4.18.0 [skip ci]
## [4.18.0](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.17.2...v4.18.0) (2025-10-19)

### Features

* **tutorial:** add dark mode styling for coaching bar and abacus frame ([7e2f580](7e2f580877))
2025-10-19 18:27:22 +00:00
Thomas Hallock
7e2f580877 feat(tutorial): add dark mode styling for coaching bar and abacus frame
Enhanced the dark mode theme support for the tutorial player:

Coaching Bar:
- Updated instruction text color to use yellow.300 for dark mode instead of
  hardcoded yellow.900
- Ensures coaching instructions are readable against dark backgrounds

Abacus Frame:
- Added custom frame styling for dark mode using customStyles prop
- Column dividers: rgba(255, 255, 255, 0.15) with 2px stroke
- Reckoning bar: rgba(255, 255, 255, 0.2) with 3px stroke
- Outer border: rgba(255, 255, 255, 0.15) with 2px stroke
- Provides subtle, elegant appearance that blends with dark theme

The frame styling is automatically applied when theme="dark" and does not
affect light mode or other tutorial instances.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-19 13:26:14 -05:00
semantic-release-bot
f18a89974a chore(release): 4.17.2 [skip ci]
## [4.17.2](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.17.1...v4.17.2) (2025-10-19)

### Bug Fixes

* **tutorial:** correct column index calculation for variable column counts ([bf1ced4](bf1ced43f8))
2025-10-19 18:25:43 +00:00
Thomas Hallock
bf1ced43f8 fix(tutorial): correct column index calculation for variable column counts
Fixed a critical bug where tooltip overlays were referencing invalid column
indices when using fewer than 5 columns. The issue occurred because column
index calculations assumed a 5-column layout (0-4), but when using
abacusColumns={2}, the valid indices should be 0-1.

Changes:
- Updated targetColumnIndex calculation to use (abacusColumns - 1) - placeValue
  instead of hardcoded 4 - placeValue
- Fixed hasActiveBeadsToLeft logic to use abacusColumns for padding and
  column index conversions
- All column index calculations now properly account for the actual number
  of columns

This resolves the "Cannot read properties of undefined (reading 'heavenActive')"
error that occurred when using fewer than 5 columns on the homepage tutorial.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-19 13:24:35 -05:00
semantic-release-bot
6435027147 chore(release): 4.17.1 [skip ci]
## [4.17.1](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.17.0...v4.17.1) (2025-10-19)

### Bug Fixes

* **tutorial:** filter bead highlights when using fewer columns ([4d906ec](4d906ec20e))
2025-10-19 18:17:38 +00:00
Thomas Hallock
4d906ec20e fix(tutorial): filter bead highlights when using fewer columns
Fix runtime error when abacusColumns < 5 by filtering all bead highlights
to only include columns that actually exist.

Changes:
- Filter highlightBeads prop to only include valid place values
- Filter stepBeadHighlights to only include valid place values
- Filter customStyles column highlights to only include valid columns
- Add abacusColumns to dependencies of relevant useMemo/useCallback

This prevents accessing undefined column states when rendering with
fewer than 5 columns (e.g., abacusColumns={2} for simple demos).

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-19 13:16:24 -05:00
semantic-release-bot
ff7b711fe0 chore(release): 4.17.0 [skip ci]
## [4.17.0](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.16.0...v4.17.0) (2025-10-19)

### Features

* **tutorial:** add dark theme and column control props ([d42f9b2](d42f9b2d9a))

### Styles

* **homepage:** soften tutorial styling for dark theme cohesion ([faaefba](faaefbacff)), closes [#f9](https://github.com/antialias/soroban-abacus-flashcards/issues/f9)
2025-10-19 18:13:26 +00:00
Thomas Hallock
d42f9b2d9a feat(tutorial): add dark theme and column control props
Add `theme` and `abacusColumns` props to TutorialPlayer for better customization:
- theme: 'light' | 'dark' controls all color schemes
- abacusColumns: number controls abacus column count (default 5)

Updated homepage to use:
- abacusColumns={2} for simpler 2+3 demo
- theme="dark" for cohesive integration with dark page design
- Vertical layout with "What You'll Learn" below tutorial

Dark theme styling:
- Transparent dark backgrounds for all containers
- Muted text colors (gray.200-gray.400)
- Subtle borders and shadows
- Removed bright yellow/amber gradients

All changes maintain backward compatibility - defaults to light theme with 5 columns.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-19 13:12:18 -05:00
Thomas Hallock
faaefbacff style(homepage): soften tutorial styling for dark theme cohesion
Apply visual improvements to homepage tutorial demo only (not tutorial system):
- Hide close (X) button in CoachBar - not needed on homepage
- Soften white backgrounds to light gray (#f9fafb)
- Mute text colors (h2 to gray.800, p to gray.600)
- Add transparency to guidance box (reduced opacity)
- Improve spacing and padding throughout
- Soften all shadows (reduced opacity)
- Mute amber/slate text colors for better dark theme integration

All changes scoped to .homepage-tutorial-demo wrapper via CSS overrides.
Tutorial system remains unchanged.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-19 12:59:27 -05:00
7 changed files with 310 additions and 101 deletions

View File

@@ -1,3 +1,91 @@
## [4.20.4](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.20.3...v4.20.4) (2025-10-19)
### Bug Fixes
* **homepage:** use inline styles for Your Journey text contrast ([8e51390](https://github.com/antialias/soroban-abacus-flashcards/commit/8e5139001818d7013e1b2654ac707f7429316d58)), closes [#e5e7](https://github.com/antialias/soroban-abacus-flashcards/issues/e5e7) [#e5e7](https://github.com/antialias/soroban-abacus-flashcards/issues/e5e7) [#9ca3](https://github.com/antialias/soroban-abacus-flashcards/issues/9ca3) [#d1d5](https://github.com/antialias/soroban-abacus-flashcards/issues/d1d5)
## [4.20.3](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.20.2...v4.20.3) (2025-10-19)
### Bug Fixes
* **homepage:** use explicit RGBA colors for Your Journey text ([9c51cc9](https://github.com/antialias/soroban-abacus-flashcards/commit/9c51cc94eec4efcab9c0b9d1190f5b79c0c7d365))
## [4.20.2](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.20.1...v4.20.2) (2025-10-19)
### Bug Fixes
* **homepage:** improve text contrast in Your Journey section ([24d1200](https://github.com/antialias/soroban-abacus-flashcards/commit/24d120004dccecc1ce2f08c1b73eec902868fb23))
* **tutorial:** resolve TypeScript errors in TutorialPlayer ([88f57ce](https://github.com/antialias/soroban-abacus-flashcards/commit/88f57ce6df125142d6ea7feec60c475926bd4929))
## [4.20.1](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.20.0...v4.20.1) (2025-10-19)
### Bug Fixes
* **homepage:** correct positioning of progression arrows in Your Journey section ([3fff9ef](https://github.com/antialias/soroban-abacus-flashcards/commit/3fff9ef140bf1f462042f8319ed6c5e2a376e4ba))
### Code Refactoring
* **homepage:** move What You'll Learn above tutorial ([ca1c6d8](https://github.com/antialias/soroban-abacus-flashcards/commit/ca1c6d86029c891e019a96ba161e49b08b5be1bf))
## [4.20.0](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.19.0...v4.20.0) (2025-10-19)
### Features
* **tutorial:** add hideTooltip prop and improve dark mode coaching bar ([1ee25b3](https://github.com/antialias/soroban-abacus-flashcards/commit/1ee25b3dd2f0ee9dd7ed571ba818b7ca5a247f85))
## [4.19.0](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.18.1...v4.19.0) (2025-10-19)
### Features
* **tutorial:** add fill color support for dark mode column posts and reckoning bar ([2eb3ff3](https://github.com/antialias/soroban-abacus-flashcards/commit/2eb3ff340613301df20bf14f5b461371a27d7f05))
## [4.18.1](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.18.0...v4.18.1) (2025-10-19)
### Bug Fixes
* **tutorial:** use correct customStyles API for dark mode frame styling ([fdc882c](https://github.com/antialias/soroban-abacus-flashcards/commit/fdc882cb046e3d8835fbca59841e9af5329bcc52))
## [4.18.0](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.17.2...v4.18.0) (2025-10-19)
### Features
* **tutorial:** add dark mode styling for coaching bar and abacus frame ([7e2f580](https://github.com/antialias/soroban-abacus-flashcards/commit/7e2f580877af9d21409f427778fa3569c950fcf5))
## [4.17.2](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.17.1...v4.17.2) (2025-10-19)
### Bug Fixes
* **tutorial:** correct column index calculation for variable column counts ([bf1ced4](https://github.com/antialias/soroban-abacus-flashcards/commit/bf1ced43f801938b05f01548eea5fe771de1b58f))
## [4.17.1](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.17.0...v4.17.1) (2025-10-19)
### Bug Fixes
* **tutorial:** filter bead highlights when using fewer columns ([4d906ec](https://github.com/antialias/soroban-abacus-flashcards/commit/4d906ec20e90a9b0b3838f9b8428e0c68992f381))
## [4.17.0](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.16.0...v4.17.0) (2025-10-19)
### Features
* **tutorial:** add dark theme and column control props ([d42f9b2](https://github.com/antialias/soroban-abacus-flashcards/commit/d42f9b2d9ad630826c55b753dc581c469e8f9083))
### Styles
* **homepage:** soften tutorial styling for dark theme cohesion ([faaefba](https://github.com/antialias/soroban-abacus-flashcards/commit/faaefbacff419b337aa0fac4a101d5106a18c77c)), closes [#f9](https://github.com/antialias/soroban-abacus-flashcards/issues/f9)
## [4.16.0](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.15.1...v4.16.0) (2025-10-19)

View File

@@ -292,10 +292,10 @@ export default function RoomPage() {
disabled={isDisabled}
style={{
background: gameDef.manifest.gradient,
borderColor: gameDef.manifest.borderColor,
}}
className={css({
border: '2px solid',
borderColor: gameDef.manifest.borderColor,
borderRadius: '2xl',
padding: '6',
cursor: isDisabled ? 'not-allowed' : 'pointer',

View File

@@ -206,42 +206,40 @@ export default function HomePage() {
</p>
</div>
<div className={grid({ columns: { base: 1, lg: 2 }, gap: '8' })}>
{/* Live demo */}
{/* Live demo and learning objectives */}
<div
className={css({
bg: 'rgba(0, 0, 0, 0.4)',
rounded: 'xl',
p: '8',
border: '1px solid',
borderColor: 'gray.700',
shadow: 'lg',
maxW: '900px',
mx: 'auto',
})}
>
{/* What you'll learn - above tutorial */}
<div
className={css({
bg: 'rgba(0, 0, 0, 0.4)',
rounded: 'xl',
p: '6',
border: '1px solid',
mb: '8',
pb: '6',
borderBottom: '1px solid',
borderColor: 'gray.700',
shadow: 'lg',
})}
>
<TutorialPlayer
tutorial={friendsOf5Tutorial}
isDebugMode={false}
showDebugPanel={false}
hideNavigation={true}
/>
</div>
{/* What you'll learn */}
<div
className={stack({
gap: '4',
bg: 'rgba(0, 0, 0, 0.4)',
p: '6',
borderRadius: 'xl',
border: '1px solid',
borderColor: 'gray.700',
justifyContent: 'center',
})}
>
<h3 className={css({ fontSize: 'xl', fontWeight: 'bold', color: 'white' })}>
<h3
className={css({
fontSize: 'xl',
fontWeight: 'bold',
color: 'white',
mb: '4',
textAlign: 'center',
})}
>
What You'll Learn
</h3>
<div className={stack({ gap: '3' })}>
<div className={grid({ columns: { base: 1, sm: 2 }, gap: '3' })}>
{[
'Read and set numbers on an abacus',
'Add and subtract with "friends" techniques',
@@ -250,11 +248,21 @@ export default function HomePage() {
].map((skill, i) => (
<div key={i} className={hstack({ gap: '3' })}>
<span className={css({ color: 'yellow.400', fontSize: 'lg' })}>✓</span>
<span className={css({ color: 'gray.300', fontSize: 'md' })}>{skill}</span>
<span className={css({ color: 'gray.300', fontSize: 'sm' })}>{skill}</span>
</div>
))}
</div>
</div>
<TutorialPlayer
tutorial={friendsOf5Tutorial}
isDebugMode={false}
showDebugPanel={false}
hideNavigation={true}
hideTooltip={true}
abacusColumns={2}
theme="dark"
/>
</div>
</section>
@@ -371,9 +379,7 @@ export default function HomePage() {
>
Your Journey
</h2>
<p className={css({ color: 'gray.400', fontSize: 'md' })}>
Progress from beginner to master
</p>
<p style={{ color: '#e5e7eb', fontSize: '16px' }}>Progress from beginner to master</p>
</div>
<div
@@ -400,7 +406,15 @@ export default function HomePage() {
{ level: '1 Kyu', label: 'Advanced', color: 'purple.400' },
{ level: 'Dan', label: 'Master', color: 'yellow.400' },
].map((stage, i) => (
<div key={i} className={stack({ gap: '2', textAlign: 'center', flex: '1' })}>
<div
key={i}
className={stack({
gap: '2',
textAlign: 'center',
flex: '1',
position: 'relative',
})}
>
<div
className={css({
fontSize: 'xl',
@@ -410,15 +424,17 @@ export default function HomePage() {
>
{stage.level}
</div>
<div className={css({ fontSize: 'sm', color: 'gray.400' })}>{stage.label}</div>
<div style={{ fontSize: '14px', color: '#e5e7eb' }}>{stage.label}</div>
{i < 3 && (
<div
className={css({
display: { base: 'none', md: 'block' },
style={{
position: 'absolute',
right: '-50%',
fontSize: 'xl',
color: 'gray.600',
fontSize: '20px',
color: '#9ca3af',
}}
className={css({
display: { base: 'none', md: 'block' },
})}
>
@@ -428,12 +444,14 @@ export default function HomePage() {
))}
</div>
<div
style={{
textAlign: 'center',
fontSize: '14px',
color: '#d1d5db',
fontStyle: 'italic',
}}
className={css({
mt: '6',
textAlign: 'center',
fontSize: 'sm',
color: 'gray.500',
fontStyle: 'italic',
})}
>
You'll progress through all these levels eventually

View File

@@ -216,6 +216,9 @@ interface TutorialPlayerProps {
isDebugMode?: boolean
showDebugPanel?: boolean
hideNavigation?: boolean
hideTooltip?: boolean
abacusColumns?: number
theme?: 'light' | 'dark'
onStepChange?: (stepIndex: number, step: TutorialStep) => void
onStepComplete?: (stepIndex: number, step: TutorialStep, success: boolean) => void
onTutorialComplete?: (score: number, timeSpent: number) => void
@@ -229,6 +232,9 @@ function TutorialPlayerContent({
isDebugMode = false,
showDebugPanel = false,
hideNavigation = false,
hideTooltip = false,
abacusColumns = 5,
theme = 'light',
onStepChange,
onStepComplete,
onTutorialComplete,
@@ -381,16 +387,20 @@ function TutorialPlayerContent({
}
// Convert bead diff results to StepBeadHighlight format expected by AbacusReact
const stepBeadHighlights: StepBeadHighlight[] = beadDiff.changes.map((change, _index) => ({
placeValue: change.placeValue,
beadType: change.beadType,
position: change.position,
direction: change.direction,
stepIndex: currentMultiStep, // Use current multi-step index to match AbacusReact filtering
order: change.order,
}))
// Filter to only include beads from columns that exist
const minValidPlaceValue = Math.max(0, 5 - abacusColumns)
const stepBeadHighlights: StepBeadHighlight[] = beadDiff.changes
.filter((change) => change.placeValue < abacusColumns)
.map((change, _index) => ({
placeValue: change.placeValue,
beadType: change.beadType,
position: change.position,
direction: change.direction,
stepIndex: currentMultiStep, // Use current multi-step index to match AbacusReact filtering
order: change.order,
}))
return stepBeadHighlights
return stepBeadHighlights.length > 0 ? stepBeadHighlights : undefined
} catch (error) {
console.error('Error generating step beads with bead diff:', error)
return undefined
@@ -401,6 +411,7 @@ function TutorialPlayerContent({
expectedSteps,
currentMultiStep,
currentStep.stepBeadHighlights,
abacusColumns,
])
// Get the current step's bead diff summary for real-time user feedback
@@ -424,6 +435,14 @@ function TutorialPlayerContent({
// Get current step summary for real-time user feedback
const currentStepSummary = getCurrentStepSummary()
// Filter highlightBeads to only include valid columns
const filteredHighlightBeads = useMemo(() => {
if (!currentStep.highlightBeads) return undefined
return currentStep.highlightBeads.filter((highlight) => {
return highlight.placeValue < abacusColumns
})
}, [currentStep.highlightBeads, abacusColumns])
// Helper function to highlight the current mathematical term in the full decomposition
const renderHighlightedDecomposition = useCallback(() => {
if (!fullDecomposition || expectedSteps.length === 0) return null
@@ -521,16 +540,27 @@ function TutorialPlayerContent({
return null
}
// Validate that the bead is from a column that exists
if (topmostBead.placeValue >= abacusColumns) {
// Bead is from an invalid column, skip tooltip
return null
}
// Smart positioning logic: avoid covering active beads
const targetColumnIndex = 4 - topmostBead.placeValue // Convert placeValue to columnIndex (5 columns: 0-4)
// Convert placeValue to columnIndex based on actual number of columns
const targetColumnIndex = abacusColumns - 1 - topmostBead.placeValue
// Check if there are any active beads (against reckoning bar OR with arrows) in columns to the left
const hasActiveBeadsToLeft = (() => {
// Get current abacus state - we need to check which beads are against the reckoning bar
const abacusDigits = currentValue.toString().padStart(5, '0').split('').map(Number)
const abacusDigits = currentValue
.toString()
.padStart(abacusColumns, '0')
.split('')
.map(Number)
for (let col = 0; col < targetColumnIndex; col++) {
const _placeValue = 4 - col // Convert columnIndex back to placeValue
const placeValue = abacusColumns - 1 - col // Convert columnIndex back to placeValue
const digitValue = abacusDigits[col]
// Check if any beads are active (against reckoning bar) in this column
@@ -546,7 +576,7 @@ function TutorialPlayerContent({
// Also check if this column has beads with direction arrows (from current step)
const hasArrowsInColumn =
currentStepBeads?.some((bead) => {
const beadColumnIndex = 4 - bead.placeValue
const beadColumnIndex = abacusColumns - 1 - bead.placeValue
return beadColumnIndex === col && bead.direction && bead.direction !== 'none'
}) ?? false
if (hasArrowsInColumn) {
@@ -672,6 +702,7 @@ function TutorialPlayerContent({
currentValue,
currentStep,
isMeaningfulDecomposition,
abacusColumns,
])
// Timer for smart help detection
@@ -866,8 +897,8 @@ function TutorialPlayerContent({
// Check if this is the correct action
if (currentStep.highlightBeads && Array.isArray(currentStep.highlightBeads)) {
const isCorrectBead = currentStep.highlightBeads.some((highlight) => {
// Get place value from highlight (convert columnIndex to placeValue if needed)
const highlightPlaceValue = highlight.placeValue ?? 4 - highlight.columnIndex
// Get place value from highlight
const highlightPlaceValue = highlight.placeValue
// Get place value from bead click event
const beadPlaceValue = beadInfo.bead ? beadInfo.bead.placeValue : 4 - beadInfo.columnIndex
@@ -879,9 +910,10 @@ function TutorialPlayerContent({
})
if (!isCorrectBead) {
const errorMessage = "That's not the highlighted bead. Try clicking the highlighted bead."
dispatch({
type: 'SET_ERROR',
error: currentStep.errorMessages.wrongBead,
error: errorMessage,
})
dispatch({
@@ -889,7 +921,7 @@ function TutorialPlayerContent({
event: {
type: 'ERROR_OCCURRED',
stepId: currentStep.id,
error: currentStep.errorMessages.wrongBead,
error: errorMessage,
timestamp: new Date(),
},
})
@@ -1005,14 +1037,21 @@ function TutorialPlayerContent({
// Memoize custom styles calculation to avoid expensive recalculation on every render
const customStyles = useMemo(() => {
// Calculate valid column range based on abacusColumns
const minValidColumn = 5 - abacusColumns
// Start with static highlights from step configuration
const staticHighlights: Record<number, any> = {}
if (currentStep.highlightBeads && Array.isArray(currentStep.highlightBeads)) {
currentStep.highlightBeads.forEach((highlight) => {
// Convert placeValue to columnIndex for AbacusReact compatibility
const columnIndex =
highlight.placeValue !== undefined ? 4 - highlight.placeValue : highlight.columnIndex
const columnIndex = abacusColumns - 1 - highlight.placeValue
// Skip highlights for columns that don't exist
if (columnIndex < minValidColumn) {
return
}
// Initialize column if it doesn't exist
if (!staticHighlights[columnIndex]) {
@@ -1043,6 +1082,12 @@ function TutorialPlayerContent({
const mergedHighlights = { ...staticHighlights }
Object.keys(dynamicColumnHighlights).forEach((columnIndexStr) => {
const columnIndex = parseInt(columnIndexStr, 10)
// Skip highlights for columns that don't exist
if (columnIndex < minValidColumn) {
return
}
if (!mergedHighlights[columnIndex]) {
mergedHighlights[columnIndex] = {}
}
@@ -1050,8 +1095,32 @@ function TutorialPlayerContent({
Object.assign(mergedHighlights[columnIndex], dynamicColumnHighlights[columnIndex])
})
return Object.keys(mergedHighlights).length > 0 ? { columns: mergedHighlights } : undefined
}, [currentStep.highlightBeads, dynamicColumnHighlights])
// Build the custom styles object
const styles: any = {}
// Add column highlights if any
if (Object.keys(mergedHighlights).length > 0) {
styles.columns = mergedHighlights
}
// Add frame styling for dark mode
if (theme === 'dark') {
// Column dividers (global for all columns)
styles.columnPosts = {
fill: 'rgba(255, 255, 255, 0.3)', // High contrast fill for visibility
stroke: 'rgba(255, 255, 255, 0.2)',
strokeWidth: 2,
}
// Reckoning bar (horizontal middle bar)
styles.reckoningBar = {
fill: 'rgba(255, 255, 255, 0.4)', // High contrast fill for visibility
stroke: 'rgba(255, 255, 255, 0.25)',
strokeWidth: 3,
}
}
return Object.keys(styles).length > 0 ? styles : undefined
}, [currentStep.highlightBeads, dynamicColumnHighlights, abacusColumns, theme])
if (!currentStep) {
return <div>No steps available</div>
@@ -1071,9 +1140,9 @@ function TutorialPlayerContent({
<div
className={css({
borderBottom: '1px solid',
borderColor: 'gray.200',
borderColor: theme === 'dark' ? 'rgba(255, 255, 255, 0.1)' : 'gray.200',
p: 4,
bg: 'white',
bg: theme === 'dark' ? 'rgba(30, 30, 40, 0.6)' : 'white',
})}
>
<div
@@ -1319,11 +1388,18 @@ function TutorialPlayerContent({
fontSize: '2xl',
fontWeight: 'bold',
mb: 2,
color: theme === 'dark' ? 'gray.200' : 'gray.900',
})}
>
{currentStep.problem}
</h2>
<p className={css({ fontSize: 'lg', color: 'gray.700', mb: 4 })}>
<p
className={css({
fontSize: 'lg',
color: theme === 'dark' ? 'gray.400' : 'gray.700',
mb: 4,
})}
>
{currentStep.description}
</p>
{/* Hide action description for multi-step problems since it duplicates pedagogical decomposition */}
@@ -1335,18 +1411,26 @@ function TutorialPlayerContent({
</div>
{/* Multi-step instructions panel */}
{currentStep.multiStepInstructions &&
{!hideTooltip &&
currentStep.multiStepInstructions &&
currentStep.multiStepInstructions.length > 0 && (
<div
className={css({
p: 5,
background:
'linear-gradient(135deg, rgba(255,248,225,0.95) 0%, rgba(254,252,232,0.95) 50%, rgba(255,245,157,0.15) 100%)',
theme === 'dark'
? 'linear-gradient(135deg, rgba(40,40,50,0.6) 0%, rgba(50,50,60,0.6) 50%, rgba(60,50,70,0.3) 100%)'
: 'linear-gradient(135deg, rgba(255,248,225,0.95) 0%, rgba(254,252,232,0.95) 50%, rgba(255,245,157,0.15) 100%)',
backdropFilter: 'blur(10px)',
border: '1px solid rgba(251,191,36,0.3)',
border:
theme === 'dark'
? '1px solid rgba(255,255,255,0.1)'
: '1px solid rgba(251,191,36,0.3)',
borderRadius: 'xl',
boxShadow:
'0 8px 32px rgba(251,191,36,0.1), 0 2px 8px rgba(0,0,0,0.05), inset 0 1px 0 rgba(255,255,255,0.6)',
theme === 'dark'
? '0 4px 16px rgba(0,0,0,0.3), inset 0 1px 0 rgba(255,255,255,0.05)'
: '0 8px 32px rgba(251,191,36,0.1), 0 2px 8px rgba(0,0,0,0.05), inset 0 1px 0 rgba(255,255,255,0.6)',
position: 'relative',
maxW: '600px',
w: 'full',
@@ -1356,7 +1440,9 @@ function TutorialPlayerContent({
inset: '0',
borderRadius: 'xl',
background:
'linear-gradient(135deg, rgba(251,191,36,0.1) 0%, rgba(168,85,247,0.05) 100%)',
theme === 'dark'
? 'linear-gradient(135deg, rgba(100,100,120,0.1) 0%, rgba(80,60,100,0.05) 100%)'
: 'linear-gradient(135deg, rgba(251,191,36,0.1) 0%, rgba(168,85,247,0.05) 100%)',
zIndex: -1,
},
})}
@@ -1365,10 +1451,10 @@ function TutorialPlayerContent({
className={css({
fontSize: 'base',
fontWeight: '600',
color: 'amber.900',
color: theme === 'dark' ? 'gray.300' : 'amber.900',
mb: 4,
letterSpacing: 'wide',
textShadow: '0 1px 2px rgba(0,0,0,0.1)',
textShadow: theme === 'dark' ? 'none' : '0 1px 2px rgba(0,0,0,0.1)',
})}
>
Guidance
@@ -1381,18 +1467,25 @@ function TutorialPlayerContent({
mb: 4,
p: 3,
background:
'linear-gradient(135deg, rgba(255,255,255,0.8) 0%, rgba(248,250,252,0.9) 100%)',
border: '1px solid rgba(203,213,225,0.4)',
theme === 'dark'
? 'linear-gradient(135deg, rgba(50,50,60,0.4) 0%, rgba(40,40,50,0.5) 100%)'
: 'linear-gradient(135deg, rgba(255,255,255,0.8) 0%, rgba(248,250,252,0.9) 100%)',
border:
theme === 'dark'
? '1px solid rgba(255,255,255,0.1)'
: '1px solid rgba(203,213,225,0.4)',
borderRadius: 'lg',
boxShadow:
'0 2px 8px rgba(0,0,0,0.06), inset 0 1px 0 rgba(255,255,255,0.7)',
theme === 'dark'
? '0 1px 4px rgba(0,0,0,0.3), inset 0 1px 0 rgba(255,255,255,0.05)'
: '0 2px 8px rgba(0,0,0,0.06), inset 0 1px 0 rgba(255,255,255,0.7)',
backdropFilter: 'blur(4px)',
})}
>
<div
className={css({
fontSize: 'base',
color: 'slate.800',
color: theme === 'dark' ? 'gray.300' : 'slate.800',
fontFamily: 'mono',
fontWeight: '500',
letterSpacing: 'tight',
@@ -1411,7 +1504,7 @@ function TutorialPlayerContent({
<div
className={css({
fontSize: 'sm',
color: 'amber.800',
color: theme === 'dark' ? 'gray.400' : 'amber.800',
fontWeight: '500',
lineHeight: '1.6',
})}
@@ -1455,7 +1548,10 @@ function TutorialPlayerContent({
className={css({
mb: 1,
fontWeight: 'bold',
color: 'yellow.900',
color: theme === 'dark' ? 'yellow.200' : 'yellow.900',
textShadow:
theme === 'dark' ? '0 0 12px rgba(251, 191, 36, 0.4)' : 'none',
fontSize: theme === 'dark' ? 'lg' : 'base',
})}
>
{currentInstruction}
@@ -1489,17 +1585,17 @@ function TutorialPlayerContent({
{/* Abacus */}
<div
className={css({
bg: 'white',
bg: theme === 'dark' ? 'rgba(30, 30, 40, 0.4)' : 'white',
border: '2px solid',
borderColor: 'gray.200',
borderColor: theme === 'dark' ? 'rgba(255, 255, 255, 0.1)' : 'gray.200',
borderRadius: 'lg',
p: 6,
shadow: 'lg',
shadow: theme === 'dark' ? '0 4px 6px rgba(0, 0, 0, 0.3)' : 'lg',
})}
>
<AbacusReact
value={currentValue}
columns={5}
columns={abacusColumns}
interactive={true}
animated={true}
scaleFactor={2.5}
@@ -1508,7 +1604,7 @@ function TutorialPlayerContent({
hideInactiveBeads={abacusConfig.hideInactiveBeads}
soundEnabled={abacusConfig.soundEnabled}
soundVolume={abacusConfig.soundVolume}
highlightBeads={currentStep.highlightBeads}
highlightBeads={filteredHighlightBeads}
stepBeadHighlights={currentStepBeads}
currentStep={currentMultiStep}
showDirectionIndicators={true}
@@ -1573,7 +1669,7 @@ function TutorialPlayerContent({
</div>
{/* Tooltip */}
{currentStep.tooltip && (
{!hideTooltip && currentStep.tooltip && (
<div
className={css({
maxW: '500px',

View File

@@ -21,52 +21,52 @@ export const GAME_THEMES = {
blue: {
color: 'blue',
gradient: 'linear-gradient(135deg, #dbeafe, #bfdbfe)', // blue.100 to blue.200
borderColor: 'blue.200',
borderColor: '#bfdbfe', // blue.200
},
purple: {
color: 'purple',
gradient: 'linear-gradient(135deg, #e9d5ff, #ddd6fe)', // purple.100 to purple.200
borderColor: 'purple.200',
borderColor: '#ddd6fe', // purple.200
},
green: {
color: 'green',
gradient: 'linear-gradient(135deg, #d1fae5, #a7f3d0)', // green.100 to green.200
borderColor: 'green.200',
borderColor: '#a7f3d0', // green.200
},
teal: {
color: 'teal',
gradient: 'linear-gradient(135deg, #ccfbf1, #99f6e4)', // teal.100 to teal.200
borderColor: 'teal.200',
borderColor: '#99f6e4', // teal.200
},
indigo: {
color: 'indigo',
gradient: 'linear-gradient(135deg, #e0e7ff, #c7d2fe)', // indigo.100 to indigo.200
borderColor: 'indigo.200',
borderColor: '#c7d2fe', // indigo.200
},
pink: {
color: 'pink',
gradient: 'linear-gradient(135deg, #fce7f3, #fbcfe8)', // pink.100 to pink.200
borderColor: 'pink.200',
borderColor: '#fbcfe8', // pink.200
},
orange: {
color: 'orange',
gradient: 'linear-gradient(135deg, #ffedd5, #fed7aa)', // orange.100 to orange.200
borderColor: 'orange.200',
borderColor: '#fed7aa', // orange.200
},
yellow: {
color: 'yellow',
gradient: 'linear-gradient(135deg, #fef3c7, #fde68a)', // yellow.100 to yellow.200
borderColor: 'yellow.200',
borderColor: '#fde68a', // yellow.200
},
red: {
color: 'red',
gradient: 'linear-gradient(135deg, #fee2e2, #fecaca)', // red.100 to red.200
borderColor: 'red.200',
borderColor: '#fecaca', // red.200
},
gray: {
color: 'gray',
gradient: 'linear-gradient(135deg, #f3f4f6, #e5e7eb)', // gray.100 to gray.200
borderColor: 'gray.200',
borderColor: '#e5e7eb', // gray.200
},
} as const satisfies Record<string, GameTheme>

View File

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

View File

@@ -27,6 +27,7 @@ export interface BeadStyle {
}
export interface ColumnPostStyle {
fill?: string;
stroke?: string;
strokeWidth?: number;
opacity?: number;
@@ -34,6 +35,7 @@ export interface ColumnPostStyle {
}
export interface ReckoningBarStyle {
fill?: string;
stroke?: string;
strokeWidth?: number;
opacity?: number;
@@ -1979,7 +1981,10 @@ export const AbacusReact: React.FC<AbacusConfig> = ({
const columnStyles = customStyles?.columns?.[colIndex];
const globalColumnPosts = customStyles?.columnPosts;
const rodStyle = {
fill: "rgb(0, 0, 0, 0.1)", // Default Typst color
fill:
columnStyles?.columnPost?.fill ||
globalColumnPosts?.fill ||
"rgb(0, 0, 0, 0.1)", // Default Typst color
stroke:
columnStyles?.columnPost?.stroke ||
globalColumnPosts?.stroke ||
@@ -2017,8 +2022,10 @@ export const AbacusReact: React.FC<AbacusConfig> = ({
(effectiveColumns - 1) * dimensions.rodSpacing + dimensions.beadSize
}
height={dimensions.barThickness}
fill="black" // Typst uses black
stroke="none"
fill={customStyles?.reckoningBar?.fill || "black"} // Typst default is black
stroke={customStyles?.reckoningBar?.stroke || "none"}
strokeWidth={customStyles?.reckoningBar?.strokeWidth ?? 0}
opacity={customStyles?.reckoningBar?.opacity ?? 1}
/>
{/* Beads */}