Commit Graph

2910 Commits

Author SHA1 Message Date
Thomas Hallock 055813205a fix(know-your-world): fix celebration timer restart and mobile magnifier dismissal bugs
Bug fixes:
1. Celebration/confetti timers would restart when mouse moved during animation
   - Root cause: onComplete callback in useEffect dependency array
   - Fix: Store callback in ref to prevent timer restart on re-render
   - Fixed in: Confetti.tsx (both components), CelebrationOverlay.tsx

2. Mobile magnifier would dismiss on every other drag release
   - Root cause: handleMapTouchEnd only checked map dragging, not magnifier dragging
   - Fix: Also check isMagnifierDragging and isPinching before dismissing
   - Touch events can escape magnifier bounds and reach map container

Also adds debug console logging for touch end handlers.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-04 13:17:48 -06:00
Thomas Hallock 1e1ce30dbd refactor(know-your-world): Phase 5 - Extract hooks and fix mobile magnifier panning
Phase 5 extractions:
- Extract useCrosshairRotation hook (spring-for-speed, manual-integration-for-angle pattern)
- Extract AutoZoomDebugOverlay and SafeZoneDebugOverlay components
- Extract pointerLockMovement utilities (calculatePointerLockMovement, checkDragThreshold)
- Extract useCelebrationAnimation hook
- Extract useHintAnimation hook
- Extract useGiveUpReveal hook
- Create MapGameProvider context for game state
- Extract MagnifierOverlay and MagnifierOverlayWithHandlers components
- Extract useMagnifierTouchHandlers hook
- Extract ZoomLinesOverlay component

Bug fix:
- Fix mobile magnifier 1:1 panning by converting touch coordinates to container
  coordinates before dispatching to state machine (was using client coordinates)

MapRenderer.tsx reduced from ~3,387 to ~2,991 lines (~396 lines saved)

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-04 11:43:36 -06:00
Thomas Hallock 021a75f583 fix(know-your-world): Remove redundant preventDefault calls in touch handlers
Remove e.preventDefault() calls from touch handlers that were causing
"Unable to preventDefault inside passive event listener" warnings.

The container already has touchAction: 'none' CSS which prevents
browser gestures (scrolling, zooming) without needing preventDefault().
React marks touch events as passive by default, so preventDefault()
doesn't work and just triggers console warnings.

Affected handlers:
- handleContainerTouchMove (mobile map drag)
- handleMagnifierTouchStart (pinch start, drag start)
- handleMagnifierTouchMove (pinch zoom, pan)

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-03 18:02:55 -06:00
Thomas Hallock ca1f60c3c6 refactor(know-your-world): Phase 3.1 - Create useAnimationController hook
Create consolidated animation controller hook for give-up, hint, and celebration animations:

- Create features/animations/useAnimationController.ts:
  - Manages state for all 3 animation types in one place
  - Give-up animation: 3 pulses over 2s + 500ms cooldown
  - Hint animation: 2 pulses over 1.5s
  - Celebration animation: 2-4 pulses based on type (lightning/standard/hard-earned)

- Exposes unified API:
  - State: giveUpProgress, isGiveUpAnimating, hintProgress, isHintAnimating, celebrationProgress
  - Actions: startGiveUp, cancelGiveUp, startHint, cancelHint, startCelebration, cancelCelebration, cancelAll

- Uses usePulsingAnimation internally for consistent animation behavior
- Ready to replace scattered useState calls in MapRenderer

- Update features/animations/index.ts with new exports

Part of MapRenderer refactoring Phase 6.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-03 17:56:31 -06:00
Thomas Hallock 6d68c68a3f refactor(know-your-world): Phase 2.3 - Extract HeatCrosshair component
Deduplicate compass crosshair code by creating reusable HeatCrosshair component:

- Create features/cursor/HeatCrosshair.tsx with size-configurable compass crosshair
  - Proportional sizing calculations based on size prop
  - Outer ring, 12 compass tick marks (cardinals highlighted in white)
  - Center dot, fixed north indicator (red triangle)
  - Spring-animated rotation with configurable shadow intensity

- Update CustomCursor.tsx to use HeatCrosshair component
  - Simplified from inline SVG (~65 lines) to component usage (~1 line)

- Update MapRenderer.tsx heat crosshair overlay to use HeatCrosshair
  - Replaced ~73 lines of inline SVG with 6-line component usage
  - Uses size=40 and shadowIntensity=0.6 to match original styling

- Update features/cursor/index.ts with HeatCrosshair exports

Net reduction: ~135 lines of duplicated compass SVG code

Part of MapRenderer refactoring Phase 6.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-03 17:52:05 -06:00
Thomas Hallock 8b3e01b127 refactor(know-your-world): extract heat styling, state machine integration, and CustomCursor
Phase 2.1a: Heat Styling Extraction
- Create utils/heatStyles.ts with heat-based styling functions
- Create useMagnifierStyle hook for memoized styling calculations
- Replace 5 inline calls with memoized hook values
- Remove 2 JSX IIFEs for cleaner render path

Phase 2.2: CustomCursor Component Extraction
- Create features/cursor/CustomCursor.tsx component
- Compass-style crosshair with rotating ring and fixed north indicator
- Region name label with heat-based styling
- Replace ~113 lines of inline JSX with 10-line component usage

Phase 2.5/3.2: State Machine Integration (from previous session)
- Create useInteractionStateMachine hook with useReducer pattern
- Implement desktop phases: idle → hovering → dragging → selecting → pointerLocked → releasing
- Migrate isDesktopMapDragging and isReleasingPointerLock to state machine
- Fix ESC key flow bug in pointer lock release

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-03 17:47:04 -06:00
Thomas Hallock 4c44cb7fa5 docs(know-your-world): add state machine design for interaction refactoring
Add Phase 2.5 to refactoring plan:
- Design for useInteractionStateMachine hook
- ASCII state diagrams for desktop and mobile modes
- Type definitions for states and events
- Migration path from boolean flags to explicit states

Key insight: The state machine manages "what kind of input am I processing?"
not all state (animations, positions stay separate).

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-03 13:34:28 -06:00
Thomas Hallock 1241e4bf2e refactor(know-your-world): MapRenderer Phase 1 quick wins
Phase 1 improvements:
- Memoize viewBox parsing (eliminate 24 redundant split/map calls per render)
- Extract usePulsingAnimation hook (deduplicate 3 identical animation patterns)
- Add cursorToSvgCoordinates utility for coordinate conversion
- Remove CursorShare console spam (5 debug logs)

New files:
- features/animations/usePulsingAnimation.ts - reusable pulsing animation hook
- features/animations/index.ts - barrel export

Updated MAPRENDERER_REFACTORING_PLAN.md with progress and Phase 2 analysis notes.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-03 13:29:30 -06:00
Thomas Hallock b315c0fac0 refactor(know-your-world): revert failed extractions, keep working improvements
Reverted state machine and premature component extractions that broke functionality.
Kept working improvements:
- usePrecisionCalculations hook (breaks circular dependency)
- panningMath utilities (1:1 panning calculations)
- useMagnifierState hook integration
- Precision mode scrim/filter using hook values

Deleted (reverted):
- useInteractionStateMachine (overcomplicated, broke UX)
- Component extractions that weren't integrated
- Various docs from failed approaches

Modified (kept improvements):
- MapRenderer.tsx: Uses new hooks, cleaner precision mode
- magnifier/index.ts: Exports new utilities
- precision/index.ts: Exports usePrecisionCalculations

This represents a clean baseline for the next refactoring phase.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-03 12:59:59 -06:00
Thomas Hallock d85b976f8b perf(know-your-world): memoize state machine return value and remove debug logging
- Memoize useInteractionStateMachine return object to prevent unnecessary
  re-renders in consumers that depend on interactionMachine
- Remove debug logging effect that fired on every state change

These changes reduce the number of callback recreations and console output,
which could affect performance.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-03 10:37:47 -06:00
Thomas Hallock d0a877ee8e refactor(know-your-world): wire precision mode to state machine
Integrate precision mode with the interaction state machine:

- handleLockAcquired dispatches REQUEST_PRECISION directly
- handleLockReleased dispatches RELEASE_ANIMATION_DONE directly
- setIsReleasingPointerLock wrapper dispatches PRECISION_ESCAPE_BOUNDARY
- Remove sync effects (no longer needed with direct dispatch)

Replace rendering conditionals with state machine values:
- precisionCalcs.isAtThreshold && !pointerLocked → isPrecisionMode
- MagnifierLabel pointerLocked prop → isPrecisionMode
- MagnifierControls pointerLocked prop → isPrecisionMode
- isReleasingPointerLock checks → isReleasingPrecision

Handler logic that interacts with browser API still uses
pointerLocked from usePointerLock for correct browser behavior.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-03 10:22:43 -06:00
Thomas Hallock 0cd9ef59b3 refactor(know-your-world): replace showMagnifier with state machine checks
Replace all runtime usages of showMagnifier boolean with
interactionMachine.showMagnifier from the interaction state machine:

- useHotColdFeedback enabled condition
- Mobile touch end handler (tap to dismiss)
- Magnifier region indicator conditional
- ZoomLines visibility conditional
- HotColdDebugPanel prop

The old showMagnifier variable remains temporarily for debug comparison
logging during the migration period.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-03 09:44:55 -06:00
Thomas Hallock 9e19cec60b refactor(know-your-world): migrate setters to dispatch directly to state machine
Update setShowMagnifier and setIsMagnifierExpanded to dispatch events
directly to both old state AND state machine during migration:

- setShowMagnifier now dispatches SHOW_MAGNIFIER/DISMISS_MAGNIFIER
- setIsMagnifierExpanded now dispatches EXPAND_MAGNIFIER/COLLAPSE_MAGNIFIER
- Removed redundant sync effects for magnifier visibility and expanded state
- This eliminates the one-render-cycle lag from useEffect sync

The wrappers ensure both systems stay in perfect sync, enabling safe
replacement of rendering conditionals with state machine values.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-03 09:40:47 -06:00
Thomas Hallock 64ace43f87 test(know-your-world): add comprehensive unit tests for interaction state machine
Add 45 unit tests covering all state transitions:
- Initial state verification
- IDLE state transitions (MOUSE_ENTER, SHOW_MAGNIFIER, TOUCH_START)
- HOVERING state transitions (MOUSE_LEAVE, MOUSE_MOVE, MOUSE_DOWN, SHOW_MAGNIFIER)
- MAGNIFIER_VISIBLE transitions (MOUSE_MOVE, DISMISS, REQUEST_PRECISION, EXPAND, TOUCH_START)
- MAGNIFIER_PANNING transitions (TOUCH_MOVE, TOUCH_END, DISMISS_MAGNIFIER)
- MAGNIFIER_PINCHING transitions (TOUCH_MOVE, TOUCH_END, ZOOM_THRESHOLD, DISMISS_MAGNIFIER)
- MAGNIFIER_EXPANDED transitions (COLLAPSE, DISMISS)
- MAP_PANNING_MOBILE transitions (TOUCH_MOVE, TOUCH_END, DISMISS)
- MAP_PANNING_DESKTOP transitions (MOUSE_UP, MOUSE_LEAVE)
- PRECISION_MODE transitions (MOUSE_MOVE, ESCAPE_BOUNDARY, EXIT_PRECISION)
- RELEASING_PRECISION transitions (RELEASE_ANIMATION_DONE)
- Context preservation across transitions
- Previous state tracking
- Invalid transitions (no-ops)

Also adds missing DISMISS_MAGNIFIER handlers in MAGNIFIER_PANNING and MAGNIFIER_PINCHING
states to ensure dismissal works from any magnifier interaction state.

Exports interactionReducer and initialMachineState for testing.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-03 09:36:59 -06:00
Thomas Hallock 7e55953eee feat(know-your-world): wire interaction state machine to MapRenderer
Add state machine integration with sync effects for incremental migration:
- Call useInteractionStateMachine hook in MapRenderer
- Add sync effects to drive state machine from existing boolean state:
  - Precision mode sync (pointerLocked, isReleasingPointerLock)
  - Magnifier visibility sync (showMagnifier)
  - Magnifier expanded sync (isMagnifierExpanded)
- Add debug logging to compare machine state vs boolean state
- Clean up unused imports (CompassCrosshair, FeedbackType)
- Organize imports per Biome rules

The state machine now runs in parallel with existing state. Next steps:
1. Test sync effects work correctly in dev console
2. Migrate handlers to dispatch events directly to state machine
3. Replace rendering conditionals with state machine checks
4. Remove old boolean state once migration is complete

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-03 09:26:13 -06:00
Thomas Hallock e4d6748d70 feat(know-your-world): add interaction state machine foundation
Design and implement state machine to replace scattered boolean flags:

- INTERACTION_STATE_MACHINE.md: Full design doc with states and transitions
- useInteractionStateMachine.ts: Hook with 10 explicit states, events, and reducer

States: IDLE, HOVERING, MAGNIFIER_VISIBLE, MAGNIFIER_PANNING,
        MAGNIFIER_PINCHING, MAGNIFIER_EXPANDED, MAP_PANNING_MOBILE,
        MAP_PANNING_DESKTOP, PRECISION_MODE, RELEASING_PRECISION

This replaces 9 independent booleans (512 combinations) with ~10 valid states.
Next: Wire up handlers to dispatch events to machine.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-03 09:18:38 -06:00
Thomas Hallock c21f44260f refactor(know-your-world): extract components from MapRenderer (3912→3594 lines)
Extract three modules from MapRenderer to improve maintainability:

- OtherPlayerCursors: Renders multiplayer cursor crosshairs (-148 lines)
- DebugAutoZoomPanel: Auto-zoom detection debug visualization (-127 lines)
- useUserPreferences: localStorage-persisted user settings hook (-43 lines)

Total reduction: 318 lines (8.1%)

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-03 09:07:34 -06:00
Thomas Hallock f0bf2050d3 revert(know-your-world): undo premature extractions, restore working state
Reverts previous extraction attempts that went down a problematic path:
- Remove DebugAutoZoomPanel component
- Remove OtherPlayerCursors component
- Remove useUserPreferences hook
- Restore inline implementations in MapRenderer

MapRenderer is at 3912 lines. Will proceed with a more careful
extraction strategy based on updated deep dive analysis.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-03 08:39:41 -06:00
Thomas Hallock 022ee0256a docs(know-your-world): update deep dive with extraction progress
Update MAPRENDERER_DEEP_DIVE.md with completed extractions:
- OtherPlayerCursors  (-148 lines)
- DebugAutoZoomPanel  (-128 lines)
- useUserPreferences  (-43 lines)

Total reduction now 1086 lines (23.2%) from original 4679 to 3593.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-02 19:09:55 -06:00
Thomas Hallock 0c77ec5daf refactor(know-your-world): extract components from MapRenderer
Extract cleanly separable code from MapRenderer (3912 → 3593 lines, -8.2%):

- OtherPlayerCursors: Multiplayer cursor rendering (~148 lines)
- DebugAutoZoomPanel: Auto-zoom debug visualization (~128 lines)
- useUserPreferences: localStorage-persisted user settings (~43 lines)

New feature modules:
- features/multiplayer/ - Multiplayer cursor components
- features/user-preferences/ - User preference hooks
- features/debug/DebugAutoZoomPanel.tsx - Debug panel component

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-02 19:08:45 -06:00
Thomas Hallock 598f4db41e refactor(know-your-world): extract features from MapRenderer (4679→3912 lines)
Major refactoring to break down the monolithic MapRenderer component:

Extracted features:
- features/cursor/CompassCrosshair - Reusable compass-style crosshair
- features/cursor/CursorOverlay - Custom cursor and heat crosshair overlays
- features/debug/HotColdDebugPanel - Hot/cold feedback debug panel
- features/debug/SafeZoneDebugPanel - Safe zone visualization
- features/regions/RegionLayer - Map region rendering
- features/map-renderer/MapRendererContext - Shared state context
- features/magnifier/* - Magnifier components and hooks
- features/labels/* - Label layer and D3 force layout
- features/precision/* - Precision mode calculations
- features/animations/* - Pulsing animation hook
- features/letter-confirmation/* - Letter confirmation system
- utils/hotColdStyles - Heat/cold styling utilities

MapRenderer reduced from 4679 to 3912 lines (-767 lines, 16.4% reduction).
Context integration eliminates prop drilling for shared state.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-02 18:02:59 -06:00
Thomas Hallock 3c189efef8 docs(know-your-world): restructure documentation with architecture and patterns
Create comprehensive documentation structure:
- docs/ARCHITECTURE.md: System overview with ASCII diagrams, data flow,
  component responsibilities
- docs/FEATURES.md: Complete feature inventory with file references and
  test coverage status
- docs/PATTERNS.md: Code conventions including component size limits
  (500 hard/300 soft), feature module pattern, hook composition,
  test co-location, naming conventions

Consolidate implementation docs:
- Move .claude/*.md to docs/implementation/
- Move hidden .md files to docs/implementation/
- Move MAGNIFIER_ARCHITECTURE.md and PRECISION_CONTROLS.md to docs/

Update README.md:
- Add documentation table with links to all docs
- Streamline content to focus on quick start
- Add maintenance notes section

This establishes the foundation for Phase 2 refactoring of large
components (MapRenderer 6285 lines, GameInfoPanel 2090 lines) into
feature modules following documented patterns.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-02 08:11:24 -06:00
Thomas Hallock 493313a3bb feat(know-your-world): add hot/cold debug panel and production debug mode
- Add ?debug=1 URL param to unlock debug mode in production
- Debug mode persists in localStorage once unlocked
- Add hot/cold debug panel showing all enable conditions:
  - Assistance level, user toggle, fine pointer, magnifier, mobile dragging
  - Overall enabled/disabled status
  - Current feedback type and target region
- Change debug flags from build-time to runtime gating
- Fix isDevelopment reference in AppNavBar dropdown menu

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-02 07:23:47 -06:00
Thomas Hallock 2ce5e180b7 feat(know-your-world): add mobile cursor sharing and fix multi-device coop mode
- Broadcast cursor position during mobile drag gesture for magnifier
- Key cursors by userId (session ID) instead of playerId to support
  multiple devices per player in cooperative mode
- Enable hot/cold feedback during initial mobile drag (not just magnifier pan)
- Fall back to memberPlayers lookup for remote player metadata when
  rendering cursors (fixes cursor visibility for remote players)

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-01 18:39:48 -06:00
Thomas Hallock 54402501e5 feat(know-your-world): add adaptive hint cycling for struggling users
- Modify useRegionHint hook to support cycling through multiple hints
  - Returns { currentHint, hintIndex, totalHints, nextHint, hasMoreHints }
  - Starts with random hint, advances on nextHint() call
  - Resets cycle when region changes
  - Add useRegionHintSimple for backwards compatibility

- Add struggle detection in MapRenderer
  - Monitors time spent searching for region
  - Gives next hint every 30 seconds of struggle
  - Announces new hint via speech synthesis when it changes

- Fix Part 1 hint announcement regression
  - Was passing null instead of hintText to speakWithRegionName
  - Now correctly announces region name followed by hint text

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-01 17:47:59 -06:00
Thomas Hallock e0b762e3ee feat(know-your-world): add speech announcements and compass-style crosshairs
Speech announcements:
- Announce region name when takeover shows (Part 1)
- Announce "You found {region}" at start of celebration (Part 2)
- Add 2-second breather delay between celebrations and next region
- Delay starts after BOTH celebration AND speech finish (async tracking)

Compass-style crosshairs:
- Replace simple crosshairs with compass design on all cursors
- Add 12 tick marks around ring (cardinal directions more prominent)
- Add red "N" indicator that counter-rotates to always point up
- Cardinal ticks are white with dark shadow for contrast
- Magnifier compass has precise rotating crosshair lines

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-01 17:36:53 -06:00
Thomas Hallock 0584863bdd fix(know-your-world): improve crosshair UX and fix mobile Select button
Crosshair improvements:
- Add wind-back animation: crosshairs smoothly return to upright when not rotating
- Remove fire particles (broken animation)
- Remove glow effects from crosshairs
- Increase crosshair ring radius for better visibility
- Remove hot/cold emoji badge (spinning crosshairs are superior feedback)

Mobile Select button fix:
- Fix intermittent Select button not working by updating hoveredRegion state
  during mobile map drag and magnifier drag operations
- Previously detectRegions was called but setHoveredRegion was not,
  causing Select button to use stale hover state

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-01 16:21:30 -06:00
Thomas Hallock b7fe2af369 fix(know-your-world): use spring-for-speed pattern for smooth crosshair rotation
Replace CSS animation with spring-for-speed, manual-integration-for-angle pattern:
- Spring animates rotation SPEED for smooth transitions between heat levels
- requestAnimationFrame loop integrates angle from speed (no jumps)
- useSpringValue binds angle directly to animated SVG elements

This solves position jumps that occurred when CSS animation duration changed.

Add documentation:
- .claude/ANIMATION_PATTERNS.md - complete pattern explanation
- Reference in CLAUDE.md for future similar tasks

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-01 15:27:03 -06:00
Thomas Hallock af5e7b59dc fix(know-your-world): replace react-spring with CSS animation for crosshair rotation
React-spring was lagging 1000+ degrees behind the target rotation value due to
internal batching/queueing when called 60fps from requestAnimationFrame while
React was also re-rendering from cursor movement.

CSS animations run on the browser's compositor thread, completely independent
of JavaScript execution and React re-renders, eliminating the wild spinning bug.

Key changes:
- Remove useSpring and requestAnimationFrame-based rotation loop
- Use CSS @keyframes crosshairSpin animation with variable duration
- Duration calculated from heat level: 360 / (speed * 60) seconds
- animation-play-state controls running/paused state
- Debounced shouldRotate state prevents flicker from feedback type flickering

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-01 14:07:54 -06:00
Thomas Hallock 824325b843 feat(know-your-world): add hot/cold feedback for mobile magnifier
- Show hot/cold toggle on mobile devices (removed hasAnyFinePointer gate)
- Enable hot/cold feedback when magnifier is visible on touch devices
- Add checkHotCold calls to map touch and magnifier pan handlers
- Heat-tinted magnifier border based on temperature feedback
- Hot/cold emoji badge in magnifier corner showing current state
- Respects existing hot/cold game setting on all devices

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-01 11:05:10 -06:00
Thomas Hallock f6d1295c6f fix(know-your-world): restore no-music celebration sounds
Pass startTime in addition to type from Provider to MusicProvider.
The previous fix required startTime for deduplication but Provider
was only passing type, causing the effect to never trigger.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-01 10:29:55 -06:00
Thomas Hallock a8c6b84855 feat(know-your-world): improve takeover UI and fix celebration sound bug
UI Improvements:
- Add frosted glass backdrop + enhanced text shadow for region name contrast
- Heat effect on region outline: blue → purple → orange → gold as letters typed
- Puzzle piece now uses gold styling to match map celebration
- Consistent gold styling from takeover through to map celebration

Bug Fix:
- Fix celebration sound playing 30+ times layered on top of each other
- Remove duplicate sound trigger from CelebrationOverlay (MusicContext handles it)
- Use celebration.startTime as stable identifier to prevent re-triggers
- Track last played celebration in ref to ensure single playback

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-01 10:14:16 -06:00
Thomas Hallock 1e6153ee8b feat(know-your-world): add fire tracer animation for learning mode takeover
Add animated fire/sparkle tracer effect around region outlines in learning mode:
- Tracer follows simplified path (using simplify-js) for smooth coastlines
- Supports multiple islands with simultaneous animations
- Progressive intensity as letters are typed:
  - Speed increases exponentially (15s → 0.075s, 200x faster)
  - Opacity fades in (25% → 100%)
  - Sparkle count grows exponentially (1 → 48)
- 750ms laser effect delay after final letter before dismissing
- Uses react-spring for smooth transitions
- Performance optimized: removed filters from particles

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-01 09:44:03 -06:00
Thomas Hallock f8acc4aa6a fix(know-your-world): use getBBox() for consistent takeover positioning
- Add hidden SVG to measure accurate bounding box via getBBox() for part 1
- Both takeover parts now use getBBox() instead of different calculation methods
- Add vectorEffect="non-scaling-stroke" to takeover region shapes
- Add stroke to takeover region shape for visual consistency
- Add vectorEffect to label pointer lines in MapRenderer
- Document failure pattern in CLAUDE.md: verify agreed approach is
  implemented everywhere, not just obvious cases

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-01 08:32:47 -06:00
Thomas Hallock 7c496525e9 feat(know-your-world): add puzzle piece fly-to-map animation for learning mode
Add animated region silhouette that flies from takeover screen to map position
when user correctly identifies a region in learning mode:

- Add PuzzlePieceTarget interface with sourceRect for animation positioning
- Capture takeover region position with ref and getBoundingClientRect
- Use react-spring to animate from takeover to map position
- Preserve aspect ratio during takeover display
- Fix flash at animation end by checking isInTakeoverLocal
- Add debug logging for accent normalization

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-01 07:49:42 -06:00
Thomas Hallock dcc32c288f feat(know-your-world): auto-enable hot/cold for learning mode
When a game starts with the "learning" assistance level, automatically
enable hot/cold audio feedback for all players. This ensures the full
learning experience is available without requiring manual toggle.

The setting is also persisted to localStorage so it remains enabled
if the player navigates away and returns.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-30 20:35:38 -06:00
Thomas Hallock 45730bb4db feat(know-your-world): add turn-based restrictions for letter typing
In turn-based learning mode:
- Show current player's emoji next to typing instruction
- Only allow the current player to type letters
- Show "Waiting for [player] to type..." when it's not your turn
- Display "Not your turn!" notice when attempting to type during another player's turn

This makes it clear whose turn it is and prevents confusion in multiplayer games.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-30 20:34:51 -06:00
Thomas Hallock f5ce53efc0 fix(know-your-world): enable hot/cold only for current player in turn mode
In turn-based multiplayer, hot/cold feedback should only be active for
the player whose turn it is. Previously, all players with fine pointers
would get hot/cold feedback regardless of turn, which could be confusing.

Added turn check: (gameMode !== 'turn-based' || currentPlayer === localPlayerId)

This matches the pattern used elsewhere in the component for cursor
broadcasting and other turn-based restrictions.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-30 20:27:55 -06:00
Thomas Hallock c0f50c53dc test(know-your-world): add storybook stories for accented characters
Added stories to test the normalizeToBaseLetter function:
- Côte d'Ivoire (ô → o)
- São Tomé and Príncipe (ã → a)
- Curaçao (ç → c)
- Réunion (é → e)
- México (é → e)
- Perú (ú → u)
- Saint Barthélemy (space + accent)

Also updated the demo component to use the same normalization
logic as the actual game.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-30 20:24:23 -06:00
Thomas Hallock b27856e9fc fix(know-your-world): normalize accented letters for keyboard input
Added normalizeToBaseLetter() function that converts accented characters
to their base ASCII equivalents (e.g., 'é' → 'e', 'ñ' → 'n', 'ç' → 'c').

This allows users to type region names like "Côte d'Ivoire" or "São Tomé"
using a regular keyboard without needing to input accented characters.

Applied to both physical keyboard and virtual keyboard handlers.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-30 19:26:48 -06:00
Thomas Hallock 77033f0b22 fix: hide abacus on /arcade and /arcade-rooms routes
The previous check for `/arcade/` (with trailing slash) missed:
- /arcade (the arcade landing page)
- /arcade-rooms/* (actual game rooms)

Changed to `/arcade` prefix check which catches all arcade routes.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-30 18:39:29 -06:00
Thomas Hallock fa1514d351 feat(know-your-world): improve magnifier UX and hide abacus on games
Magnifier improvements:
- Add auto-zoom when dragging on magnifier (mobile)
- Add desktop click-and-drag to show magnifier (like shift key)
- Add fullscreen expand button (mobile only, top-right corner)
- Add Select button inside magnifier (mobile only, bottom-right)
- Add Full Map button to exit fullscreen (mobile only, bottom-left)
- Select button disabled when crosshair is over ocean or found region
- All magnifier buttons only appear on touch devices
- Click-drag magnifier works in pointer lock mode

Abacus visibility:
- Hide floating abacus on all /arcade/* routes by default
- Games can opt-in via setShowInGame(true) context

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-30 17:49:25 -06:00
Thomas Hallock c502a4fa92 feat(know-your-world): add device capability hooks and improve mobile support
- Create useDeviceCapabilities.ts with three hooks:
  - useIsTouchDevice(): detect touch-only devices
  - useCanUsePrecisionMode(): check pointer lock + fine pointer support
  - useHasAnyFinePointer(): detect any fine pointer (for hybrid devices)
- Update usePointerLock to accept canUsePrecisionMode option:
  - Prevents pointer lock on unsupported devices
  - Auto-exits pointer lock when switching to mobile mode (DevTools)
- Update MapRenderer to use new hooks:
  - Replace manual isTouchDevice detection with hooks
  - Use canUsePrecisionMode for precision mode UI visibility
  - Use hasAnyFinePointer for hot/cold feedback
- Add pinch-to-zoom magnifier expansion:
  - Magnifier expands to fill leftover area during pinch gesture
  - Tap outside dismisses and resets size
- Update SimpleLetterKeyboard to import from shared hooks file

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-30 16:55:23 -06:00
Thomas Hallock ea8965bc95 fix(know-your-world): guard against undefined state during session init
Add loading guard in GameComponent to prevent crash when state is
undefined during session initialization.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-30 15:04:37 -06:00
Thomas Hallock 60cf98e77a feat(know-your-world): improve mobile magnifier with adaptive zoom and select button
- Use findOptimalZoom for mobile (same algorithm as desktop) instead of hardcoded 2.5-4x
- Keep magnifier visible after drag ends so user can confirm selection
- Add green "Select ✓" button below magnifier for confirming region selection
- Tap elsewhere on map to dismiss magnifier without selecting
- Disable pull-to-refresh with touchAction: none and overscrollBehavior: none
- Add defensive check for undefined includeSizes in filterRegionsBySizes

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-30 15:01:51 -06:00
Thomas Hallock 9a254e2933 fix(know-your-world): improve mobile magnifier positioning and sizing
- Add mobile drag gesture detection to show magnifier when dragging on map
- Constrain magnifier to leftover rectangle (below nav/floating UI)
- Size magnifier based on leftover area dimensions, not full viewport
- Use leftover rectangle center for positioning decisions
- Prevent text selection during drag with CSS and preventDefault()
- Fix runtime error in filterRegionsBySizes with undefined check

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-30 14:10:37 -06:00
Thomas Hallock a02a7108e9 feat(know-your-world): show magnifier on mobile drag gesture
Detect touch drag gestures on the map to show the magnifier on mobile:
- Track touch start position and detect drag when moved past threshold
- Show magnifier when user drags on the map (not just on the magnifier)
- Position magnifier in opposite corner from touch point
- Use adaptive zoom based on region detection
- Hide magnifier when touch ends

This makes the magnifier discoverable on mobile by appearing automatically
when the user starts dragging on the map to search for regions.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-30 13:46:44 -06:00
Thomas Hallock 7dab07b3a7 feat(know-your-world): add Strudel-based music system
Add procedurally generated background music using Strudel.cc:
- MusicContext/MusicProvider for global music state
- useMusicEngine hook with Strudel.cc integration
- Regional music presets (continental themes + hyper-local variations)
- MusicControls and MusicControlPanel for user control
- MusicDebugPanel for development testing
- Region-to-music mapping system for adaptive soundtrack
- Integration with CelebrationOverlay and PlayingPhase

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-30 13:25:37 -06:00
Thomas Hallock 5318d0dd89 feat(know-your-world): add mobile virtual keyboard and space-skipping
- Add SimpleLetterKeyboard component for touch devices (react-simple-keyboard)
- Skip spaces when counting letters for name confirmation
  (e.g., "US Virgin Islands" → type U, S, V)
- Hide MyAbacus floating button when virtual keyboard is shown
- Add Storybook stories for testing letter confirmation flow

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-30 13:08:39 -06:00
Thomas Hallock 655660f7cf feat(know-your-world): sync letter confirmation across multiplayer sessions
Add shared state for learning mode name confirmation:
- Add nameConfirmationProgress to KnowYourWorldState
- Add CONFIRM_LETTER move type for validating letter entry
- Implement validateConfirmLetter in Validator (looks up region name from ID)
- Add confirmLetter action to Provider context
- Update GameInfoPanel to use shared state with optimistic updates

Fixes race condition when typing fast by using an optimistic ref that
updates immediately before server responds, then syncs with server state.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-30 11:46:01 -06:00