feat(know-your-world): unify setup UI positions with gameplay
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 <noreply@anthropic.com>
This commit is contained in:
parent
a4f9db6d3f
commit
c1a0485b1d
|
|
@ -36,7 +36,6 @@ import {
|
||||||
filterRegionsBySizes,
|
filterRegionsBySizes,
|
||||||
calculateSafeZoneViewBox,
|
calculateSafeZoneViewBox,
|
||||||
type SafeZoneMargins,
|
type SafeZoneMargins,
|
||||||
ASSISTANCE_LEVELS,
|
|
||||||
} from '../maps'
|
} from '../maps'
|
||||||
import { getCustomCrop } from '../customCrops'
|
import { getCustomCrop } from '../customCrops'
|
||||||
import type { RegionSize, ImportanceLevel, PopulationLevel, FilterCriteria } from '../maps'
|
import type { RegionSize, ImportanceLevel, PopulationLevel, FilterCriteria } from '../maps'
|
||||||
|
|
@ -54,6 +53,7 @@ import {
|
||||||
populationToRange,
|
populationToRange,
|
||||||
rangeToPopulation,
|
rangeToPopulation,
|
||||||
} from '../utils/regionSizeUtils'
|
} from '../utils/regionSizeUtils'
|
||||||
|
import { preventFlexExpansion } from '../utils/responsiveStyles'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Safe zone margins - must match MapRenderer for consistent positioning
|
* 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 },
|
{ 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 {
|
interface DrillDownMapSelectorProps {
|
||||||
/** Callback when selection changes (map/continent for game start) */
|
/** Callback when selection changes (map/continent for game start) */
|
||||||
onSelectionChange: (mapId: 'world' | 'usa', continentId: ContinentId | 'all') => void
|
onSelectionChange: (mapId: 'world' | 'usa', continentId: ContinentId | 'all') => void
|
||||||
|
|
@ -173,14 +160,6 @@ interface DrillDownMapSelectorProps {
|
||||||
regionCountsBySize: Record<string, number>
|
regionCountsBySize: Record<string, number>
|
||||||
/** When true, fills parent container and uses overlay positioning for UI elements */
|
/** When true, fills parent container and uses overlay positioning for UI elements */
|
||||||
fillContainer?: boolean
|
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 {
|
interface BreadcrumbItem {
|
||||||
|
|
@ -199,10 +178,6 @@ export function DrillDownMapSelector({
|
||||||
onRegionSizesChange,
|
onRegionSizesChange,
|
||||||
regionCountsBySize,
|
regionCountsBySize,
|
||||||
fillContainer = false,
|
fillContainer = false,
|
||||||
gameMode,
|
|
||||||
onGameModeChange,
|
|
||||||
assistanceLevel,
|
|
||||||
onAssistanceLevelChange,
|
|
||||||
}: DrillDownMapSelectorProps) {
|
}: DrillDownMapSelectorProps) {
|
||||||
const { resolvedTheme } = useTheme()
|
const { resolvedTheme } = useTheme()
|
||||||
const isDark = resolvedTheme === 'dark'
|
const isDark = resolvedTheme === 'dark'
|
||||||
|
|
@ -1340,8 +1315,8 @@ export function DrillDownMapSelector({
|
||||||
rounded: 'xl',
|
rounded: 'xl',
|
||||||
shadow: 'lg',
|
shadow: 'lg',
|
||||||
width: '205px',
|
width: '205px',
|
||||||
maxHeight: { base: 'none', md: fillContainer ? 'calc(100vh - 200px)' : 'none' },
|
maxHeight: { base: 'none', md: fillContainer ? '450px' : 'none' },
|
||||||
overflowY: 'auto',
|
overflow: 'hidden',
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
{/* Filter Criteria Tabs */}
|
{/* Filter Criteria Tabs */}
|
||||||
|
|
@ -1522,157 +1497,6 @@ export function DrillDownMapSelector({
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Game Mode & Assistance Level - only in fillContainer mode */}
|
|
||||||
{fillContainer && gameMode && onGameModeChange && (
|
|
||||||
<div
|
|
||||||
data-element="game-settings"
|
|
||||||
className={css({
|
|
||||||
borderTop: '1px solid',
|
|
||||||
borderColor: isDark ? 'gray.700' : 'gray.300',
|
|
||||||
marginTop: '2',
|
|
||||||
paddingTop: '2',
|
|
||||||
display: 'flex',
|
|
||||||
flexDirection: 'column',
|
|
||||||
gap: '2',
|
|
||||||
})}
|
|
||||||
>
|
|
||||||
{/* Game Mode Selector */}
|
|
||||||
<div data-element="game-mode-selector">
|
|
||||||
<div
|
|
||||||
className={css({
|
|
||||||
fontSize: 'xs',
|
|
||||||
color: isDark ? 'gray.400' : 'gray.500',
|
|
||||||
marginBottom: '1',
|
|
||||||
})}
|
|
||||||
>
|
|
||||||
Mode
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
className={css({
|
|
||||||
display: 'flex',
|
|
||||||
gap: '1px',
|
|
||||||
bg: isDark ? 'gray.700' : 'gray.200',
|
|
||||||
rounded: 'md',
|
|
||||||
padding: '2px',
|
|
||||||
})}
|
|
||||||
>
|
|
||||||
{GAME_MODE_OPTIONS.map((option) => {
|
|
||||||
const isActive = gameMode === option.value
|
|
||||||
return (
|
|
||||||
<button
|
|
||||||
key={option.value}
|
|
||||||
onClick={() => onGameModeChange(option.value)}
|
|
||||||
className={css({
|
|
||||||
flex: 1,
|
|
||||||
display: 'flex',
|
|
||||||
alignItems: 'center',
|
|
||||||
justifyContent: 'center',
|
|
||||||
gap: '1',
|
|
||||||
padding: '1.5',
|
|
||||||
fontSize: 'xs',
|
|
||||||
fontWeight: isActive ? 'bold' : 'normal',
|
|
||||||
color: isActive
|
|
||||||
? isDark
|
|
||||||
? 'white'
|
|
||||||
: 'gray.900'
|
|
||||||
: isDark
|
|
||||||
? 'gray.400'
|
|
||||||
: 'gray.600',
|
|
||||||
bg: isActive ? (isDark ? 'gray.600' : 'white') : 'transparent',
|
|
||||||
rounded: 'sm',
|
|
||||||
cursor: 'pointer',
|
|
||||||
border: 'none',
|
|
||||||
transition: 'all 0.15s',
|
|
||||||
_hover: {
|
|
||||||
bg: isActive
|
|
||||||
? isDark
|
|
||||||
? 'gray.600'
|
|
||||||
: 'white'
|
|
||||||
: isDark
|
|
||||||
? 'gray.600/50'
|
|
||||||
: 'gray.100',
|
|
||||||
},
|
|
||||||
})}
|
|
||||||
>
|
|
||||||
<span>{option.emoji}</span>
|
|
||||||
<span>{option.label}</span>
|
|
||||||
</button>
|
|
||||||
)
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Assistance Level Selector */}
|
|
||||||
{assistanceLevel && onAssistanceLevelChange && (
|
|
||||||
<div data-element="assistance-selector">
|
|
||||||
<div
|
|
||||||
className={css({
|
|
||||||
fontSize: 'xs',
|
|
||||||
color: isDark ? 'gray.400' : 'gray.500',
|
|
||||||
marginBottom: '1',
|
|
||||||
})}
|
|
||||||
>
|
|
||||||
Assistance
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
className={css({
|
|
||||||
display: 'flex',
|
|
||||||
gap: '1px',
|
|
||||||
bg: isDark ? 'gray.700' : 'gray.200',
|
|
||||||
rounded: 'md',
|
|
||||||
padding: '2px',
|
|
||||||
flexWrap: 'wrap',
|
|
||||||
})}
|
|
||||||
>
|
|
||||||
{ASSISTANCE_LEVELS.map((level) => {
|
|
||||||
const isActive = assistanceLevel === level.id
|
|
||||||
return (
|
|
||||||
<button
|
|
||||||
key={level.id}
|
|
||||||
onClick={() => onAssistanceLevelChange(level.id as AssistanceLevel)}
|
|
||||||
title={level.description}
|
|
||||||
className={css({
|
|
||||||
flex: '1 1 auto',
|
|
||||||
minWidth: '36px',
|
|
||||||
display: 'flex',
|
|
||||||
alignItems: 'center',
|
|
||||||
justifyContent: 'center',
|
|
||||||
padding: '1.5',
|
|
||||||
fontSize: 'sm',
|
|
||||||
fontWeight: isActive ? 'bold' : 'normal',
|
|
||||||
color: isActive
|
|
||||||
? isDark
|
|
||||||
? 'white'
|
|
||||||
: 'gray.900'
|
|
||||||
: isDark
|
|
||||||
? 'gray.400'
|
|
||||||
: 'gray.600',
|
|
||||||
bg: isActive ? (isDark ? 'gray.600' : 'white') : 'transparent',
|
|
||||||
rounded: 'sm',
|
|
||||||
cursor: 'pointer',
|
|
||||||
border: 'none',
|
|
||||||
transition: 'all 0.15s',
|
|
||||||
_hover: {
|
|
||||||
bg: isActive
|
|
||||||
? isDark
|
|
||||||
? 'gray.600'
|
|
||||||
: 'white'
|
|
||||||
: isDark
|
|
||||||
? 'gray.600/50'
|
|
||||||
: 'gray.100',
|
|
||||||
},
|
|
||||||
})}
|
|
||||||
>
|
|
||||||
<span>{level.emoji}</span>
|
|
||||||
</button>
|
|
||||||
)
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,13 @@
|
||||||
'use client'
|
'use client'
|
||||||
|
|
||||||
import { useCallback, useMemo } from 'react'
|
import { useCallback, useMemo } from 'react'
|
||||||
|
import * as Select from '@radix-ui/react-select'
|
||||||
import { css } from '@styled/css'
|
import { css } from '@styled/css'
|
||||||
import { useTheme } from '@/contexts/ThemeContext'
|
import { useTheme } from '@/contexts/ThemeContext'
|
||||||
import { useKnowYourWorld } from '../Provider'
|
import { useKnowYourWorld } from '../Provider'
|
||||||
import { DrillDownMapSelector } from './DrillDownMapSelector'
|
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'
|
import { CONTINENTS, type ContinentId } from '../continents'
|
||||||
|
|
||||||
// Travel-themed content for each region
|
// Travel-themed content for each region
|
||||||
|
|
@ -78,6 +80,49 @@ const REGION_THEMES: Record<string, RegionTheme> = {
|
||||||
|
|
||||||
const DEFAULT_THEME: RegionTheme = REGION_THEMES.World
|
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() {
|
export function SetupPhase() {
|
||||||
const { resolvedTheme } = useTheme()
|
const { resolvedTheme } = useTheme()
|
||||||
const isDark = resolvedTheme === 'dark'
|
const isDark = resolvedTheme === 'dark'
|
||||||
|
|
@ -111,6 +156,11 @@ export function SetupPhase() {
|
||||||
[setMap, setContinent]
|
[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
|
// Calculate total region count for start button
|
||||||
const totalRegionCount = useMemo(() => {
|
const totalRegionCount = useMemo(() => {
|
||||||
return state.includeSizes.reduce((sum, size) => sum + (regionCountsBySize[size] || 0), 0)
|
return state.includeSizes.reduce((sum, size) => sum + (regionCountsBySize[size] || 0), 0)
|
||||||
|
|
@ -130,6 +180,57 @@ export function SetupPhase() {
|
||||||
return REGION_THEMES[contextLabel] ?? DEFAULT_THEME
|
return REGION_THEMES[contextLabel] ?? DEFAULT_THEME
|
||||||
}, [contextLabel])
|
}, [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 (
|
return (
|
||||||
<div
|
<div
|
||||||
data-component="setup-phase"
|
data-component="setup-phase"
|
||||||
|
|
@ -153,15 +254,272 @@ export function SetupPhase() {
|
||||||
onRegionSizesChange={setRegionSizes}
|
onRegionSizesChange={setRegionSizes}
|
||||||
regionCountsBySize={regionCountsBySize}
|
regionCountsBySize={regionCountsBySize}
|
||||||
fillContainer
|
fillContainer
|
||||||
gameMode={state.gameMode}
|
|
||||||
onGameModeChange={setMode}
|
|
||||||
assistanceLevel={state.assistanceLevel}
|
|
||||||
onAssistanceLevelChange={setAssistanceLevel}
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Start Button - centered at bottom */}
|
{/* TOP-RIGHT: Settings Panel - positioned like gameplay controls */}
|
||||||
<div
|
<div
|
||||||
data-element="start-button-container"
|
data-element="setup-settings-panel"
|
||||||
|
className={css({
|
||||||
|
position: 'absolute',
|
||||||
|
top: { base: '160px', sm: '166px' }, // Same as gameplay controls
|
||||||
|
right: { base: '2', sm: '4' },
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
alignItems: 'flex-end',
|
||||||
|
gap: '2',
|
||||||
|
zIndex: 50,
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className={css({
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: { base: 'column', sm: 'row' },
|
||||||
|
alignItems: 'stretch',
|
||||||
|
gap: '2',
|
||||||
|
padding: '2',
|
||||||
|
bg: isDark ? 'gray.800/95' : 'gray.100/95',
|
||||||
|
backdropFilter: 'blur(12px)',
|
||||||
|
rounded: '2xl',
|
||||||
|
shadow: 'xl',
|
||||||
|
maxWidth: { base: '180px', sm: 'fit-content' },
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
{/* Game Mode Selector */}
|
||||||
|
<Select.Root
|
||||||
|
value={state.gameMode}
|
||||||
|
onValueChange={(value) => setMode(value as 'cooperative' | 'race' | 'turn-based')}
|
||||||
|
>
|
||||||
|
<Select.Trigger className={cardTriggerStyles}>
|
||||||
|
<span
|
||||||
|
className={css({
|
||||||
|
fontSize: { base: 'lg', sm: '2xl' },
|
||||||
|
flexShrink: 0,
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
{selectedMode?.emoji}
|
||||||
|
</span>
|
||||||
|
<div className={css({ flex: 1, minWidth: 0 })}>
|
||||||
|
<div
|
||||||
|
className={css({
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: { base: '1', sm: '2' },
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
className={css({
|
||||||
|
fontWeight: '600',
|
||||||
|
fontSize: { base: 'sm', sm: 'md' },
|
||||||
|
color: isDark ? 'gray.100' : 'gray.800',
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
{selectedMode?.label}
|
||||||
|
</span>
|
||||||
|
<Select.Icon
|
||||||
|
className={css({
|
||||||
|
color: isDark ? 'gray.400' : 'gray.500',
|
||||||
|
fontSize: 'xs',
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
▼
|
||||||
|
</Select.Icon>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className={css({
|
||||||
|
fontSize: 'xs',
|
||||||
|
color: isDark ? 'gray.400' : 'gray.500',
|
||||||
|
marginTop: '0.5',
|
||||||
|
lineHeight: 'tight',
|
||||||
|
display: { base: 'none', sm: 'block' },
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
{selectedMode?.description}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Select.Trigger>
|
||||||
|
<Select.Portal>
|
||||||
|
<Select.Content className={contentStyles} position="popper" sideOffset={5}>
|
||||||
|
<Select.Viewport>
|
||||||
|
{GAME_MODE_OPTIONS.map((option) => (
|
||||||
|
<Select.Item key={option.value} value={option.value} className={itemStyles}>
|
||||||
|
<span className={css({ fontSize: '2xl' })}>{option.emoji}</span>
|
||||||
|
<div className={css({ flex: 1 })}>
|
||||||
|
<Select.ItemText>
|
||||||
|
<span
|
||||||
|
className={css({
|
||||||
|
fontWeight: '600',
|
||||||
|
fontSize: 'md',
|
||||||
|
color: isDark ? 'gray.100' : 'gray.900',
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
{option.label}
|
||||||
|
</span>
|
||||||
|
</Select.ItemText>
|
||||||
|
<div
|
||||||
|
className={css({
|
||||||
|
fontSize: 'sm',
|
||||||
|
color: isDark ? 'gray.400' : 'gray.500',
|
||||||
|
marginTop: '1',
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
{option.description}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Select.Item>
|
||||||
|
))}
|
||||||
|
</Select.Viewport>
|
||||||
|
</Select.Content>
|
||||||
|
</Select.Portal>
|
||||||
|
</Select.Root>
|
||||||
|
|
||||||
|
{/* Assistance Level Selector */}
|
||||||
|
<Select.Root
|
||||||
|
value={state.assistanceLevel}
|
||||||
|
onValueChange={(value) =>
|
||||||
|
setAssistanceLevel(value as 'learning' | 'guided' | 'helpful' | 'standard' | 'none')
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Select.Trigger className={cardTriggerStyles}>
|
||||||
|
<span
|
||||||
|
className={css({
|
||||||
|
fontSize: { base: 'lg', sm: '2xl' },
|
||||||
|
flexShrink: 0,
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
{selectedAssistance?.emoji || '💡'}
|
||||||
|
</span>
|
||||||
|
<div className={css({ flex: 1, minWidth: 0 })}>
|
||||||
|
<div
|
||||||
|
className={css({
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: { base: '1', sm: '2' },
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
className={css({
|
||||||
|
fontWeight: '600',
|
||||||
|
fontSize: { base: 'sm', sm: 'md' },
|
||||||
|
color: isDark ? 'gray.100' : 'gray.800',
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
{selectedAssistance?.label}
|
||||||
|
</span>
|
||||||
|
<Select.Icon
|
||||||
|
className={css({
|
||||||
|
color: isDark ? 'gray.400' : 'gray.500',
|
||||||
|
fontSize: 'xs',
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
▼
|
||||||
|
</Select.Icon>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className={css({
|
||||||
|
fontSize: 'xs',
|
||||||
|
color: isDark ? 'gray.400' : 'gray.500',
|
||||||
|
marginTop: '0.5',
|
||||||
|
lineHeight: 'tight',
|
||||||
|
display: { base: 'none', sm: 'block' },
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
{selectedAssistance?.description}
|
||||||
|
</div>
|
||||||
|
{selectedAssistanceBadges.length > 0 && (
|
||||||
|
<div
|
||||||
|
className={css({
|
||||||
|
display: { base: 'none', sm: 'flex' },
|
||||||
|
gap: '1',
|
||||||
|
mt: '1.5',
|
||||||
|
flexWrap: 'wrap',
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
{selectedAssistanceBadges.map((badge) => (
|
||||||
|
<span
|
||||||
|
key={badge.label}
|
||||||
|
className={css({
|
||||||
|
fontSize: '2xs',
|
||||||
|
padding: '0.5 1.5',
|
||||||
|
bg: isDark ? 'gray.600' : 'gray.300',
|
||||||
|
color: isDark ? 'gray.300' : 'gray.700',
|
||||||
|
rounded: 'full',
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
{badge.icon} {badge.label}
|
||||||
|
</span>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</Select.Trigger>
|
||||||
|
<Select.Portal>
|
||||||
|
<Select.Content className={contentStyles} position="popper" sideOffset={5}>
|
||||||
|
<Select.Viewport>
|
||||||
|
{ASSISTANCE_LEVELS.map((level) => {
|
||||||
|
const badges = getFeatureBadges(level)
|
||||||
|
return (
|
||||||
|
<Select.Item key={level.id} value={level.id} className={itemStyles}>
|
||||||
|
<span className={css({ fontSize: '2xl' })}>{level.emoji}</span>
|
||||||
|
<div className={css({ flex: 1 })}>
|
||||||
|
<Select.ItemText>
|
||||||
|
<span
|
||||||
|
className={css({
|
||||||
|
fontWeight: '600',
|
||||||
|
fontSize: 'md',
|
||||||
|
color: isDark ? 'gray.100' : 'gray.900',
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
{level.label}
|
||||||
|
</span>
|
||||||
|
</Select.ItemText>
|
||||||
|
<div
|
||||||
|
className={css({
|
||||||
|
fontSize: 'sm',
|
||||||
|
color: isDark ? 'gray.400' : 'gray.500',
|
||||||
|
marginTop: '1',
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
{level.description}
|
||||||
|
</div>
|
||||||
|
{badges.length > 0 && (
|
||||||
|
<div
|
||||||
|
className={css({
|
||||||
|
display: 'flex',
|
||||||
|
gap: '1',
|
||||||
|
mt: '2',
|
||||||
|
flexWrap: 'wrap',
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
{badges.map((badge) => (
|
||||||
|
<span
|
||||||
|
key={badge.label}
|
||||||
|
className={css({
|
||||||
|
fontSize: 'xs',
|
||||||
|
padding: '1 2',
|
||||||
|
bg: isDark ? 'gray.600' : 'gray.200',
|
||||||
|
color: isDark ? 'gray.300' : 'gray.600',
|
||||||
|
rounded: 'md',
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
{badge.icon} {badge.label}
|
||||||
|
</span>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</Select.Item>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</Select.Viewport>
|
||||||
|
</Select.Content>
|
||||||
|
</Select.Portal>
|
||||||
|
</Select.Root>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* BOTTOM-CENTER: Start Button - positioned prominently */}
|
||||||
|
<div
|
||||||
|
data-element="setup-start-container"
|
||||||
className={css({
|
className={css({
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
bottom: { base: '2', sm: '4' },
|
bottom: { base: '2', sm: '4' },
|
||||||
|
|
@ -181,10 +539,8 @@ export function SetupPhase() {
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
gap: { base: '2', sm: '3' },
|
gap: { base: '2', sm: '3' },
|
||||||
padding: { base: '2 4', sm: '3 5' },
|
padding: { base: '2 4', sm: '3 5' },
|
||||||
width: { base: '100%', sm: 'auto' },
|
width: { base: '280px', sm: '320px' },
|
||||||
minWidth: { base: 'auto', sm: '220px' },
|
height: { base: '56px', sm: '80px' },
|
||||||
flex: { base: 'none', sm: 1 },
|
|
||||||
height: { base: '56px', sm: '88px' },
|
|
||||||
fontSize: { base: 'md', sm: 'lg' },
|
fontSize: { base: 'md', sm: 'lg' },
|
||||||
fontWeight: 'bold',
|
fontWeight: 'bold',
|
||||||
color: 'white',
|
color: 'white',
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue