Commit Graph

31 Commits

Author SHA1 Message Date
Thomas Hallock cb4114f344 fix: improve magnifier zoom calculation for multi-piece regions
Problem:
- Portugal's bounding box includes distant Atlantic islands, causing mainland to be ignored
- Algorithm was selecting "largest piece by area" which picked island groups instead of mainland
- This caused Portugal to dominate zoom calculations and prevent Gibraltar from being prioritized

Solution:
- Changed pre-computation to use FIRST piece instead of largest (mainland is typically piece 1)
- Added showDebugBoundingBoxes prop to hide debug rectangles in production
- Improved zoom animation smoothness with gentler spring easing (tension: 120, friction: 30)

Technical details:
- Multi-piece SVG paths split by `z m` separator
- First piece is mainland, subsequent pieces are islands/territories
- Pre-computed sizes cached in useEffect for performance
- Only Portugal logs to console for debugging

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-19 18:06:35 -06:00
Thomas Hallock 1a54f09814 feat: implement binary search for optimal zoom level
Replace area-based formula with binary search to find zoom where
regions occupy 10-20% of magnifier area.

Binary search algorithm:
1. Start with minZoom=1, maxZoom=1000
2. Test midpoint zoom level
3. Calculate how much of magnified view each region occupies
4. If no regions fit → zoom out (maxZoom = mid)
5. If regions < 10% → zoom in (minZoom = mid)
6. If regions > 20% → zoom out (maxZoom = mid)
7. If 10-20% → perfect, done!
8. Iterate max 20 times or until range < 0.1

This handles all regions in the detection box, not just the one
under cursor, giving better overall framing.

For Gibraltar area:
- Binary search will find zoom ~800-1000x where Gibraltar occupies
  10-20% of magnifier
- Converges in ~10-15 iterations

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-19 18:06:35 -06:00
Thomas Hallock 1a690e00b0 fix: remove duplicate containerRect declaration
TypeScript error: containerRect was declared twice in the same scope.
The variable is already declared at the top of handleMouseMove, no
need to redeclare it when calculating magnifier position.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-19 18:06:35 -06:00
Thomas Hallock 6736336317 feat: calculate zoom based on region under cursor, target 15% area
Previous approach used smallest region in detection box for zoom, which
caused Spain to zoom to 1000x just because Gibraltar was nearby.

New approach:
- Use region UNDER CURSOR for zoom calculation (not smallest in box)
- Calculate zoom so region occupies 15% of magnifier area
- Formula: zoom = sqrt((magnifierArea × 0.15) / regionArea)

For Gibraltar (0.018px² area) in ~200,000px² magnifier:
- Target: 200,000 × 0.15 = 30,000px² in magnifier
- zoom = sqrt(30,000 / 0.018) ≈ 1,291x → capped at 1000x
- Result: Gibraltar occupies ~14% of magnifier (close to target)

For Spain (5,000px² area):
- Target: 200,000 × 0.15 = 30,000px² in magnifier
- zoom = sqrt(30,000 / 5,000) ≈ 2.4x
- Result: Spain occupies 15% of magnifier

This gives predictable, proportional zoom levels based on what
you're actually hovering over, not what's nearby.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-19 18:06:35 -06:00
Thomas Hallock 912dc385b3 fix: use screen pixels for zoom, abandon SVG path parsing
The calculateBoundingBox function doesn't handle relative SVG path
commands correctly. Gibraltar's path uses 'm' (relative move) followed
by tiny relative offsets like -0.009, 0.047, but the function treated
them as absolute coordinates, giving width of 460 units instead of 0.08.

Proper SVG path parsing requires:
- Tracking current position as you iterate through commands
- Handling both absolute (M,L,H,V,C,S,Q,T,A) and relative (m,l,h,v,c,s,q,t,a)
- Curves need Bezier math to find actual bounds
- Too complex for this use case

Solution: Use screen pixels (getBoundingClientRect) for everything.
Screen pixel measurements work correctly for tiny regions:
- Gibraltar: 0.08px (accurate)
- Jersey: 0.82px (accurate)

New zoom formula for screen pixels:
- Sub-pixel (< 1px): 1000/(size+0.05)
  - Gibraltar (0.08px): ~7692x → capped at 1000x
- Tiny (1-10px): 500/(size+0.5)
  - 1px: ~333x
  - 5px: ~91x
- Small (10-50px): Linear up to +50x

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-19 18:06:35 -06:00
Thomas Hallock d72f309bad fix: track both SVG units and screen pixels for zoom and dampening
The previous commit broke cursor dampening and magnifier triggers by
using SVG units for everything. We need both measurements:

- **SVG units** (0-1010): Accurate zoom calculation
- **Screen pixels**: Cursor dampening and magnifier triggers

Changes:
- Track detectedSmallestSize (SVG units) for zoom calculation
- Track detectedSmallestScreenSize (screen pixels) for dampening
- Use screen pixels for getMovementMultiplier() (expects <1px, <5px, <15px)
- Use screen pixels for hasSmallRegion trigger (<15px)
- Debug logging now shows both values

Example for Gibraltar:
- SVG size: 0.08 units → 1000x zoom
- Screen size: 0.08px → 3% cursor speed (ultra precision)

This restores cursor dampening behavior while keeping accurate
SVG-based zoom calculations.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-19 18:06:35 -06:00
Thomas Hallock 0dcaabb8a5 fix: use SVG viewBox units instead of screen pixels for zoom
The previous implementation used getBoundingClientRect() which returns
axis-aligned bounding boxes in screen pixels. This has two problems:

1. **Inaccurate sizing**: Bounding boxes for irregular/rotated shapes
   are much larger than the actual visible area
2. **Floating-point precision**: Sub-pixel screen measurements lose
   precision and cause most regions to trigger max zoom

Solution: Use SVG path bounding box from viewBox coordinates

The world map viewBox is 1010 × 666 SVG units:
- Gibraltar: ~0.08 SVG units (extremely tiny!)
- Jersey: ~0.5 SVG units
- Most countries: 10-100 SVG units

New zoom formula for SVG units:
- Tiny regions (< 5 units): 200 / (size + 0.1)
  - Gibraltar (0.08): 200/0.18 ≈ 1111x → capped at 1000x
  - 1 unit: 200/1.1 ≈ 182x
  - 5 units: 200/5.1 ≈ 39x
- Small regions (5-100 units): Linear scaling up to +50x
- Large regions (>100 units): Minimal size-based zoom

This avoids floating-point precision issues and gives accurate
zoom levels based on the region's true SVG path dimensions.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-19 18:06:35 -06:00
Thomas Hallock a6be05f8c1 feat: increase max zoom to 1000x with detailed debug logging
Increase MAX_ZOOM from 500x to 1000x and double the exponential
scaling formula to handle Gibraltar's extreme size.

New zoom calculation for sub-pixel regions:
- Formula: 1000 / (size + 0.05)
- Gibraltar (0.08px): ~770x size zoom → ~800x total (capped at 1000x)
- Jersey (0.82px): ~115x size zoom → ~145x total
- 1px: ~95x size zoom → ~125x total

Added detailed debug logging for Gibraltar to diagnose:
- Exact region size (4 decimal places)
- Breakdown: base (10) + density + size zoom
- Total before/after clamping
- Whether we're hitting max zoom limit

This will help determine if we're hitting floating-point precision
limits or if Gibraltar needs even more magnification.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-19 18:06:35 -06:00
Thomas Hallock 101213ba1c feat: add exponential zoom scaling for sub-pixel regions
Gibraltar at 0.08px needs extreme magnification to be clickable.
Increased max zoom from 120x to 500x and added exponential scaling
for sub-pixel regions.

New zoom calculation:
- Base: 10x
- Density: up to +20x
- Size-based (exponential for <1px): 500 / (size + 0.05)
  - Gibraltar (0.08px): ~385x + base/density = ~415x total
  - Jersey (0.82px): ~57x + base/density = ~87x total
  - 1px regions: ~50x + base/density = ~80x total
- Size-based (linear for >=1px): up to +150x

Gold border threshold increased to 100x to indicate extreme zoom.

This exponential scaling ensures the tiniest regions get massive
magnification while regular small regions get reasonable zoom.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-19 18:06:35 -06:00
Thomas Hallock 0195a6dc6d fix: remove unused velocity tracking and fix TypeScript errors
Remove leftover velocity calculation code that was used for quick-escape
logic (no longer needed with unified zoom).

Fix TypeScript errors:
- Remove lastMoveTimeRef reference (not needed)
- Fix detectedRegions type - it's string[], not MapRegion[]
- Use .includes('gi') instead of .some(r => r.id === 'gi')
- Log detectedRegionIds instead of trying to map to names

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-19 18:06:34 -06:00
Thomas Hallock d1acdd9e54 refactor: unify zoom modes into single adaptive zoom
Simplify precision controls by removing dual zoom modes (normal + super).
Now uses a single unified adaptive zoom that immediately calculates the
right magnification based on region size.

Changes:
- Remove hover delay timer (HOVER_DELAY_MS)
- Remove super zoom state (superZoomActive)
- Remove quick-escape velocity threshold
- Unify zoom calculation: 8x base + up to 16x density + up to 96x size
- Gold border now shows for any zoom >60x (not just "super zoom")

Zoom calculation for Gibraltar (0.08px):
- Base: 8x
- Density (4 regions): +6x
- Size factor (0.996): +92x
- Total: ~106x (immediately, no delay)

Benefits:
- Simpler UX - no waiting for super zoom to activate
- More responsive - zoom adjusts immediately to region size
- Less code complexity - no timer management or state tracking
- Predictable behavior - zoom level purely based on what's visible

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-19 18:06:34 -06:00
Thomas Hallock 138e6c071b fix: increase super zoom multiplier from 2.5x to 5.0x for Gibraltar
Gibraltar is only 0.08px wide and needs extreme magnification.
Previous multiplier of 2.5x only achieved ~48x zoom, which was
insufficient to make Gibraltar visible and clickable.

New calculation:
- Base adaptive zoom: ~19x (based on size + density)
- Super zoom multiplier: 5.0x
- Result: ~95-120x zoom for Gibraltar

This should make Gibraltar clearly visible in the magnifier at
maximum super zoom.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-19 18:06:34 -06:00
Thomas Hallock 07fe9e882c debug: add targeted logging for Gibraltar tracking
Add console logging to help debug why Gibraltar isn't visible:

1. Map filtering: Log whether Gibraltar is included or excluded
2. Region sizing: Log Gibraltar's actual pixel dimensions
3. Magnifier detection: Log when Gibraltar is detected in hover area
4. Zoom levels: Log final zoom when Gibraltar is in view

Logs are targeted to avoid excessive output:
- Only log Gibraltar specifically (id='gi')
- Only log ultra-tiny regions (< 1-2px)
- Skip logging for normal-sized regions

This will help diagnose if Gibraltar is:
- Being filtered out by difficulty
- Too small to render on screen
- Not being detected by precision controls
- Not receiving sufficient zoom magnification

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-19 18:06:34 -06:00
Thomas Hallock 9b782beabf feat: increase max super zoom to 120x for ultra-tiny regions
Increase MAX_ZOOM_SUPER from 60x to 120x to handle extremely small
regions like Gibraltar (0.08px). The previous 60x zoom was still
insufficient for clicking such tiny regions.

Changes:
- Add MAX_ZOOM_NORMAL constant (24x)
- Add MAX_ZOOM_SUPER constant (120x)
- Use constants instead of hardcoded values in zoom calculations
- Super zoom now reaches up to 120x magnification for sub-pixel regions

This should make Gibraltar and other extremely tiny territories
clickable with the pointer lock precision controls.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-19 18:06:34 -06:00
Thomas Hallock d7ce474a51 feat: make super zoom threshold configurable and increase to 3px
Add SUPER_ZOOM_SIZE_THRESHOLD constant (default: 3px)
- Previously hardcoded at < 1px (only sub-pixel regions)
- Now activates for regions < 3px (more helpful range)
- Easy to tune for different preferences

Update logging to reference the configurable threshold.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-19 18:06:34 -06:00
Thomas Hallock 814bf949f2 fix: center crosshairs and re-enable pointer lock after escape
**Crosshair positioning:**
- Fix vertical line: left: 50%, top: 0 (was top: 50%)
- Fix horizontal line: left: 0, top: 50% (was left: 50%)
- Both now properly centered in the circle

**Re-enable pointer lock:**
- Show prompt overlay again when pointer lock is released
- User can press Escape to exit, then click to re-enter
- Handles both user-initiated unlock (Escape) and errors

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-19 18:06:34 -06:00
Thomas Hallock 0ed4d13db6 fix: move pointer lock management to MapRenderer
**Problem:** Pointer lock was activating on MapRenderer's container,
but PlayingPhase was trying to manage it, so state never updated.

**Solution:** Move all pointer lock management into MapRenderer:
- Add pointerLocked state in MapRenderer
- Add pointer lock event listeners in MapRenderer
- Add click handler to request lock on MapRenderer container
- Add "Enable Precision Controls" overlay in MapRenderer
- Remove all pointer lock code from PlayingPhase

Now pointer lock state tracks correctly and custom cursor will render.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-19 18:06:34 -06:00
Thomas Hallock 0dbb2eb83c debug: add logging for pointer lock and custom cursor
Add extensive console logging to debug cursor visibility:

**PlayingPhase:**
- Log when pointer lock listeners attach/detach
- Log pointer lock state changes with element details
- Log container click events with conditions
- Log pointerLocked state changes

**MapRenderer:**
- Log custom cursor render conditions on every render
- Log when cursor should be visible
- Log cursor position when rendering

This will help diagnose why custom cursor isn't visible.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-19 18:06:34 -06:00
Thomas Hallock 5d6d6b4ddc fix: add custom cursor when pointer lock is active
When pointer lock is active, the browser hides the native cursor.
Add a custom crosshair cursor that follows the tracked position:
- 20px circle with crosshair lines
- Blue color (matches theme)
- Positioned at tracked cursor coordinates
- Only visible when pointerLocked is true

This ensures users can always see where they're pointing.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-19 18:06:34 -06:00
Thomas Hallock 749b16ac27 refactor: simplify pointer lock with movement delta multipliers
Major refactoring of precision controls for Know Your World game:

**New Flow:**
- Pointer lock requested once when PlayingPhase mounts (user click)
- Active for entire game session until unmount
- Simpler: use movementX/movementY with adaptive multiplier

**PlayingPhase.tsx:**
- Add pointer lock management with event listeners
- Show "Enable Precision Controls" overlay on mount
- Request pointer lock on first click (user gesture)
- Release on unmount (game ends)
- Pass pointerLocked prop to MapRenderer

**MapRenderer.tsx:**
- Accept pointerLocked as prop (don't manage internally)
- Remove complex cursor dampening (dual refs: raw vs dampened)
- Simplified: apply multiplier to movementX/movementY based on region size
  - Sub-pixel regions (<1px): 3% speed
  - Tiny regions (1-5px): 10% speed
  - Small regions (5-15px): 25% speed
  - Larger regions: 100% speed
- Remove precision mode state (not needed)
- Remove cooldown logic (not needed)
- Remove click capture handler (not needed)
- Update cursor style and mouse handlers to use pointerLocked

**Benefits:**
- Much simpler code (removed ~100 lines of complexity)
- No lag when changing direction (no interpolation)
- Pointer lock active entire session (can't drift off map)
- Movement multiplier clearer than dampening

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-19 18:06:34 -06:00
Thomas Hallock 5e42aabfa9 refactor: remove debug logs for fixed issues
Remove verbose console logging that was used to debug issues that are now fixed:
- Velocity logging (quick-escape threshold is now correct)
- Dampening state logs (dampening lag is fixed)
- Region detection logs (crosshair accuracy is fixed)
- Per-frame precision mode state checks (too verbose)

Keep only important logs:
- Quick escape triggers
- Precision mode state changes
- Pointer lock debugging (current issue)
- Magnifier state changes

This reduces console noise while keeping useful feedback.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-19 18:06:34 -06:00
Thomas Hallock cf2d63f5e6 debug: add verbose pointer lock logging
Add detailed logging to debug pointer lock activation:
- Log useEffect triggers with all conditions
- Log pointer lock request attempts and results
- Log pointer lock state in precision mode checks
- Add try/catch around requestPointerLock
- Warn if container ref is missing

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-19 18:06:34 -06:00
Thomas Hallock 4d5953d034 feat: add Pointer Lock API for precision mode to prevent edge issues
When cursor dampening is active, the real mouse can drift off the map while the dampened cursor is still on it, causing magnifier to disappear. Pointer Lock solves this.

Changes:
- Add pointerLocked state and event listeners
- Request pointer lock when precision mode activates
- Use movementX/Y deltas instead of absolute position when locked
- Clamp calculated position to container bounds
- Update SVG bounds check for both locked and normal modes
- Ignore mouse leave events when pointer is locked
- Release pointer lock when precision mode deactivates

Benefits:
- No more hitting viewport edges during precision mode
- Real cursor disappears, only dampened cursor visible
- Smooth movement tracking without boundary issues
- Magnifier stays active even if real mouse would leave container

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-19 18:06:34 -06:00
Thomas Hallock 97b214da12 fix: lower quick-escape threshold to 15px/frame for easier triggering
Console logs showed user reaching 22px/frame when trying to escape, but 30px threshold was too high. This required an unrealistically aggressive mouse flick.

Changes:
- Lower QUICK_MOVE_THRESHOLD from 30px to 15px/frame (2x easier to trigger)
- Increase PRECISION_MODE_COOLDOWN from 800ms to 1200ms (more time to escape area)

With these values, moderate-speed mouse movements will trigger escape, and users have more time to move away before precision mode can re-activate.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-19 18:06:34 -06:00
Thomas Hallock e712bcd66d debug: add verbose logging for quick-escape and precision mode state
Add detailed console logging to debug quick-escape issues:

- Log velocity every frame when in precision mode or moving
- Show velocity vs threshold and whether escape will trigger
- Log when quick-escape actually triggers
- Log precision mode state check every frame:
  - shouldShow, cooldownActive, time remaining
  - current vs target precision mode state
  - smallest region size
- Log when precision mode state actually changes

This will help identify why quick-escape isn't working as expected.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-19 18:06:34 -06:00
Thomas Hallock e885ae7ef4 fix: add cooldown after quick-escape to prevent precision mode re-activation
After quick-escape, precision mode was immediately re-enabling because the user was still hovering over small regions. This made it feel impossible to escape dampening.

Changes:
- Add precisionModeCooldownRef to track cooldown expiration time
- Lower QUICK_MOVE_THRESHOLD from 50px to 30px (easier to trigger)
- Add PRECISION_MODE_COOLDOWN_MS = 800ms grace period after escape
- Precision mode won't re-activate during cooldown even if over small regions
- Cooldown resets on mouse leave

Result: Users can now easily escape precision mode with a quick mouse flick, and it won't immediately re-engage. After 800ms cooldown, precision mode can auto-activate again if still over small regions.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-19 18:06:34 -06:00
Thomas Hallock fc08b775db fix: eliminate cursor dampening lag when changing direction
Fix cursor dampening feedback loop that caused lag when reversing mouse direction. The previous implementation tracked the dampened position, creating momentum/lag when changing direction.

Changes:
- Split lastCursorRef into two separate refs:
  - lastRawCursorRef: tracks actual raw mouse position
  - dampenedCursorRef: tracks current dampened position
- Calculate delta from raw-to-raw (not dampened-to-raw)
- Direction changes now respond instantly, just at reduced speed
- No more "continuation" effect when reversing direction

Technical details:
- Velocity calculation now uses raw positions for accuracy
- First frame of precision mode initializes dampened cursor at raw position
- Both refs reset on mouse leave
- Debug logging updated to show both raw and dampened positions

Result: Cursor dampening feels responsive and precise, no lag when changing direction.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-19 18:06:34 -06:00
Thomas Hallock 3bf127f344 feat: add precision controls for tiny regions in Know Your World
Implement automatic cursor dampening, super zoom on hover, and quick-escape to make sub-pixel regions (Gibraltar 0.08px, Jersey 0.82px) clickable. Fix crosshair accuracy to match dampened cursor position, add excluded region visualization (gray pre-labeled), and increase unfound region contrast (0.3→0.7 opacity).

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-19 18:06:34 -06:00
Thomas Hallock 1e8846cdb1 feat: add adaptive zoom magnifier for Know Your World map
Add intelligent magnifying glass feature with smooth animations:

Magnifier Features:
- Appears when 7+ regions overlap in 50×50px box OR region <8px
- Adaptive zoom (8×-24×) based on region density and size
- Smooth zoom/opacity animations using react-spring
- Dynamic positioning to avoid covering cursor
- Visual indicator box on main map shows magnified area
- Crosshair shows exact cursor position in magnified view

Implementation Details:
- Uses react-spring for smooth zoom and fade transitions
- Position calculated per quadrant (opposite corner from cursor)
- Zoom formula: base 8× + density factor + size factor
- Animated SVG viewBox for seamless zooming
- Dashed blue indicator rectangle tracks magnified region

UI/UX Improvements:
- Remove duplicate turn indicator (use player avatar dock)
- Hide arrow labels feature behind flag (disabled by default)
- Add Storybook stories for map renderer with tuning controls

This makes clicking tiny island nations and crowded regions much easier!

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-18 20:04:52 -06:00
Thomas Hallock 7bb03b8409 feat: add continent filtering to Know Your World game
Add continent-based filtering for the world map to make the game more playable by reducing visual clutter:

Continent Selection:
- Add continent selector to setup screen (only for world map)
- 7 continents + "All" option: Africa, Asia, Europe, North America, South America, Oceania, Antarctica
- Each continent button shows emoji and name

Map Filtering:
- Filter world map regions by selected continent using ISO 3166-1 country codes
- Calculate bounding box for filtered regions
- Automatically crop and scale viewBox to show only selected continent
- Add 10% padding around continent bounding box for better visibility

Technical Implementation:
- Create continents.ts with comprehensive country-to-continent mappings (256 countries)
- Add getFilteredMapData() function to filter regions and adjust viewBox
- Add calculateBoundingBox() to compute min/max coordinates from SVG paths
- Add selectedContinent field to game state and config (persisted to database)
- Add SET_CONTINENT move type and validator
- Update all map rendering components (StudyPhase, PlayingPhase, MapRenderer)

Benefits:
- Solves "too many small countries" problem on world map
- Allows focused study of specific geographic regions
- Dynamic viewBox adjustment provides optimal zoom level
- Maintains full world map option for comprehensive play

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-18 20:04:52 -06:00
Thomas Hallock 25e24a7cbc feat: add Know Your World geography quiz game
Add new arcade game for testing geography knowledge:

Game Features:
- 4 phases: Setup, Study, Playing, Results
- 3 multiplayer modes: Cooperative, Race, Turn-Based
- 2 maps: World countries, USA states
- Configurable study mode (0, 30, 60, or 120 seconds)
- Return to Setup and New Game options in game menu
- Small region labels with arrows for improved visibility

Map Rendering:
- 8-color deterministic palette with hash-based assignment
- Opacity-based states (20-27% unfound, 100% found)
- Enhanced label visibility with text shadows
- Smart bounding box calculation for small regions
- Supports both easy (outlines always visible) and hard (outlines on hover/found) difficulty

Game Modes:
- Cooperative: All players work together to find all regions
- Race: First to click gets the point
- Turn-Based: Players take turns finding regions

Study Phase:
- Optional timed study period before quiz starts
- Shows all region labels for memorization
- Countdown timer with skip option

Dependencies:
- Add @svg-maps/world and @svg-maps/usa packages

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-18 20:04:52 -06:00