Compare commits
12 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
be68af0d56 | ||
|
|
2f09cb5539 | ||
|
|
aa6cea07df | ||
|
|
ebe123ed7e | ||
|
|
9afd3a7e92 | ||
|
|
efb9c37380 | ||
|
|
c00cfa3de0 | ||
|
|
da53e084f0 | ||
|
|
22df1b0b66 | ||
|
|
c0680cad0f | ||
|
|
0fef1dc9db | ||
|
|
c92ff3971c |
41
CHANGELOG.md
41
CHANGELOG.md
@@ -1,3 +1,44 @@
|
||||
## [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)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* replace native alerts with inline confirmations in ModerationPanel ([ebe123e](https://github.com/antialias/soroban-abacus-flashcards/commit/ebe123ed7edf24fbc7b8765ed709455a8513d6d5))
|
||||
|
||||
|
||||
### Documentation
|
||||
|
||||
* add UI style guide documenting no native alerts rule ([9afd3a7](https://github.com/antialias/soroban-abacus-flashcards/commit/9afd3a7e925fddb76fa587747881b61f7cb077a5))
|
||||
|
||||
## [4.14.5](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.14.4...v4.14.5) (2025-10-19)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **rooms:** add real-time ownership transfer updates via WebSocket ([c00cfa3](https://github.com/antialias/soroban-abacus-flashcards/commit/c00cfa3de011720f3399fa340182b347f7e0d456))
|
||||
|
||||
## [4.14.4](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.14.3...v4.14.4) (2025-10-19)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **arcade:** add host-only game selection with clear messaging ([22df1b0](https://github.com/antialias/soroban-abacus-flashcards/commit/22df1b0b661efe69fac1a6bd716531c904757412))
|
||||
* **arcade:** add host-only game selection with clear messaging ([c0680ca](https://github.com/antialias/soroban-abacus-flashcards/commit/c0680cad0fa26af0933e93a06c50317bf443cc7d))
|
||||
|
||||
## [4.14.3](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.14.2...v4.14.3) (2025-10-19)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **docker:** add qpdf for PDF linearization and validation ([c92ff39](https://github.com/antialias/soroban-abacus-flashcards/commit/c92ff3971c853e4e55ccd632ff3ee292fcce8315))
|
||||
|
||||
## [4.14.2](https://github.com/antialias/soroban-abacus-flashcards/compare/v4.14.1...v4.14.2) (2025-10-19)
|
||||
|
||||
|
||||
|
||||
@@ -48,8 +48,8 @@ RUN turbo build --filter=@soroban/web
|
||||
FROM node:18-alpine AS runner
|
||||
WORKDIR /app
|
||||
|
||||
# Install Python, pip, build tools for better-sqlite3, and Typst (needed at runtime)
|
||||
RUN apk add --no-cache python3 py3-pip py3-setuptools make g++ typst
|
||||
# Install Python, pip, build tools for better-sqlite3, Typst, and qpdf (needed at runtime)
|
||||
RUN apk add --no-cache python3 py3-pip py3-setuptools make g++ typst qpdf
|
||||
|
||||
# Create non-root user
|
||||
RUN addgroup --system --gid 1001 nodejs
|
||||
|
||||
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.
|
||||
94
apps/web/.claude/UI_STYLE_GUIDE.md
Normal file
94
apps/web/.claude/UI_STYLE_GUIDE.md
Normal file
@@ -0,0 +1,94 @@
|
||||
# UI Style Guide
|
||||
|
||||
## Confirmations and Dialogs
|
||||
|
||||
**NEVER use native browser dialogs:**
|
||||
- ❌ `alert()`
|
||||
- ❌ `confirm()`
|
||||
- ❌ `prompt()`
|
||||
|
||||
**ALWAYS use inline React-based confirmations:**
|
||||
- Show confirmation UI in-place using React state
|
||||
- Provide Cancel and Confirm buttons
|
||||
- Use descriptive warning messages with appropriate emoji (⚠️)
|
||||
- Follow the Panda CSS styling system
|
||||
- Match the visual style of the surrounding UI
|
||||
|
||||
### Pattern: Inline Confirmation
|
||||
|
||||
```typescript
|
||||
const [confirming, setConfirming] = useState(false)
|
||||
|
||||
{!confirming ? (
|
||||
<button onClick={() => setConfirming(true)}>
|
||||
Delete Item
|
||||
</button>
|
||||
) : (
|
||||
<div>
|
||||
<div style={{ /* warning styling */ }}>
|
||||
⚠️ Are you sure you want to delete this item?
|
||||
</div>
|
||||
<div style={{ /* description styling */ }}>
|
||||
This action cannot be undone.
|
||||
</div>
|
||||
<div style={{ display: 'flex', gap: '8px' }}>
|
||||
<button onClick={() => setConfirming(false)}>
|
||||
Cancel
|
||||
</button>
|
||||
<button onClick={handleDelete}>
|
||||
Confirm Delete
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
```
|
||||
|
||||
### Real Examples
|
||||
|
||||
See `/src/components/nav/ModerationPanel.tsx` for production examples:
|
||||
- Transfer ownership confirmation (lines 1793-1929)
|
||||
- Unban user confirmation (shows inline warning with Cancel/Confirm)
|
||||
|
||||
### Why This Pattern?
|
||||
|
||||
1. **Consistency**: Native dialogs look different across browsers and platforms
|
||||
2. **Control**: We can style, position, and enhance confirmations to match our design
|
||||
3. **Accessibility**: We can add proper ARIA attributes and keyboard navigation
|
||||
4. **UX**: Users stay in context rather than being interrupted by modal dialogs
|
||||
5. **Testing**: Inline confirmations are easier to test than native browser dialogs
|
||||
|
||||
### Migration Checklist
|
||||
|
||||
When replacing native dialogs:
|
||||
- [ ] Add state variable for confirmation (e.g., `const [confirming, setConfirming] = useState(false)`)
|
||||
- [ ] Remove the `confirm()` or `alert()` call from the handler
|
||||
- [ ] Replace the original UI with conditional rendering
|
||||
- [ ] Show initial state with primary action button
|
||||
- [ ] Show confirmation state with warning message + Cancel/Confirm buttons
|
||||
- [ ] Ensure Cancel button resets state: `onClick={() => setConfirming(false)}`
|
||||
- [ ] Ensure Confirm button performs action and resets state
|
||||
- [ ] Add loading states if the action is async
|
||||
- [ ] Style to match surrounding UI using Panda CSS
|
||||
|
||||
## Styling System
|
||||
|
||||
This project uses **Panda CSS**, not Tailwind CSS.
|
||||
|
||||
- ❌ Never use Tailwind utility classes (e.g., `className="bg-blue-500"`)
|
||||
- ✅ Always use Panda CSS `css()` function
|
||||
- ✅ Use Panda's token system (defined in `panda.config.ts`)
|
||||
|
||||
See `.claude/CLAUDE.md` for complete Panda CSS documentation.
|
||||
|
||||
## Emoji Usage
|
||||
|
||||
Emojis are used liberally throughout the UI for visual communication:
|
||||
- 👑 Host/owner status
|
||||
- ⏳ Waiting states
|
||||
- ⚠️ Warnings and confirmations
|
||||
- ✅ Success states
|
||||
- ❌ Error states
|
||||
- 👀 Spectating mode
|
||||
- 🎮 Gaming context
|
||||
|
||||
Use emojis to enhance clarity, not replace text.
|
||||
@@ -101,7 +101,8 @@
|
||||
"WebFetch(domain:abaci.one)",
|
||||
"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 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\\\"\"}}\"\"')"
|
||||
],
|
||||
"deny": [],
|
||||
"ask": []
|
||||
|
||||
@@ -24,7 +24,8 @@ function getBuildInfo() {
|
||||
const gitCommitShort = process.env.GIT_COMMIT_SHORT || exec('git rev-parse --short HEAD')
|
||||
const gitBranch = process.env.GIT_BRANCH || exec('git rev-parse --abbrev-ref HEAD')
|
||||
const gitTag = process.env.GIT_TAG || exec('git describe --tags --exact-match 2>/dev/null')
|
||||
const gitDirty = process.env.GIT_DIRTY === 'true' || exec('git diff --quiet || echo "dirty"') === 'dirty'
|
||||
const gitDirty =
|
||||
process.env.GIT_DIRTY === 'true' || exec('git diff --quiet || echo "dirty"') === 'dirty'
|
||||
|
||||
const packageJson = require('../package.json')
|
||||
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
'use client'
|
||||
|
||||
import { useRouter } from 'next/navigation'
|
||||
import { useState } from 'react'
|
||||
import { useRoomData, useSetRoomGame } from '@/hooks/useRoomData'
|
||||
import { useViewerId } from '@/hooks/useViewerId'
|
||||
import { GAMES_CONFIG } from '@/components/GameSelector'
|
||||
import type { GameType } from '@/components/GameSelector'
|
||||
import { PageWithNav } from '@/components/PageWithNav'
|
||||
@@ -23,7 +25,9 @@ import { getAllGames, getGame, hasGame } from '@/lib/arcade/game-registry'
|
||||
export default function RoomPage() {
|
||||
const router = useRouter()
|
||||
const { roomData, isLoading } = useRoomData()
|
||||
const { data: viewerId } = useViewerId()
|
||||
const { mutate: setRoomGame } = useSetRoomGame()
|
||||
const [permissionError, setPermissionError] = useState<string | null>(null)
|
||||
|
||||
// Show loading state
|
||||
if (isLoading) {
|
||||
@@ -74,9 +78,27 @@ export default function RoomPage() {
|
||||
|
||||
// Show game selection if no game is set
|
||||
if (!roomData.gameName) {
|
||||
// Determine if current user is the host
|
||||
const currentMember = roomData.members.find((m) => m.userId === viewerId)
|
||||
const isHost = currentMember?.isCreator === true
|
||||
const hostMember = roomData.members.find((m) => m.isCreator)
|
||||
|
||||
const handleGameSelect = (gameType: GameType) => {
|
||||
console.log('[RoomPage] handleGameSelect called with gameType:', gameType)
|
||||
|
||||
// Check if user is host before allowing selection
|
||||
if (!isHost) {
|
||||
setPermissionError(
|
||||
`Only the room host can select a game. Ask ${hostMember?.displayName || 'the host'} to choose.`
|
||||
)
|
||||
// Clear error after 5 seconds
|
||||
setTimeout(() => setPermissionError(null), 5000)
|
||||
return
|
||||
}
|
||||
|
||||
// Clear any previous errors
|
||||
setPermissionError(null)
|
||||
|
||||
// All games are now in the registry
|
||||
if (hasGame(gameType)) {
|
||||
const gameDef = getGame(gameType)
|
||||
@@ -86,10 +108,21 @@ export default function RoomPage() {
|
||||
}
|
||||
|
||||
console.log('[RoomPage] Selecting registry game:', gameType)
|
||||
setRoomGame({
|
||||
roomId: roomData.id,
|
||||
gameName: gameType,
|
||||
})
|
||||
setRoomGame(
|
||||
{
|
||||
roomId: roomData.id,
|
||||
gameName: gameType,
|
||||
},
|
||||
{
|
||||
onError: (error: any) => {
|
||||
console.error('[RoomPage] Failed to set game:', error)
|
||||
setPermissionError(
|
||||
error.message || 'Failed to select game. Only the host can change games.'
|
||||
)
|
||||
setTimeout(() => setPermissionError(null), 5000)
|
||||
},
|
||||
}
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -119,13 +152,70 @@ export default function RoomPage() {
|
||||
fontSize: { base: '2xl', md: '3xl' },
|
||||
fontWeight: 'bold',
|
||||
color: 'white',
|
||||
mb: '8',
|
||||
mb: '4',
|
||||
textAlign: 'center',
|
||||
})}
|
||||
>
|
||||
Choose a Game
|
||||
</h1>
|
||||
|
||||
{/* Host info and permission messaging */}
|
||||
<div
|
||||
className={css({
|
||||
maxWidth: '800px',
|
||||
width: '100%',
|
||||
mb: '6',
|
||||
})}
|
||||
>
|
||||
{isHost ? (
|
||||
<div
|
||||
className={css({
|
||||
background: 'rgba(34, 197, 94, 0.1)',
|
||||
border: '1px solid rgba(34, 197, 94, 0.3)',
|
||||
borderRadius: '8px',
|
||||
padding: '12px 16px',
|
||||
color: '#86efac',
|
||||
fontSize: 'sm',
|
||||
textAlign: 'center',
|
||||
})}
|
||||
>
|
||||
👑 You're the room host. Select a game to start playing.
|
||||
</div>
|
||||
) : (
|
||||
<div
|
||||
className={css({
|
||||
background: 'rgba(234, 179, 8, 0.1)',
|
||||
border: '1px solid rgba(234, 179, 8, 0.3)',
|
||||
borderRadius: '8px',
|
||||
padding: '12px 16px',
|
||||
color: '#fde047',
|
||||
fontSize: 'sm',
|
||||
textAlign: 'center',
|
||||
})}
|
||||
>
|
||||
⏳ Waiting for {hostMember?.displayName || 'the host'} to select a game...
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Permission error message */}
|
||||
{permissionError && (
|
||||
<div
|
||||
className={css({
|
||||
background: 'rgba(239, 68, 68, 0.1)',
|
||||
border: '1px solid rgba(239, 68, 68, 0.3)',
|
||||
borderRadius: '8px',
|
||||
padding: '12px 16px',
|
||||
color: '#fca5a5',
|
||||
fontSize: 'sm',
|
||||
textAlign: 'center',
|
||||
mt: '3',
|
||||
})}
|
||||
>
|
||||
⚠️ {permissionError}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div
|
||||
className={css({
|
||||
display: 'grid',
|
||||
@@ -138,21 +228,22 @@ export default function RoomPage() {
|
||||
{/* Legacy games */}
|
||||
{Object.entries(GAMES_CONFIG).map(([gameType, config]: [string, any]) => {
|
||||
const isAvailable = !('available' in config) || config.available !== false
|
||||
const isDisabled = !isHost || !isAvailable
|
||||
return (
|
||||
<button
|
||||
key={gameType}
|
||||
onClick={() => handleGameSelect(gameType as GameType)}
|
||||
disabled={!isAvailable}
|
||||
disabled={isDisabled}
|
||||
className={css({
|
||||
background: config.gradient,
|
||||
border: '2px solid',
|
||||
borderColor: config.borderColor || 'blue.200',
|
||||
borderRadius: '2xl',
|
||||
padding: '6',
|
||||
cursor: !isAvailable ? 'not-allowed' : 'pointer',
|
||||
opacity: !isAvailable ? 0.5 : 1,
|
||||
cursor: isDisabled ? 'not-allowed' : 'pointer',
|
||||
opacity: isDisabled ? 0.4 : 1,
|
||||
transition: 'all 0.3s ease',
|
||||
_hover: !isAvailable
|
||||
_hover: isDisabled
|
||||
? {}
|
||||
: {
|
||||
transform: 'translateY(-4px) scale(1.02)',
|
||||
@@ -193,21 +284,22 @@ export default function RoomPage() {
|
||||
{/* Registry games */}
|
||||
{getAllGames().map((gameDef) => {
|
||||
const isAvailable = gameDef.manifest.available
|
||||
const isDisabled = !isHost || !isAvailable
|
||||
return (
|
||||
<button
|
||||
key={gameDef.manifest.name}
|
||||
onClick={() => handleGameSelect(gameDef.manifest.name)}
|
||||
disabled={!isAvailable}
|
||||
disabled={isDisabled}
|
||||
className={css({
|
||||
background: gameDef.manifest.gradient,
|
||||
border: '2px solid',
|
||||
borderColor: gameDef.manifest.borderColor,
|
||||
borderRadius: '2xl',
|
||||
padding: '6',
|
||||
cursor: !isAvailable ? 'not-allowed' : 'pointer',
|
||||
opacity: !isAvailable ? 0.5 : 1,
|
||||
cursor: isDisabled ? 'not-allowed' : 'pointer',
|
||||
opacity: isDisabled ? 0.4 : 1,
|
||||
transition: 'all 0.3s ease',
|
||||
_hover: !isAvailable
|
||||
_hover: isDisabled
|
||||
? {}
|
||||
: {
|
||||
transform: 'translateY(-4px) scale(1.02)',
|
||||
|
||||
@@ -1,29 +1,28 @@
|
||||
'use client'
|
||||
|
||||
import { useState } from 'react'
|
||||
import Link from 'next/link'
|
||||
import { PageWithNav } from '@/components/PageWithNav'
|
||||
import { AbacusReact, useAbacusConfig } from '@soroban/abacus-react'
|
||||
import { FriendsOfFiveDemo } from '@/components/FriendsOfFiveDemo'
|
||||
import { css } from '../../styled-system/css'
|
||||
import { container, grid, hstack, stack } from '../../styled-system/patterns'
|
||||
|
||||
export default function HomePage() {
|
||||
const [abacusValue, setAbacusValue] = useState(1234567)
|
||||
const appConfig = useAbacusConfig()
|
||||
|
||||
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 +33,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 +148,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 +169,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 +178,291 @@ 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>
|
||||
|
||||
<div className={grid({ columns: { base: 1, lg: 2 }, gap: '8' })}>
|
||||
{/* Live demo */}
|
||||
<FriendsOfFiveDemo />
|
||||
|
||||
{/* What you'll learn */}
|
||||
<div
|
||||
className={stack({
|
||||
gap: '4',
|
||||
bg: 'rgba(0, 0, 0, 0.4)',
|
||||
p: '6',
|
||||
borderRadius: 'xl',
|
||||
border: '1px solid',
|
||||
borderColor: 'gray.700',
|
||||
justifyContent: 'center',
|
||||
})}
|
||||
>
|
||||
<h3 className={css({ fontSize: 'xl', fontWeight: 'bold', color: 'white' })}>
|
||||
What You'll Learn
|
||||
</h3>
|
||||
<div className={stack({ gap: '3' })}>
|
||||
{[
|
||||
'Read and set numbers on an abacus',
|
||||
'Add and subtract with "friends" techniques',
|
||||
'Multiply and divide fluently',
|
||||
'Calculate mentally without the abacus',
|
||||
].map((skill, i) => (
|
||||
<div key={i} className={hstack({ gap: '3' })}>
|
||||
<span className={css({ color: 'yellow.400', fontSize: 'lg' })}>✓</span>
|
||||
<span className={css({ color: 'gray.300', fontSize: 'md' })}>{skill}</span>
|
||||
</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 className={css({ color: 'gray.400', fontSize: 'md' })}>
|
||||
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: 'green.400' },
|
||||
{ level: '5 Kyu', label: 'Intermediate', color: 'blue.400' },
|
||||
{ level: '1 Kyu', label: 'Advanced', color: 'purple.400' },
|
||||
{ level: 'Dan', label: 'Master', color: 'yellow.400' },
|
||||
].map((stage, i) => (
|
||||
<div key={i} className={stack({ gap: '2', textAlign: 'center', flex: '1' })}>
|
||||
<div
|
||||
className={css({
|
||||
fontSize: 'xl',
|
||||
fontWeight: 'bold',
|
||||
color: stage.color,
|
||||
})}
|
||||
>
|
||||
{stage.level}
|
||||
</div>
|
||||
<div className={css({ fontSize: 'sm', color: 'gray.400' })}>{stage.label}</div>
|
||||
{i < 3 && (
|
||||
<div
|
||||
className={css({
|
||||
display: { base: 'none', md: 'block' },
|
||||
position: 'absolute',
|
||||
right: '-50%',
|
||||
fontSize: 'xl',
|
||||
color: 'gray.600',
|
||||
})}
|
||||
>
|
||||
→
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div
|
||||
className={css({
|
||||
mt: '6',
|
||||
textAlign: 'center',
|
||||
fontSize: 'sm',
|
||||
color: 'gray.500',
|
||||
fontStyle: 'italic',
|
||||
})}
|
||||
>
|
||||
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 +532,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 +562,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 +570,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>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -12,6 +12,8 @@ import { CardSortingProvider } from './Provider'
|
||||
import type { CardSortingConfig, CardSortingMove, CardSortingState } from './types'
|
||||
import { cardSortingValidator } from './Validator'
|
||||
|
||||
const theme = getGameTheme('teal')
|
||||
|
||||
const manifest: GameManifest = {
|
||||
name: 'card-sorting',
|
||||
displayName: 'Card Sorting Challenge',
|
||||
@@ -24,7 +26,9 @@ const manifest: GameManifest = {
|
||||
maxPlayers: 1, // Single player only
|
||||
difficulty: 'Intermediate',
|
||||
chips: ['🧠 Pattern Recognition', '🎯 Solo Challenge', '📊 Smart Scoring'],
|
||||
...getGameTheme('teal'),
|
||||
color: theme.color,
|
||||
gradient: theme.gradient,
|
||||
borderColor: theme.borderColor,
|
||||
available: true,
|
||||
}
|
||||
|
||||
|
||||
127
apps/web/src/components/FriendsOfFiveDemo.tsx
Normal file
127
apps/web/src/components/FriendsOfFiveDemo.tsx
Normal file
@@ -0,0 +1,127 @@
|
||||
'use client'
|
||||
|
||||
import { AbacusReact, useAbacusConfig } from '@soroban/abacus-react'
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
import { css } from '../../styled-system/css'
|
||||
import { stack } from '../../styled-system/patterns'
|
||||
|
||||
/**
|
||||
* Compact "Friends of 5" demo for the homepage
|
||||
* Shows an interactive example of learning soroban concepts
|
||||
*/
|
||||
export function FriendsOfFiveDemo() {
|
||||
const [currentValue, setCurrentValue] = useState(2)
|
||||
const [targetValue] = useState(5)
|
||||
const [feedback, setFeedback] = useState<string>('Try adding 3 more beads to make 5!')
|
||||
const [isCorrect, setIsCorrect] = useState(false)
|
||||
const appConfig = useAbacusConfig()
|
||||
|
||||
const handleValueChange = useCallback(
|
||||
(newValue: number) => {
|
||||
setCurrentValue(newValue)
|
||||
|
||||
if (newValue === targetValue) {
|
||||
setIsCorrect(true)
|
||||
setFeedback('Perfect! You made 5! This is the "Friends of 5" concept.')
|
||||
} else if (newValue > targetValue) {
|
||||
setFeedback('Oops! Too many beads. Try to make exactly 5.')
|
||||
} else {
|
||||
setFeedback(`Add ${targetValue - newValue} more to make 5!`)
|
||||
}
|
||||
},
|
||||
[targetValue]
|
||||
)
|
||||
|
||||
// Reset after 3 seconds when correct
|
||||
useEffect(() => {
|
||||
if (isCorrect) {
|
||||
const timeout = setTimeout(() => {
|
||||
setCurrentValue(2)
|
||||
setIsCorrect(false)
|
||||
setFeedback('Try adding 3 more beads to make 5!')
|
||||
}, 3000)
|
||||
return () => clearTimeout(timeout)
|
||||
}
|
||||
}, [isCorrect])
|
||||
|
||||
return (
|
||||
<div
|
||||
className={stack({
|
||||
gap: '4',
|
||||
bg: 'rgba(255, 255, 255, 0.05)',
|
||||
p: '6',
|
||||
borderRadius: 'xl',
|
||||
border: '1px solid rgba(139, 92, 246, 0.2)',
|
||||
})}
|
||||
>
|
||||
{/* Title */}
|
||||
<div className={css({ textAlign: 'center' })}>
|
||||
<h4
|
||||
className={css({
|
||||
fontSize: 'lg',
|
||||
fontWeight: 'bold',
|
||||
color: 'white',
|
||||
mb: '2',
|
||||
})}
|
||||
>
|
||||
Try It Now: Friends of 5
|
||||
</h4>
|
||||
<p className={css({ fontSize: 'sm', color: 'gray.300' })}>Problem: 2 + 3 = ?</p>
|
||||
</div>
|
||||
|
||||
{/* Interactive Abacus */}
|
||||
<div
|
||||
className={css({
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
bg: 'white',
|
||||
p: '4',
|
||||
borderRadius: 'lg',
|
||||
minHeight: '200px',
|
||||
alignItems: 'center',
|
||||
})}
|
||||
>
|
||||
<AbacusReact
|
||||
value={currentValue}
|
||||
columns={1}
|
||||
beadShape={appConfig.beadShape}
|
||||
colorScheme={appConfig.colorScheme}
|
||||
hideInactiveBeads={appConfig.hideInactiveBeads}
|
||||
interactive={true}
|
||||
animated={true}
|
||||
soundEnabled={true}
|
||||
soundVolume={0.3}
|
||||
scaleFactor={1.8}
|
||||
showNumbers={true}
|
||||
onValueChange={handleValueChange}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Feedback */}
|
||||
<div
|
||||
className={css({
|
||||
p: '3',
|
||||
bg: isCorrect ? 'rgba(34, 197, 94, 0.1)' : 'rgba(59, 130, 246, 0.1)',
|
||||
border: '1px solid',
|
||||
borderColor: isCorrect ? 'rgba(34, 197, 94, 0.3)' : 'rgba(59, 130, 246, 0.3)',
|
||||
borderRadius: 'md',
|
||||
textAlign: 'center',
|
||||
})}
|
||||
>
|
||||
<p
|
||||
className={css({
|
||||
fontSize: 'sm',
|
||||
color: isCorrect ? 'green.300' : 'blue.300',
|
||||
fontWeight: 'medium',
|
||||
})}
|
||||
>
|
||||
{feedback}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className={css({ textAlign: 'center', fontSize: 'xs', color: 'gray.400' })}>
|
||||
Click the beads to move them up or down
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -114,6 +114,9 @@ export function ModerationPanel({
|
||||
null
|
||||
)
|
||||
|
||||
// Transfer ownership confirmation state
|
||||
const [confirmingTransferOwnership, setConfirmingTransferOwnership] = useState(false)
|
||||
|
||||
// Auto-switch to Members tab when focusedUserId is provided
|
||||
useEffect(() => {
|
||||
if (isOpen && focusedUserId) {
|
||||
@@ -171,8 +174,6 @@ export function ModerationPanel({
|
||||
}, [isOpen, roomId, members])
|
||||
|
||||
const handleKick = async (userId: string) => {
|
||||
if (!confirm('Kick this player from the room?')) return
|
||||
|
||||
setActionLoading(`kick-${userId}`)
|
||||
try {
|
||||
const res = await fetch(`/api/arcade/rooms/${roomId}/kick`, {
|
||||
@@ -414,10 +415,9 @@ export function ModerationPanel({
|
||||
const newOwner = members.find((m) => m.userId === selectedNewOwner)
|
||||
if (!newOwner) return
|
||||
|
||||
if (!confirm(`Transfer ownership to ${newOwner.displayName}? You will no longer be the host.`))
|
||||
return
|
||||
|
||||
setConfirmingTransferOwnership(false)
|
||||
setActionLoading('transfer-ownership')
|
||||
|
||||
try {
|
||||
const res = await fetch(`/api/arcade/rooms/${roomId}/transfer-ownership`, {
|
||||
method: 'POST',
|
||||
@@ -436,6 +436,7 @@ export function ModerationPanel({
|
||||
showError(err instanceof Error ? err.message : 'Failed to transfer ownership')
|
||||
} finally {
|
||||
setActionLoading(null)
|
||||
setSelectedNewOwner('') // Reset selection
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1789,61 +1790,143 @@ export function ModerationPanel({
|
||||
Transfer host privileges to another member. You will no longer be the host.
|
||||
</p>
|
||||
|
||||
<select
|
||||
value={selectedNewOwner}
|
||||
onChange={(e) => setSelectedNewOwner(e.target.value)}
|
||||
style={{
|
||||
width: '100%',
|
||||
padding: '10px',
|
||||
background: 'rgba(255, 255, 255, 0.05)',
|
||||
border: '1px solid rgba(75, 85, 99, 0.5)',
|
||||
borderRadius: '6px',
|
||||
color: 'rgba(209, 213, 219, 1)',
|
||||
fontSize: '14px',
|
||||
marginBottom: '12px',
|
||||
cursor: 'pointer',
|
||||
}}
|
||||
>
|
||||
<option value="">Select new owner...</option>
|
||||
{otherMembers.map((member) => (
|
||||
<option key={member.userId} value={member.userId}>
|
||||
{member.displayName}
|
||||
{member.isOnline ? ' (Online)' : ' (Offline)'}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
{!confirmingTransferOwnership ? (
|
||||
<>
|
||||
<select
|
||||
value={selectedNewOwner}
|
||||
onChange={(e) => setSelectedNewOwner(e.target.value)}
|
||||
style={{
|
||||
width: '100%',
|
||||
padding: '10px',
|
||||
background: 'rgba(255, 255, 255, 0.05)',
|
||||
border: '1px solid rgba(75, 85, 99, 0.5)',
|
||||
borderRadius: '6px',
|
||||
color: 'rgba(209, 213, 219, 1)',
|
||||
fontSize: '14px',
|
||||
marginBottom: '12px',
|
||||
cursor: 'pointer',
|
||||
}}
|
||||
>
|
||||
<option value="">Select new owner...</option>
|
||||
{otherMembers.map((member) => (
|
||||
<option key={member.userId} value={member.userId}>
|
||||
{member.displayName}
|
||||
{member.isOnline ? ' (Online)' : ' (Offline)'}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleTransferOwnership}
|
||||
disabled={!selectedNewOwner || actionLoading === 'transfer-ownership'}
|
||||
style={{
|
||||
width: '100%',
|
||||
padding: '10px',
|
||||
background:
|
||||
!selectedNewOwner || actionLoading === 'transfer-ownership'
|
||||
? 'rgba(75, 85, 99, 0.3)'
|
||||
: 'linear-gradient(135deg, rgba(251, 146, 60, 0.8), rgba(249, 115, 22, 0.8))',
|
||||
color: 'white',
|
||||
border:
|
||||
!selectedNewOwner || actionLoading === 'transfer-ownership'
|
||||
? '1px solid rgba(75, 85, 99, 0.5)'
|
||||
: '1px solid rgba(251, 146, 60, 0.6)',
|
||||
borderRadius: '6px',
|
||||
fontSize: '14px',
|
||||
fontWeight: '600',
|
||||
cursor:
|
||||
!selectedNewOwner || actionLoading === 'transfer-ownership'
|
||||
? 'not-allowed'
|
||||
: 'pointer',
|
||||
opacity:
|
||||
!selectedNewOwner || actionLoading === 'transfer-ownership' ? 0.5 : 1,
|
||||
}}
|
||||
>
|
||||
{actionLoading === 'transfer-ownership'
|
||||
? 'Transferring...'
|
||||
: 'Transfer Ownership'}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setConfirmingTransferOwnership(true)}
|
||||
disabled={!selectedNewOwner}
|
||||
style={{
|
||||
width: '100%',
|
||||
padding: '10px',
|
||||
background: !selectedNewOwner
|
||||
? 'rgba(75, 85, 99, 0.3)'
|
||||
: 'linear-gradient(135deg, rgba(251, 146, 60, 0.8), rgba(249, 115, 22, 0.8))',
|
||||
color: 'white',
|
||||
border: !selectedNewOwner
|
||||
? '1px solid rgba(75, 85, 99, 0.5)'
|
||||
: '1px solid rgba(251, 146, 60, 0.6)',
|
||||
borderRadius: '6px',
|
||||
fontSize: '14px',
|
||||
fontWeight: '600',
|
||||
cursor: !selectedNewOwner ? 'not-allowed' : 'pointer',
|
||||
opacity: !selectedNewOwner ? 0.5 : 1,
|
||||
}}
|
||||
>
|
||||
Transfer Ownership
|
||||
</button>
|
||||
</>
|
||||
) : (
|
||||
<div>
|
||||
<div
|
||||
style={{
|
||||
fontSize: '13px',
|
||||
fontWeight: '600',
|
||||
color: 'rgba(251, 191, 36, 1)',
|
||||
marginBottom: '8px',
|
||||
}}
|
||||
>
|
||||
⚠️ Confirm Transfer to{' '}
|
||||
{members.find((m) => m.userId === selectedNewOwner)?.displayName}?
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
fontSize: '12px',
|
||||
color: 'rgba(209, 213, 219, 0.8)',
|
||||
marginBottom: '12px',
|
||||
}}
|
||||
>
|
||||
You will no longer be the host and will lose moderation privileges. This
|
||||
cannot be undone.
|
||||
</div>
|
||||
<div style={{ display: 'flex', gap: '8px' }}>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setConfirmingTransferOwnership(false)}
|
||||
disabled={actionLoading === 'transfer-ownership'}
|
||||
style={{
|
||||
flex: 1,
|
||||
padding: '10px',
|
||||
background: 'rgba(75, 85, 99, 0.3)',
|
||||
color: 'rgba(209, 213, 219, 1)',
|
||||
border: '1px solid rgba(75, 85, 99, 0.5)',
|
||||
borderRadius: '6px',
|
||||
fontSize: '14px',
|
||||
fontWeight: '600',
|
||||
cursor:
|
||||
actionLoading === 'transfer-ownership' ? 'not-allowed' : 'pointer',
|
||||
opacity: actionLoading === 'transfer-ownership' ? 0.5 : 1,
|
||||
transition: 'all 0.2s ease',
|
||||
}}
|
||||
onMouseEnter={(e) => {
|
||||
if (actionLoading !== 'transfer-ownership') {
|
||||
e.currentTarget.style.background = 'rgba(75, 85, 99, 0.4)'
|
||||
}
|
||||
}}
|
||||
onMouseLeave={(e) => {
|
||||
if (actionLoading !== 'transfer-ownership') {
|
||||
e.currentTarget.style.background = 'rgba(75, 85, 99, 0.3)'
|
||||
}
|
||||
}}
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleTransferOwnership}
|
||||
disabled={actionLoading === 'transfer-ownership'}
|
||||
style={{
|
||||
flex: 1,
|
||||
padding: '10px',
|
||||
background:
|
||||
actionLoading === 'transfer-ownership'
|
||||
? 'rgba(75, 85, 99, 0.3)'
|
||||
: 'linear-gradient(135deg, rgba(251, 146, 60, 0.8), rgba(249, 115, 22, 0.8))',
|
||||
color: 'white',
|
||||
border:
|
||||
actionLoading === 'transfer-ownership'
|
||||
? '1px solid rgba(75, 85, 99, 0.5)'
|
||||
: '1px solid rgba(251, 146, 60, 0.6)',
|
||||
borderRadius: '6px',
|
||||
fontSize: '14px',
|
||||
fontWeight: '600',
|
||||
cursor:
|
||||
actionLoading === 'transfer-ownership' ? 'not-allowed' : 'pointer',
|
||||
opacity: actionLoading === 'transfer-ownership' ? 0.5 : 1,
|
||||
transition: 'all 0.2s ease',
|
||||
}}
|
||||
>
|
||||
{actionLoading === 'transfer-ownership'
|
||||
? 'Transferring...'
|
||||
: 'Confirm Transfer'}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -464,6 +464,24 @@ export function useRoomData() {
|
||||
}
|
||||
}
|
||||
|
||||
const handleOwnershipTransferred = (data: {
|
||||
roomId: string
|
||||
oldOwnerId: string
|
||||
newOwnerId: string
|
||||
newOwnerName: string
|
||||
members: RoomMember[]
|
||||
}) => {
|
||||
if (data.roomId === roomData?.id) {
|
||||
queryClient.setQueryData<RoomData | null>(roomKeys.current(), (prev) => {
|
||||
if (!prev) return null
|
||||
return {
|
||||
...prev,
|
||||
members: data.members,
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
socket.on('room-joined', handleRoomJoined)
|
||||
socket.on('member-joined', handleMemberJoined)
|
||||
socket.on('member-left', handleMemberLeft)
|
||||
@@ -474,6 +492,7 @@ export function useRoomData() {
|
||||
socket.on('room-invitation-received', handleInvitationReceived)
|
||||
socket.on('join-request-submitted', handleJoinRequestSubmitted)
|
||||
socket.on('room-game-changed', handleRoomGameChanged)
|
||||
socket.on('ownership-transferred', handleOwnershipTransferred)
|
||||
|
||||
return () => {
|
||||
socket.off('room-joined', handleRoomJoined)
|
||||
@@ -486,6 +505,7 @@ export function useRoomData() {
|
||||
socket.off('room-invitation-received', handleInvitationReceived)
|
||||
socket.off('join-request-submitted', handleJoinRequestSubmitted)
|
||||
socket.off('room-game-changed', handleRoomGameChanged)
|
||||
socket.off('ownership-transferred', handleOwnershipTransferred)
|
||||
}
|
||||
}, [socket, roomData?.id, queryClient])
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "soroban-monorepo",
|
||||
"version": "4.14.2",
|
||||
"version": "4.15.0",
|
||||
"private": true,
|
||||
"description": "Beautiful Soroban Flashcard Generator - Monorepo",
|
||||
"workspaces": [
|
||||
|
||||
Reference in New Issue
Block a user