From 2331f1038c094fadc5fb1994b0ff8e7a6303d351 Mon Sep 17 00:00:00 2001 From: Thomas Hallock Date: Sun, 23 Nov 2025 21:35:32 -0600 Subject: [PATCH] fix: cap zoom when releasing pointer lock (escape key) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When user pressed Escape to exit precision mode, the zoom would stay at the uncapped value (e.g., 50×) instead of animating back down to the capped threshold (20 px/px). This was because: - Zoom was set to uncapped value when precision mode activated - When pointer lock released, no zoom recalculation occurred - Zoom remained "stuck" at the uncapped value Solution: - When pointer lock is released, recalculate zoom with capping applied - If uncapped zoom exceeds threshold, cap it at threshold - This triggers animation to zoom back down to the threshold Changes: - Added zoom recalculation logic in handlePointerLockChange when !isLocked - Calculate screen pixel ratio for uncapped zoom - Cap zoom at threshold if it would exceed it - Set targetZoom to capped value, triggering smooth zoom-out animation 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .../components/MapRenderer.tsx | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/apps/web/src/arcade-games/know-your-world/components/MapRenderer.tsx b/apps/web/src/arcade-games/know-your-world/components/MapRenderer.tsx index e8b697d4..d1a80b9b 100644 --- a/apps/web/src/arcade-games/know-your-world/components/MapRenderer.tsx +++ b/apps/web/src/arcade-games/know-your-world/components/MapRenderer.tsx @@ -251,6 +251,35 @@ export function MapRenderer({ setCursorSquish({ x: 1, y: 1 }) setIsReleasingPointerLock(false) initialCapturePositionRef.current = null + + // When releasing pointer lock, recalculate zoom with capping applied + // The current zoom may be above the threshold (uncapped), so we need to cap it + if (containerRef.current && svgRef.current && uncappedAdaptiveZoomRef.current !== null) { + const containerRect = containerRef.current.getBoundingClientRect() + const svgRect = svgRef.current.getBoundingClientRect() + const magnifierWidth = containerRect.width * 0.5 + const viewBoxParts = mapData.viewBox.split(' ').map(Number) + const viewBoxWidth = viewBoxParts[2] + + if (viewBoxWidth && !isNaN(viewBoxWidth)) { + // Calculate what the screen pixel ratio would be at the uncapped zoom + const uncappedZoom = uncappedAdaptiveZoomRef.current + const magnifiedViewBoxWidth = viewBoxWidth / uncappedZoom + const magnifierScreenPixelsPerSvgUnit = magnifierWidth / magnifiedViewBoxWidth + const mainMapSvgUnitsPerScreenPixel = viewBoxWidth / svgRect.width + const screenPixelRatio = mainMapSvgUnitsPerScreenPixel * magnifierScreenPixelsPerSvgUnit + + // If it exceeds threshold, cap the zoom to stay at threshold + if (screenPixelRatio > PRECISION_MODE_THRESHOLD) { + const maxZoom = PRECISION_MODE_THRESHOLD / (magnifierWidth / svgRect.width) + const cappedZoom = Math.min(uncappedZoom, maxZoom) + console.log( + `[Pointer Lock] Released - capping zoom from ${uncappedZoom.toFixed(1)}× to ${cappedZoom.toFixed(1)}× (threshold: ${PRECISION_MODE_THRESHOLD} px/px)` + ) + setTargetZoom(cappedZoom) + } + } + } } // When pointer lock activates, update target zoom to the uncapped value