From 6242b7f16ba1e1ef10917c2f7fe497b6cada7de7 Mon Sep 17 00:00:00 2001 From: davidmonterocrespo24 Date: Thu, 14 May 2026 22:53:34 +0200 Subject: [PATCH] fix(minimap): hit-test against clamped rect + shrink to 100x75 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two unrelated minimap issues from user feedback: 1. Click on the red viewport rect was sometimes teleporting the canvas instead of starting a drag. Cause: insideRect compared click coords against the UNCLAMPED rectX/rectY/rectW/rectH, but the rendered rect uses clampedX/clampedY (which differ when the user pans past a world edge). The user clicked on the visible red rect, but the logical rect was off-minimap → insideRect returned false → fell through to the teleport branch. Fix: compute clamped values once at the top, render and hit-test against the same values. Drag now only fires when the click really lands inside the visible rect. 2. The 140x105 default still ate too much canvas at typical zoom. Drop to 100x75 (12% of world width by 2.5%, same proportions as the world). Mobile breakpoint dropped to 90x68 to stay proportionally smaller on phones. --- .../components/simulator/CanvasMinimap.css | 4 +-- .../components/simulator/CanvasMinimap.tsx | 32 +++++++++++-------- 2 files changed, 20 insertions(+), 16 deletions(-) diff --git a/frontend/src/components/simulator/CanvasMinimap.css b/frontend/src/components/simulator/CanvasMinimap.css index 3bcac176..79f5590a 100644 --- a/frontend/src/components/simulator/CanvasMinimap.css +++ b/frontend/src/components/simulator/CanvasMinimap.css @@ -70,8 +70,8 @@ @media (max-width: 720px) { /* On phones the minimap eats too much of the canvas; shrink it. */ .canvas-minimap { - width: 140px !important; - height: 105px !important; + width: 90px !important; + height: 68px !important; bottom: 12px; right: 12px; } diff --git a/frontend/src/components/simulator/CanvasMinimap.tsx b/frontend/src/components/simulator/CanvasMinimap.tsx index 840b837f..ccb7f71a 100644 --- a/frontend/src/components/simulator/CanvasMinimap.tsx +++ b/frontend/src/components/simulator/CanvasMinimap.tsx @@ -28,8 +28,8 @@ import type { Component } from '../../types/components'; import type { BoardInstance } from '../../types/board'; import './CanvasMinimap.css'; -const MINIMAP_W = 140; -const MINIMAP_H = 105; +const MINIMAP_W = 100; +const MINIMAP_H = 75; const WORLD_W = 4000; const WORLD_H = 3000; const SCALE_X = MINIMAP_W / WORLD_W; @@ -81,6 +81,17 @@ export const CanvasMinimap: React.FC = ({ const rectW = (vp.w / zoom) * SCALE_X; const rectH = (vp.h / zoom) * SCALE_Y; + // Clamp the rectangle to the minimap bounds so it never paints outside + // (transiently during pinch-zoom-out, and persistently when the user + // pans past a world edge). We hit-test against these clamped values + // too — otherwise clicking the visible red rect can fall outside the + // unclamped logical rect and erroneously trigger a teleport instead + // of a drag. + const clampedW = Math.min(rectW, MINIMAP_W); + const clampedH = Math.min(rectH, MINIMAP_H); + const clampedX = Math.max(0, Math.min(MINIMAP_W - clampedW, rectX)); + const clampedY = Math.max(0, Math.min(MINIMAP_H - clampedH, rectY)); + // Drag state. We use a ref + window listeners (rather than React's // onMouseMove on the minimap div) so the gesture keeps working even if // the cursor leaves the minimap during a fast pan. @@ -137,10 +148,10 @@ export const CanvasMinimap: React.FC = ({ const localX = e.clientX - rect.left; const localY = e.clientY - rect.top; const insideRect = - localX >= rectX && - localX <= rectX + rectW && - localY >= rectY && - localY <= rectY + rectH; + localX >= clampedX && + localX <= clampedX + clampedW && + localY >= clampedY && + localY <= clampedY + clampedH; if (insideRect) { // Drag mode — record start state, window listeners do the rest. dragRef.current = { @@ -155,7 +166,7 @@ export const CanvasMinimap: React.FC = ({ teleportTo(localX / SCALE_X, localY / SCALE_Y); } }, - [rectX, rectY, rectW, rectH, pan, teleportTo], + [clampedX, clampedY, clampedW, clampedH, pan, teleportTo], ); useEffect(() => { @@ -186,13 +197,6 @@ export const CanvasMinimap: React.FC = ({ }; }, [zoom, setPan, clampPan]); - // Clamp the rendered rectangle to the minimap bounds so it never paints - // outside (happens transiently during pinch-zoom-out). - const clampedX = Math.max(0, Math.min(MINIMAP_W - rectW, rectX)); - const clampedY = Math.max(0, Math.min(MINIMAP_H - rectH, rectY)); - const clampedW = Math.min(rectW, MINIMAP_W); - const clampedH = Math.min(rectH, MINIMAP_H); - return (