Compare commits

...

27 Commits

Author SHA1 Message Date
semantic-release-bot
eed890dc81 chore(release): 4.20.5 [skip ci]
## [4.20.5](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.20.4...v4.20.5) (2025-10-20)

### Bug Fixes

* **docker:** include Panda CSS styled-system in production image ([57fabff](57fabffe60))
2025-10-20 01:12:42 +00:00
Thomas Hallock
57fabffe60 fix(docker): include Panda CSS styled-system in production image
The styled-system directory containing generated Panda CSS was being
created during build but not copied to the production image, causing
all Panda CSS classes to be undefined at runtime.

This fix copies the generated styled-system directory from the builder
stage to the production image, ensuring styles.css is available.

Fixes missing CSS definitions for classes like fs_xl, font_bold, text_blue.400

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-19 20:11:38 -05:00
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
8 changed files with 320 additions and 101 deletions

View File

@@ -1,3 +1,98 @@
## [4.20.5](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.20.4...v4.20.5) (2025-10-20)
### Bug Fixes
* **docker:** include Panda CSS styled-system in production image ([57fabff](https://github.com/antialias/soroban-abacus-flashcards/commit/57fabffe605d953b4a4d7e05032401cbf1ab2d14))
## [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

@@ -59,6 +59,9 @@ RUN adduser --system --uid 1001 nextjs
COPY --from=builder --chown=nextjs:nodejs /app/apps/web/.next ./apps/web/.next
COPY --from=builder --chown=nextjs:nodejs /app/apps/web/public ./apps/web/public
# Copy Panda CSS generated styles
COPY --from=builder --chown=nextjs:nodejs /app/apps/web/styled-system ./apps/web/styled-system
# Copy server files (compiled from TypeScript)
COPY --from=builder --chown=nextjs:nodejs /app/apps/web/server.js ./apps/web/
COPY --from=builder --chown=nextjs:nodejs /app/apps/web/dist ./apps/web/dist

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.5",
"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 */}