Compare commits
59 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e37ee87ea3 | ||
|
|
38ef16a8f9 | ||
|
|
2f0304eb81 | ||
|
|
1da9ed1ce6 | ||
|
|
c5103d049f | ||
|
|
c4066d6879 | ||
|
|
1432afd6e6 | ||
|
|
1fa0df85f7 | ||
|
|
8baeba6987 | ||
|
|
e028e342ad | ||
|
|
263237a152 | ||
|
|
4ec1b952f2 | ||
|
|
aa9d389540 | ||
|
|
d1423420e6 | ||
|
|
47640f3486 | ||
|
|
cb7595c95b | ||
|
|
8b4eceebfa | ||
|
|
850fd33943 | ||
|
|
8835e1c57a | ||
|
|
b635ed1c2d | ||
|
|
4e6cecfe27 | ||
|
|
d52ba6373a | ||
|
|
002c2888ac | ||
|
|
5d85e898d6 | ||
|
|
eed890dc81 | ||
|
|
57fabffe60 | ||
|
|
89fb670f93 | ||
|
|
8e51390018 | ||
|
|
e7e54619ae | ||
|
|
9c51cc94ee | ||
|
|
df674426c5 | ||
|
|
24d120004d | ||
|
|
88f57ce6df | ||
|
|
3a5dc0f1c8 | ||
|
|
3fff9ef140 | ||
|
|
ca1c6d8602 | ||
|
|
e6bcf20807 | ||
|
|
1ee25b3dd2 | ||
|
|
468bdebe3a | ||
|
|
2eb3ff3406 | ||
|
|
efbe99a9e2 | ||
|
|
fdc882cb04 | ||
|
|
a7778c648d | ||
|
|
7e2f580877 | ||
|
|
f18a89974a | ||
|
|
bf1ced43f8 | ||
|
|
6435027147 | ||
|
|
4d906ec20e | ||
|
|
ff7b711fe0 | ||
|
|
d42f9b2d9a | ||
|
|
faaefbacff | ||
|
|
8d650c5c52 | ||
|
|
79ea52af80 | ||
|
|
6b017b0fe9 | ||
|
|
8f8b1e80db | ||
|
|
c883d9e4c1 | ||
|
|
19b03bc77c | ||
|
|
be68af0d56 | ||
|
|
2f09cb5539 |
197
CHANGELOG.md
197
CHANGELOG.md
@@ -1,3 +1,200 @@
|
||||
## [4.24.1](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.24.0...v4.24.1) (2025-10-20)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **homepage:** adjust mini abacus container height ([c4066d6](https://github.com/antialias/soroban-abacus-flashcards/commit/c4066d687925bbe7737ebfeefdada7365ff97c6c))
|
||||
* **homepage:** fix MiniAbacus runtime error and improve sizing ([1fa0df8](https://github.com/antialias/soroban-abacus-flashcards/commit/1fa0df85f7d3988cbc61701d89476419ccf0a13c))
|
||||
* **homepage:** use correct AbacusReact API and fix clipping/styling issues ([1432afd](https://github.com/antialias/soroban-abacus-flashcards/commit/1432afd6e6bd547bd0da76dbeea1c2b71244826f))
|
||||
* **homepage:** use direct conditionals for mini abacus padding ([38ef16a](https://github.com/antialias/soroban-abacus-flashcards/commit/38ef16a8f91f8ab4ad0d717b0321e2002636fafb))
|
||||
|
||||
|
||||
### Styles
|
||||
|
||||
* **homepage:** add more padding around mini abacus ([c5103d0](https://github.com/antialias/soroban-abacus-flashcards/commit/c5103d049f73a8f7ef26915edfbef9ea56d59094))
|
||||
* **homepage:** balance mini abacus padding horizontally and vertically ([2f0304e](https://github.com/antialias/soroban-abacus-flashcards/commit/2f0304eb81cdf84c21b0554c9cd4bd5478896dd8))
|
||||
* **homepage:** increase mini abacus padding to '5' ([1da9ed1](https://github.com/antialias/soroban-abacus-flashcards/commit/1da9ed1ce6995c605622fc2863f248e5e91ab9c3))
|
||||
|
||||
## [4.24.0](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.23.0...v4.24.0) (2025-10-20)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **homepage:** add animated mini abacus to "Read and set numbers" card ([e028e34](https://github.com/antialias/soroban-abacus-flashcards/commit/e028e342ad4bc01491e05a4ba074628155926fd8))
|
||||
|
||||
## [4.23.0](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.22.0...v4.23.0) (2025-10-20)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **homepage:** add more visual embellishments to learning cards ([4ec1b95](https://github.com/antialias/soroban-abacus-flashcards/commit/4ec1b952f202d50f6db287c41732ec65ca17c142))
|
||||
|
||||
## [4.22.0](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.21.1...v4.22.0) (2025-10-20)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **homepage:** enhance "What You'll Learn" with visual cards ([d142342](https://github.com/antialias/soroban-abacus-flashcards/commit/d1423420e653b26b2f89d9d17ae5d597807d6979))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **tutorial:** reduce tooltip z-index to scroll under nav bar ([47640f3](https://github.com/antialias/soroban-abacus-flashcards/commit/47640f3486c6d4a7107d59bdcce043f76fabbb1d))
|
||||
|
||||
## [4.21.1](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.21.0...v4.21.1) (2025-10-20)
|
||||
|
||||
|
||||
### Code Refactoring
|
||||
|
||||
* **homepage:** rearrange tutorial demo layout side by side ([8b4ecee](https://github.com/antialias/soroban-abacus-flashcards/commit/8b4eceebfaaaf07e38ea64c7fe015aec86ac754f))
|
||||
|
||||
## [4.21.0](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.20.7...v4.21.0) (2025-10-20)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **tutorial:** add silentErrors prop to suppress error messages ([8835e1c](https://github.com/antialias/soroban-abacus-flashcards/commit/8835e1c57ab8adcecefe0db082360dd98fbfaac7))
|
||||
|
||||
## [4.20.7](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.20.6...v4.20.7) (2025-10-20)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **home:** use Panda CSS token() for dynamic colors and center arrows properly ([d52ba63](https://github.com/antialias/soroban-abacus-flashcards/commit/d52ba6373a4577655dc1e5f5ff4926af7f7d96c3))
|
||||
|
||||
## [4.20.6](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.20.5...v4.20.6) (2025-10-20)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **homepage:** use inline styles for journey level colors ([5d85e89](https://github.com/antialias/soroban-abacus-flashcards/commit/5d85e898d65d44d8d09bee952fad44b5a9c0cd20)), closes [#4ade80](https://github.com/antialias/soroban-abacus-flashcards/issues/4ade80) [#60a5](https://github.com/antialias/soroban-abacus-flashcards/issues/60a5) [#a78](https://github.com/antialias/soroban-abacus-flashcards/issues/a78) [#fbbf24](https://github.com/antialias/soroban-abacus-flashcards/issues/fbbf24)
|
||||
|
||||
## [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)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **tutorial:** add hideNavigation prop to TutorialPlayer ([79ea52a](https://github.com/antialias/soroban-abacus-flashcards/commit/79ea52af80c8cbb482bbdd87f77caf32ada737ee))
|
||||
|
||||
|
||||
### Styles
|
||||
|
||||
* **homepage:** update tutorial container to match dark theme ([6b017b0](https://github.com/antialias/soroban-abacus-flashcards/commit/6b017b0fe92d4277843d9fe2645c22366f219d76))
|
||||
|
||||
## [4.15.1](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.15.0...v4.15.1) (2025-10-19)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **tutorial:** resolve React hydration error in TutorialPlayer ([c883d9e](https://github.com/antialias/soroban-abacus-flashcards/commit/c883d9e4c1b3a2f52c9d41e3ddce7418399f2649))
|
||||
|
||||
|
||||
### Code Refactoring
|
||||
|
||||
* replace demo component with real TutorialPlayer system ([19b03bc](https://github.com/antialias/soroban-abacus-flashcards/commit/19b03bc77c649cf51d7b9a3617417c6ec8229ac7))
|
||||
|
||||
## [4.15.0](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.14.6...v4.15.0) (2025-10-19)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* redesign homepage with educational vision and interactive demo ([2f09cb5](https://github.com/antialias/soroban-abacus-flashcards/commit/2f09cb5539f2bb0b8c77359c6f774c3742313e1e))
|
||||
|
||||
## [4.14.6](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.14.5...v4.14.6) (2025-10-19)
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
935
apps/web/.claude/EDUCATION_ROADMAP.md
Normal file
935
apps/web/.claude/EDUCATION_ROADMAP.md
Normal file
@@ -0,0 +1,935 @@
|
||||
# Soroban Abacus Education Platform - Comprehensive Roadmap
|
||||
|
||||
## Vision Statement
|
||||
|
||||
**Mission:** Fill the gap in the USA school system by providing a complete, self-directed abacus curriculum that trains students from beginner to mastery using the Japanese kyu/dan ranking system.
|
||||
|
||||
**Target Users:**
|
||||
- Primary: Elementary school students (ages 6-12)
|
||||
- Secondary: Middle school students and adult learners
|
||||
- Teachers/Parents: Dashboard for monitoring progress
|
||||
|
||||
**Core Experience Principles:**
|
||||
1. **Integrated Learning Loop:** Tutorial → Practice → Play → Assessment → Progress
|
||||
2. **Self-Directed:** Simple enough for kids to fire up and start learning independently
|
||||
3. **Gamified Progression:** Games reinforce lessons, feel like play but teach skills
|
||||
4. **Physical + Virtual:** Support both physical abacus and AbacusReact component
|
||||
5. **Mastery-Based:** Students advance through clear skill levels with certification
|
||||
|
||||
---
|
||||
|
||||
## Current State Assessment
|
||||
|
||||
### ✅ What We Have (Well-Built)
|
||||
|
||||
**1. Interactive Abacus Component (AbacusReact)**
|
||||
- Highly polished, production-ready
|
||||
- Excellent pedagogical features (bead highlighting, direction arrows, tooltips)
|
||||
- Multiple color schemes and accessibility options
|
||||
- Interactive and display-only modes
|
||||
- **Rating: 95% Complete**
|
||||
|
||||
**2. Game System (4 Games)**
|
||||
- Memory Lightning (memorization skills)
|
||||
- Matching Pairs Battle (pattern recognition, complements)
|
||||
- Card Sorting (visual literacy, ordering)
|
||||
- Complement Race (speed calculation, friends-of-5/10)
|
||||
- Type-safe architecture with game SDK
|
||||
- Multiplayer and spectator modes
|
||||
- **Rating: 80% Complete** (games exist but need curriculum integration)
|
||||
|
||||
**3. Tutorial Infrastructure**
|
||||
- Tutorial player with step-based guidance
|
||||
- Tutorial editor for content creation
|
||||
- Bead highlighting system for instruction
|
||||
- Event tracking and progress monitoring
|
||||
- **Rating: 70% Complete** (infrastructure exists but lacks content)
|
||||
|
||||
**4. Real-time Multiplayer**
|
||||
- Socket.IO integration
|
||||
- Room-based architecture
|
||||
- State synchronization
|
||||
- **Rating: 90% Complete**
|
||||
|
||||
**5. Flashcard Generator**
|
||||
- PDF/PNG/SVG export
|
||||
- Customizable layouts and themes
|
||||
- **Rating: 100% Complete**
|
||||
|
||||
### ⚠️ What We Have (Partially Built)
|
||||
|
||||
**1. Progress Tracking**
|
||||
- Basic user stats (games played, wins, accuracy)
|
||||
- No skill-level tracking
|
||||
- No tutorial completion tracking
|
||||
- No assessment history
|
||||
- **Rating: 30% Complete**
|
||||
|
||||
**2. Tutorial Content**
|
||||
- One example tutorial (GuidedAdditionTutorial)
|
||||
- Type system for tutorials defined
|
||||
- No comprehensive curriculum
|
||||
- **Rating: 15% Complete**
|
||||
|
||||
**3. Assessment System**
|
||||
- Per-game scoring exists
|
||||
- Achievement system exists
|
||||
- No formal tests or certification
|
||||
- No placement tests
|
||||
- **Rating: 25% Complete**
|
||||
|
||||
### ❌ What We're Missing (Critical Gaps)
|
||||
|
||||
**1. Kyu/Dan Ranking System** - 0% Complete
|
||||
**2. Structured Curriculum** - 5% Complete
|
||||
**3. Adaptive Learning** - 0% Complete
|
||||
**4. Student Dashboard** - 0% Complete
|
||||
**5. Teacher/Parent Dashboard** - 0% Complete
|
||||
**6. Formal Assessment/Testing** - 0% Complete
|
||||
**7. Learning Path Sequencing** - 0% Complete
|
||||
**8. Content Library** - 10% Complete
|
||||
|
||||
---
|
||||
|
||||
## Kyu/Dan Level System (Japanese Abacus Standard)
|
||||
|
||||
### Beginner Levels (Kyu)
|
||||
|
||||
**10 Kyu - "First Steps"**
|
||||
- Age: 6-7 years
|
||||
- Skills: Basic bead manipulation, numbers 1-10
|
||||
- Curriculum: Recognize and set numbers on abacus, understand place value
|
||||
- Assessment: Set numbers 1-99 correctly, basic addition single digits
|
||||
- Games: Card Sorting (visual recognition), Memory Lightning (basic)
|
||||
|
||||
**9 Kyu - "Number Explorer"**
|
||||
- Skills: Addition/subtraction with no carry (1-9)
|
||||
- Curriculum: Friends of 5 concept introduction
|
||||
- Assessment: 20 problems, 2-digit addition/subtraction, no carry, 80% accuracy
|
||||
- Games: Complement Race (practice mode), Matching Pairs (numerals)
|
||||
|
||||
**8 Kyu - "Complement Apprentice"**
|
||||
- Skills: Friends of 5 mastery, introduction to friends of 10
|
||||
- Curriculum: All combinations that make 5, carry concepts
|
||||
- Assessment: 30 problems including carries using friends of 5, 85% accuracy
|
||||
- Games: Complement Race (friends-5 sprint), Matching Pairs (complement pairs)
|
||||
|
||||
**7 Kyu - "Addition Warrior"**
|
||||
- Skills: Friends of 10 mastery, 2-digit addition/subtraction with carries
|
||||
- Curriculum: All combinations that make 10, mixed complement strategies
|
||||
- Assessment: 40 problems, 2-3 digit calculations, mixed operations, 85% accuracy
|
||||
- Games: Complement Race (friends-10 sprint), All games at medium difficulty
|
||||
|
||||
**6 Kyu - "Speed Calculator"**
|
||||
- Skills: Multi-digit addition/subtraction (3-4 digits), speed emphasis
|
||||
- Curriculum: Chain calculations, mental imagery beginning
|
||||
- Assessment: 50 problems, 3-4 digits, 3 minutes time limit, 90% accuracy
|
||||
- Games: Complement Race (survival mode), Memory Lightning (medium)
|
||||
|
||||
**5 Kyu - "Multiplication Initiate"**
|
||||
- Skills: Single-digit multiplication (1-5)
|
||||
- Curriculum: Multiplication tables 1-5, abacus multiplication method
|
||||
- Assessment: 30 multiplication problems, 40 add/subtract problems, 90% accuracy
|
||||
- Games: All games at hard difficulty
|
||||
|
||||
**4 Kyu - "Multiplication Master"**
|
||||
- Skills: Full multiplication tables (1-9), 2-digit × 1-digit
|
||||
- Curriculum: All multiplication patterns, division introduction
|
||||
- Assessment: 40 multiplication, 20 division, 40 add/subtract, 90% accuracy
|
||||
|
||||
**3 Kyu - "Division Explorer"**
|
||||
- Skills: Division mastery (2-digit ÷ 1-digit), mixed operations
|
||||
- Curriculum: Division algorithm, remainders, mixed problem solving
|
||||
- Assessment: 100 mixed problems in 10 minutes, 92% accuracy
|
||||
|
||||
**2 Kyu - "Advanced Operator"**
|
||||
- Skills: Multi-digit multiplication/division, decimals introduction
|
||||
- Curriculum: 3-digit × 2-digit, decimals, percentages
|
||||
- Assessment: 120 mixed problems including decimals, 10 minutes, 93% accuracy
|
||||
|
||||
**1 Kyu - "Pre-Mastery"**
|
||||
- Skills: Decimal operations, fractions, complex multi-step problems
|
||||
- Curriculum: Real-world applications, word problems
|
||||
- Assessment: 150 mixed problems, 10 minutes, 95% accuracy
|
||||
- Mental calculation ability without physical abacus
|
||||
|
||||
### Master Levels (Dan)
|
||||
|
||||
**1 Dan - "Shodan" (First Degree)**
|
||||
- Skills: Mental imagery without abacus, complex calculations
|
||||
- Assessment: 200 mixed problems, 10 minutes, 96% accuracy
|
||||
- Mental arithmetic certification
|
||||
|
||||
**2 Dan - "Nidan"**
|
||||
- Skills: Advanced mental calculation, speed competitions
|
||||
- Assessment: 250 problems, 10 minutes, 97% accuracy
|
||||
|
||||
**3 Dan - "Sandan"**
|
||||
- Skills: Championship-level speed and accuracy
|
||||
- Assessment: 300 problems, 10 minutes, 98% accuracy
|
||||
|
||||
**4-10 Dan** - Expert/Master levels with increasing complexity
|
||||
|
||||
---
|
||||
|
||||
## Integrated Learning Experience Design
|
||||
|
||||
### The Core Loop (Per Skill/Concept)
|
||||
|
||||
```
|
||||
1. ASSESS → Placement test determines current level
|
||||
2. LEARN → Tutorial teaches new concept
|
||||
3. PRACTICE → Guided exercises with immediate feedback
|
||||
4. PLAY → Games reinforce the skill in fun context
|
||||
5. TEST → Formal assessment for mastery certification
|
||||
6. ADVANCE → Unlock next level, update progress
|
||||
```
|
||||
|
||||
### Example: Teaching "Friends of 5"
|
||||
|
||||
**1. Assessment (Placement)**
|
||||
- Quick quiz: "Can you add 3 + 4 using the abacus?"
|
||||
- Result: Student struggles → Assign Friends of 5 tutorial
|
||||
|
||||
**2. Learn (Tutorial)**
|
||||
- Interactive tutorial: "Friends of 5"
|
||||
- Steps:
|
||||
1. Show that 5 = 1+4, 2+3, 3+2, 4+1
|
||||
2. Demonstrate on abacus: setting 3, adding 2 to make 5
|
||||
3. Explain heaven bead (top) = 5, earth beads (bottom) = 1 each
|
||||
4. Interactive: Student sets 3, adds 2 using heaven bead
|
||||
5. Practice all combinations
|
||||
|
||||
**3. Practice (Structured Exercises)**
|
||||
- 20 problems: Set number, add its friend
|
||||
- Real-time feedback on bead movements
|
||||
- Hints available: "Use the heaven bead!"
|
||||
- Must achieve 90% accuracy to proceed
|
||||
|
||||
**4. Play (Game Reinforcement)**
|
||||
- Complement Race: Friends-5 mode
|
||||
- Matching Pairs: Match numbers that make 5
|
||||
- Makes practice feel like play
|
||||
|
||||
**5. Test (Formal Assessment)**
|
||||
- 30 problems mixing friends-5 with previous skills
|
||||
- Timed: 5 minutes
|
||||
- Must achieve 85% to certify skill
|
||||
- Can retake after reviewing mistakes
|
||||
|
||||
**6. Advance (Progress Update)**
|
||||
- Friends of 5 skill marked as "Mastered"
|
||||
- Unlock: Friends of 10 tutorial
|
||||
- Update skill matrix
|
||||
- Award achievement badge
|
||||
|
||||
---
|
||||
|
||||
## Detailed Curriculum Structure
|
||||
|
||||
### Curriculum Database Schema
|
||||
|
||||
```typescript
|
||||
// Skill taxonomy
|
||||
enum SkillCategory {
|
||||
NUMBER_SENSE = 'number-sense',
|
||||
ADDITION = 'addition',
|
||||
SUBTRACTION = 'subtraction',
|
||||
MULTIPLICATION = 'multiplication',
|
||||
DIVISION = 'division',
|
||||
MENTAL_CALC = 'mental-calculation',
|
||||
COMPLEMENTS = 'complements',
|
||||
SPEED = 'speed',
|
||||
ACCURACY = 'accuracy'
|
||||
}
|
||||
|
||||
// Individual skill (atomic unit)
|
||||
interface Skill {
|
||||
id: string
|
||||
name: string
|
||||
category: SkillCategory
|
||||
kyuLevel: number // Which kyu level this skill belongs to
|
||||
prerequisiteSkills: string[] // Must master these first
|
||||
description: string
|
||||
estimatedPracticeTime: number // minutes
|
||||
}
|
||||
|
||||
// Learning module (collection of related skills)
|
||||
interface Module {
|
||||
id: string
|
||||
title: string
|
||||
kyuLevel: number
|
||||
description: string
|
||||
skills: string[] // Skill IDs
|
||||
estimatedCompletionTime: number // hours
|
||||
sequence: number // Order within kyu level
|
||||
}
|
||||
|
||||
// Tutorial (teaches one or more skills)
|
||||
interface Tutorial {
|
||||
id: string
|
||||
skillIds: string[]
|
||||
moduleId: string
|
||||
type: 'interactive' | 'video' | 'reading'
|
||||
content: TutorialStep[]
|
||||
estimatedDuration: number
|
||||
}
|
||||
|
||||
// Practice set (reinforces skills)
|
||||
interface PracticeSet {
|
||||
id: string
|
||||
skillIds: string[]
|
||||
problemCount: number
|
||||
timeLimit?: number
|
||||
passingAccuracy: number
|
||||
difficulty: 'easy' | 'medium' | 'hard'
|
||||
}
|
||||
|
||||
// Game mapping (which games teach which skills)
|
||||
interface GameSkillMapping {
|
||||
gameId: string
|
||||
skillIds: string[]
|
||||
difficulty: string
|
||||
recommendedKyuRange: [number, number]
|
||||
}
|
||||
|
||||
// Assessment (formal test)
|
||||
interface Assessment {
|
||||
id: string
|
||||
type: 'placement' | 'skill-check' | 'kyu-certification'
|
||||
kyuLevel?: number
|
||||
skillIds: string[]
|
||||
problemCount: number
|
||||
timeLimit: number
|
||||
passingAccuracy: number
|
||||
}
|
||||
```
|
||||
|
||||
### Sample Curriculum Map
|
||||
|
||||
**10 Kyu Module Sequence:**
|
||||
|
||||
1. **Module 1: "Introduction to Abacus" (Week 1)**
|
||||
- Skill: Understand abacus structure
|
||||
- Skill: Recognize place values (ones, tens, hundreds)
|
||||
- Tutorial: "What is an Abacus?"
|
||||
- Tutorial: "Parts of the Abacus"
|
||||
- Practice: Set numbers 1-10
|
||||
- Game: Card Sorting (visual recognition)
|
||||
|
||||
2. **Module 2: "Setting Numbers" (Week 2)**
|
||||
- Skill: Set single-digit numbers (1-9)
|
||||
- Skill: Set two-digit numbers (10-99)
|
||||
- Tutorial: "Setting Numbers on Abacus"
|
||||
- Practice: 50 number-setting exercises
|
||||
- Game: Memory Lightning (set and remember)
|
||||
|
||||
3. **Module 3: "Basic Addition" (Week 3-4)**
|
||||
- Skill: Add single digits without carry (1+1 through 4+4)
|
||||
- Tutorial: "Simple Addition"
|
||||
- Practice: 100 addition problems
|
||||
- Game: Complement Race (practice mode)
|
||||
- Assessment: 10 Kyu Certification Test
|
||||
|
||||
**9 Kyu Module Sequence:**
|
||||
|
||||
1. **Module 4: "Friends of 5 - Introduction" (Week 5)**
|
||||
- Skill: Recognize pairs that make 5
|
||||
- Skill: Add using heaven bead (5 bead)
|
||||
- Tutorial: "Friends of 5 - Part 1"
|
||||
- Practice: Pattern recognition exercises
|
||||
- Game: Matching Pairs (complement mode)
|
||||
|
||||
2. **Module 5: "Friends of 5 - Application" (Week 6-7)**
|
||||
- Skill: Add crossing 5 (e.g., 3+4, 2+5)
|
||||
- Tutorial: "Friends of 5 - Part 2"
|
||||
- Practice: 200 problems with friends of 5
|
||||
- Game: Complement Race (friends-5 mode)
|
||||
- Assessment: 9 Kyu Certification Test
|
||||
|
||||
... (Continue through all kyu levels)
|
||||
|
||||
---
|
||||
|
||||
## Implementation Roadmap
|
||||
|
||||
### Phase 1: Foundation (Months 1-3) - "MVP for 10-9 Kyu"
|
||||
|
||||
**Goal:** Students can learn and certify 10 Kyu and 9 Kyu levels
|
||||
|
||||
**Database Schema Updates:**
|
||||
- [ ] Create `skills` table
|
||||
- [ ] Create `modules` table
|
||||
- [ ] Create `curriculum_tutorials` table (links tutorials to skills)
|
||||
- [ ] Create `curriculum_practice_sets` table
|
||||
- [ ] Create `curriculum_assessments` table
|
||||
- [ ] Create `user_progress` table
|
||||
- Fields: userId, skillId, status (not_started, in_progress, mastered), attempts, bestScore, lastAttemptAt
|
||||
- [ ] Create `user_skill_history` table (track all practice attempts)
|
||||
- [ ] Create `user_assessments` table (formal test results)
|
||||
- [ ] Create `user_kyu_levels` table
|
||||
- Fields: userId, currentKyu, currentDan, certifiedAt, expiresAt
|
||||
- [ ] Extend `user_stats` table: add `currentKyuLevel`, `currentDanLevel`, `skillsMastered`
|
||||
|
||||
**Tutorial Content Creation:**
|
||||
- [ ] 10 Kyu tutorials (5 tutorials):
|
||||
1. Introduction to Abacus
|
||||
2. Understanding Place Value
|
||||
3. Setting Numbers 1-99
|
||||
4. Basic Addition (single digit, no carry)
|
||||
5. Basic Subtraction (single digit, no borrow)
|
||||
- [ ] 9 Kyu tutorials (3 tutorials):
|
||||
1. Friends of 5 - Concept
|
||||
2. Friends of 5 - Addition
|
||||
3. Friends of 5 - Subtraction
|
||||
|
||||
**Practice Sets:**
|
||||
- [ ] Build practice set generator for each skill
|
||||
- [ ] Implement immediate feedback system
|
||||
- [ ] Add hint system for common mistakes
|
||||
- [ ] Track accuracy and time per problem
|
||||
|
||||
**Assessment System:**
|
||||
- [ ] Build placement test component (determines starting level)
|
||||
- [ ] Build skill-check test component (practice test before certification)
|
||||
- [ ] Build kyu certification test component (formal test)
|
||||
- [ ] Implement grading engine
|
||||
- [ ] Create detailed results/feedback page
|
||||
- [ ] Allow test retakes with review of mistakes
|
||||
|
||||
**Game Integration:**
|
||||
- [ ] Map existing games to skills
|
||||
- Memory Lightning → Number recognition, memory
|
||||
- Card Sorting → Visual pattern recognition, ordering
|
||||
- Matching Pairs → Complements, pattern matching
|
||||
- Complement Race → Friends-5, Friends-10, speed
|
||||
- [ ] Add skill-based game recommendations
|
||||
- [ ] Track game performance per skill
|
||||
|
||||
**Student Dashboard:**
|
||||
- [ ] Create dashboard showing:
|
||||
- Current kyu level
|
||||
- Skills mastered / in progress / locked
|
||||
- Next recommended activity
|
||||
- Recent achievements
|
||||
- Progress bar toward next kyu level
|
||||
- [ ] Implement simple, kid-friendly UI
|
||||
- [ ] Add celebratory animations for milestones
|
||||
|
||||
**Core User Flow:**
|
||||
- [ ] Onboarding: Placement test → Assign kyu level
|
||||
- [ ] Home: Dashboard shows next recommended activity
|
||||
- [ ] Click "Start Learning" → Next tutorial
|
||||
- [ ] Complete tutorial → Practice exercises
|
||||
- [ ] Complete practice → Game suggestion
|
||||
- [ ] Master all module skills → Unlock certification test
|
||||
- [ ] Pass certification → Advance to next kyu level
|
||||
- [ ] Celebration and badge award
|
||||
|
||||
**Deliverables:**
|
||||
- Students can complete 10 Kyu and 9 Kyu
|
||||
- ~8 tutorials
|
||||
- ~10 skills defined
|
||||
- Placement test + 2 certification tests
|
||||
- Student dashboard
|
||||
- Progress tracking fully functional
|
||||
|
||||
---
|
||||
|
||||
### Phase 2: Core Curriculum (Months 4-8) - "8 Kyu through 5 Kyu"
|
||||
|
||||
**Goal:** Complete beginner curriculum through multiplication introduction
|
||||
|
||||
**Content Creation:**
|
||||
- [ ] 8 Kyu: Friends of 10 tutorials and practice (4 weeks)
|
||||
- [ ] 7 Kyu: Mixed complements, 2-digit operations (4 weeks)
|
||||
- [ ] 6 Kyu: Multi-digit, speed training (6 weeks)
|
||||
- [ ] 5 Kyu: Multiplication introduction, tables 1-5 (8 weeks)
|
||||
- Total: ~40 tutorials, ~30 skills
|
||||
|
||||
**Enhanced Features:**
|
||||
- [ ] Adaptive difficulty in practice sets (adjusts based on performance)
|
||||
- [ ] Spaced repetition system (review mastered skills periodically)
|
||||
- [ ] Daily recommended practice (10-15 min sessions)
|
||||
- [ ] Streaks and habit formation
|
||||
- [ ] Peer comparison (anonymous, optional)
|
||||
|
||||
**New Games:**
|
||||
- [ ] Multiplication tables game
|
||||
- [ ] Speed drill game (flash calculation)
|
||||
- [ ] Mental math game (visualization without physical abacus)
|
||||
|
||||
**Parent/Teacher Dashboard:**
|
||||
- [ ] View student progress
|
||||
- [ ] See time spent learning
|
||||
- [ ] Review test results
|
||||
- [ ] Assign specific modules or skills
|
||||
- [ ] Generate progress reports
|
||||
|
||||
**Gamification Enhancements:**
|
||||
- [ ] Achievement badges for milestones
|
||||
- [ ] Experience points (XP) system
|
||||
- [ ] Level-up animations
|
||||
- [ ] Customizable avatars (unlocked via achievements)
|
||||
- [ ] Virtual rewards (stickers, themes)
|
||||
|
||||
**Deliverables:**
|
||||
- Complete 8-5 Kyu curriculum
|
||||
- ~50 total tutorials (cumulative)
|
||||
- ~40 total skills (cumulative)
|
||||
- Parent/teacher dashboard
|
||||
- 2-3 new games
|
||||
- Enhanced gamification
|
||||
|
||||
---
|
||||
|
||||
### Phase 3: Advanced Skills (Months 9-14) - "4 Kyu through 1 Kyu"
|
||||
|
||||
**Goal:** Advanced operations, real-world applications, mental calculation
|
||||
|
||||
**Content Creation:**
|
||||
- [ ] 4 Kyu: Full multiplication, division introduction (8 weeks)
|
||||
- [ ] 3 Kyu: Division mastery, mixed operations (8 weeks)
|
||||
- [ ] 2 Kyu: Decimals, percentages (10 weeks)
|
||||
- [ ] 1 Kyu: Fractions, word problems, mental calculation (12 weeks)
|
||||
- Total: ~60 additional tutorials, ~40 additional skills
|
||||
|
||||
**Mental Calculation Training:**
|
||||
- [ ] Visualization exercises (see abacus in mind)
|
||||
- [ ] Flash anzan (rapid mental calculation)
|
||||
- [ ] Mental calculation games
|
||||
- [ ] Transition from physical to mental abacus
|
||||
|
||||
**Real-World Applications:**
|
||||
- [ ] Shopping math (money, change, discounts)
|
||||
- [ ] Measurement conversions
|
||||
- [ ] Time calculations
|
||||
- [ ] Real-world word problems
|
||||
|
||||
**Competition Features:**
|
||||
- [ ] Speed competitions (leaderboards)
|
||||
- [ ] Accuracy challenges
|
||||
- [ ] Weekly tournaments
|
||||
- [ ] Regional/global rankings (optional)
|
||||
|
||||
**AI Tutor Assistant:**
|
||||
- [ ] Smart hints during practice
|
||||
- [ ] Personalized learning paths
|
||||
- [ ] Concept explanations on demand
|
||||
- [ ] Answer specific questions ("Why do I use friends of 5 here?")
|
||||
|
||||
**Deliverables:**
|
||||
- Complete 4-1 Kyu curriculum
|
||||
- ~110 total tutorials (cumulative)
|
||||
- ~80 total skills (cumulative)
|
||||
- Mental calculation training
|
||||
- AI assistant
|
||||
- Competition system
|
||||
|
||||
---
|
||||
|
||||
### Phase 4: Mastery Levels (Months 15-18) - "Dan Levels"
|
||||
|
||||
**Goal:** Championship-level speed and accuracy, mental calculation mastery
|
||||
|
||||
**Content Creation:**
|
||||
- [ ] Dan level certification tests
|
||||
- [ ] Advanced mental calculation curriculum
|
||||
- [ ] Championship preparation materials
|
||||
- [ ] Expert-level problem sets
|
||||
|
||||
**Advanced Features:**
|
||||
- [ ] Customized training plans for dan levels
|
||||
- [ ] Video lessons from expert abacus users
|
||||
- [ ] Community forum for advanced learners
|
||||
- [ ] Virtual competitions
|
||||
- [ ] Certification/diploma generation (printable)
|
||||
|
||||
**Integration with Standards:**
|
||||
- [ ] Align with League of Soroban of Americas standards
|
||||
- [ ] Japan Abacus Committee certification mapping
|
||||
- [ ] International competition preparation
|
||||
|
||||
**Deliverables:**
|
||||
- 1-10 Dan curriculum
|
||||
- Certification system
|
||||
- Community features
|
||||
- Championship training
|
||||
|
||||
---
|
||||
|
||||
### Phase 5: Ecosystem (Months 18+) - "Complete Platform"
|
||||
|
||||
**Content Management System:**
|
||||
- [ ] Tutorial builder UI (create without code)
|
||||
- [ ] Content versioning
|
||||
- [ ] Community-contributed content (vetted)
|
||||
- [ ] Multilingual support (Spanish, Japanese, Hindi)
|
||||
|
||||
**Classroom Features:**
|
||||
- [ ] Teacher creates classes
|
||||
- [ ] Bulk student enrollment
|
||||
- [ ] Class-wide assignments
|
||||
- [ ] Class leaderboards
|
||||
- [ ] Live teaching mode (project for class)
|
||||
|
||||
**Analytics & Insights:**
|
||||
- [ ] Student learning velocity
|
||||
- [ ] Skill gap analysis
|
||||
- [ ] Predictive success modeling
|
||||
- [ ] Recommendations engine
|
||||
- [ ] Export data for research
|
||||
|
||||
**Mobile App:**
|
||||
- [ ] iOS and React Native apps
|
||||
- [ ] Offline mode
|
||||
- [ ] Sync across devices
|
||||
|
||||
**Integrations:**
|
||||
- [ ] Google Classroom
|
||||
- [ ] Canvas LMS
|
||||
- [ ] Schoology
|
||||
- [ ] Export to SIS systems
|
||||
|
||||
**Advanced Gamification:**
|
||||
- [ ] Story mode (learning quest)
|
||||
- [ ] Cooperative challenges
|
||||
- [ ] Guild/team system
|
||||
- [ ] Seasonal events
|
||||
|
||||
---
|
||||
|
||||
## Success Metrics
|
||||
|
||||
### Student Engagement
|
||||
- **Daily Active Users (DAU):** Target 40% of registered students
|
||||
- **Weekly Active Users (WAU):** Target 70% of registered students
|
||||
- **Average session time:** 20-30 minutes
|
||||
- **Completion rate per module:** >80%
|
||||
- **Retention (30-day):** >60%
|
||||
- **Streak length:** Average 7+ days
|
||||
|
||||
### Learning Outcomes
|
||||
- **Certification pass rate:** >70% on first attempt per kyu level
|
||||
- **Skill mastery rate:** >85% accuracy on mastered skills after 30 days
|
||||
- **Time to mastery:** Track average time per kyu level
|
||||
- **Progression velocity:** Students advance 1 kyu level per 4-8 weeks (varies by level)
|
||||
|
||||
### Content Quality
|
||||
- **Tutorial completion rate:** >90%
|
||||
- **Practice set completion rate:** >85%
|
||||
- **Game play rate:** >60% of students play games weekly
|
||||
- **Assessment completion rate:** >75%
|
||||
|
||||
### Platform Health
|
||||
- **System uptime:** >99.5%
|
||||
- **Load time:** <2 seconds
|
||||
- **Error rate:** <0.1%
|
||||
|
||||
### Business/Growth
|
||||
- **Monthly signups:** Track growth month-over-month
|
||||
- **Paid conversion** (if applicable): Target 10-20%
|
||||
- **Teacher/school adoption:** Track institutional users
|
||||
- **Net Promoter Score (NPS):** Target >50
|
||||
|
||||
---
|
||||
|
||||
## Technical Architecture Changes
|
||||
|
||||
### Database Changes Priority
|
||||
|
||||
**Immediate (Phase 1):**
|
||||
```sql
|
||||
-- Skills and curriculum structure
|
||||
CREATE TABLE skills (...)
|
||||
CREATE TABLE modules (...)
|
||||
CREATE TABLE skill_prerequisites (...)
|
||||
|
||||
-- Tutorial and practice content
|
||||
CREATE TABLE tutorial_content (...)
|
||||
CREATE TABLE practice_sets (...)
|
||||
CREATE TABLE assessments (...)
|
||||
|
||||
-- User progress tracking
|
||||
CREATE TABLE user_progress (...)
|
||||
CREATE TABLE user_skill_history (...)
|
||||
CREATE TABLE user_assessments (...)
|
||||
CREATE TABLE user_kyu_levels (...)
|
||||
|
||||
-- Game-skill mapping
|
||||
CREATE TABLE game_skill_mappings (...)
|
||||
```
|
||||
|
||||
**Phase 2:**
|
||||
- Add spaced repetition tables
|
||||
- Achievement tracking enhancements
|
||||
- Peer comparison data
|
||||
|
||||
**Phase 3:**
|
||||
- Mental calculation tracking
|
||||
- Competition results
|
||||
- AI tutor interaction logs
|
||||
|
||||
### API Endpoints Needed
|
||||
|
||||
**Progress & Skills:**
|
||||
- `GET /api/student/progress` - Current kyu level, skills, next steps
|
||||
- `GET /api/student/skills/:skillId` - Skill details and progress
|
||||
- `POST /api/student/skills/:skillId/practice` - Record practice attempt
|
||||
- `GET /api/student/dashboard` - Dashboard data
|
||||
|
||||
**Curriculum:**
|
||||
- `GET /api/curriculum/kyu/:level` - All modules for kyu level
|
||||
- `GET /api/curriculum/modules/:moduleId` - Module details
|
||||
- `GET /api/curriculum/tutorials/:tutorialId` - Tutorial content
|
||||
- `GET /api/curriculum/next` - Next recommended activity
|
||||
|
||||
**Assessments:**
|
||||
- `POST /api/assessments/placement` - Take placement test
|
||||
- `POST /api/assessments/skill-check/:skillId` - Practice test
|
||||
- `POST /api/assessments/certification/:kyuLevel` - Certification test
|
||||
- `POST /api/assessments/:assessmentId/submit` - Submit answers
|
||||
- `GET /api/assessments/:assessmentId/results` - Get results
|
||||
|
||||
**Games:**
|
||||
- `GET /api/games/recommended` - Games for current skills
|
||||
- `POST /api/games/:gameId/result` - Log game completion
|
||||
- `GET /api/games/:gameId/skills` - Which skills this game teaches
|
||||
|
||||
**Teacher/Parent:**
|
||||
- `GET /api/teacher/students` - List of students
|
||||
- `GET /api/teacher/students/:studentId/progress` - Student progress
|
||||
- `POST /api/teacher/assignments` - Create assignment
|
||||
|
||||
### Component Architecture
|
||||
|
||||
**New Components Needed:**
|
||||
|
||||
```
|
||||
/src/components/curriculum/
|
||||
- SkillCard.tsx - Display skill with progress
|
||||
- ModuleCard.tsx - Module overview with skills
|
||||
- CurriculumMap.tsx - Visual map of curriculum
|
||||
- SkillTree.tsx - Dependency graph visualization
|
||||
|
||||
/src/components/practice/
|
||||
- PracticeSession.tsx - Practice exercise UI
|
||||
- ProblemDisplay.tsx - Show problem to solve
|
||||
- AnswerInput.tsx - Accept answer (with abacus)
|
||||
- FeedbackDisplay.tsx - Show correctness and hints
|
||||
|
||||
/src/components/assessment/
|
||||
- PlacementTest.tsx - Initial assessment
|
||||
- SkillCheckTest.tsx - Practice test
|
||||
- CertificationTest.tsx - Formal kyu test
|
||||
- TestResults.tsx - Detailed results page
|
||||
|
||||
/src/components/dashboard/
|
||||
- StudentDashboard.tsx - Main dashboard
|
||||
- ProgressOverview.tsx - Current level and progress
|
||||
- NextActivity.tsx - Recommended next step
|
||||
- AchievementShowcase.tsx - Badges and milestones
|
||||
- ActivityFeed.tsx - Recent activity
|
||||
|
||||
/src/components/teacher/
|
||||
- TeacherDashboard.tsx
|
||||
- StudentRoster.tsx
|
||||
- StudentDetail.tsx
|
||||
- AssignmentCreator.tsx
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Content Creation Process
|
||||
|
||||
### Tutorial Creation Workflow
|
||||
|
||||
1. **Define Skill:** What specific skill does this teach?
|
||||
2. **Outline Steps:** Break down into 5-10 learning steps
|
||||
3. **Create Interactive Elements:**
|
||||
- Which beads to highlight
|
||||
- What movements to demonstrate
|
||||
- Example problems
|
||||
4. **Add Explanations:** Clear, kid-friendly language
|
||||
5. **Test with Students:** Iterate based on confusion points
|
||||
6. **Publish:** Add to curriculum map
|
||||
|
||||
### Tutorial Template
|
||||
|
||||
```typescript
|
||||
{
|
||||
id: "friends-of-5-intro",
|
||||
title: "Friends of 5 - Introduction",
|
||||
skillIds: ["friends-5-recognition"],
|
||||
kyuLevel: 9,
|
||||
estimatedDuration: 15,
|
||||
steps: [
|
||||
{
|
||||
instruction: "Let's learn about friends of 5! These are pairs of numbers that add up to 5.",
|
||||
problem: null,
|
||||
highlighting: [],
|
||||
explanation: "When you add friends together, they always make 5!"
|
||||
},
|
||||
{
|
||||
instruction: "1 and 4 are friends! See how 1 + 4 = 5?",
|
||||
problem: { operation: 'add', terms: [1, 4] },
|
||||
highlighting: [
|
||||
{ column: 0, value: 1, direction: 'activate' },
|
||||
{ column: 0, value: 4, direction: 'up', step: 2 }
|
||||
],
|
||||
explanation: "We set 1 earth bead, then add 4 more by using the heaven bead (5) and removing 1."
|
||||
},
|
||||
// ... more steps
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Practice Set Template
|
||||
|
||||
```typescript
|
||||
{
|
||||
id: "friends-5-practice-1",
|
||||
skillIds: ["friends-5-recognition", "friends-5-addition"],
|
||||
problemCount: 20,
|
||||
timeLimit: 300, // 5 minutes
|
||||
passingAccuracy: 0.85,
|
||||
problemGenerator: {
|
||||
type: 'addition',
|
||||
numberRange: [1, 9],
|
||||
requiresFriends5: true,
|
||||
maxTerms: 2
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## File Structure for Curriculum
|
||||
|
||||
```
|
||||
/apps/web/src/curriculum/
|
||||
/schema/
|
||||
- skills.ts (skill definitions)
|
||||
- modules.ts (module definitions)
|
||||
- assessments.ts (test definitions)
|
||||
|
||||
/content/
|
||||
/10-kyu/
|
||||
- module-1-intro.ts
|
||||
- module-2-setting-numbers.ts
|
||||
- module-3-basic-addition.ts
|
||||
/9-kyu/
|
||||
- module-4-friends-5-intro.ts
|
||||
- module-5-friends-5-application.ts
|
||||
/8-kyu/
|
||||
... and so on
|
||||
|
||||
/tutorials/
|
||||
/10-kyu/
|
||||
- intro-to-abacus.ts
|
||||
- place-value.ts
|
||||
- setting-numbers.ts
|
||||
- basic-addition.ts
|
||||
- basic-subtraction.ts
|
||||
/9-kyu/
|
||||
- friends-5-concept.ts
|
||||
- friends-5-addition.ts
|
||||
- friends-5-subtraction.ts
|
||||
... and so on
|
||||
|
||||
/practice/
|
||||
/10-kyu/
|
||||
- number-setting-practice.ts
|
||||
- basic-addition-practice.ts
|
||||
/9-kyu/
|
||||
- friends-5-practice.ts
|
||||
... and so on
|
||||
|
||||
/assessments/
|
||||
- placement-test.ts
|
||||
- 10-kyu-certification.ts
|
||||
- 9-kyu-certification.ts
|
||||
... and so on
|
||||
|
||||
- curriculum-map.ts (master curriculum definition)
|
||||
- game-skill-mappings.ts (which games teach which skills)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Next Immediate Steps
|
||||
|
||||
### Week 1: Database Schema Design
|
||||
- [ ] Design complete schema for Phase 1
|
||||
- [ ] Write migration scripts
|
||||
- [ ] Document schema decisions
|
||||
- [ ] Review with stakeholders
|
||||
|
||||
### Week 2-3: Content Planning
|
||||
- [ ] Write detailed 10 Kyu curriculum outline
|
||||
- [ ] Write detailed 9 Kyu curriculum outline
|
||||
- [ ] Define all skills for 10-9 Kyu
|
||||
- [ ] Map skills to existing games
|
||||
|
||||
### Week 4-5: Tutorial Content Creation
|
||||
- [ ] Write 5 tutorials for 10 Kyu
|
||||
- [ ] Write 3 tutorials for 9 Kyu
|
||||
- [ ] Create interactive steps with highlighting
|
||||
- [ ] Add kid-friendly explanations
|
||||
|
||||
### Week 6-7: Assessment System Build
|
||||
- [ ] Build assessment component UI
|
||||
- [ ] Implement grading engine
|
||||
- [ ] Create placement test (20 problems)
|
||||
- [ ] Create 10 Kyu certification test (30 problems)
|
||||
- [ ] Create 9 Kyu certification test (40 problems)
|
||||
|
||||
### Week 8-9: Practice System
|
||||
- [ ] Build practice session component
|
||||
- [ ] Implement problem generator for each skill
|
||||
- [ ] Add immediate feedback system
|
||||
- [ ] Create hint system
|
||||
|
||||
### Week 10-11: Student Dashboard
|
||||
- [ ] Design dashboard UI (kid-friendly)
|
||||
- [ ] Build progress visualization
|
||||
- [ ] Implement "next recommended activity" logic
|
||||
- [ ] Add achievement display
|
||||
|
||||
### Week 12: Integration & Testing
|
||||
- [ ] Connect all pieces: tutorials → practice → games → assessment
|
||||
- [ ] Test complete user flow
|
||||
- [ ] User testing with kids
|
||||
- [ ] Iterate based on feedback
|
||||
|
||||
---
|
||||
|
||||
## Questions to Resolve
|
||||
|
||||
1. **Certification Validity:** Should kyu certifications expire? (Traditional abacus schools: no expiration)
|
||||
2. **Retake Policy:** How many times can student retake certification test? (Suggest: unlimited, but must wait 24 hours)
|
||||
3. **Grading Standards:** Strict adherence to Japanese standards or adjust for USA context?
|
||||
4. **Physical Abacus:** Should we require physical abacus for certain levels? (Recommend: optional but encouraged)
|
||||
5. **Age Restrictions:** Any minimum age? (Suggest: 6+ with parent/teacher supervision)
|
||||
6. **Teacher Accounts:** Free for teachers? (Recommend: yes, free for teachers)
|
||||
7. **Pricing Model:** Free tier + premium? School licensing? (TBD)
|
||||
8. **Content Licensing:** Will curriculum be open source or proprietary? (Recommend: proprietary but allow teacher customization)
|
||||
9. **Accessibility:** WCAG compliance level? (Recommend: AA minimum)
|
||||
10. **Data Privacy:** COPPA compliance for users under 13? (Required: yes, must be compliant)
|
||||
|
||||
---
|
||||
|
||||
## Conclusion
|
||||
|
||||
This roadmap provides a clear path from current state (scattered features) to target state (complete educational platform). The phased approach allows incremental delivery while maintaining focus on core learning experience.
|
||||
|
||||
**Estimated Timeline:**
|
||||
- Phase 1 (10-9 Kyu MVP): 3 months
|
||||
- Phase 2 (8-5 Kyu): 5 months
|
||||
- Phase 3 (4-1 Kyu): 6 months
|
||||
- Phase 4 (Dan levels): 3 months
|
||||
- Phase 5 (Ecosystem): Ongoing
|
||||
|
||||
**Total to Complete Platform:** ~17 months for core curriculum, then continuous improvement
|
||||
|
||||
**Priority:** Start with Phase 1 to prove the concept, get student feedback, and validate the learning loop before building the full system.
|
||||
76
apps/web/.claude/PANDA_CSS_DYNAMIC_TOKENS.md
Normal file
76
apps/web/.claude/PANDA_CSS_DYNAMIC_TOKENS.md
Normal file
@@ -0,0 +1,76 @@
|
||||
# Panda CSS Dynamic Token Usage
|
||||
|
||||
## Problem: Dynamic Color Tokens Not Working
|
||||
|
||||
When using Panda CSS, color tokens like `blue.400`, `purple.400`, etc. don't work when used dynamically through variables in the `css()` function.
|
||||
|
||||
## Root Cause
|
||||
|
||||
Panda CSS's `css()` function requires **static values at build time**. It cannot process dynamic token references like:
|
||||
|
||||
```typescript
|
||||
// ❌ This doesn't work
|
||||
const color = 'blue.400'
|
||||
css({ color: color }) // Panda can't resolve this at build time
|
||||
```
|
||||
|
||||
The `css()` function performs static analysis during the build process to generate CSS classes. It cannot handle runtime-dynamic token paths.
|
||||
|
||||
## Solution: Use the `token()` Function
|
||||
|
||||
Panda CSS provides a `token()` function specifically for resolving token paths to their actual values at runtime:
|
||||
|
||||
```typescript
|
||||
import { token } from '../../styled-system/tokens'
|
||||
|
||||
// ✅ This works
|
||||
const stages = [
|
||||
{ level: '10 Kyu', label: 'Beginner', color: 'colors.green.400' },
|
||||
{ level: '5 Kyu', label: 'Intermediate', color: 'colors.blue.400' },
|
||||
{ level: '1 Kyu', label: 'Advanced', color: 'colors.violet.400' },
|
||||
{ level: 'Dan', label: 'Master', color: 'colors.amber.400' },
|
||||
] as const
|
||||
|
||||
// Use with inline styles, not css()
|
||||
<div style={{ color: token(stage.color) }}>
|
||||
```
|
||||
|
||||
## Important Notes
|
||||
|
||||
1. **Use `as const`**: TypeScript needs the array marked as `const` so the token strings are treated as literal types, not generic strings. The `token()` function expects the `Token` literal type.
|
||||
|
||||
2. **Use inline styles**: When using `token()`, apply colors via the `style` prop, not through the `css()` function:
|
||||
```typescript
|
||||
// ✅ Correct
|
||||
<div style={{ color: token(stage.color) }}>
|
||||
|
||||
// ❌ Won't work
|
||||
<div className={css({ color: token(stage.color) })}>
|
||||
```
|
||||
|
||||
3. **Static tokens in css()**: For static usage, you CAN use tokens directly in `css()`:
|
||||
```typescript
|
||||
// ✅ This works because it's static
|
||||
css({ color: 'blue.400' })
|
||||
```
|
||||
|
||||
## How token() Works
|
||||
|
||||
The `token()` function:
|
||||
- Takes a token path like `"colors.blue.400"`
|
||||
- Looks it up in the generated token registry (`styled-system/tokens/index.mjs`)
|
||||
- Returns the actual CSS value (e.g., `"#60a5fa"`)
|
||||
- Happens at runtime, not build time
|
||||
|
||||
## Token Type Definition
|
||||
|
||||
The `Token` type is a union of all valid token paths:
|
||||
```typescript
|
||||
type Token = "colors.blue.400" | "colors.green.400" | "colors.violet.400" | ...
|
||||
```
|
||||
|
||||
This is defined in `styled-system/tokens/tokens.d.ts`.
|
||||
|
||||
## Reference Implementation
|
||||
|
||||
See `src/app/page.tsx` lines 404-434 for a working example of dynamic token usage in the "Your Journey" section.
|
||||
222
apps/web/.claude/TUTORIAL_SYSTEM.md
Normal file
222
apps/web/.claude/TUTORIAL_SYSTEM.md
Normal file
@@ -0,0 +1,222 @@
|
||||
# Tutorial System Documentation
|
||||
|
||||
## Overview
|
||||
|
||||
The tutorial system is a sophisticated interactive learning platform for teaching soroban abacus concepts. It features step-by-step guidance, bead highlighting, pedagogical decomposition, and progress tracking.
|
||||
|
||||
## Key Components
|
||||
|
||||
### 1. TutorialPlayer (`/src/components/tutorial/TutorialPlayer.tsx`)
|
||||
The main tutorial playback component that:
|
||||
- Displays tutorial steps progressively
|
||||
- Highlights specific beads users should interact with
|
||||
- Provides real-time feedback and tooltips
|
||||
- Shows step-by-step instructions for multi-step operations
|
||||
- Tracks user progress through the tutorial
|
||||
- Auto-advances to next step on correct completion
|
||||
|
||||
**Key Features:**
|
||||
- **Bead Highlighting**: Visual indicators showing which beads to manipulate
|
||||
- **Step Progress**: Shows current step out of total steps
|
||||
- **Error Feedback**: Provides hints when user makes mistakes
|
||||
- **Multi-Step Support**: Breaks complex operations into sequential sub-steps
|
||||
- **Pedagogical Decomposition**: Explains the "why" behind each operation
|
||||
|
||||
### 2. TutorialEditor (`/src/components/tutorial/TutorialEditor.tsx`)
|
||||
A full-featured editor for creating and editing tutorials:
|
||||
- Visual step editor
|
||||
- Bead highlight configuration
|
||||
- Multi-step instruction editor
|
||||
- Live preview
|
||||
- Import/export functionality
|
||||
- Access control
|
||||
|
||||
**Editor URL:** `/tutorial-editor`
|
||||
|
||||
### 3. Tutorial Data Structure (`/src/types/tutorial.ts`)
|
||||
|
||||
```typescript
|
||||
interface Tutorial {
|
||||
id: string
|
||||
title: string
|
||||
description: string
|
||||
category: string
|
||||
difficulty: 'beginner' | 'intermediate' | 'advanced'
|
||||
estimatedDuration: number // minutes
|
||||
steps: TutorialStep[]
|
||||
tags: string[]
|
||||
author: string
|
||||
version: string
|
||||
createdAt: Date
|
||||
updatedAt: Date
|
||||
isPublished: boolean
|
||||
}
|
||||
|
||||
interface TutorialStep {
|
||||
id: string
|
||||
title: string
|
||||
problem: string // e.g. "2 + 3"
|
||||
description: string // User-facing explanation
|
||||
startValue: number // Initial abacus value
|
||||
targetValue: number // Goal value
|
||||
expectedAction: 'add' | 'remove' | 'multi-step'
|
||||
actionDescription: string
|
||||
|
||||
// Bead highlighting
|
||||
highlightBeads?: Array<{
|
||||
placeValue: number // 0=ones, 1=tens, etc.
|
||||
beadType: 'heaven' | 'earth'
|
||||
position?: number // For earth beads: 0-3
|
||||
}>
|
||||
|
||||
// Progressive step highlighting
|
||||
stepBeadHighlights?: Array<{
|
||||
placeValue: number
|
||||
beadType: 'heaven' | 'earth'
|
||||
position?: number
|
||||
stepIndex: number // Which instruction step
|
||||
direction: 'up' | 'down' | 'activate' | 'deactivate'
|
||||
order?: number // Order within step
|
||||
}>
|
||||
|
||||
totalSteps?: number // For multi-step operations
|
||||
multiStepInstructions?: string[] // Sequential instructions
|
||||
|
||||
// Tooltips and guidance
|
||||
tooltip: {
|
||||
content: string // Short title
|
||||
explanation: string // Detailed explanation
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 4. Tutorial Converter (`/src/utils/tutorialConverter.ts`)
|
||||
|
||||
Utility that converts the original `GuidedAdditionTutorial` data into the new tutorial format:
|
||||
- `guidedAdditionSteps`: Array of tutorial steps from basic addition to complements
|
||||
- `convertGuidedAdditionTutorial()`: Converts to Tutorial object
|
||||
- `getTutorialForEditor()`: Main export used in the app
|
||||
|
||||
**Current Tutorial Steps:**
|
||||
1. Basic Addition (0+1, 1+1, 2+1, 3+1)
|
||||
2. Heaven Bead Introduction (0+5, 5+1)
|
||||
3. Five Complements (3+4, 2+3 using 5-complement method)
|
||||
4. Complex Operations (6+2, 7+4 with carrying)
|
||||
|
||||
### 5. Supporting Utilities
|
||||
|
||||
**`/src/utils/abacusInstructionGenerator.ts`**
|
||||
- Automatically generates step-by-step instructions from start/target values
|
||||
- Creates bead highlight data
|
||||
- Determines movement directions
|
||||
|
||||
**`/src/utils/beadDiff.ts`**
|
||||
- Calculates differences between abacus states
|
||||
- Generates visual feedback tooltips
|
||||
- Explains what changed and why
|
||||
|
||||
## Usage Examples
|
||||
|
||||
### Basic Usage in a Page
|
||||
|
||||
```typescript
|
||||
import { TutorialPlayer } from '@/components/tutorial/TutorialPlayer'
|
||||
import { getTutorialForEditor } from '@/utils/tutorialConverter'
|
||||
|
||||
export function MyPage() {
|
||||
return (
|
||||
<TutorialPlayer
|
||||
tutorial={getTutorialForEditor()}
|
||||
isDebugMode={false}
|
||||
showDebugPanel={false}
|
||||
/>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
### Using a Subset of Steps
|
||||
|
||||
```typescript
|
||||
import { getTutorialForEditor } from '@/utils/tutorialConverter'
|
||||
|
||||
const fullTutorial = getTutorialForEditor()
|
||||
|
||||
// Extract specific steps (e.g., just "Friends of 5")
|
||||
const friendsOf5Tutorial = {
|
||||
...fullTutorial,
|
||||
id: 'friends-of-5-demo',
|
||||
title: 'Friends of 5',
|
||||
steps: fullTutorial.steps.filter(step =>
|
||||
step.id === 'complement-2' // The 2+3=5 step
|
||||
)
|
||||
}
|
||||
|
||||
return <TutorialPlayer tutorial={friendsOf5Tutorial} />
|
||||
```
|
||||
|
||||
### Creating a Custom Tutorial
|
||||
|
||||
```typescript
|
||||
const customTutorial: Tutorial = {
|
||||
id: 'my-tutorial',
|
||||
title: 'My Custom Tutorial',
|
||||
description: 'Learning something new',
|
||||
category: 'Custom',
|
||||
difficulty: 'beginner',
|
||||
estimatedDuration: 5,
|
||||
steps: [
|
||||
{
|
||||
id: 'step-1',
|
||||
title: 'Add 2',
|
||||
problem: '0 + 2',
|
||||
description: 'Move two earth beads up',
|
||||
startValue: 0,
|
||||
targetValue: 2,
|
||||
expectedAction: 'add',
|
||||
actionDescription: 'Add two earth beads',
|
||||
highlightBeads: [
|
||||
{ placeValue: 0, beadType: 'earth', position: 0 },
|
||||
{ placeValue: 0, beadType: 'earth', position: 1 }
|
||||
],
|
||||
tooltip: {
|
||||
content: 'Adding 2',
|
||||
explanation: 'Push two earth beads up to represent 2'
|
||||
}
|
||||
}
|
||||
],
|
||||
tags: ['custom'],
|
||||
author: 'Me',
|
||||
version: '1.0.0',
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
isPublished: true
|
||||
}
|
||||
```
|
||||
|
||||
## Current Implementation Locations
|
||||
|
||||
**Live Tutorials:**
|
||||
- `/guide` - Second tab "Arithmetic Operations" contains the full guided addition tutorial
|
||||
|
||||
**Editor:**
|
||||
- `/tutorial-editor` - Full tutorial editing interface
|
||||
|
||||
**Storybook:**
|
||||
- Multiple tutorial stories in `/src/components/tutorial/*.stories.tsx`
|
||||
|
||||
## Key Design Principles
|
||||
|
||||
1. **Progressive Disclosure**: Users see one step at a time
|
||||
2. **Immediate Feedback**: Real-time validation and hints
|
||||
3. **Visual Guidance**: Bead highlighting shows exactly what to do
|
||||
4. **Pedagogical Decomposition**: Multi-step operations broken into atomic actions
|
||||
5. **Auto-Advancement**: Successful completion automatically moves to next step
|
||||
6. **Error Recovery**: Helpful hints when user makes mistakes
|
||||
|
||||
## Notes
|
||||
|
||||
- The tutorial system uses the existing `AbacusReact` component
|
||||
- Tutorials can be created/edited through the TutorialEditor
|
||||
- Tutorial data can be exported/imported as JSON
|
||||
- The system supports both single-step and multi-step operations
|
||||
- Bead highlighting uses place value indexing (0=ones, 1=tens, etc.)
|
||||
@@ -102,7 +102,8 @@
|
||||
"Bash(do gh run list --limit 1 --workflow=\"Build and Deploy\" --json conclusion,status,databaseId --jq '.[0] | \"\"\\(.status) - \\(.conclusion // \"\"running\"\") - Run ID: \\(.databaseId)\"\"')",
|
||||
"Bash(node -e:*)",
|
||||
"Bash(do gh run list --limit 1 --workflow=\"Build and Deploy\" --json conclusion,status,databaseId --jq '.[0] | \"\"\\(.status) - \\(.conclusion // \"\"running\"\") - Run \\(.databaseId)\"\"')",
|
||||
"Bash(do ssh nas.home.network '/usr/local/bin/docker inspect soroban-abacus-flashcards --format=\"\"{{index .Config.Labels \\\"\"org.opencontainers.image.revision\\\"\"}}\"\"')"
|
||||
"Bash(do ssh nas.home.network '/usr/local/bin/docker inspect soroban-abacus-flashcards --format=\"\"{{index .Config.Labels \\\"\"org.opencontainers.image.revision\\\"\"}}\"\"')",
|
||||
"Bash(git rev-parse HEAD)"
|
||||
],
|
||||
"deny": [],
|
||||
"ask": []
|
||||
|
||||
@@ -290,10 +290,12 @@ export default function RoomPage() {
|
||||
key={gameDef.manifest.name}
|
||||
onClick={() => handleGameSelect(gameDef.manifest.name)}
|
||||
disabled={isDisabled}
|
||||
className={css({
|
||||
style={{
|
||||
background: gameDef.manifest.gradient,
|
||||
border: '2px solid',
|
||||
borderColor: gameDef.manifest.borderColor,
|
||||
}}
|
||||
className={css({
|
||||
border: '2px solid',
|
||||
borderRadius: '2xl',
|
||||
padding: '6',
|
||||
cursor: isDisabled ? 'not-allowed' : 'pointer',
|
||||
|
||||
@@ -1,29 +1,92 @@
|
||||
'use client'
|
||||
|
||||
import { useState } from 'react'
|
||||
import Link from 'next/link'
|
||||
import { PageWithNav } from '@/components/PageWithNav'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { AbacusReact, useAbacusConfig } from '@soroban/abacus-react'
|
||||
import { PageWithNav } from '@/components/PageWithNav'
|
||||
import { TutorialPlayer } from '@/components/tutorial/TutorialPlayer'
|
||||
import { getTutorialForEditor } from '@/utils/tutorialConverter'
|
||||
import { css } from '../../styled-system/css'
|
||||
import { container, grid, hstack, stack } from '../../styled-system/patterns'
|
||||
import { token } from '../../styled-system/tokens'
|
||||
|
||||
// Mini abacus that cycles through random 3-digit numbers
|
||||
function MiniAbacus() {
|
||||
const [currentValue, setCurrentValue] = useState(123)
|
||||
const appConfig = useAbacusConfig()
|
||||
|
||||
useEffect(() => {
|
||||
// Cycle through random 3-digit numbers every 2.5 seconds
|
||||
const interval = setInterval(() => {
|
||||
const randomNum = Math.floor(Math.random() * 1000) // 0-999
|
||||
setCurrentValue(randomNum)
|
||||
}, 2500)
|
||||
|
||||
return () => clearInterval(interval)
|
||||
}, [])
|
||||
|
||||
// Dark theme styles for the abacus
|
||||
const darkStyles = {
|
||||
columnPosts: {
|
||||
fill: 'rgba(255, 255, 255, 0.3)',
|
||||
stroke: 'rgba(255, 255, 255, 0.2)',
|
||||
strokeWidth: 2,
|
||||
},
|
||||
reckoningBar: {
|
||||
fill: 'rgba(255, 255, 255, 0.4)',
|
||||
stroke: 'rgba(255, 255, 255, 0.25)',
|
||||
strokeWidth: 3,
|
||||
},
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className={css({
|
||||
width: '75px',
|
||||
height: '80px',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
})}
|
||||
>
|
||||
<div className={css({ transform: 'scale(0.6)', transformOrigin: 'center center' })}>
|
||||
<AbacusReact
|
||||
value={currentValue}
|
||||
columns={3}
|
||||
beadShape={appConfig.beadShape}
|
||||
styles={darkStyles}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default function HomePage() {
|
||||
const [abacusValue, setAbacusValue] = useState(1234567)
|
||||
const appConfig = useAbacusConfig()
|
||||
// Extract just the "Friends of 5" step (2+3=5) for homepage demo
|
||||
const fullTutorial = getTutorialForEditor()
|
||||
const friendsOf5Tutorial = {
|
||||
...fullTutorial,
|
||||
id: 'friends-of-5-demo',
|
||||
title: 'Friends of 5',
|
||||
description: 'Learn the "Friends of 5" technique: adding 3 to make 5',
|
||||
steps: fullTutorial.steps.filter((step) => step.id === 'complement-2'),
|
||||
}
|
||||
|
||||
return (
|
||||
<PageWithNav navTitle="Soroban Mastery Platform" navEmoji="🧮">
|
||||
<PageWithNav navTitle="Soroban Learning Platform" navEmoji="🧮">
|
||||
<div className={css({ bg: 'gray.900', minHeight: '100vh' })}>
|
||||
{/* Hero with Large Abacus */}
|
||||
{/* Hero Section */}
|
||||
<div
|
||||
className={css({
|
||||
background:
|
||||
'linear-gradient(135deg, rgba(17, 24, 39, 1) 0%, rgba(88, 28, 135, 0.3) 50%, rgba(17, 24, 39, 1) 100%)',
|
||||
color: 'white',
|
||||
py: { base: '12', md: '20' },
|
||||
py: { base: '12', md: '16' },
|
||||
position: 'relative',
|
||||
overflow: 'hidden',
|
||||
})}
|
||||
>
|
||||
{/* Background pattern */}
|
||||
<div
|
||||
className={css({
|
||||
position: 'absolute',
|
||||
@@ -34,69 +97,105 @@ export default function HomePage() {
|
||||
backgroundSize: '40px 40px',
|
||||
})}
|
||||
/>
|
||||
|
||||
<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' },
|
||||
fontWeight: 'bold',
|
||||
mb: '6',
|
||||
mb: '4',
|
||||
lineHeight: 'tight',
|
||||
background: 'linear-gradient(135deg, #fbbf24 0%, #f59e0b 50%, #fbbf24 100%)',
|
||||
backgroundClip: 'text',
|
||||
color: 'transparent',
|
||||
})}
|
||||
>
|
||||
Master the Soroban
|
||||
A structured path to soroban fluency
|
||||
</h1>
|
||||
|
||||
{/* Large Featured Abacus */}
|
||||
<div
|
||||
className={css({
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
my: '10',
|
||||
p: '8',
|
||||
bg: 'white',
|
||||
borderRadius: '2xl',
|
||||
border: '2px solid',
|
||||
borderColor: 'purple.500/30',
|
||||
boxShadow: '0 25px 50px -12px rgba(139, 92, 246, 0.25)',
|
||||
color: '#1f2937',
|
||||
})}
|
||||
>
|
||||
<AbacusReact
|
||||
value={abacusValue}
|
||||
columns={7}
|
||||
beadShape={appConfig.beadShape}
|
||||
colorScheme={appConfig.colorScheme}
|
||||
hideInactiveBeads={appConfig.hideInactiveBeads}
|
||||
interactive={true}
|
||||
animated={true}
|
||||
soundEnabled={true}
|
||||
soundVolume={0.4}
|
||||
scaleFactor={2.2}
|
||||
showNumbers={true}
|
||||
onValueChange={(newValue: number) => setAbacusValue(newValue)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Subtitle */}
|
||||
<p
|
||||
className={css({
|
||||
fontSize: { base: 'lg', md: 'xl' },
|
||||
color: 'gray.300',
|
||||
mb: '8',
|
||||
mb: '6',
|
||||
maxW: '3xl',
|
||||
mx: 'auto',
|
||||
lineHeight: '1.6',
|
||||
})}
|
||||
>
|
||||
Interactive tutorials, multiplayer games, and beautiful flashcards—your complete
|
||||
soroban learning ecosystem
|
||||
Designed for self-directed learning. Start where you are, practice the skills you
|
||||
need, play games that reinforce concepts.
|
||||
</p>
|
||||
|
||||
{/* Dev status badge */}
|
||||
<div
|
||||
className={css({
|
||||
display: 'inline-block',
|
||||
px: '4',
|
||||
py: '2',
|
||||
bg: 'rgba(139, 92, 246, 0.15)',
|
||||
border: '1px solid rgba(139, 92, 246, 0.3)',
|
||||
borderRadius: 'full',
|
||||
fontSize: 'sm',
|
||||
color: 'purple.300',
|
||||
mb: '8',
|
||||
})}
|
||||
>
|
||||
🏗️ Curriculum system in active development
|
||||
</div>
|
||||
|
||||
{/* Visual learning journey */}
|
||||
<div
|
||||
className={css({
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
gap: '2',
|
||||
mb: '8',
|
||||
flexWrap: 'wrap',
|
||||
})}
|
||||
>
|
||||
{[
|
||||
{ icon: '📖', label: 'Learn' },
|
||||
{ icon: '→', label: '', isArrow: true },
|
||||
{ icon: '✏️', label: 'Practice' },
|
||||
{ icon: '→', label: '', isArrow: true },
|
||||
{ icon: '🎮', label: 'Play' },
|
||||
{ icon: '→', label: '', isArrow: true },
|
||||
{ icon: '🎯', label: 'Master' },
|
||||
].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' : '2xl',
|
||||
color: step.isArrow ? 'gray.500' : 'yellow.400',
|
||||
})}
|
||||
>
|
||||
{step.icon}
|
||||
</div>
|
||||
{step.label && (
|
||||
<div className={css({ fontSize: 'xs', color: 'gray.400' })}>{step.label}</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Primary CTAs */}
|
||||
<div className={hstack({ gap: '4', justify: 'center', flexWrap: 'wrap' })}>
|
||||
<Link
|
||||
href="/games"
|
||||
href="/guide"
|
||||
className={css({
|
||||
px: '8',
|
||||
py: '4',
|
||||
@@ -113,10 +212,10 @@ export default function HomePage() {
|
||||
transition: 'all 0.3s ease',
|
||||
})}
|
||||
>
|
||||
🎮 Play Games
|
||||
📚 Start Learning
|
||||
</Link>
|
||||
<Link
|
||||
href="/guide"
|
||||
href="/games"
|
||||
className={css({
|
||||
px: '8',
|
||||
py: '4',
|
||||
@@ -134,28 +233,7 @@ export default function HomePage() {
|
||||
transition: 'all 0.3s ease',
|
||||
})}
|
||||
>
|
||||
📚 Learn
|
||||
</Link>
|
||||
<Link
|
||||
href="/create"
|
||||
className={css({
|
||||
px: '8',
|
||||
py: '4',
|
||||
bg: 'rgba(139, 92, 246, 0.2)',
|
||||
color: 'white',
|
||||
fontWeight: 'bold',
|
||||
fontSize: 'lg',
|
||||
rounded: 'xl',
|
||||
border: '2px solid',
|
||||
borderColor: 'purple.500',
|
||||
_hover: {
|
||||
bg: 'rgba(139, 92, 246, 0.3)',
|
||||
transform: 'translateY(-2px)',
|
||||
},
|
||||
transition: 'all 0.3s ease',
|
||||
})}
|
||||
>
|
||||
🎨 Create
|
||||
🎮 Practice Through Games
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
@@ -164,173 +242,453 @@ export default function HomePage() {
|
||||
|
||||
{/* Main content container */}
|
||||
<div className={container({ maxW: '7xl', px: '4', py: '12' })}>
|
||||
{/* Arcade Games Section */}
|
||||
<section className={stack({ gap: '6', mt: '16' })}>
|
||||
<div className={hstack({ justify: 'space-between', alignItems: 'center' })}>
|
||||
<div>
|
||||
<h2
|
||||
className={css({
|
||||
fontSize: { base: '2xl', md: '3xl' },
|
||||
fontWeight: 'bold',
|
||||
color: 'white',
|
||||
mb: '2',
|
||||
})}
|
||||
>
|
||||
🕹️ Multiplayer Arcade
|
||||
</h2>
|
||||
<p className={css({ color: 'gray.400', fontSize: 'md' })}>
|
||||
Compete with friends in real-time soroban games
|
||||
</p>
|
||||
</div>
|
||||
<Link
|
||||
href="/games"
|
||||
{/* Learn by Doing Section - with inline tutorial demo */}
|
||||
<section className={stack({ gap: '8', mb: '16' })}>
|
||||
<div className={css({ textAlign: 'center' })}>
|
||||
<h2
|
||||
className={css({
|
||||
fontSize: 'md',
|
||||
color: 'yellow.400',
|
||||
fontWeight: 'semibold',
|
||||
_hover: { color: 'yellow.300' },
|
||||
display: { base: 'none', md: 'block' },
|
||||
fontSize: { base: '2xl', md: '3xl' },
|
||||
fontWeight: 'bold',
|
||||
color: 'white',
|
||||
mb: '2',
|
||||
})}
|
||||
>
|
||||
View All →
|
||||
</Link>
|
||||
Learn by Doing
|
||||
</h2>
|
||||
<p className={css({ color: 'gray.400', fontSize: 'md', maxW: '2xl', mx: 'auto' })}>
|
||||
Interactive tutorials teach you step-by-step. Try this example right now:
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* 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: '1200px',
|
||||
mx: 'auto',
|
||||
})}
|
||||
>
|
||||
<div
|
||||
className={css({
|
||||
display: 'flex',
|
||||
flexDirection: { base: 'column', md: 'row' },
|
||||
gap: '8',
|
||||
alignItems: { base: 'center', md: 'flex-start' },
|
||||
})}
|
||||
>
|
||||
{/* Tutorial on the left */}
|
||||
<div className={css({ flex: '1' })}>
|
||||
<TutorialPlayer
|
||||
tutorial={friendsOf5Tutorial}
|
||||
isDebugMode={false}
|
||||
showDebugPanel={false}
|
||||
hideNavigation={true}
|
||||
hideTooltip={true}
|
||||
silentErrors={true}
|
||||
abacusColumns={1}
|
||||
theme="dark"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* What you'll learn on the right */}
|
||||
<div
|
||||
className={css({
|
||||
flex: '0 0 auto',
|
||||
minW: '340px',
|
||||
maxW: { base: '100%', md: '420px' },
|
||||
})}
|
||||
>
|
||||
<h3
|
||||
className={css({
|
||||
fontSize: '2xl',
|
||||
fontWeight: 'bold',
|
||||
color: 'white',
|
||||
mb: '6',
|
||||
})}
|
||||
>
|
||||
What You'll Learn
|
||||
</h3>
|
||||
<div className={stack({ gap: '5' })}>
|
||||
{[
|
||||
{
|
||||
icon: '🔢',
|
||||
title: 'Read and set numbers',
|
||||
desc: 'Master abacus number representation from zero to thousands',
|
||||
example: '0-9999',
|
||||
badge: 'Foundation',
|
||||
},
|
||||
{
|
||||
icon: '🤝',
|
||||
title: 'Friends techniques',
|
||||
desc: 'Add and subtract using complement pairs and mental shortcuts',
|
||||
example: '5 = 2+3',
|
||||
badge: 'Core',
|
||||
},
|
||||
{
|
||||
icon: '✖️➗',
|
||||
title: 'Multiply & divide',
|
||||
desc: 'Fluent multi-digit calculations with advanced techniques',
|
||||
example: '12×34',
|
||||
badge: 'Advanced',
|
||||
},
|
||||
{
|
||||
icon: '🧠',
|
||||
title: 'Mental calculation',
|
||||
desc: 'Visualize and compute without the physical tool (Anzan)',
|
||||
example: 'Speed math',
|
||||
badge: 'Expert',
|
||||
},
|
||||
].map((skill, i) => (
|
||||
<div
|
||||
key={i}
|
||||
className={css({
|
||||
bg: 'linear-gradient(135deg, rgba(255, 255, 255, 0.06), rgba(255, 255, 255, 0.03))',
|
||||
borderRadius: 'xl',
|
||||
p: '4',
|
||||
border: '1px solid',
|
||||
borderColor: 'rgba(255, 255, 255, 0.15)',
|
||||
boxShadow: '0 4px 12px rgba(0, 0, 0, 0.3)',
|
||||
transition: 'all 0.2s',
|
||||
_hover: {
|
||||
bg: 'linear-gradient(135deg, rgba(255, 255, 255, 0.1), rgba(255, 255, 255, 0.05))',
|
||||
borderColor: 'rgba(255, 255, 255, 0.25)',
|
||||
transform: 'translateY(-2px)',
|
||||
boxShadow: '0 6px 16px rgba(0, 0, 0, 0.4)',
|
||||
},
|
||||
})}
|
||||
>
|
||||
<div className={hstack({ gap: '3', alignItems: 'flex-start' })}>
|
||||
<div
|
||||
className={css({
|
||||
fontSize: '3xl',
|
||||
minW: '50px',
|
||||
textAlign: 'center',
|
||||
bg: 'rgba(255, 255, 255, 0.1)',
|
||||
borderRadius: 'lg',
|
||||
py: i === 0 ? '5' : '2',
|
||||
px: i === 0 ? '3' : '2',
|
||||
})}
|
||||
>
|
||||
{i === 0 ? <MiniAbacus /> : skill.icon}
|
||||
</div>
|
||||
<div className={stack({ gap: '2', flex: '1' })}>
|
||||
<div className={hstack({ gap: '2', alignItems: 'center' })}>
|
||||
<div
|
||||
className={css({
|
||||
color: 'white',
|
||||
fontSize: 'md',
|
||||
fontWeight: 'bold',
|
||||
})}
|
||||
>
|
||||
{skill.title}
|
||||
</div>
|
||||
<div
|
||||
className={css({
|
||||
bg: 'rgba(250, 204, 21, 0.2)',
|
||||
color: 'yellow.400',
|
||||
fontSize: '2xs',
|
||||
fontWeight: 'semibold',
|
||||
px: '2',
|
||||
py: '0.5',
|
||||
borderRadius: 'md',
|
||||
})}
|
||||
>
|
||||
{skill.badge}
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className={css({
|
||||
color: 'gray.300',
|
||||
fontSize: 'xs',
|
||||
lineHeight: '1.5',
|
||||
})}
|
||||
>
|
||||
{skill.desc}
|
||||
</div>
|
||||
<div
|
||||
className={css({
|
||||
color: 'yellow.400',
|
||||
fontSize: 'xs',
|
||||
fontFamily: 'mono',
|
||||
fontWeight: 'semibold',
|
||||
mt: '1',
|
||||
bg: 'rgba(250, 204, 21, 0.1)',
|
||||
px: '2',
|
||||
py: '1',
|
||||
borderRadius: 'md',
|
||||
w: 'fit-content',
|
||||
})}
|
||||
>
|
||||
{skill.example}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Current Offerings Section */}
|
||||
<section className={stack({ gap: '6', mb: '16' })}>
|
||||
<div className={css({ textAlign: 'center' })}>
|
||||
<h2
|
||||
className={css({
|
||||
fontSize: { base: '2xl', md: '3xl' },
|
||||
fontWeight: 'bold',
|
||||
color: 'white',
|
||||
mb: '2',
|
||||
})}
|
||||
>
|
||||
Available Now
|
||||
</h2>
|
||||
<p className={css({ color: 'gray.400', fontSize: 'md' })}>
|
||||
Foundation tutorials and reinforcement games ready to use
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className={grid({ columns: { base: 1, sm: 2, lg: 4 }, gap: '5' })}>
|
||||
<GameCard
|
||||
icon="🧠"
|
||||
title="Memory Lightning"
|
||||
description="Memorize soroban numbers"
|
||||
players="1-8 players"
|
||||
tags={['Co-op', 'Competitive']}
|
||||
tags={['Memory', 'Pattern Recognition']}
|
||||
gradient="linear-gradient(135deg, #667eea 0%, #764ba2 100%)"
|
||||
href="/games"
|
||||
/>
|
||||
<GameCard
|
||||
icon="⚔️"
|
||||
title="Matching Pairs"
|
||||
description="Turn-based card battles"
|
||||
description="Match complement numbers"
|
||||
players="1-4 players"
|
||||
tags={['Pattern Recognition']}
|
||||
tags={['Friends of 5', 'Friends of 10']}
|
||||
gradient="linear-gradient(135deg, #f093fb 0%, #f5576c 100%)"
|
||||
href="/games"
|
||||
/>
|
||||
<GameCard
|
||||
icon="🏁"
|
||||
title="Speed Race"
|
||||
description="Race AI with complements"
|
||||
players="1-4 players + AI"
|
||||
tags={['Practice', 'Sprint', 'Survival']}
|
||||
title="Complement Race"
|
||||
description="Race against time"
|
||||
players="1-4 players"
|
||||
tags={['Speed', 'Practice', 'Survival']}
|
||||
gradient="linear-gradient(135deg, #4facfe 0%, #00f2fe 100%)"
|
||||
href="/games"
|
||||
/>
|
||||
<GameCard
|
||||
icon="🔢"
|
||||
title="Card Sorting"
|
||||
description="Arrange cards visually"
|
||||
description="Arrange numbers visually"
|
||||
players="Solo challenge"
|
||||
tags={['Visual Literacy']}
|
||||
tags={['Visual Literacy', 'Ordering']}
|
||||
gradient="linear-gradient(135deg, #43e97b 0%, #38f9d7 100%)"
|
||||
href="/games"
|
||||
/>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Interactive Learning & Flashcard Creator */}
|
||||
<div className={grid({ columns: { base: 1, lg: 2 }, gap: '8', mt: '16' })}>
|
||||
<FeaturePanel
|
||||
icon="📚"
|
||||
title="Interactive Learning"
|
||||
description="Master soroban through hands-on guided tutorials"
|
||||
features={[
|
||||
'Visual tutorials on reading bead positions',
|
||||
'Step-by-step arithmetic operations',
|
||||
'Interactive exercises with instant feedback',
|
||||
]}
|
||||
ctaText="Start Learning →"
|
||||
ctaHref="/guide"
|
||||
accentColor="purple"
|
||||
/>
|
||||
<FeaturePanel
|
||||
icon="🎨"
|
||||
title="Flashcard Creator"
|
||||
description="Design beautiful soroban flashcards for any purpose"
|
||||
features={[
|
||||
'Multiple export formats: PDF, PNG, SVG, HTML',
|
||||
'Custom bead shapes, colors, and layouts',
|
||||
'All paper sizes: A3, A4, A5, US Letter',
|
||||
]}
|
||||
ctaText="Create Flashcards →"
|
||||
ctaHref="/create"
|
||||
accentColor="blue"
|
||||
/>
|
||||
</div>
|
||||
{/* For Kids & Families Section */}
|
||||
<section className={stack({ gap: '6', mb: '16' })}>
|
||||
<div className={css({ textAlign: 'center' })}>
|
||||
<h2
|
||||
className={css({
|
||||
fontSize: { base: '2xl', md: '3xl' },
|
||||
fontWeight: 'bold',
|
||||
color: 'white',
|
||||
mb: '2',
|
||||
})}
|
||||
>
|
||||
For Kids & Families
|
||||
</h2>
|
||||
<p className={css({ color: 'gray.400', fontSize: 'md', maxW: '2xl', mx: 'auto' })}>
|
||||
Simple enough for kids to start on their own, structured enough for parents to trust
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className={grid({ columns: { base: 1, lg: 2 }, gap: '8' })}>
|
||||
<FeaturePanel
|
||||
icon="🧒"
|
||||
title="Self-Directed for Children"
|
||||
features={[
|
||||
'Big, obvious buttons and clear instructions',
|
||||
'Progress at your own pace',
|
||||
'Works with or without a physical abacus',
|
||||
]}
|
||||
accentColor="purple"
|
||||
/>
|
||||
<FeaturePanel
|
||||
icon="👨👩👧"
|
||||
title="Trusted by Parents"
|
||||
features={[
|
||||
'Structured curriculum following Japanese methods',
|
||||
'Traditional kyu/dan progression levels',
|
||||
'Track progress and celebrate achievements',
|
||||
]}
|
||||
accentColor="blue"
|
||||
/>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Progression Visualization */}
|
||||
<section className={stack({ gap: '6', mb: '16' })}>
|
||||
<div className={css({ textAlign: 'center' })}>
|
||||
<h2
|
||||
className={css({
|
||||
fontSize: { base: '2xl', md: '3xl' },
|
||||
fontWeight: 'bold',
|
||||
color: 'white',
|
||||
mb: '2',
|
||||
})}
|
||||
>
|
||||
Your Journey
|
||||
</h2>
|
||||
<p style={{ color: '#e5e7eb', fontSize: '16px' }}>Progress from beginner to master</p>
|
||||
</div>
|
||||
|
||||
<div
|
||||
className={css({
|
||||
bg: 'rgba(0, 0, 0, 0.4)',
|
||||
border: '1px solid',
|
||||
borderColor: 'gray.700',
|
||||
rounded: 'xl',
|
||||
p: '8',
|
||||
})}
|
||||
>
|
||||
<div
|
||||
className={css({
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
gap: '4',
|
||||
flexWrap: 'wrap',
|
||||
})}
|
||||
>
|
||||
{(
|
||||
[
|
||||
{ level: '10 Kyu', label: 'Beginner', color: 'colors.green.400' },
|
||||
{ level: '5 Kyu', label: 'Intermediate', color: 'colors.blue.400' },
|
||||
{ level: '1 Kyu', label: 'Advanced', color: 'colors.violet.400' },
|
||||
{ level: 'Dan', label: 'Master', color: 'colors.amber.400' },
|
||||
] as const
|
||||
).map((stage, i) => (
|
||||
<div
|
||||
key={i}
|
||||
className={stack({
|
||||
gap: '2',
|
||||
textAlign: 'center',
|
||||
flex: '1',
|
||||
position: 'relative',
|
||||
})}
|
||||
>
|
||||
<div
|
||||
className={css({
|
||||
fontSize: 'xl',
|
||||
fontWeight: 'bold',
|
||||
})}
|
||||
style={{ color: token(stage.color) }}
|
||||
>
|
||||
{stage.level}
|
||||
</div>
|
||||
<div
|
||||
className={css({
|
||||
fontSize: 'sm',
|
||||
color: 'gray.300',
|
||||
})}
|
||||
>
|
||||
{stage.label}
|
||||
</div>
|
||||
{i < 3 && (
|
||||
<div
|
||||
style={{
|
||||
position: 'absolute',
|
||||
left: '100%',
|
||||
marginLeft: '0.5rem',
|
||||
top: '50%',
|
||||
transform: 'translate(-50%, -50%)',
|
||||
fontSize: '20px',
|
||||
color: '#9ca3af',
|
||||
}}
|
||||
className={css({
|
||||
display: { base: 'none', md: 'block' },
|
||||
})}
|
||||
>
|
||||
→
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
textAlign: 'center',
|
||||
fontSize: '14px',
|
||||
color: '#d1d5db',
|
||||
fontStyle: 'italic',
|
||||
}}
|
||||
className={css({
|
||||
mt: '6',
|
||||
})}
|
||||
>
|
||||
You'll progress through all these levels eventually ↑
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Additional Tools Section */}
|
||||
<section className={stack({ gap: '6' })}>
|
||||
<div className={css({ textAlign: 'center' })}>
|
||||
<h2
|
||||
className={css({
|
||||
fontSize: { base: '2xl', md: '3xl' },
|
||||
fontWeight: 'bold',
|
||||
color: 'white',
|
||||
mb: '2',
|
||||
})}
|
||||
>
|
||||
Additional Tools
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<div className={grid({ columns: { base: 1, lg: 2 }, gap: '8' })}>
|
||||
<FeaturePanel
|
||||
icon="🎨"
|
||||
title="Flashcard Creator"
|
||||
features={[
|
||||
'Multiple formats: PDF, PNG, SVG, HTML',
|
||||
'Custom bead shapes, colors, and layouts',
|
||||
'All paper sizes: A3, A4, A5, US Letter',
|
||||
]}
|
||||
accentColor="blue"
|
||||
ctaText="Create Flashcards →"
|
||||
ctaHref="/create"
|
||||
/>
|
||||
<FeaturePanel
|
||||
icon="🧮"
|
||||
title="Interactive Abacus"
|
||||
features={[
|
||||
'Practice anytime in your browser',
|
||||
'Multiple color schemes and bead styles',
|
||||
'Sound effects and animations',
|
||||
]}
|
||||
accentColor="purple"
|
||||
ctaText="Try the Abacus →"
|
||||
ctaHref="/guide"
|
||||
/>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</PageWithNav>
|
||||
)
|
||||
}
|
||||
|
||||
function ColorSchemeCard({
|
||||
title,
|
||||
description,
|
||||
colorScheme,
|
||||
value,
|
||||
beadShape,
|
||||
}: {
|
||||
title: string
|
||||
description: string
|
||||
colorScheme: 'monochrome' | 'place-value' | 'heaven-earth' | 'alternating'
|
||||
value: number
|
||||
beadShape: 'diamond' | 'circle' | 'square'
|
||||
}) {
|
||||
return (
|
||||
<div
|
||||
className={css({
|
||||
bg: 'rgba(0, 0, 0, 0.4)',
|
||||
rounded: 'xl',
|
||||
p: '6',
|
||||
border: '2px solid',
|
||||
borderColor: 'gray.700',
|
||||
transition: 'all 0.3s ease',
|
||||
_hover: {
|
||||
borderColor: 'purple.500',
|
||||
transform: 'translateY(-4px)',
|
||||
boxShadow: '0 20px 40px rgba(139, 92, 246, 0.2)',
|
||||
},
|
||||
})}
|
||||
>
|
||||
<h3
|
||||
className={css({
|
||||
fontSize: 'lg',
|
||||
fontWeight: 'bold',
|
||||
color: 'white',
|
||||
mb: '2',
|
||||
})}
|
||||
>
|
||||
{title}
|
||||
</h3>
|
||||
<p className={css({ fontSize: 'sm', color: 'gray.400', mb: '4' })}>{description}</p>
|
||||
<div
|
||||
className={css({
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
p: '4',
|
||||
bg: 'rgba(255, 255, 255, 0.05)',
|
||||
rounded: 'lg',
|
||||
})}
|
||||
>
|
||||
<AbacusReact
|
||||
value={value}
|
||||
columns={3}
|
||||
beadShape={beadShape}
|
||||
colorScheme={colorScheme}
|
||||
hideInactiveBeads={false}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function GameCard({
|
||||
icon,
|
||||
title,
|
||||
@@ -400,19 +758,17 @@ function GameCard({
|
||||
function FeaturePanel({
|
||||
icon,
|
||||
title,
|
||||
description,
|
||||
features,
|
||||
accentColor,
|
||||
ctaText,
|
||||
ctaHref,
|
||||
accentColor,
|
||||
}: {
|
||||
icon: string
|
||||
title: string
|
||||
description: string
|
||||
features: string[]
|
||||
ctaText: string
|
||||
ctaHref: string
|
||||
accentColor: 'purple' | 'blue'
|
||||
ctaText?: string
|
||||
ctaHref?: string
|
||||
}) {
|
||||
const borderColor = accentColor === 'purple' ? 'purple.500/30' : 'blue.500/30'
|
||||
const bgColor = accentColor === 'purple' ? 'purple.500/10' : 'blue.500/10'
|
||||
@@ -432,8 +788,7 @@ function FeaturePanel({
|
||||
<span className={css({ fontSize: '3xl' })}>{icon}</span>
|
||||
<h2 className={css({ fontSize: '2xl', fontWeight: 'bold', color: 'white' })}>{title}</h2>
|
||||
</div>
|
||||
<p className={css({ fontSize: 'md', color: 'gray.300', mb: '6' })}>{description}</p>
|
||||
<div className={stack({ gap: '3', mb: '6' })}>
|
||||
<div className={stack({ gap: '3', mb: ctaText ? '6' : '0' })}>
|
||||
{features.map((feature, i) => (
|
||||
<div key={i} className={hstack({ gap: '3' })}>
|
||||
<span className={css({ color: 'yellow.400', fontSize: 'lg' })}>✓</span>
|
||||
@@ -441,25 +796,27 @@ function FeaturePanel({
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<Link
|
||||
href={ctaHref}
|
||||
className={css({
|
||||
display: 'block',
|
||||
textAlign: 'center',
|
||||
py: '3',
|
||||
px: '6',
|
||||
bg: bgColor,
|
||||
color: 'white',
|
||||
fontWeight: 'bold',
|
||||
rounded: 'lg',
|
||||
border: '2px solid',
|
||||
borderColor,
|
||||
_hover: { bg: hoverBg },
|
||||
transition: 'all 0.2s ease',
|
||||
})}
|
||||
>
|
||||
{ctaText}
|
||||
</Link>
|
||||
{ctaText && ctaHref && (
|
||||
<Link
|
||||
href={ctaHref}
|
||||
className={css({
|
||||
display: 'block',
|
||||
textAlign: 'center',
|
||||
py: '3',
|
||||
px: '6',
|
||||
bg: bgColor,
|
||||
color: 'white',
|
||||
fontWeight: 'bold',
|
||||
rounded: 'lg',
|
||||
border: '2px solid',
|
||||
borderColor,
|
||||
_hover: { bg: hoverBg },
|
||||
transition: 'all 0.2s ease',
|
||||
})}
|
||||
>
|
||||
{ctaText}
|
||||
</Link>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -215,6 +215,11 @@ interface TutorialPlayerProps {
|
||||
initialStepIndex?: number
|
||||
isDebugMode?: boolean
|
||||
showDebugPanel?: boolean
|
||||
hideNavigation?: boolean
|
||||
hideTooltip?: boolean
|
||||
silentErrors?: 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
|
||||
@@ -227,6 +232,11 @@ function TutorialPlayerContent({
|
||||
initialStepIndex = 0,
|
||||
isDebugMode = false,
|
||||
showDebugPanel = false,
|
||||
hideNavigation = false,
|
||||
hideTooltip = false,
|
||||
silentErrors = false,
|
||||
abacusColumns = 5,
|
||||
theme = 'light',
|
||||
onStepChange,
|
||||
onStepComplete,
|
||||
onTutorialComplete,
|
||||
@@ -379,16 +389,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
|
||||
@@ -399,6 +413,7 @@ function TutorialPlayerContent({
|
||||
expectedSteps,
|
||||
currentMultiStep,
|
||||
currentStep.stepBeadHighlights,
|
||||
abacusColumns,
|
||||
])
|
||||
|
||||
// Get the current step's bead diff summary for real-time user feedback
|
||||
@@ -422,6 +437,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
|
||||
@@ -519,16 +542,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
|
||||
@@ -544,7 +578,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) {
|
||||
@@ -606,7 +640,7 @@ function TutorialPlayerContent({
|
||||
maxWidth: '200px',
|
||||
minWidth: '150px',
|
||||
wordBreak: 'break-word',
|
||||
zIndex: 1000,
|
||||
zIndex: 50,
|
||||
opacity: 0.95,
|
||||
transition: 'all 0.3s ease',
|
||||
transform: showCelebration ? 'scale(1.05)' : 'scale(1)',
|
||||
@@ -670,6 +704,7 @@ function TutorialPlayerContent({
|
||||
currentValue,
|
||||
currentStep,
|
||||
isMeaningfulDecomposition,
|
||||
abacusColumns,
|
||||
])
|
||||
|
||||
// Timer for smart help detection
|
||||
@@ -864,8 +899,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
|
||||
|
||||
@@ -876,10 +911,11 @@ function TutorialPlayerContent({
|
||||
)
|
||||
})
|
||||
|
||||
if (!isCorrectBead) {
|
||||
if (!isCorrectBead && !silentErrors) {
|
||||
const errorMessage = "That's not the highlighted bead. Try clicking the highlighted bead."
|
||||
dispatch({
|
||||
type: 'SET_ERROR',
|
||||
error: currentStep.errorMessages.wrongBead,
|
||||
error: errorMessage,
|
||||
})
|
||||
|
||||
dispatch({
|
||||
@@ -887,7 +923,7 @@ function TutorialPlayerContent({
|
||||
event: {
|
||||
type: 'ERROR_OCCURRED',
|
||||
stepId: currentStep.id,
|
||||
error: currentStep.errorMessages.wrongBead,
|
||||
error: errorMessage,
|
||||
timestamp: new Date(),
|
||||
},
|
||||
})
|
||||
@@ -1003,14 +1039,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]) {
|
||||
@@ -1041,6 +1084,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] = {}
|
||||
}
|
||||
@@ -1048,8 +1097,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>
|
||||
@@ -1061,183 +1134,187 @@ function TutorialPlayerContent({
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
height: '100%',
|
||||
minHeight: '600px',
|
||||
minHeight: hideNavigation ? 'auto' : '600px',
|
||||
})} ${className || ''}`}
|
||||
>
|
||||
{/* Header */}
|
||||
<div
|
||||
className={css({
|
||||
borderBottom: '1px solid',
|
||||
borderColor: 'gray.200',
|
||||
p: 4,
|
||||
bg: 'white',
|
||||
})}
|
||||
>
|
||||
{!hideNavigation && (
|
||||
<div
|
||||
className={hstack({
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
className={css({
|
||||
borderBottom: '1px solid',
|
||||
borderColor: theme === 'dark' ? 'rgba(255, 255, 255, 0.1)' : 'gray.200',
|
||||
p: 4,
|
||||
bg: theme === 'dark' ? 'rgba(30, 30, 40, 0.6)' : 'white',
|
||||
})}
|
||||
>
|
||||
<div>
|
||||
<h1 className={css({ fontSize: 'xl', fontWeight: 'bold' })}>{tutorial.title}</h1>
|
||||
<p className={css({ fontSize: 'sm', color: 'gray.600' })}>
|
||||
Step {currentStepIndex + 1} of {tutorial.steps.length}: {currentStep.title}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className={hstack({ gap: 2 })}>
|
||||
{isDebugMode && (
|
||||
<>
|
||||
<button
|
||||
onClick={toggleDebugPanel}
|
||||
className={css({
|
||||
px: 3,
|
||||
py: 1,
|
||||
fontSize: 'sm',
|
||||
border: '1px solid',
|
||||
borderColor: 'blue.300',
|
||||
borderRadius: 'md',
|
||||
bg: uiState.showDebugPanel ? 'blue.100' : 'white',
|
||||
color: 'blue.700',
|
||||
cursor: 'pointer',
|
||||
_hover: { bg: 'blue.50' },
|
||||
})}
|
||||
>
|
||||
Debug
|
||||
</button>
|
||||
<button
|
||||
onClick={toggleStepList}
|
||||
className={css({
|
||||
px: 3,
|
||||
py: 1,
|
||||
fontSize: 'sm',
|
||||
border: '1px solid',
|
||||
borderColor: 'gray.300',
|
||||
borderRadius: 'md',
|
||||
bg: uiState.showStepList ? 'gray.100' : 'white',
|
||||
cursor: 'pointer',
|
||||
_hover: { bg: 'gray.50' },
|
||||
})}
|
||||
>
|
||||
Steps
|
||||
</button>
|
||||
|
||||
{/* Multi-step navigation controls */}
|
||||
{currentStep.multiStepInstructions &&
|
||||
currentStep.multiStepInstructions.length > 1 && (
|
||||
<>
|
||||
<div
|
||||
className={css({
|
||||
fontSize: 'xs',
|
||||
color: 'gray.600',
|
||||
px: 2,
|
||||
borderLeft: '1px solid',
|
||||
borderColor: 'gray.300',
|
||||
ml: 2,
|
||||
pl: 3,
|
||||
})}
|
||||
>
|
||||
Multi-Step: {currentMultiStep + 1} /{' '}
|
||||
{currentStep.multiStepInstructions.length}
|
||||
</div>
|
||||
<button
|
||||
onClick={() => dispatch({ type: 'RESET_MULTI_STEP' })}
|
||||
disabled={currentMultiStep === 0}
|
||||
className={css({
|
||||
px: 2,
|
||||
py: 1,
|
||||
fontSize: 'xs',
|
||||
border: '1px solid',
|
||||
borderColor: currentMultiStep === 0 ? 'gray.200' : 'orange.300',
|
||||
borderRadius: 'md',
|
||||
bg: currentMultiStep === 0 ? 'gray.100' : 'white',
|
||||
color: currentMultiStep === 0 ? 'gray.400' : 'orange.700',
|
||||
cursor: currentMultiStep === 0 ? 'not-allowed' : 'pointer',
|
||||
_hover: currentMultiStep === 0 ? {} : { bg: 'orange.50' },
|
||||
})}
|
||||
>
|
||||
⏮ First
|
||||
</button>
|
||||
<button
|
||||
onClick={() => previousMultiStep()}
|
||||
disabled={currentMultiStep === 0}
|
||||
className={css({
|
||||
px: 2,
|
||||
py: 1,
|
||||
fontSize: 'xs',
|
||||
border: '1px solid',
|
||||
borderColor: currentMultiStep === 0 ? 'gray.200' : 'orange.300',
|
||||
borderRadius: 'md',
|
||||
bg: currentMultiStep === 0 ? 'gray.100' : 'white',
|
||||
color: currentMultiStep === 0 ? 'gray.400' : 'orange.700',
|
||||
cursor: currentMultiStep === 0 ? 'not-allowed' : 'pointer',
|
||||
_hover: currentMultiStep === 0 ? {} : { bg: 'orange.50' },
|
||||
})}
|
||||
>
|
||||
⏪ Prev
|
||||
</button>
|
||||
<button
|
||||
onClick={() => advanceMultiStep()}
|
||||
disabled={currentMultiStep >= currentStep.multiStepInstructions.length - 1}
|
||||
className={css({
|
||||
px: 2,
|
||||
py: 1,
|
||||
fontSize: 'xs',
|
||||
border: '1px solid',
|
||||
borderColor:
|
||||
currentMultiStep >= currentStep.multiStepInstructions.length - 1
|
||||
? 'gray.200'
|
||||
: 'green.300',
|
||||
borderRadius: 'md',
|
||||
bg:
|
||||
currentMultiStep >= currentStep.multiStepInstructions.length - 1
|
||||
? 'gray.100'
|
||||
: 'white',
|
||||
color:
|
||||
currentMultiStep >= currentStep.multiStepInstructions.length - 1
|
||||
? 'gray.400'
|
||||
: 'green.700',
|
||||
cursor:
|
||||
currentMultiStep >= currentStep.multiStepInstructions.length - 1
|
||||
? 'not-allowed'
|
||||
: 'pointer',
|
||||
_hover:
|
||||
currentMultiStep >= currentStep.multiStepInstructions.length - 1
|
||||
? {}
|
||||
: { bg: 'green.50' },
|
||||
})}
|
||||
>
|
||||
Next ⏩
|
||||
</button>
|
||||
</>
|
||||
)}
|
||||
<label className={hstack({ gap: 2, fontSize: 'sm' })}>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={uiState.autoAdvance}
|
||||
onChange={toggleAutoAdvance}
|
||||
/>
|
||||
Auto-advance
|
||||
</label>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Progress bar */}
|
||||
<div className={css({ mt: 2, bg: 'gray.200', borderRadius: 'full', h: 2 })}>
|
||||
<div
|
||||
className={css({
|
||||
bg: 'blue.500',
|
||||
h: 'full',
|
||||
borderRadius: 'full',
|
||||
transition: 'width 0.3s ease',
|
||||
className={hstack({
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
})}
|
||||
style={{ width: `${navigationState.completionPercentage}%` }}
|
||||
/>
|
||||
>
|
||||
<div>
|
||||
<h1 className={css({ fontSize: 'xl', fontWeight: 'bold' })}>{tutorial.title}</h1>
|
||||
<p className={css({ fontSize: 'sm', color: 'gray.600' })}>
|
||||
Step {currentStepIndex + 1} of {tutorial.steps.length}: {currentStep.title}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className={hstack({ gap: 2 })}>
|
||||
{isDebugMode && (
|
||||
<>
|
||||
<button
|
||||
onClick={toggleDebugPanel}
|
||||
className={css({
|
||||
px: 3,
|
||||
py: 1,
|
||||
fontSize: 'sm',
|
||||
border: '1px solid',
|
||||
borderColor: 'blue.300',
|
||||
borderRadius: 'md',
|
||||
bg: uiState.showDebugPanel ? 'blue.100' : 'white',
|
||||
color: 'blue.700',
|
||||
cursor: 'pointer',
|
||||
_hover: { bg: 'blue.50' },
|
||||
})}
|
||||
>
|
||||
Debug
|
||||
</button>
|
||||
<button
|
||||
onClick={toggleStepList}
|
||||
className={css({
|
||||
px: 3,
|
||||
py: 1,
|
||||
fontSize: 'sm',
|
||||
border: '1px solid',
|
||||
borderColor: 'gray.300',
|
||||
borderRadius: 'md',
|
||||
bg: uiState.showStepList ? 'gray.100' : 'white',
|
||||
cursor: 'pointer',
|
||||
_hover: { bg: 'gray.50' },
|
||||
})}
|
||||
>
|
||||
Steps
|
||||
</button>
|
||||
|
||||
{/* Multi-step navigation controls */}
|
||||
{currentStep.multiStepInstructions &&
|
||||
currentStep.multiStepInstructions.length > 1 && (
|
||||
<>
|
||||
<div
|
||||
className={css({
|
||||
fontSize: 'xs',
|
||||
color: 'gray.600',
|
||||
px: 2,
|
||||
borderLeft: '1px solid',
|
||||
borderColor: 'gray.300',
|
||||
ml: 2,
|
||||
pl: 3,
|
||||
})}
|
||||
>
|
||||
Multi-Step: {currentMultiStep + 1} /{' '}
|
||||
{currentStep.multiStepInstructions.length}
|
||||
</div>
|
||||
<button
|
||||
onClick={() => dispatch({ type: 'RESET_MULTI_STEP' })}
|
||||
disabled={currentMultiStep === 0}
|
||||
className={css({
|
||||
px: 2,
|
||||
py: 1,
|
||||
fontSize: 'xs',
|
||||
border: '1px solid',
|
||||
borderColor: currentMultiStep === 0 ? 'gray.200' : 'orange.300',
|
||||
borderRadius: 'md',
|
||||
bg: currentMultiStep === 0 ? 'gray.100' : 'white',
|
||||
color: currentMultiStep === 0 ? 'gray.400' : 'orange.700',
|
||||
cursor: currentMultiStep === 0 ? 'not-allowed' : 'pointer',
|
||||
_hover: currentMultiStep === 0 ? {} : { bg: 'orange.50' },
|
||||
})}
|
||||
>
|
||||
⏮ First
|
||||
</button>
|
||||
<button
|
||||
onClick={() => previousMultiStep()}
|
||||
disabled={currentMultiStep === 0}
|
||||
className={css({
|
||||
px: 2,
|
||||
py: 1,
|
||||
fontSize: 'xs',
|
||||
border: '1px solid',
|
||||
borderColor: currentMultiStep === 0 ? 'gray.200' : 'orange.300',
|
||||
borderRadius: 'md',
|
||||
bg: currentMultiStep === 0 ? 'gray.100' : 'white',
|
||||
color: currentMultiStep === 0 ? 'gray.400' : 'orange.700',
|
||||
cursor: currentMultiStep === 0 ? 'not-allowed' : 'pointer',
|
||||
_hover: currentMultiStep === 0 ? {} : { bg: 'orange.50' },
|
||||
})}
|
||||
>
|
||||
⏪ Prev
|
||||
</button>
|
||||
<button
|
||||
onClick={() => advanceMultiStep()}
|
||||
disabled={
|
||||
currentMultiStep >= currentStep.multiStepInstructions.length - 1
|
||||
}
|
||||
className={css({
|
||||
px: 2,
|
||||
py: 1,
|
||||
fontSize: 'xs',
|
||||
border: '1px solid',
|
||||
borderColor:
|
||||
currentMultiStep >= currentStep.multiStepInstructions.length - 1
|
||||
? 'gray.200'
|
||||
: 'green.300',
|
||||
borderRadius: 'md',
|
||||
bg:
|
||||
currentMultiStep >= currentStep.multiStepInstructions.length - 1
|
||||
? 'gray.100'
|
||||
: 'white',
|
||||
color:
|
||||
currentMultiStep >= currentStep.multiStepInstructions.length - 1
|
||||
? 'gray.400'
|
||||
: 'green.700',
|
||||
cursor:
|
||||
currentMultiStep >= currentStep.multiStepInstructions.length - 1
|
||||
? 'not-allowed'
|
||||
: 'pointer',
|
||||
_hover:
|
||||
currentMultiStep >= currentStep.multiStepInstructions.length - 1
|
||||
? {}
|
||||
: { bg: 'green.50' },
|
||||
})}
|
||||
>
|
||||
Next ⏩
|
||||
</button>
|
||||
</>
|
||||
)}
|
||||
<label className={hstack({ gap: 2, fontSize: 'sm' })}>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={uiState.autoAdvance}
|
||||
onChange={toggleAutoAdvance}
|
||||
/>
|
||||
Auto-advance
|
||||
</label>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Progress bar */}
|
||||
<div className={css({ mt: 2, bg: 'gray.200', borderRadius: 'full', h: 2 })}>
|
||||
<div
|
||||
className={css({
|
||||
bg: 'blue.500',
|
||||
h: 'full',
|
||||
borderRadius: 'full',
|
||||
transition: 'width 0.3s ease',
|
||||
})}
|
||||
style={{ width: `${navigationState.completionPercentage}%` }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className={hstack({ flex: 1, gap: 0 })}>
|
||||
{/* Step list sidebar */}
|
||||
@@ -1313,11 +1390,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 */}
|
||||
@@ -1329,18 +1413,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',
|
||||
@@ -1350,7 +1442,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,
|
||||
},
|
||||
})}
|
||||
@@ -1359,10 +1453,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
|
||||
@@ -1375,18 +1469,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)',
|
||||
})}
|
||||
>
|
||||
<p
|
||||
<div
|
||||
className={css({
|
||||
fontSize: 'base',
|
||||
color: 'slate.800',
|
||||
color: theme === 'dark' ? 'gray.300' : 'slate.800',
|
||||
fontFamily: 'mono',
|
||||
fontWeight: '500',
|
||||
letterSpacing: 'tight',
|
||||
@@ -1398,14 +1499,14 @@ function TutorialPlayerContent({
|
||||
termPositions={termPositions}
|
||||
segments={pedagogicalSegments}
|
||||
/>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div
|
||||
className={css({
|
||||
fontSize: 'sm',
|
||||
color: 'amber.800',
|
||||
color: theme === 'dark' ? 'gray.400' : 'amber.800',
|
||||
fontWeight: '500',
|
||||
lineHeight: '1.6',
|
||||
})}
|
||||
@@ -1449,7 +1550,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}
|
||||
@@ -1483,17 +1587,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}
|
||||
@@ -1502,7 +1606,7 @@ function TutorialPlayerContent({
|
||||
hideInactiveBeads={abacusConfig.hideInactiveBeads}
|
||||
soundEnabled={abacusConfig.soundEnabled}
|
||||
soundVolume={abacusConfig.soundVolume}
|
||||
highlightBeads={currentStep.highlightBeads}
|
||||
highlightBeads={filteredHighlightBeads}
|
||||
stepBeadHighlights={currentStepBeads}
|
||||
currentStep={currentMultiStep}
|
||||
showDirectionIndicators={true}
|
||||
@@ -1567,7 +1671,7 @@ function TutorialPlayerContent({
|
||||
</div>
|
||||
|
||||
{/* Tooltip */}
|
||||
{currentStep.tooltip && (
|
||||
{!hideTooltip && currentStep.tooltip && (
|
||||
<div
|
||||
className={css({
|
||||
maxW: '500px',
|
||||
@@ -1596,57 +1700,60 @@ function TutorialPlayerContent({
|
||||
</div>
|
||||
|
||||
{/* Navigation controls */}
|
||||
<div
|
||||
className={css({
|
||||
borderTop: '1px solid',
|
||||
borderColor: 'gray.200',
|
||||
p: 4,
|
||||
bg: 'gray.50',
|
||||
})}
|
||||
>
|
||||
<div className={hstack({ justifyContent: 'space-between' })}>
|
||||
<button
|
||||
onClick={goToPreviousStep}
|
||||
disabled={!navigationState.canGoPrevious}
|
||||
className={css({
|
||||
px: 4,
|
||||
py: 2,
|
||||
border: '1px solid',
|
||||
borderColor: 'gray.300',
|
||||
borderRadius: 'md',
|
||||
bg: 'white',
|
||||
cursor: navigationState.canGoPrevious ? 'pointer' : 'not-allowed',
|
||||
opacity: navigationState.canGoPrevious ? 1 : 0.5,
|
||||
_hover: navigationState.canGoPrevious ? { bg: 'gray.50' } : {},
|
||||
})}
|
||||
>
|
||||
← Previous
|
||||
</button>
|
||||
{!hideNavigation && (
|
||||
<div
|
||||
className={css({
|
||||
borderTop: '1px solid',
|
||||
borderColor: 'gray.200',
|
||||
p: 4,
|
||||
bg: 'gray.50',
|
||||
})}
|
||||
>
|
||||
<div className={hstack({ justifyContent: 'space-between' })}>
|
||||
<button
|
||||
onClick={goToPreviousStep}
|
||||
disabled={!navigationState.canGoPrevious}
|
||||
className={css({
|
||||
px: 4,
|
||||
py: 2,
|
||||
border: '1px solid',
|
||||
borderColor: 'gray.300',
|
||||
borderRadius: 'md',
|
||||
bg: 'white',
|
||||
cursor: navigationState.canGoPrevious ? 'pointer' : 'not-allowed',
|
||||
opacity: navigationState.canGoPrevious ? 1 : 0.5,
|
||||
_hover: navigationState.canGoPrevious ? { bg: 'gray.50' } : {},
|
||||
})}
|
||||
>
|
||||
← Previous
|
||||
</button>
|
||||
|
||||
<div className={css({ fontSize: 'sm', color: 'gray.600' })}>
|
||||
Step {currentStepIndex + 1} of {navigationState.totalSteps}
|
||||
<div className={css({ fontSize: 'sm', color: 'gray.600' })}>
|
||||
Step {currentStepIndex + 1} of {navigationState.totalSteps}
|
||||
</div>
|
||||
|
||||
<button
|
||||
onClick={goToNextStep}
|
||||
disabled={!navigationState.canGoNext && !isStepCompleted}
|
||||
className={css({
|
||||
px: 4,
|
||||
py: 2,
|
||||
border: '1px solid',
|
||||
borderColor:
|
||||
navigationState.canGoNext || isStepCompleted ? 'blue.300' : 'gray.300',
|
||||
borderRadius: 'md',
|
||||
bg: navigationState.canGoNext || isStepCompleted ? 'blue.500' : 'gray.200',
|
||||
color: navigationState.canGoNext || isStepCompleted ? 'white' : 'gray.500',
|
||||
cursor:
|
||||
navigationState.canGoNext || isStepCompleted ? 'pointer' : 'not-allowed',
|
||||
_hover: navigationState.canGoNext || isStepCompleted ? { bg: 'blue.600' } : {},
|
||||
})}
|
||||
>
|
||||
{navigationState.canGoNext ? 'Next →' : 'Complete Tutorial'}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<button
|
||||
onClick={goToNextStep}
|
||||
disabled={!navigationState.canGoNext && !isStepCompleted}
|
||||
className={css({
|
||||
px: 4,
|
||||
py: 2,
|
||||
border: '1px solid',
|
||||
borderColor:
|
||||
navigationState.canGoNext || isStepCompleted ? 'blue.300' : 'gray.300',
|
||||
borderRadius: 'md',
|
||||
bg: navigationState.canGoNext || isStepCompleted ? 'blue.500' : 'gray.200',
|
||||
color: navigationState.canGoNext || isStepCompleted ? 'white' : 'gray.500',
|
||||
cursor: navigationState.canGoNext || isStepCompleted ? 'pointer' : 'not-allowed',
|
||||
_hover: navigationState.canGoNext || isStepCompleted ? { bg: 'blue.600' } : {},
|
||||
})}
|
||||
>
|
||||
{navigationState.canGoNext ? 'Next →' : 'Complete Tutorial'}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Debug panel */}
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "soroban-monorepo",
|
||||
"version": "4.14.6",
|
||||
"version": "4.24.1",
|
||||
"private": true,
|
||||
"description": "Beautiful Soroban Flashcard Generator - Monorepo",
|
||||
"workspaces": [
|
||||
|
||||
@@ -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 */}
|
||||
|
||||
Reference in New Issue
Block a user