fix(know-your-world): improve mobile layout for setup screen

- Fix broken CSS by simplifying breadcrumb styles
- Stack breadcrumbs vertically on phones to avoid overlap with region selector
- Shrink mode/guidance menus on phones (64px → 48px height)
- Scale region size selector to 75% on phones
- Remove complex calc() and alpha channel values causing issues

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Thomas Hallock 2025-11-27 14:45:21 -06:00
parent 02762fad81
commit 81b44a6422
2 changed files with 119 additions and 211 deletions

View File

@ -90,7 +90,13 @@ const PLANETS: PlanetData[] = [
{ id: 'mercury', name: 'Mercury', color: '#b0b0b0', size: 0.38 },
{ id: 'venus', name: 'Venus', color: '#e6c87a', size: 0.95 },
{ id: 'mars', name: 'Mars', color: '#c1440e', size: 0.53 },
{ id: 'jupiter', name: 'Jupiter', color: '#d8ca9d', size: 2.0, hasStripes: true },
{
id: 'jupiter',
name: 'Jupiter',
color: '#d8ca9d',
size: 2.0,
hasStripes: true,
},
{ id: 'saturn', name: 'Saturn', color: '#ead6b8', size: 1.7, hasRings: true },
]
@ -152,7 +158,10 @@ export function DrillDownMapSelector({
const [sizeRangePreview, setSizeRangePreview] = useState<RangePreviewState<RegionSize> | null>(
null
)
const [containerDimensions, setContainerDimensions] = useState({ width: 0, height: 0 })
const [containerDimensions, setContainerDimensions] = useState({
width: 0,
height: 0,
})
const containerRef = useRef<HTMLDivElement>(null)
// Track which region name is being hovered in the popover (for zoom preview)
const [previewRegionName, setPreviewRegionName] = useState<string | null>(null)
@ -477,7 +486,10 @@ export function DrillDownMapSelector({
}
}
return { previewAddRegions: addRegions, previewRemoveRegions: removeRegions }
return {
previewAddRegions: addRegions,
previewRemoveRegions: removeRegions,
}
}, [sizeRangePreview, currentLevel, path, selectedContinent, includeSizes])
// Compute the label to display for the hovered region
@ -634,7 +646,12 @@ export function DrillDownMapSelector({
}
: null
})
.filter(Boolean) as Array<{ id: string; label: string; emoji: string; path: SelectionPath }>
.filter(Boolean) as Array<{
id: string
label: string
emoji: string
path: SelectionPath
}>
}
return []
@ -761,205 +778,76 @@ export function DrillDownMapSelector({
>
{/* Breadcrumb Navigation - different styles for fillContainer vs normal */}
{fillContainer ? (
/* Navigation breadcrumb - positioned well below nav bar */
/* Breadcrumb navigation overlay for full-screen mode */
<div
data-element="navigation-overlay"
className={css({
position: 'absolute',
top: 'calc(var(--app-nav-height, 92px) + 72px)',
left: { base: '16px', sm: '24px' },
top: '164px',
left: { base: '12px', sm: '24px' },
zIndex: 50,
display: 'flex',
alignItems: 'center',
gap: '3',
padding: '10px 16px',
bg: isDark
? 'linear-gradient(135deg, rgba(30, 41, 59, 0.95) 0%, rgba(15, 23, 42, 0.95) 100%)'
: 'linear-gradient(135deg, rgba(255, 255, 255, 0.95) 0%, rgba(248, 250, 252, 0.95) 100%)',
backdropFilter: 'blur(12px)',
border: '1px solid',
borderColor: isDark ? 'rgba(71, 85, 105, 0.5)' : 'rgba(203, 213, 225, 0.8)',
rounded: 'full',
shadow: isDark
? '0 4px 20px rgba(0, 0, 0, 0.4), inset 0 1px 0 rgba(255, 255, 255, 0.05)'
: '0 4px 20px rgba(0, 0, 0, 0.08), inset 0 1px 0 rgba(255, 255, 255, 0.8)',
flexDirection: { base: 'column', sm: 'row' },
alignItems: { base: 'flex-start', sm: 'center' },
gap: { base: '1', sm: '2' },
padding: '2',
bg: isDark ? 'gray.800' : 'gray.100',
rounded: 'xl',
shadow: 'lg',
fontSize: 'sm',
})}
>
{currentLevel > 0 ? (
<>
{/* Back button with mini-map */}
{(() => {
const backToWorld = currentLevel === 1
const backToContinentId = currentLevel === 2 ? path[0] : null
const backLabel = backToWorld
? 'World'
: (CONTINENTS.find((c) => c.id === backToContinentId)?.name ?? 'Continent')
const backEmoji = backToWorld
? '🌍'
: (CONTINENTS.find((c) => c.id === backToContinentId)?.emoji ?? '🗺️')
// Get viewBox and regions for the mini-map preview
const backViewBox = backToWorld
? WORLD_MAP.viewBox
: backToContinentId
? calculateContinentViewBox(
WORLD_MAP.regions,
backToContinentId,
WORLD_MAP.viewBox,
'world'
)
: WORLD_MAP.viewBox
const backRegions = backToWorld
? WORLD_MAP.regions
: backToContinentId
? filterRegionsByContinent(WORLD_MAP.regions, backToContinentId)
: []
return (
<button
data-action="navigate-back"
onClick={handleZoomOut}
className={css({
display: 'flex',
alignItems: 'center',
gap: '10px',
padding: '6px 12px 6px 8px',
bg: isDark ? 'rgba(51, 65, 85, 0.6)' : 'rgba(241, 245, 249, 0.8)',
border: '1px solid',
borderColor: isDark ? 'rgba(71, 85, 105, 0.4)' : 'rgba(203, 213, 225, 0.6)',
rounded: 'full',
cursor: 'pointer',
transition: 'all 0.2s ease',
_hover: {
bg: isDark ? 'rgba(71, 85, 105, 0.7)' : 'rgba(226, 232, 240, 0.9)',
transform: 'translateX(-2px)',
},
})}
{breadcrumbs.map((crumb, index) => (
<span
key={crumb.label}
className={css({ display: 'flex', alignItems: 'center', gap: '1' })}
>
{/* Mini-map preview */}
<div
className={css({
width: '32px',
height: '32px',
rounded: 'full',
overflow: 'hidden',
border: '2px solid',
borderColor: isDark ? 'rgba(100, 116, 139, 0.5)' : 'rgba(203, 213, 225, 0.8)',
boxShadow: 'inset 0 1px 3px rgba(0,0,0,0.1)',
})}
>
<svg
viewBox={backViewBox}
className={css({
width: '100%',
height: '100%',
display: 'block',
})}
preserveAspectRatio="xMidYMid slice"
>
<rect
x="-10000"
y="-10000"
width="30000"
height="30000"
fill={isDark ? '#1e3a5f' : '#bae6fd'}
/>
{backRegions.map((region) => (
<path
key={region.id}
d={region.path}
fill={isDark ? '#64748b' : '#a8d4a8'}
stroke={isDark ? '#475569' : '#86b386'}
strokeWidth={0.3}
/>
))}
</svg>
</div>
<div className={css({ display: 'flex', flexDirection: 'column', alignItems: 'flex-start' })}>
{index > 0 && (
<span
className={css({
fontSize: '10px',
fontWeight: '500',
color: isDark ? 'gray.400' : 'gray.500',
textTransform: 'uppercase',
letterSpacing: '0.05em',
lineHeight: 1,
color: isDark ? 'gray.500' : 'gray.400',
display: { base: 'none', sm: 'inline' },
})}
>
Back to
</span>
<span
className={css({
fontSize: '13px',
fontWeight: '600',
color: isDark ? 'gray.200' : 'gray.700',
lineHeight: 1.2,
})}
>
{backEmoji} {backLabel}
</span>
</div>
</button>
)
})()}
{/* Current location badge */}
<div
className={css({
display: 'flex',
alignItems: 'center',
gap: '8px',
padding: '6px 14px',
bg: isDark
? 'linear-gradient(135deg, rgba(59, 130, 246, 0.2) 0%, rgba(139, 92, 246, 0.2) 100%)'
: 'linear-gradient(135deg, rgba(59, 130, 246, 0.1) 0%, rgba(139, 92, 246, 0.1) 100%)',
border: '1px solid',
borderColor: isDark ? 'rgba(59, 130, 246, 0.3)' : 'rgba(59, 130, 246, 0.2)',
rounded: 'full',
})}
>
<span className={css({ fontSize: 'md' })}>
{breadcrumbs[breadcrumbs.length - 1]?.emoji}
</span>
<span
className={css({
fontSize: '14px',
fontWeight: '600',
color: isDark ? 'white' : 'gray.800',
})}
>
{breadcrumbs[breadcrumbs.length - 1]?.label}
</span>
</div>
</>
) : (
/* World level - current location badge */
<div
className={css({
display: 'flex',
alignItems: 'center',
gap: '8px',
padding: '6px 14px',
bg: isDark
? 'linear-gradient(135deg, rgba(59, 130, 246, 0.2) 0%, rgba(139, 92, 246, 0.2) 100%)'
: 'linear-gradient(135deg, rgba(59, 130, 246, 0.1) 0%, rgba(139, 92, 246, 0.1) 100%)',
border: '1px solid',
borderColor: isDark ? 'rgba(59, 130, 246, 0.3)' : 'rgba(59, 130, 246, 0.2)',
rounded: 'full',
})}
>
<span className={css({ fontSize: 'md' })}>🌍</span>
<span
className={css({
fontSize: '14px',
fontWeight: '600',
color: isDark ? 'white' : 'gray.800',
})}
>
World
</span>
</div>
)}
{crumb.isClickable ? (
<button
data-action={`nav-${crumb.label.toLowerCase().replace(/\s/g, '-')}`}
onClick={() => handleBreadcrumbClick(crumb.path)}
className={css({
display: 'flex',
alignItems: 'center',
gap: '1',
color: isDark ? 'blue.400' : 'blue.600',
cursor: 'pointer',
background: 'none',
border: 'none',
padding: '0',
font: 'inherit',
_hover: { textDecoration: 'underline' },
})}
>
<span>{crumb.emoji}</span>
<span>{crumb.label}</span>
</button>
) : (
<span
className={css({
display: 'flex',
alignItems: 'center',
gap: '1',
fontWeight: 'bold',
color: isDark ? 'gray.100' : 'gray.800',
})}
>
<span>{crumb.emoji}</span>
<span>{crumb.label}</span>
</span>
)}
</span>
))}
</div>
) : (
/* Normal breadcrumb for non-fillContainer mode */
@ -977,7 +865,11 @@ export function DrillDownMapSelector({
{breadcrumbs.map((crumb, index) => (
<span
key={crumb.label}
className={css({ display: 'flex', alignItems: 'center', gap: '1' })}
className={css({
display: 'flex',
alignItems: 'center',
gap: '1',
})}
>
{index > 0 && (
<span className={css({ color: isDark ? 'gray.500' : 'gray.400' })}></span>
@ -1153,20 +1045,15 @@ export function DrillDownMapSelector({
data-element="region-size-filters"
className={css({
position: 'absolute',
top: fillContainer ? 'calc(var(--app-nav-height, 92px) + 72px)' : '3',
right: { base: '16px', sm: '24px' },
padding: '12px',
bg: isDark
? 'linear-gradient(135deg, rgba(30, 41, 59, 0.95) 0%, rgba(15, 23, 42, 0.95) 100%)'
: 'linear-gradient(135deg, rgba(255, 255, 255, 0.95) 0%, rgba(248, 250, 252, 0.95) 100%)',
backdropFilter: 'blur(12px)',
border: '1px solid',
borderColor: isDark ? 'rgba(71, 85, 105, 0.5)' : 'rgba(203, 213, 225, 0.8)',
top: fillContainer ? '164px' : '3',
right: { base: '8px', sm: '24px' },
padding: { base: '2', sm: '3' },
bg: isDark ? 'gray.800' : 'gray.100',
rounded: 'xl',
shadow: isDark
? '0 4px 20px rgba(0, 0, 0, 0.4), inset 0 1px 0 rgba(255, 255, 255, 0.05)'
: '0 4px 20px rgba(0, 0, 0, 0.08), inset 0 1px 0 rgba(255, 255, 255, 0.8)',
shadow: 'lg',
zIndex: 10,
transform: { base: 'scale(0.75)', sm: 'scale(1)' },
transformOrigin: 'top right',
})}
>
<RangeThermometer

View File

@ -184,14 +184,14 @@ export function SetupPhase() {
const cardTriggerStyles = css({
display: 'flex',
alignItems: 'center',
gap: { base: '2', sm: '3' },
padding: { base: '2', sm: '3' },
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: 'calc(50% - 4px)', sm: '260px' },
height: { base: '64px', sm: '88px' },
height: { base: '48px', sm: '88px' },
textAlign: 'left',
_hover: {
bg: isDark ? 'gray.600/90' : 'white',
@ -283,7 +283,12 @@ export function SetupPhase() {
onValueChange={(value) => setMode(value as 'cooperative' | 'race' | 'turn-based')}
>
<Select.Trigger className={cardTriggerStyles}>
<span className={css({ fontSize: { base: 'xl', sm: '2xl' }, flexShrink: 0 })}>
<span
className={css({
fontSize: { base: 'lg', sm: '2xl' },
flexShrink: 0,
})}
>
{selectedMode?.emoji}
</span>
<div className={css({ flex: 1, minWidth: 0 })}>
@ -304,7 +309,10 @@ export function SetupPhase() {
{selectedMode?.label}
</span>
<Select.Icon
className={css({ color: isDark ? 'gray.400' : 'gray.500', fontSize: 'xs' })}
className={css({
color: isDark ? 'gray.400' : 'gray.500',
fontSize: 'xs',
})}
>
</Select.Icon>
@ -365,7 +373,12 @@ export function SetupPhase() {
}
>
<Select.Trigger className={cardTriggerStyles}>
<span className={css({ fontSize: { base: 'xl', sm: '2xl' }, flexShrink: 0 })}>
<span
className={css({
fontSize: { base: 'lg', sm: '2xl' },
flexShrink: 0,
})}
>
{selectedAssistance?.emoji || '💡'}
</span>
<div className={css({ flex: 1, minWidth: 0 })}>
@ -386,7 +399,10 @@ export function SetupPhase() {
{selectedAssistance?.label}
</span>
<Select.Icon
className={css({ color: isDark ? 'gray.400' : 'gray.500', fontSize: 'xs' })}
className={css({
color: isDark ? 'gray.400' : 'gray.500',
fontSize: 'xs',
})}
>
</Select.Icon>
@ -578,7 +594,12 @@ export function SetupPhase() {
})}
>
<span>{regionTheme.icons[0]}</span>
<span className={css({ fontSize: { base: 'xs', sm: 'sm' }, marginTop: '-2px' })}>
<span
className={css({
fontSize: { base: 'xs', sm: 'sm' },
marginTop: '-2px',
})}
>
{regionTheme.icons[1]}
</span>
</div>