From b0305581f90c8c6751b2ac84b02d5e1b05386c6e Mon Sep 17 00:00:00 2001 From: Thomas Hallock Date: Fri, 28 Nov 2025 19:49:53 -0600 Subject: [PATCH] feat(know-your-world): match setup phase map positioning with gameplay MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Update DrillDownMapSelector to use same safe zone calculation as MapRenderer - When fillContainer is true, use custom crop (or full map bounds) with SAFE_ZONE_MARGINS - Setup and gameplay phases now show maps positioned identically - Non-fillContainer mode keeps expanded viewBox for browsing context 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .../components/DrillDownMapSelector.tsx | 88 ++++++++++++++++++- 1 file changed, 86 insertions(+), 2 deletions(-) diff --git a/apps/web/src/arcade-games/know-your-world/components/DrillDownMapSelector.tsx b/apps/web/src/arcade-games/know-your-world/components/DrillDownMapSelector.tsx index f94e3470..0a8404f8 100644 --- a/apps/web/src/arcade-games/know-your-world/components/DrillDownMapSelector.tsx +++ b/apps/web/src/arcade-games/know-your-world/components/DrillDownMapSelector.tsx @@ -34,7 +34,10 @@ import { filterRegionsByImportance, filterRegionsByPopulation, filterRegionsBySizes, + calculateSafeZoneViewBox, + type SafeZoneMargins, } from '../maps' +import { getCustomCrop } from '../customCrops' import type { RegionSize, ImportanceLevel, PopulationLevel, FilterCriteria } from '../maps' import { CONTINENTS, @@ -52,6 +55,17 @@ import { } from '../utils/regionSizeUtils' import { preventFlexExpansion } from '../utils/responsiveStyles' +/** + * Safe zone margins - must match MapRenderer for consistent positioning + * These define areas reserved for floating UI elements during gameplay + */ +const SAFE_ZONE_MARGINS: SafeZoneMargins = { + top: 290, // Space for nav (~150px) + floating prompt (~140px with name input) + right: 200, // Space for controls panel (hint, give up, hot/cold buttons) + bottom: 0, // Error banner can overlap map + left: 0, +} + /** * Size options for the range thermometer, ordered from largest to smallest */ @@ -252,6 +266,29 @@ export function DrillDownMapSelector({ const subMapId = path[1] const subMapData = getSubMapData(subMapId) if (subMapData) { + // For USA in fillContainer mode, use safe zone calculation to match gameplay + if (fillContainer && containerDimensions.width > 0 && containerDimensions.height > 0) { + const originalBounds = parseViewBox(subMapData.viewBox) + // USA doesn't have custom crops, so use full map bounds + const customCrop = getCustomCrop('usa', 'all') + const cropRegion = customCrop ? parseViewBox(customCrop) : originalBounds + + const safeZoneViewBox = calculateSafeZoneViewBox( + containerDimensions.width, + containerDimensions.height, + SAFE_ZONE_MARGINS, + cropRegion, + originalBounds + ) + + return { + mapData: subMapData, + viewBox: safeZoneViewBox, + visibleRegions: undefined, + highlightedRegions: [], + } + } + return { mapData: subMapData, viewBox: subMapData.viewBox, @@ -265,8 +302,55 @@ export function DrillDownMapSelector({ // path[0] is guaranteed to be ContinentId when currentLevel >= 1 const continentId: ContinentId | 'all' = currentLevel >= 1 && path[0] ? path[0] : 'all' + // For fillContainer mode (playing phase), use the same safe zone calculation as MapRenderer + // This ensures the map positioning matches exactly between setup and gameplay + if (fillContainer && containerDimensions.width > 0 && containerDimensions.height > 0) { + const originalBounds = parseViewBox(WORLD_MAP.viewBox) + + // Use custom crop if defined, otherwise use full map bounds (same logic as MapRenderer) + const customCrop = continentId !== 'all' ? getCustomCrop('world', continentId) : null + const cropRegion = customCrop ? parseViewBox(customCrop) : originalBounds + + const safeZoneViewBox = calculateSafeZoneViewBox( + containerDimensions.width, + containerDimensions.height, + SAFE_ZONE_MARGINS, + cropRegion, + originalBounds + ) + + // Filter visible regions if on continent level + const visible = + continentId !== 'all' + ? filterRegionsByContinent(WORLD_MAP.regions, continentId).map((r) => r.id) + : undefined + + // Get regions with sub-maps for this continent (or all continents if world level) + let highlighted: string[] = [] + if (continentId !== 'all') { + highlighted = getSubMapsForContinent(continentId) + } else { + const allContinentIds: ContinentId[] = [ + 'africa', + 'asia', + 'europe', + 'north-america', + 'oceania', + 'south-america', + ] + highlighted = allContinentIds.flatMap((cId) => getSubMapsForContinent(cId)) + } + + return { + mapData: WORLD_MAP, + viewBox: safeZoneViewBox, + visibleRegions: visible, + highlightedRegions: highlighted, + } + } + + // Non-fillContainer mode: use expanded viewBox for better context during setup // Calculate viewBox for continent (or full world) - // For the selector, we zoom out a bit more than gameplay for better context const gameplayViewBox = calculateContinentViewBox( WORLD_MAP.regions, continentId, @@ -341,7 +425,7 @@ export function DrillDownMapSelector({ visibleRegions: visible, highlightedRegions: highlighted, } - }, [currentLevel, path, containerDimensions]) + }, [currentLevel, path, containerDimensions, fillContainer]) // Region groups for hover highlighting at world level // Maps each country to its continent so hovering one country highlights all countries in that continent