From c1a0485b1d5421c09afec480664261e033bc22fa Mon Sep 17 00:00:00 2001 From: Thomas Hallock Date: Sat, 29 Nov 2025 06:06:49 -0600 Subject: [PATCH] feat(know-your-world): unify setup UI positions with gameplay MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Move setup controls to match gameplay UI positions for minimal transition: - Move Mode/Guidance selectors to top-right (same position as gameplay controls) - Keep Start button at bottom-center (prominent, no conflict) - Adjust selector sizes for vertical stack on mobile, horizontal on desktop This minimizes map movement when transitioning from setup to gameplay since both phases now use the same safe zone margins effectively. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .../components/DrillDownMapSelector.tsx | 182 +-------- .../know-your-world/components/SetupPhase.tsx | 378 +++++++++++++++++- 2 files changed, 370 insertions(+), 190 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 22ccb3cb..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 @@ -36,7 +36,6 @@ import { filterRegionsBySizes, calculateSafeZoneViewBox, type SafeZoneMargins, - ASSISTANCE_LEVELS, } from '../maps' import { getCustomCrop } from '../customCrops' import type { RegionSize, ImportanceLevel, PopulationLevel, FilterCriteria } from '../maps' @@ -54,6 +53,7 @@ import { populationToRange, rangeToPopulation, } from '../utils/regionSizeUtils' +import { preventFlexExpansion } from '../utils/responsiveStyles' /** * Safe zone margins - must match MapRenderer for consistent positioning @@ -143,19 +143,6 @@ const PLANETS: PlanetData[] = [ { id: 'saturn', name: 'Saturn', color: '#ead6b8', size: 1.7, hasRings: true }, ] -/** Game mode options */ -type GameMode = 'cooperative' | 'race' | 'turn-based' - -/** Assistance level options */ -type AssistanceLevel = 'learning' | 'guided' | 'helpful' | 'standard' | 'none' - -/** Game mode display options */ -const GAME_MODE_OPTIONS: Array<{ value: GameMode; emoji: string; label: string }> = [ - { value: 'cooperative', emoji: '🤝', label: 'Co-op' }, - { value: 'race', emoji: '🏁', label: 'Race' }, - { value: 'turn-based', emoji: '↔️', label: 'Turns' }, -] - interface DrillDownMapSelectorProps { /** Callback when selection changes (map/continent for game start) */ onSelectionChange: (mapId: 'world' | 'usa', continentId: ContinentId | 'all') => void @@ -173,14 +160,6 @@ interface DrillDownMapSelectorProps { regionCountsBySize: Record /** When true, fills parent container and uses overlay positioning for UI elements */ fillContainer?: boolean - /** Current game mode (for unified controls in fillContainer mode) */ - gameMode?: GameMode - /** Callback when game mode changes */ - onGameModeChange?: (mode: GameMode) => void - /** Current assistance level (for unified controls in fillContainer mode) */ - assistanceLevel?: AssistanceLevel - /** Callback when assistance level changes */ - onAssistanceLevelChange?: (level: AssistanceLevel) => void } interface BreadcrumbItem { @@ -199,10 +178,6 @@ export function DrillDownMapSelector({ onRegionSizesChange, regionCountsBySize, fillContainer = false, - gameMode, - onGameModeChange, - assistanceLevel, - onAssistanceLevelChange, }: DrillDownMapSelectorProps) { const { resolvedTheme } = useTheme() const isDark = resolvedTheme === 'dark' @@ -1340,8 +1315,8 @@ export function DrillDownMapSelector({ rounded: 'xl', shadow: 'lg', width: '205px', - maxHeight: { base: 'none', md: fillContainer ? 'calc(100vh - 200px)' : 'none' }, - overflowY: 'auto', + maxHeight: { base: 'none', md: fillContainer ? '450px' : 'none' }, + overflow: 'hidden', })} > {/* Filter Criteria Tabs */} @@ -1522,157 +1497,6 @@ export function DrillDownMapSelector({ /> )} - - {/* Game Mode & Assistance Level - only in fillContainer mode */} - {fillContainer && gameMode && onGameModeChange && ( -
- {/* Game Mode Selector */} -
-
- Mode -
-
- {GAME_MODE_OPTIONS.map((option) => { - const isActive = gameMode === option.value - return ( - - ) - })} -
-
- - {/* Assistance Level Selector */} - {assistanceLevel && onAssistanceLevelChange && ( -
-
- Assistance -
-
- {ASSISTANCE_LEVELS.map((level) => { - const isActive = assistanceLevel === level.id - return ( - - ) - })} -
-
- )} -
- )} diff --git a/apps/web/src/arcade-games/know-your-world/components/SetupPhase.tsx b/apps/web/src/arcade-games/know-your-world/components/SetupPhase.tsx index 86fb9e92..4d6c932e 100644 --- a/apps/web/src/arcade-games/know-your-world/components/SetupPhase.tsx +++ b/apps/web/src/arcade-games/know-your-world/components/SetupPhase.tsx @@ -1,11 +1,13 @@ 'use client' import { useCallback, useMemo } from 'react' +import * as Select from '@radix-ui/react-select' import { css } from '@styled/css' import { useTheme } from '@/contexts/ThemeContext' import { useKnowYourWorld } from '../Provider' import { DrillDownMapSelector } from './DrillDownMapSelector' -import { ALL_REGION_SIZES, getFilteredMapDataBySizesSync } from '../maps' +import { ALL_REGION_SIZES, ASSISTANCE_LEVELS, getFilteredMapDataBySizesSync } from '../maps' +import type { AssistanceLevelConfig } from '../maps' import { CONTINENTS, type ContinentId } from '../continents' // Travel-themed content for each region @@ -78,6 +80,49 @@ const REGION_THEMES: Record = { const DEFAULT_THEME: RegionTheme = REGION_THEMES.World +// Generate feature badges for an assistance level +function getFeatureBadges(level: AssistanceLevelConfig): Array<{ label: string; icon: string }> { + const badges: Array<{ label: string; icon: string }> = [] + + if (level.hotColdEnabled) { + badges.push({ label: 'Hot/cold', icon: '🔥' }) + } + + if (level.hintsMode === 'onRequest') { + if (level.autoHintDefault) { + badges.push({ label: 'Auto-hints', icon: '💡' }) + } else { + badges.push({ label: 'Hints', icon: '💡' }) + } + } else if (level.hintsMode === 'limited' && level.hintLimit) { + badges.push({ label: `${level.hintLimit} hints`, icon: '💡' }) + } + + return badges +} + +// Game mode options +const GAME_MODE_OPTIONS = [ + { + value: 'cooperative' as const, + emoji: '🤝', + label: 'Cooperative', + description: 'Work together to find all regions', + }, + { + value: 'race' as const, + emoji: '🏁', + label: 'Race', + description: 'First to click the correct region wins', + }, + { + value: 'turn-based' as const, + emoji: '↔️', + label: 'Turn-Based', + description: 'Take turns finding regions', + }, +] + export function SetupPhase() { const { resolvedTheme } = useTheme() const isDark = resolvedTheme === 'dark' @@ -111,6 +156,11 @@ export function SetupPhase() { [setMap, setContinent] ) + // Get selected options for display + const selectedMode = GAME_MODE_OPTIONS.find((opt) => opt.value === state.gameMode) + const selectedAssistance = ASSISTANCE_LEVELS.find((level) => level.id === state.assistanceLevel) + const selectedAssistanceBadges = selectedAssistance ? getFeatureBadges(selectedAssistance) : [] + // Calculate total region count for start button const totalRegionCount = useMemo(() => { return state.includeSizes.reduce((sum, size) => sum + (regionCountsBySize[size] || 0), 0) @@ -130,6 +180,57 @@ export function SetupPhase() { return REGION_THEMES[contextLabel] ?? DEFAULT_THEME }, [contextLabel]) + // Card trigger styles - responsive dimensions + // On mobile, full width in vertical stack; on desktop, fixed width in horizontal row + const cardTriggerStyles = css({ + display: 'flex', + alignItems: 'center', + gap: { base: '1.5', sm: '3' }, + padding: { base: '1.5', sm: '3' }, + bg: isDark ? 'gray.700/80' : 'white/80', + rounded: 'xl', + cursor: 'pointer', + transition: 'all 0.15s', + width: { base: '160px', sm: '220px' }, + height: { base: '48px', sm: '72px' }, + textAlign: 'left', + _hover: { + bg: isDark ? 'gray.600/90' : 'white', + }, + _focus: { + outline: 'none', + ring: '2px solid', + ringColor: 'blue.500', + }, + }) + + const contentStyles = css({ + bg: isDark ? 'gray.800' : 'white', + border: '1px solid', + borderColor: isDark ? 'gray.600' : 'gray.200', + rounded: 'xl', + shadow: 'xl', + overflow: 'hidden', + zIndex: 1000, + minWidth: '280px', + }) + + const itemStyles = css({ + display: 'flex', + alignItems: 'center', + gap: '3', + padding: '3', + cursor: 'pointer', + outline: 'none', + transition: 'all 0.1s', + _hover: { + bg: isDark ? 'gray.700' : 'blue.50', + }, + '&[data-state="checked"]': { + bg: isDark ? 'blue.900/50' : 'blue.100', + }, + }) + return (
- {/* Start Button - centered at bottom */} + {/* TOP-RIGHT: Settings Panel - positioned like gameplay controls */}
+
+ {/* Game Mode Selector */} + setMode(value as 'cooperative' | 'race' | 'turn-based')} + > + + + {selectedMode?.emoji} + +
+
+ + {selectedMode?.label} + + + ▼ + +
+
+ {selectedMode?.description} +
+
+
+ + + + {GAME_MODE_OPTIONS.map((option) => ( + + {option.emoji} +
+ + + {option.label} + + +
+ {option.description} +
+
+
+ ))} +
+
+
+
+ + {/* Assistance Level Selector */} + + setAssistanceLevel(value as 'learning' | 'guided' | 'helpful' | 'standard' | 'none') + } + > + + + {selectedAssistance?.emoji || '💡'} + +
+
+ + {selectedAssistance?.label} + + + ▼ + +
+
+ {selectedAssistance?.description} +
+ {selectedAssistanceBadges.length > 0 && ( +
+ {selectedAssistanceBadges.map((badge) => ( + + {badge.icon} {badge.label} + + ))} +
+ )} +
+
+ + + + {ASSISTANCE_LEVELS.map((level) => { + const badges = getFeatureBadges(level) + return ( + + {level.emoji} +
+ + + {level.label} + + +
+ {level.description} +
+ {badges.length > 0 && ( +
+ {badges.map((badge) => ( + + {badge.icon} {badge.label} + + ))} +
+ )} +
+
+ ) + })} +
+
+
+
+
+
+ + {/* BOTTOM-CENTER: Start Button - positioned prominently */} +