Commit Graph

1922 Commits

Author SHA1 Message Date
Thomas Hallock e937c05323 chore: miscellaneous updates and documentation
- Update know-your-world implementation docs
- Update decomposition CSS styles
- Update AbacusReact component
- Update gallery template
- Update dependencies (pnpm-lock.yaml)
- Update biome config

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-08 11:40:59 -06:00
Thomas Hallock b56c8f439b refactor(practice): add centralized style system for practice components
Add new style utilities to reduce duplication and improve maintainability:
- practiceTheme.ts: Color definitions with light/dark variants and themed() helper
- practiceStyles.ts: Reusable style functions (cards, buttons, badges, progress bars)
- practiceMixins.ts: Layout primitives (centerStack, row, gap/padding presets)
- index.ts: Central export for all utilities

Migrate NumericKeypad and StudentSelector as proof of concept.
Also fix StudentSelector.stories.tsx invalid isGuest property.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-08 11:29:25 -06:00
Thomas Hallock 4c00d92ccb fix(practice): use explicit padding to prevent shorthand override
Replace padding shorthand with explicit paddingTop/Right/Bottom/Left
values to ensure the extra 4rem top padding for help overlays is
applied correctly. CSS shorthand properties can override specific
properties depending on declaration order.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-08 11:14:02 -06:00
Thomas Hallock e9b123a7b3 fix(practice): remove overflow clipping to allow help overlays
Remove overflow-x/overflow-y clipping as CSS doesn't allow mixing
visible and hidden on different axes. The outgoing problem fades
to opacity 0 anyway so clipping isn't needed.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-08 11:07:05 -06:00
Thomas Hallock 1ddf9fc94f fix(practice): allow vertical overflow for help overlays
Change overflow: hidden to overflowX: hidden + overflowY: visible
so help elements above the problem are not clipped while still
hiding horizontal transition animation overflow.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-08 09:57:58 -06:00
Thomas Hallock b12112e8da feat(practice): add smooth problem transition animation
- Add react-spring animation for transitioning between problems
- New problem fades in to the right, then track slides left to center it
- Outgoing problem fades out during the slide
- Use useLayoutEffect to prevent layout jank from flexbox recentering
- Use flushSync for smooth cleanup when removing outgoing problem
- Hide decomposition section when not meaningful (e.g., "5 = 5")
- Add DecompositionSection component for self-contained display
- Simplify HelpAbacus visibility with opacity + pointer-events

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-08 09:56:51 -06:00
Thomas Hallock 52ea3f10fa fix(practice): prevent decomposition math from wrapping
Add whiteSpace: 'nowrap' to keep the step-by-step math on one line.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-08 06:41:46 -06:00
Thomas Hallock 88a2b82f55 refactor(practice): remove usePracticeHelp hook and progressive help levels
We no longer use progressive help levels - help shows all at once when
a prefix sum is detected. Removed:
- usePracticeHelp hook and all helpState/helpActions usage
- helpSettings and isBeginnerMode props
- currentTermIndex state (unused after help system removal)
- handleDismissHelp callback (unused)
- Help tracking fields from result (helpLevelUsed, helpTrigger)

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-08 06:37:20 -06:00
Thomas Hallock 10c210c3b1 refactor(practice): simplify submit button, remove unused help button state
Since help auto-triggers when a prefix sum is detected, the "Get Help"
button state was never actually usable. Simplified to just Submit/disabled.

- Replace buttonState ('help'|'submit'|'disabled') with canSubmit boolean
- Remove handleGetHelp callback (auto-help useEffect handles it)
- Button now always shows "Submit" or "Enter Total"

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-08 06:26:39 -06:00
Thomas Hallock b46a99a3a1 refactor(practice): remove part instruction banner
The part type is already shown in the HUD - redundant banner removed.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-07 21:05:56 -06:00
Thomas Hallock e9ccfb9186 fix(practice): remove redundant 'already at target' message
Return null instead of showing message when target is reached -
the success feedback is already shown elsewhere in the component.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-07 20:59:01 -06:00
Thomas Hallock 19169ad9fe feat(practice): improve help UX with coach hints and simplified UI
- Add coach hint generator using same hints as tutorial (segment.readable.summary)
- Hide abacus numerals in help mode for cleaner display
- Restructure layout: abacus centered, coaching panel on right side
- Exit help mode completely when student finishes adding term to abacus
- Remove checkmarks from term indicators, keep only arrow for current term
- Clean up unused props (confirmedTermCount, detectedPrefixIndex, countdownElement)
- Add HelpCountdown component (pie chart timer, not currently used)

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-07 20:41:33 -06:00
Thomas Hallock 9a4ab8296e feat(practice): add progressive help overlay with proper positioning
- Create PracticeHelpOverlay component showing interactive abacus with
  bead arrows and tooltips (uses same system as TutorialPlayer)
- Extract BeadTooltipContent to shared component for consistency
- Add helpOverlay prop to VerticalProblem for proper positioning
- Position help abacus directly above the term being helped using
  bottom: 100% on the term row (overflow visible)
- Dynamically size abacus columns based on max(currentValue, targetValue)
- Add timing configuration in helpTiming.ts (debug vs production)
- Add beadTooltipUtils.ts for tooltip positioning calculations

The help overlay now correctly covers the confirmed terms in the
vertical problem, with the "Adding: +X" badge and interactive abacus
positioned above the term being worked on.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-07 15:38:17 -06:00
Thomas Hallock 804d937dd9 feat(practice): integrate progressive help with decomposition display
- Extract standalone DecompositionContext from TutorialContext
- Create reusable DecompositionDisplay and ReasonTooltip components
- Wire prefix-sum "Get Help" button to progressive help system (L1→L2→L3)
- Sync abacus interactions with decomposition step highlighting
- Add currentStepIndex tracking in PracticeHelpPanel
- Make HelpAbacus interactive at L3 to update decomposition display
- Update documentation linking decomposition components

The progressive help system now:
- L1: Shows coach hint when user clicks "Get Help" after typing prefix sum
- L2: Shows interactive decomposition with hoverable explanations
- L3: Shows visual abacus with arrows, synced with decomposition highlighting

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-07 08:56:35 -06:00
Thomas Hallock 2f7cb03c3f feat: add auto-submit on correct answer + Newton poem blog post
Practice session improvements:
- Auto-submit when correct answer entered with ≤2 corrections
- Show celebration animation ("Perfect!") before auto-submit
- Display prefix sum checkmarks/arrows before clicking "Get Help"

New blog post: "The Fluxion of Fortune"
- Poem about Newton losing money in the South Sea Bubble
- Hero image of Newton with his calculations and sinking ships
- Custom CSS for properly centered x-bar notation

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-07 06:46:12 -06:00
Thomas Hallock 026993cb05 feat(practice): add dark mode support and fix doubled answer digits
- Add dark mode support to all practice components:
  - ActiveSession, VerticalProblem, NumericKeypad, HelpAbacus
  - StudentSelector, ProgressDashboard, PlanReview, SessionSummary
  - OfflineSessionForm, ManualSkillSelector, PlacementTest, PracticeHelpPanel
- Fix doubled answer digit cells in VerticalProblem by consolidating
  two separate cell-rendering loops into a single unified loop

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-06 19:59:23 -06:00
Thomas Hallock a50b268d35 fix(practice): add 80px top padding to account for app nav height
Push main content down below the fixed app navigation bar.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-06 19:20:47 -06:00
Thomas Hallock b19c6d0eca feat(practice): add session HUD with tape-deck controls and PageWithNav
Major UI improvements to the practice session:
- Add dark control bar at top with session info and transport controls
- Replace pause/end buttons with tape-deck style buttons (⏸️/▶ and ⏹️)
- Move part type, problem count, and progress info into compact HUD
- Add overall progress counter (X/Y total) and health indicator
- Wrap practice page with PageWithNav for consistent app navigation
- Begin dark mode support with isDark prop from useTheme

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-06 19:16:13 -06:00
Thomas Hallock 871390d8e1 feat(help-system): add focus areas for skills needing reinforcement
Add tracking and display of skills that need extra practice:
- Add needsReinforcement, lastHelpLevel, reinforcementStreak to SkillProgress
- Add Focus Areas section to ProgressDashboard
- Add teacher controls to clear reinforcement flags
- Simplify helpSettings schema (remove default value from schema)

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-06 19:12:13 -06:00
Thomas Hallock 3ce12c59fc feat(abacus-react): add defaultValue prop for uncontrolled mode
Add standard React controlled/uncontrolled component pattern to AbacusReact:
- Add `defaultValue` prop to support uncontrolled mode (component owns state)
- When `value` is provided, component operates in controlled mode (syncs to prop)
- When only `defaultValue` is provided, component operates in uncontrolled mode
- Update HelpAbacus to use defaultValue for interactive help

This enables interactive abacus in help mode where the component tracks its own
state while parent monitors via onValueChange callback.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-06 19:11:43 -06:00
Thomas Hallock 373ec34e46 feat(help-system): integrate PracticeHelpPanel into ActiveSession
Phase 3 of help system implementation:

New component:
- PracticeHelpPanel.tsx: Progressive help display for practice sessions
  - L0: "Need Help?" button
  - L1: Coach hint with verbal guidance
  - L2: Mathematical decomposition with segment explanations
  - L3: Bead movement steps with instructions
  - Help level indicator dots
  - "More Help" escalation button
  - Max help level tracking display

ActiveSession integration:
- Added usePracticeHelp hook for help state management
- Track running total and current term for help context
- Reset help context when advancing to new term
- Record help usage in SlotResult (helpLevelUsed, incorrectAttempts, helpTrigger)
- Display PracticeHelpPanel after problem, before input area
- Pass isAbacusPart to enable bead-specific help messaging

Props added:
- helpSettings: StudentHelpSettings for configurable help behavior
- isBeginnerMode: Enable free help without mastery penalty

Stories updated:
- Fixed Date timestamp types
- Added default help tracking fields in interactive demo

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-06 15:27:51 -06:00
Thomas Hallock 0b1ad1f896 feat(help-system): add usePracticeHelp hook and skill extraction
Phase 2 of help system implementation:

New utilities:
- skillExtraction.ts: Maps pedagogical rules to SkillSet identifiers
  - extractSkillsFromSequence(): Extract skills from instruction sequence
  - extractSkillsFromProblem(): Extract skills for multi-term problems
  - getUniqueSkillIds(): Get deduplicated skill list
  - Helper functions for skill ID parsing

New hooks:
- usePracticeHelp.ts: Manages progressive help during practice
  - L0-L3 help levels (none → hint → decomposition → bead arrows)
  - Timer-based auto-escalation in 'auto' mode
  - Error-based auto-escalation (2+ errors triggers help)
  - Manual help request in 'manual' mode
  - Teacher-approved mode placeholder for L2+ help
  - Generates help content from UnifiedInstructionSequence
  - Tracks maxLevelUsed for feedback loop

Test coverage:
- 18 tests for skill extraction covering:
  - Direct addition, heaven bead, simple combinations
  - Five's complement patterns (4=5-1, 3=5-2, 2=5-3, 1=5-4)
  - Ten's complement patterns (9=10-1 through 5=10-5)
  - Multi-digit additions with multiple skills
  - Multi-term problem skill extraction

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-06 15:21:22 -06:00
Thomas Hallock 41c46038d8 feat(help-system): add schema for progressive help and feedback loop
Phase 1 of help system implementation:

Schema changes:
- Add HelpLevel type (0-3) to session-plans.ts
- Extend SlotResult with helpLevelUsed, incorrectAttempts, helpTrigger
- Add REINFORCEMENT_CONFIG constants for mastery credit multipliers
- Add reinforcement tracking columns to player_skill_mastery:
  - needsReinforcement: flag for skills needing extra practice
  - lastHelpLevel: track struggling patterns
  - reinforcementStreak: track progress toward clearing reinforcement
- Add StudentHelpSettings interface and column to players:
  - helpMode: 'auto' | 'manual' | 'teacher-approved'
  - autoEscalationTimingMs: configurable help timing thresholds
  - beginnerFreeHelp: unlimited L1-L2 help without penalty
  - advancedRequiresApproval: require teacher auth for L2+ help

This closes the feedback loop between help usage and session planning:
- Help usage informs skill mastery scoring
- Reinforcement flags guide session planner to include extra practice
- Teacher has visibility into which skills need attention

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-06 15:16:52 -06:00
Thomas Hallock f153dddfce refactor(nav): replace Guide with Practice in main nav
- Main nav bar now shows Practice instead of Guide
- Guide moved to hamburger menu (deprecation path)
- Practice added to hamburger menu for consistency
- Reordered hamburger menu: Home, Create, Practice, Games, Guide, Blog

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-06 14:28:30 -06:00
Thomas Hallock b52f0547af feat(practice): add student onboarding and offline sync features
Add three onboarding features for students coming from books/tutors:

1. Placement Test - Adaptive diagnostic quiz with configurable thresholds
   - Tests skills progressively following curriculum order
   - Tracks consecutive correct/wrong to determine mastery level
   - Presets: Quick, Standard, Thorough assessment modes
   - Shows real-time progress and skill-by-skill results

2. Manual Skill Selector - Teacher-controlled skill mastery setting
   - SAI Abacus Mind Math book level presets (Level 1, 2, 3)
   - Accordion UI organized by skill category
   - Checkbox selection for individual skills

3. Offline Session Form - Record practice done outside the app
   - Date picker, problem count, accuracy slider
   - Skill focus dropdown and notes field

Includes Storybook stories for all new components.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-06 14:04:17 -06:00
Thomas Hallock 585543809a feat(practice): add three-part daily practice session system
Implement complete daily practice session system with:

**Practice Components:**
- StudentSelector: Select which student is practicing
- ProgressDashboard: Show student's current level and progress
- PlanReview: Review and approve generated session plan
- ActiveSession: Main practice UI with three-part structure
- SessionSummary: Show results after session completion
- NumericKeypad: Touch-friendly number input for mobile
- VerticalProblem: Columnar problem display

**Session Structure:**
- Part 1 (Abacus): Physical abacus practice, vertical format
- Part 2 (Visualization): Mental math visualizing beads
- Part 3 (Linear): Mental math with sentence format

**Infrastructure:**
- Database schemas for curriculum, skills, sessions
- Session planner with skill-based problem generation
- React Query hooks for session management
- Consolidated device capability detection hooks
- API routes for curriculum and session management

**Problem Generation:**
- ActiveSession now uses actual skill-based algorithm
- Problems generated with appropriate skills constraints
- Storybook stories use real problem generation

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-06 12:23:53 -06:00
Thomas Hallock d00c70750e feat(worksheets): smooth dice rotation settle to final face
Instead of abruptly snapping to the final face, the dice now:
- Gradually lerps rotation toward target face as it approaches home
- Uses cubic easing for natural deceleration
- Normalizes angles for shortest rotation path
- Waits until rotation is within 5° of target before stopping

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-05 17:32:02 -06:00
Thomas Hallock c6db7dcfa2 feat(worksheets): add viewport edge ricochet to dice physics
Dice now bounces off viewport edges instead of flying through them:
- Bounce damping (0.7) reduces velocity on each bounce
- Extra spin added on collision for dynamic feel
- Takes scaled dice size into account for accurate edge detection

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-05 17:14:47 -06:00
Thomas Hallock 047a960567 feat(worksheets): enhance dice throw physics for natural feel
Improvements during drag:
- Live rotation based on drag velocity (dice tumbles while dragging)
- Gradual scale up to 1.5x as you pull further
- Dynamic drop shadow that grows with distance

Improvements during flight:
- Throw power affects gravity (stronger throws fly further)
- Gravity ramps up over time for momentum carry-through
- Quadratic gravity falloff for natural physics
- More dramatic rotation during tumble
- Shadow tied to scale for depth perception
- Slower scale shrink for dramatic return

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-05 16:42:39 -06:00
Thomas Hallock 920a855eb5 feat(worksheets): add 3x scale effect to thrown dice
When the dice is thrown, it now grows to 3x its normal size while
flying, then smoothly shrinks back to normal as it returns home.
The scaling is center-stable and won't complete until fully shrunk.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-05 16:29:11 -06:00
Thomas Hallock b8e66dfc17 feat(worksheets): add draggable dice easter egg with physics
Add a fun easter egg where the dice in the worksheet action menu can be
dragged and thrown. The dice:
- Tracks pointer movement and calculates throw velocity
- Uses physics simulation with gravity pulling back to origin
- Rolls continuously based on movement direction and speed
- Uses direct DOM manipulation for smooth 60fps animation
- Triggers shuffle when thrown and returns home

Also includes worksheet improvements:
- Conditional name field display (hide when empty/default)
- Date positioned top-right next to QR code
- Reduced problem number size
- Tightened header-to-grid spacing
- Problem numbers aligned to cell corners

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-05 16:24:58 -06:00
Thomas Hallock a0e73d971b feat(worksheets): add QR codes with share codes for easy worksheet sharing
- Add optional QR code embedding in worksheet PDFs (toggle in Layout settings)
- Display 7-character share code under QR code for manual entry
- Create share record when generating PDF with QR code enabled
- Add "Load Share Code" modal for entering share codes without smartphone
- Update preview to show QR code placement when enabled
- Fix async/await for generateTypstSource across all callers

The QR code appears in the header next to the date, with the share code
printed below it. Users without smartphones can type the code into the
"Load Share Code" option in the worksheet generator menu.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-05 11:57:49 -06:00
Thomas Hallock ed25b323e8 fix(create): use inline styles for dynamic gradient backgrounds
Panda CSS's css() function doesn't properly interpolate dynamic gradient
strings. Move background and boxShadow with theme.gradient values to
inline style props for:
- Top gradient bar (converted from _before pseudo to actual div)
- Icon background
- CTA button background

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-05 10:38:23 -06:00
Thomas Hallock 02463df8e5 fix(worksheets): prevent infinite loop when problem space is empty
Add guards against empty problem space in both addition and subtraction
generators. When constraints are impossible to satisfy (e.g., 1-digit
with 100% borrowing), the generate-all approach would return 0 problems,
causing:
- Subtraction: infinite while loop trying to fill from empty array
- Addition (interpolate): array index -1 crash
- Addition (non-interpolate): division by zero in cycle calculation

All three paths now detect empty arrays and return safe fallback problems
with appropriate error logging instead of hanging the server at 85% CPU.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-05 10:17:52 -06:00
Thomas Hallock 822ef78e58 fix(worksheets): sync preview and download problem generation
Fix parameter mismatch in generatePreview.ts that caused preview to
show different problems than downloaded PDF:

- Addition: fix parameter order (was digitRange first, should be pAnyStart)
- Addition: add missing interpolate parameter
- Subtraction: add missing interpolate parameter
- Mixed: add missing interpolate parameter

Now preview and download use identical parameters, generating the same
problem set for the same seed.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-05 10:03:40 -06:00
Thomas Hallock 739303ef5a style(create): improve responsive layout and center cards
Refactor create hub page for better mobile experience:
- Extract reusable CreatorCard component with theme config
- Center 3-card grid properly (was 4-column with 3 cards)
- Responsive breakpoints: 1 col mobile, 2 col tablet, 3 col desktop
- Hide feature lists on very small screens for compact cards
- Scale down icons, fonts, padding, and shadows on mobile
- Reduce code from 688 to ~310 lines

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-05 10:00:52 -06:00
Thomas Hallock cdd0de797f fix(worksheets): render operators last for proper layering
Use Typst's place() function to overlay + and − operators on top of
all other problem elements. This ensures operators are always visible
and properly layered over carry/borrow boxes, scratch work, and other
decorations.

Changes:
- Addition: wrap grid in box, use place() for + sign overlay
- Subtraction: extract operator to operatorOverlay.ts, use place()
- Both operators positioned at correct row using dy offset

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-05 09:46:46 -06:00
Thomas Hallock 2ae5fbfac9 refactor(worksheets): migrate dice animation to react-spring
Replace CSS keyframe-based dice animation with react-spring for smoother
physics-based animation:

- Use spring physics instead of baked quadratic ease-out keyframes
- Track spin count and target face for reliable face landing
- Ensure dice always shows 2-6 (never 1), never consecutive same number
- Face number is deterministic based on seed
- Add dark/light mode theming with distinct color schemes:
  - Light: deep indigo (#4f46e5) with white dots
  - Dark: light indigo (#818cf8) with dark indigo dots

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-05 09:41:50 -06:00
Thomas Hallock 3cd5e4992b feat(worksheets): upgrade to 3D dice with random rotation animation
- Replace 2D SVG dice with CSS 3D cube using transform-style: preserve-3d
- Each face shows correct dot pattern (1-6) with proper dice layout
- Random rotation direction and spin count on each shuffle
- Quadratic ease-out for realistic deceleration (settles naturally)
- Opaque faces to prevent render artifacts during rotation
- WebKit backface-visibility for cross-browser support

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-05 09:33:30 -06:00
Thomas Hallock f97efb5c94 feat(worksheets): add shuffle button with animated dice icon
Add a shuffle button to the worksheet preview floating action bar that
generates a new random seed for problem generation:

- 1/3 split button design: [Download] [🎲] [▼ dropdown]
- Animated dice that rolls and changes faces (2-6) during regeneration
- Final dice face derived from seed, never lands on same number twice
- Excludes face 1 to ensure the icon is clearly recognizable as a dice

Also includes attempted fix for operator layering in Typst templates
(changed operator box width to 0.5em).

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-05 09:17:49 -06:00
Thomas Hallock 4449fb19b4 feat(know-your-world): improve mobile magnifier controls and animations
- Add animated expand/collapse transitions using react-spring for smooth magnifier resizing
- Maintain 20px margin around expanded magnifier to allow clicking outside to dismiss
- Add close button (X) to magnifier controls for dismissing the magnifier entirely
- Replace "Full Map" text button with expand/collapse icons
- Animate button visibility with opacity transitions instead of instant show/hide
- Add width/height to MagnifierSpring for animated size transitions

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-05 08:39:52 -06:00
Thomas Hallock 17c113e68b fix(know-your-world): raise auto-zoom thresholds for tiny regions
Increase the max acceptance thresholds so sub-pixel regions like Gibraltar,
Vatican City, and Monaco get maximum zoom instead of being rejected:

- Sub-pixel (<1px): 2-8% → 2-40% (allows 1000x zoom)
- Tiny (1-5px): 5-15% → 5-25%
- Normal small: 10-25% → 10-30%

Previously, at 1000x zoom a 0.08px region would occupy ~20% of the magnifier,
exceeding the 8% max threshold. The algorithm would then step DOWN trying to
find a zoom where it fits within 2-8%, making tiny regions harder to select.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-05 08:16:59 -06:00
Thomas Hallock e4c35e9425 fix(know-your-world): use shared MAX_ZOOM constant for mobile magnifier
Replace local MAX_ZOOM = 50 with shared MAX_ZOOM = 1000 from constants.
This fixes mobile magnifier auto-zoom being incorrectly capped at 50x
when panning within the magnifier, while map dragging was uncapped.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-05 07:56:05 -06:00
Thomas Hallock 3a8a8d3e86 refactor(know-your-world): add semantic precisionModeRecommended to state machine
Add precision mode tracking to the interaction state machine so components
can check a single semantic flag instead of combining multiple booleans.

Changes:
- Add PrecisionModeState to desktop state (mobile has no precision mode)
- Add PRECISION_THRESHOLD_UPDATE event to sync threshold status
- Add precisionModeRecommended derived state (desktop only, at threshold, not locked)
- Update MagnifierOverlay to use interaction.precisionModeRecommended
- Add enabled prop to MagnifierPixelGrid to hide on mobile

This fixes precision mode UI (scrim, pixel grid) incorrectly showing on mobile
where precision mode doesn't exist.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-05 07:21:40 -06:00
Thomas Hallock f80e49a0da refactor(know-your-world): sync magnifier.isVisible with phase transitions
Simplify showMagnifier logic by automatically setting magnifier.isVisible
when entering/exiting magnifier phases in the state machine:

- TOUCH_MOVE: Set magnifier.isVisible=true when entering mapPanning phase
- MAGNIFIER_DEACTIVATED: Set magnifier.isVisible=false (plus opacity, expanded)
- MapRenderer: Simplify showMagnifier to just use interaction.showMagnifier
- dismissMagnifier: Use MAGNIFIER_DEACTIVATED for mobile, MAGNIFIER_HIDE for desktop

This eliminates the dual check (isMagnifierActive || showMagnifier) that was
needed for backward compatibility.

Decision: Keep targetTop/targetLeft as local useState. Position updates
frequently and doesn't have the dual-source problem that motivated the
visibility migration.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-04 18:03:18 -06:00
Thomas Hallock 08a0bc3d9a refactor(know-your-world): consolidate magnifier state into interaction state machine
Migrate magnifier display state from useMagnifierState hook to the
interaction state machine for unified state management.

Changes:
- Add MagnifierDisplayState to state machine with visibility, opacity,
  zoom, position, and expansion state
- Add magnifier events: MAGNIFIER_SHOW, MAGNIFIER_HIDE, MAGNIFIER_SET_ZOOM,
  MAGNIFIER_SET_POSITION, MAGNIFIER_SET_OPACITY, MAGNIFIER_SET_EXPANDED
- Create shared handleMagnifierEvent handler for desktop/mobile reducers
- Update MapRenderer to source magnifier state from state machine
- Delete useMagnifierState hook (183 lines)
- Delete unused MapRendererContext (146 lines)
- Clean up obsolete refactoring plan docs

This resolves the "dual-source problem" where showMagnifier came from
different sources for mobile vs desktop. Now both use the state machine.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-04 17:59:05 -06:00
Thomas Hallock ab30adda25 fix(know-your-world): stabilize mobile magnifier 1:1 touch tracking
Use empirical scale measurement with direct delta tracking instead of
closed-loop feedback approach. The feedback loop was causing instability
due to one-frame lag between error measurement and cursor updates.

The empirical approach measures actual pixels-per-SVG-unit using probe
circles, then applies frame-by-frame deltas with that scale factor.
This automatically accounts for zoom, aspect ratio, and letterboxing
without accumulating error.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-04 16:36:26 -06:00
Thomas Hallock 39886e859c feat(know-your-world): implement empirical scale measurement for 1:1 magnifier tracking
Replace calculated touch multiplier with empirical measurement approach:
- Add invisible probe circles in magnifier SVG at known SVG coordinates (100 units apart)
- Measure actual screen pixel distance between probes via getBoundingClientRect()
- Calculate pixelsPerSvgUnit from measured distance / known SVG distance
- Use empirical scale for touch multiplier: viewportScale / pixelsPerSvgUnit
- Falls back to calculated method if measurement fails

This approach is robust to rendering pipeline changes since it measures
what's actually on screen rather than calculating through transform layers.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-04 14:27:20 -06:00
Thomas Hallock e712fcbcb7 chore(know-your-world): remove dead getHotColdEmoji function
Remove unused getHotColdEmoji function and its FeedbackType import from
GameInfoPanel. The hot/cold system now uses the spinning crosshair visual
and audio feedback instead of emoji display.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-04 13:33:09 -06:00
Thomas Hallock a6352ec624 fix(know-your-world): fix hot/cold visual feedback delay
Remove early return that was blocking visual feedback when speech was active.
Previously, `if (isSpeaking || externalSpeaking) return` blocked BOTH visual
AND audio feedback. Now visual feedback (spinning crosshair) updates immediately
while audio feedback is still properly gated.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-04 13:31:20 -06:00