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>
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>
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>
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>
- 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>
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>
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>
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>
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>
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>
- 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>
- 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>
- 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>
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>
- 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>
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>
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>
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>
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>
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>
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>
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>
- 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>
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>
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>
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>
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>
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>
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>
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>
- 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>
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>
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>
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>
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>
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>
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>
- 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>
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>
- 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>
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>
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>
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>
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>
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>
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>
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>
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>
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>