feat(know-your-world): responsive setup + travel-themed start button
- Add responsive layout for setup sequence (mobile: two rows, desktop: one row) - Create travel-themed start button with region-specific gradients, flags, and icons - Position breadcrumbs and region filter below nav using CSS variable - Improve breadcrumb styling with pill design, mini-map preview, and proper effects - Add themed content for all regions (World, USA, continents) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
57dd61b994
commit
02762fad81
|
|
@ -109,6 +109,8 @@ interface DrillDownMapSelectorProps {
|
|||
onRegionSizesChange: (sizes: RegionSize[]) => void
|
||||
/** Region counts per size category */
|
||||
regionCountsBySize: Record<string, number>
|
||||
/** When true, fills parent container and uses overlay positioning for UI elements */
|
||||
fillContainer?: boolean
|
||||
}
|
||||
|
||||
interface BreadcrumbItem {
|
||||
|
|
@ -126,6 +128,7 @@ export function DrillDownMapSelector({
|
|||
includeSizes,
|
||||
onRegionSizesChange,
|
||||
regionCountsBySize,
|
||||
fillContainer = false,
|
||||
}: DrillDownMapSelectorProps) {
|
||||
const { resolvedTheme } = useTheme()
|
||||
const isDark = resolvedTheme === 'dark'
|
||||
|
|
@ -749,68 +752,282 @@ export function DrillDownMapSelector({
|
|||
}, [currentLevel, path, onSelectionChange])
|
||||
|
||||
return (
|
||||
<div data-component="drill-down-map-selector" className={css({ width: '100%' })}>
|
||||
{/* Breadcrumb Navigation */}
|
||||
<div
|
||||
data-element="breadcrumbs"
|
||||
className={css({
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: '2',
|
||||
marginBottom: '3',
|
||||
fontSize: 'sm',
|
||||
flexWrap: 'wrap',
|
||||
})}
|
||||
>
|
||||
{breadcrumbs.map((crumb, index) => (
|
||||
<span
|
||||
key={crumb.label}
|
||||
className={css({ display: 'flex', alignItems: 'center', gap: '1' })}
|
||||
>
|
||||
{index > 0 && (
|
||||
<span className={css({ color: isDark ? 'gray.500' : 'gray.400' })}>›</span>
|
||||
)}
|
||||
{crumb.isClickable ? (
|
||||
<button
|
||||
data-action={`nav-${crumb.label.toLowerCase().replace(/\s/g, '-')}`}
|
||||
onClick={() => handleBreadcrumbClick(crumb.path)}
|
||||
<div
|
||||
data-component="drill-down-map-selector"
|
||||
className={css({
|
||||
width: '100%',
|
||||
...(fillContainer && { height: '100%', position: 'relative' }),
|
||||
})}
|
||||
>
|
||||
{/* Breadcrumb Navigation - different styles for fillContainer vs normal */}
|
||||
{fillContainer ? (
|
||||
/* Navigation breadcrumb - positioned well below nav bar */
|
||||
<div
|
||||
data-element="navigation-overlay"
|
||||
className={css({
|
||||
position: 'absolute',
|
||||
top: 'calc(var(--app-nav-height, 92px) + 72px)',
|
||||
left: { base: '16px', 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)',
|
||||
})}
|
||||
>
|
||||
{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)',
|
||||
},
|
||||
})}
|
||||
>
|
||||
{/* 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' })}>
|
||||
<span
|
||||
className={css({
|
||||
fontSize: '10px',
|
||||
fontWeight: '500',
|
||||
color: isDark ? 'gray.400' : 'gray.500',
|
||||
textTransform: 'uppercase',
|
||||
letterSpacing: '0.05em',
|
||||
lineHeight: 1,
|
||||
})}
|
||||
>
|
||||
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: '1',
|
||||
color: isDark ? 'blue.400' : 'blue.600',
|
||||
cursor: 'pointer',
|
||||
_hover: { textDecoration: 'underline' },
|
||||
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>{crumb.emoji}</span>
|
||||
<span>{crumb.label}</span>
|
||||
</button>
|
||||
) : (
|
||||
<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({
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: '1',
|
||||
fontWeight: 'bold',
|
||||
color: isDark ? 'gray.100' : 'gray.800',
|
||||
fontSize: '14px',
|
||||
fontWeight: '600',
|
||||
color: isDark ? 'white' : 'gray.800',
|
||||
})}
|
||||
>
|
||||
<span>{crumb.emoji}</span>
|
||||
<span>{crumb.label}</span>
|
||||
World
|
||||
</span>
|
||||
)}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
/* Normal breadcrumb for non-fillContainer mode */
|
||||
<div
|
||||
data-element="breadcrumbs"
|
||||
className={css({
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: '2',
|
||||
fontSize: 'sm',
|
||||
flexWrap: 'wrap',
|
||||
marginBottom: '3',
|
||||
})}
|
||||
>
|
||||
{breadcrumbs.map((crumb, index) => (
|
||||
<span
|
||||
key={crumb.label}
|
||||
className={css({ display: 'flex', alignItems: 'center', gap: '1' })}
|
||||
>
|
||||
{index > 0 && (
|
||||
<span className={css({ color: isDark ? 'gray.500' : 'gray.400' })}>›</span>
|
||||
)}
|
||||
{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',
|
||||
_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>
|
||||
)}
|
||||
|
||||
{/* Interactive Map - wrapped in ref'd container for dimension measurement */}
|
||||
<div
|
||||
ref={containerRef}
|
||||
data-element="map-container"
|
||||
className={css({ position: 'relative' })}
|
||||
className={css({
|
||||
position: 'relative',
|
||||
...(fillContainer && { width: '100%', height: '100%' }),
|
||||
})}
|
||||
>
|
||||
<MapSelectorMap
|
||||
fillContainer={fillContainer}
|
||||
mapData={mapData}
|
||||
viewBox={viewBox}
|
||||
onRegionClick={handleRegionClick}
|
||||
|
|
@ -830,8 +1047,9 @@ export function DrillDownMapSelector({
|
|||
focusedRegion={focusedRegionId}
|
||||
/>
|
||||
|
||||
{/* Zoom Out Button - positioned inside map, upper right */}
|
||||
{currentLevel > 0 &&
|
||||
{/* Zoom Out Button - only shown when NOT in fillContainer mode (fillContainer has navigation overlay) */}
|
||||
{!fillContainer &&
|
||||
currentLevel > 0 &&
|
||||
(() => {
|
||||
// Calculate what we're going back to
|
||||
const backToWorld = currentLevel === 1
|
||||
|
|
@ -935,15 +1153,19 @@ export function DrillDownMapSelector({
|
|||
data-element="region-size-filters"
|
||||
className={css({
|
||||
position: 'absolute',
|
||||
top: '3',
|
||||
right: '3',
|
||||
padding: '2',
|
||||
bg: isDark ? 'gray.800/90' : 'white/90',
|
||||
backdropFilter: 'blur(4px)',
|
||||
rounded: 'lg',
|
||||
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 ? 'gray.700' : 'gray.300',
|
||||
boxShadow: 'md',
|
||||
borderColor: isDark ? 'rgba(71, 85, 105, 0.5)' : 'rgba(203, 213, 225, 0.8)',
|
||||
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)',
|
||||
zIndex: 10,
|
||||
})}
|
||||
>
|
||||
|
|
@ -965,7 +1187,8 @@ export function DrillDownMapSelector({
|
|||
</div>
|
||||
|
||||
{/* Peer Navigation - Mini-map thumbnails below main map (or planets at world level) */}
|
||||
{peers.length > 0 && (
|
||||
{/* Hidden in fillContainer mode since there's no space below the map */}
|
||||
{!fillContainer && peers.length > 0 && (
|
||||
<div
|
||||
data-element="peer-navigation"
|
||||
className={css({
|
||||
|
|
|
|||
|
|
@ -18,14 +18,9 @@ export function GameComponent() {
|
|||
? state.currentPlayer
|
||||
: undefined
|
||||
|
||||
// Use StandardGameLayout only for playing phase
|
||||
const content = (
|
||||
<>
|
||||
{state.gamePhase === 'setup' && <SetupPhase />}
|
||||
{state.gamePhase === 'playing' && <PlayingPhase />}
|
||||
{state.gamePhase === 'results' && <ResultsPhase />}
|
||||
</>
|
||||
)
|
||||
// Setup phase renders its own full-screen layout (map behind nav)
|
||||
// Playing phase uses StandardGameLayout (respects nav height)
|
||||
// Results phase uses normal flow
|
||||
|
||||
return (
|
||||
<PageWithNav
|
||||
|
|
@ -41,7 +36,13 @@ export function GameComponent() {
|
|||
onSetup={state.gamePhase !== 'setup' ? returnToSetup : undefined}
|
||||
onNewGame={state.gamePhase !== 'setup' && state.gamePhase !== 'results' ? endGame : undefined}
|
||||
>
|
||||
{state.gamePhase === 'playing' ? <StandardGameLayout>{content}</StandardGameLayout> : content}
|
||||
{state.gamePhase === 'setup' && <SetupPhase />}
|
||||
{state.gamePhase === 'playing' && (
|
||||
<StandardGameLayout>
|
||||
<PlayingPhase />
|
||||
</StandardGameLayout>
|
||||
)}
|
||||
{state.gamePhase === 'results' && <ResultsPhase />}
|
||||
</PageWithNav>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -126,6 +126,11 @@ interface MapSelectorMapProps {
|
|||
* Region ID to visually highlight (e.g., when hovering over region name in popover)
|
||||
*/
|
||||
focusedRegion?: string | null
|
||||
/**
|
||||
* When true, fills the parent container (100% width and height) instead of using fixed aspect ratio.
|
||||
* Used for full-viewport setup screen layout.
|
||||
*/
|
||||
fillContainer?: boolean
|
||||
}
|
||||
|
||||
export function MapSelectorMap({
|
||||
|
|
@ -144,6 +149,7 @@ export function MapSelectorMap({
|
|||
previewRemoveRegions = [],
|
||||
animatedViewBox,
|
||||
focusedRegion,
|
||||
fillContainer = false,
|
||||
}: MapSelectorMapProps) {
|
||||
const { resolvedTheme } = useTheme()
|
||||
const isDark = resolvedTheme === 'dark'
|
||||
|
|
@ -339,10 +345,11 @@ export function MapSelectorMap({
|
|||
data-component="map-selector-map"
|
||||
className={css({
|
||||
width: '100%',
|
||||
aspectRatio: '16 / 9', // Fixed aspect ratio - doesn't change when drilling down
|
||||
// When fillContainer is true, fill the parent; otherwise use fixed 16:9 aspect ratio
|
||||
...(fillContainer ? { height: '100%' } : { aspectRatio: '16 / 9' }),
|
||||
bg: isDark ? 'gray.900' : 'gray.50',
|
||||
rounded: 'xl',
|
||||
border: '2px solid',
|
||||
rounded: fillContainer ? 'none' : 'xl',
|
||||
border: fillContainer ? 'none' : '2px solid',
|
||||
borderColor: isDark ? 'gray.700' : 'gray.200',
|
||||
overflow: 'hidden',
|
||||
position: 'relative',
|
||||
|
|
|
|||
|
|
@ -10,6 +10,76 @@ import { ALL_REGION_SIZES, ASSISTANCE_LEVELS, getFilteredMapDataBySizesSync } fr
|
|||
import type { AssistanceLevelConfig } from '../maps'
|
||||
import { CONTINENTS, type ContinentId } from '../continents'
|
||||
|
||||
// Travel-themed content for each region
|
||||
interface RegionTheme {
|
||||
gradient: string
|
||||
gradientHover: string
|
||||
icons: string[] // Decorative icons to show
|
||||
actionText: string // "Let's explore!" / "Bon voyage!" etc.
|
||||
flagEmojis: string[] // Representative flag emojis
|
||||
}
|
||||
|
||||
const REGION_THEMES: Record<string, RegionTheme> = {
|
||||
World: {
|
||||
gradient: 'linear-gradient(135deg, #3b82f6 0%, #8b5cf6 50%, #06b6d4 100%)',
|
||||
gradientHover: 'linear-gradient(135deg, #60a5fa 0%, #a78bfa 50%, #22d3ee 100%)',
|
||||
icons: ['✈️', '🌍', '🌎', '🌏'],
|
||||
actionText: 'Start World Tour!',
|
||||
flagEmojis: ['🇫🇷', '🇯🇵', '🇧🇷', '🇦🇺', '🇿🇦'],
|
||||
},
|
||||
USA: {
|
||||
gradient: 'linear-gradient(135deg, #dc2626 0%, #1d4ed8 100%)',
|
||||
gradientHover: 'linear-gradient(135deg, #ef4444 0%, #3b82f6 100%)',
|
||||
icons: ['🗽', '🦅', '🇺🇸'],
|
||||
actionText: 'Start USA Tour!',
|
||||
flagEmojis: ['🇺🇸'],
|
||||
},
|
||||
Africa: {
|
||||
gradient: 'linear-gradient(135deg, #d97706 0%, #059669 100%)',
|
||||
gradientHover: 'linear-gradient(135deg, #f59e0b 0%, #10b981 100%)',
|
||||
icons: ['🦁', '🌍', '🐘'],
|
||||
actionText: 'Start Safari!',
|
||||
flagEmojis: ['🇿🇦', '🇰🇪', '🇪🇬', '🇳🇬', '🇲🇦'],
|
||||
},
|
||||
Asia: {
|
||||
gradient: 'linear-gradient(135deg, #dc2626 0%, #f59e0b 100%)',
|
||||
gradientHover: 'linear-gradient(135deg, #ef4444 0%, #fbbf24 100%)',
|
||||
icons: ['🏯', '🌏', '🐉'],
|
||||
actionText: 'Start Journey!',
|
||||
flagEmojis: ['🇯🇵', '🇨🇳', '🇮🇳', '🇰🇷', '🇹🇭'],
|
||||
},
|
||||
Europe: {
|
||||
gradient: 'linear-gradient(135deg, #1d4ed8 0%, #7c3aed 100%)',
|
||||
gradientHover: 'linear-gradient(135deg, #3b82f6 0%, #8b5cf6 100%)',
|
||||
icons: ['🏰', '🌍', '🗼'],
|
||||
actionText: 'Start Voyage!',
|
||||
flagEmojis: ['🇫🇷', '🇩🇪', '🇮🇹', '🇪🇸', '🇬🇧'],
|
||||
},
|
||||
'North America': {
|
||||
gradient: 'linear-gradient(135deg, #059669 0%, #0891b2 100%)',
|
||||
gradientHover: 'linear-gradient(135deg, #10b981 0%, #06b6d4 100%)',
|
||||
icons: ['🗽', '🌎', '🍁'],
|
||||
actionText: 'Start Exploring!',
|
||||
flagEmojis: ['🇺🇸', '🇨🇦', '🇲🇽'],
|
||||
},
|
||||
Oceania: {
|
||||
gradient: 'linear-gradient(135deg, #0891b2 0%, #059669 100%)',
|
||||
gradientHover: 'linear-gradient(135deg, #06b6d4 0%, #10b981 100%)',
|
||||
icons: ['🦘', '🌏', '🏝️'],
|
||||
actionText: 'Start Adventure!',
|
||||
flagEmojis: ['🇦🇺', '🇳🇿', '🇫🇯'],
|
||||
},
|
||||
'South America': {
|
||||
gradient: 'linear-gradient(135deg, #059669 0%, #d97706 100%)',
|
||||
gradientHover: 'linear-gradient(135deg, #10b981 0%, #f59e0b 100%)',
|
||||
icons: ['🗿', '🌎', '🌴'],
|
||||
actionText: 'Start Expedition!',
|
||||
flagEmojis: ['🇧🇷', '🇦🇷', '🇨🇴', '🇵🇪', '🇨🇱'],
|
||||
},
|
||||
}
|
||||
|
||||
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 }> = []
|
||||
|
|
@ -31,7 +101,7 @@ function getFeatureBadges(level: AssistanceLevelConfig): Array<{ label: string;
|
|||
return badges
|
||||
}
|
||||
|
||||
// Game mode options with rich descriptions
|
||||
// Game mode options
|
||||
const GAME_MODE_OPTIONS = [
|
||||
{
|
||||
value: 'cooperative' as const,
|
||||
|
|
@ -89,6 +159,7 @@ export function SetupPhase() {
|
|||
// 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(() => {
|
||||
|
|
@ -104,39 +175,43 @@ export function SetupPhase() {
|
|||
return state.selectedMap === 'usa' ? 'USA' : 'World'
|
||||
}, [state.selectedContinent, state.selectedMap])
|
||||
|
||||
// Styles for Radix Select components
|
||||
const triggerStyles = css({
|
||||
// Get travel theme for current region
|
||||
const regionTheme = useMemo(() => {
|
||||
return REGION_THEMES[contextLabel] ?? DEFAULT_THEME
|
||||
}, [contextLabel])
|
||||
|
||||
// Card trigger styles - responsive dimensions
|
||||
const cardTriggerStyles = css({
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: '3',
|
||||
padding: '3',
|
||||
bg: isDark ? 'gray.800' : 'white',
|
||||
border: '2px solid',
|
||||
borderColor: isDark ? 'gray.600' : 'gray.300',
|
||||
gap: { base: '2', sm: '3' },
|
||||
padding: { base: '2', sm: '3' },
|
||||
bg: isDark ? 'gray.700/80' : 'white/80',
|
||||
rounded: 'xl',
|
||||
cursor: 'pointer',
|
||||
width: '100%', // Fill grid cell
|
||||
transition: 'all 0.15s',
|
||||
width: { base: 'calc(50% - 4px)', sm: '260px' },
|
||||
height: { base: '64px', sm: '88px' },
|
||||
textAlign: 'left',
|
||||
_hover: {
|
||||
borderColor: isDark ? 'blue.500' : 'blue.400',
|
||||
bg: isDark ? 'gray.750' : 'gray.50',
|
||||
bg: isDark ? 'gray.600/90' : 'white',
|
||||
},
|
||||
_focus: {
|
||||
outline: 'none',
|
||||
borderColor: 'blue.500',
|
||||
boxShadow: '0 0 0 3px rgba(59, 130, 246, 0.2)',
|
||||
ring: '2px solid',
|
||||
ringColor: 'blue.500',
|
||||
},
|
||||
})
|
||||
|
||||
const contentStyles = css({
|
||||
bg: isDark ? 'gray.800' : 'white',
|
||||
border: '2px solid',
|
||||
border: '1px solid',
|
||||
borderColor: isDark ? 'gray.600' : 'gray.200',
|
||||
rounded: 'xl',
|
||||
shadow: 'xl',
|
||||
overflow: 'hidden',
|
||||
zIndex: 1000,
|
||||
minWidth: '220px',
|
||||
minWidth: '280px',
|
||||
})
|
||||
|
||||
const itemStyles = css({
|
||||
|
|
@ -155,255 +230,240 @@ export function SetupPhase() {
|
|||
},
|
||||
})
|
||||
|
||||
const labelStyles = css({
|
||||
fontSize: 'xs',
|
||||
fontWeight: '600',
|
||||
color: isDark ? 'gray.400' : 'gray.500',
|
||||
marginBottom: '2',
|
||||
textTransform: 'uppercase',
|
||||
letterSpacing: 'wide',
|
||||
})
|
||||
|
||||
return (
|
||||
<div
|
||||
data-component="setup-phase"
|
||||
className={css({
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: '4',
|
||||
maxWidth: '800px',
|
||||
margin: '0 auto',
|
||||
paddingTop: '16',
|
||||
paddingX: '4',
|
||||
paddingBottom: '6',
|
||||
position: 'fixed',
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
overflow: 'hidden',
|
||||
zIndex: 0,
|
||||
})}
|
||||
>
|
||||
{/* Header */}
|
||||
<div
|
||||
data-element="header"
|
||||
className={css({
|
||||
textAlign: 'center',
|
||||
marginBottom: '2',
|
||||
})}
|
||||
>
|
||||
<h1
|
||||
className={css({
|
||||
fontSize: '2xl',
|
||||
fontWeight: 'bold',
|
||||
color: isDark ? 'gray.100' : 'gray.900',
|
||||
})}
|
||||
>
|
||||
Know Your World 🌍
|
||||
</h1>
|
||||
<p
|
||||
className={css({
|
||||
fontSize: 'sm',
|
||||
color: isDark ? 'gray.400' : 'gray.600',
|
||||
marginTop: '1',
|
||||
})}
|
||||
>
|
||||
Click continents to zoom in, or start playing from any level
|
||||
</p>
|
||||
</div>
|
||||
{/* Full-viewport Map - fills entire container */}
|
||||
<DrillDownMapSelector
|
||||
selectedMap={state.selectedMap}
|
||||
selectedContinent={state.selectedContinent}
|
||||
onSelectionChange={handleSelectionChange}
|
||||
onStartGame={startGame}
|
||||
includeSizes={state.includeSizes}
|
||||
onRegionSizesChange={setRegionSizes}
|
||||
regionCountsBySize={regionCountsBySize}
|
||||
fillContainer
|
||||
/>
|
||||
|
||||
{/* Drill-Down Map Selector */}
|
||||
<div data-section="map-selection">
|
||||
<DrillDownMapSelector
|
||||
selectedMap={state.selectedMap}
|
||||
selectedContinent={state.selectedContinent}
|
||||
onSelectionChange={handleSelectionChange}
|
||||
onStartGame={startGame}
|
||||
includeSizes={state.includeSizes}
|
||||
onRegionSizesChange={setRegionSizes}
|
||||
regionCountsBySize={regionCountsBySize}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Settings Row with Radix Selects */}
|
||||
{/* Game Setup Sequence - Mode → Guidance → Start */}
|
||||
<div
|
||||
data-section="settings"
|
||||
data-element="setup-sequence"
|
||||
className={css({
|
||||
display: 'grid',
|
||||
gridTemplateColumns: '1fr', // Stack on mobile
|
||||
gap: '4',
|
||||
padding: '5',
|
||||
bg: isDark ? 'gray.800/50' : 'gray.50',
|
||||
position: 'absolute',
|
||||
bottom: { base: '2', sm: '4' },
|
||||
left: { base: '2', sm: '50%' },
|
||||
right: { base: '2', sm: 'auto' },
|
||||
transform: { base: 'none', sm: 'translateX(-50%)' },
|
||||
display: 'flex',
|
||||
flexWrap: 'wrap',
|
||||
alignItems: 'stretch',
|
||||
gap: '2',
|
||||
padding: '2',
|
||||
bg: isDark ? 'gray.800/95' : 'gray.100/95',
|
||||
backdropFilter: 'blur(12px)',
|
||||
rounded: '2xl',
|
||||
border: '1px solid',
|
||||
borderColor: isDark ? 'gray.700' : 'gray.200',
|
||||
md: {
|
||||
gridTemplateColumns: 'repeat(2, 1fr)', // 2 columns on desktop
|
||||
},
|
||||
shadow: 'xl',
|
||||
zIndex: 50,
|
||||
maxWidth: { base: '100%', sm: 'fit-content' },
|
||||
})}
|
||||
>
|
||||
{/* Game Mode */}
|
||||
<div data-setting="game-mode" className={css({ display: 'flex', flexDirection: 'column' })}>
|
||||
<label className={labelStyles}>Mode</label>
|
||||
<Select.Root
|
||||
value={state.gameMode}
|
||||
onValueChange={(value) => setMode(value as 'cooperative' | 'race' | 'turn-based')}
|
||||
>
|
||||
<Select.Trigger className={triggerStyles}>
|
||||
<span className={css({ fontSize: '2xl' })}>{selectedMode?.emoji}</span>
|
||||
<div className={css({ flex: 1, textAlign: 'left' })}>
|
||||
<div
|
||||
{/* 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: 'xl', 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',
|
||||
color: isDark ? 'gray.100' : 'gray.900',
|
||||
fontSize: 'sm',
|
||||
fontSize: { base: 'sm', sm: 'md' },
|
||||
color: isDark ? 'gray.100' : 'gray.800',
|
||||
})}
|
||||
>
|
||||
{selectedMode?.label}
|
||||
</div>
|
||||
<div
|
||||
</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 'guided' | 'helpful' | 'standard' | 'none')
|
||||
}
|
||||
>
|
||||
<Select.Trigger className={cardTriggerStyles}>
|
||||
<span className={css({ fontSize: { base: 'xl', 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({
|
||||
fontSize: 'xs',
|
||||
color: isDark ? 'gray.400' : 'gray.500',
|
||||
lineHeight: 'tight',
|
||||
fontWeight: '600',
|
||||
fontSize: { base: 'sm', sm: 'md' },
|
||||
color: isDark ? 'gray.100' : 'gray.800',
|
||||
})}
|
||||
>
|
||||
{selectedMode?.description}
|
||||
</div>
|
||||
{selectedAssistance?.label}
|
||||
</span>
|
||||
<Select.Icon
|
||||
className={css({ color: isDark ? 'gray.400' : 'gray.500', fontSize: 'xs' })}
|
||||
>
|
||||
▼
|
||||
</Select.Icon>
|
||||
</div>
|
||||
<Select.Icon className={css({ color: isDark ? 'gray.400' : 'gray.500' })}>
|
||||
▼
|
||||
</Select.Icon>
|
||||
</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({
|
||||
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',
|
||||
fontSize: 'sm',
|
||||
})}
|
||||
>
|
||||
{option.label}
|
||||
{level.label}
|
||||
</span>
|
||||
</Select.ItemText>
|
||||
<div
|
||||
className={css({
|
||||
fontSize: 'xs',
|
||||
fontSize: 'sm',
|
||||
color: isDark ? 'gray.400' : 'gray.500',
|
||||
lineHeight: 'tight',
|
||||
marginTop: '1',
|
||||
})}
|
||||
>
|
||||
{option.description}
|
||||
{level.description}
|
||||
</div>
|
||||
</div>
|
||||
</Select.Item>
|
||||
))}
|
||||
</Select.Viewport>
|
||||
</Select.Content>
|
||||
</Select.Portal>
|
||||
</Select.Root>
|
||||
</div>
|
||||
|
||||
{/* Assistance Level */}
|
||||
<div
|
||||
data-setting="assistance-level"
|
||||
className={css({ display: 'flex', flexDirection: 'column' })}
|
||||
>
|
||||
<label className={labelStyles}>Assistance</label>
|
||||
<Select.Root
|
||||
value={state.assistanceLevel}
|
||||
onValueChange={(value) =>
|
||||
setAssistanceLevel(value as 'guided' | 'helpful' | 'standard' | 'none')
|
||||
}
|
||||
>
|
||||
<Select.Trigger className={triggerStyles}>
|
||||
<span className={css({ fontSize: '2xl' })}>{selectedAssistance?.emoji || '💡'}</span>
|
||||
<div className={css({ flex: 1, textAlign: 'left' })}>
|
||||
<div
|
||||
className={css({
|
||||
fontWeight: '600',
|
||||
color: isDark ? 'gray.100' : 'gray.900',
|
||||
fontSize: 'sm',
|
||||
})}
|
||||
>
|
||||
{selectedAssistance?.label}
|
||||
</div>
|
||||
<div
|
||||
className={css({
|
||||
fontSize: 'xs',
|
||||
color: isDark ? 'gray.400' : 'gray.500',
|
||||
lineHeight: 'tight',
|
||||
})}
|
||||
>
|
||||
{selectedAssistance?.description}
|
||||
</div>
|
||||
{/* Feature badges */}
|
||||
{selectedAssistance && (
|
||||
<div
|
||||
className={css({
|
||||
display: 'flex',
|
||||
gap: '1',
|
||||
marginTop: '1',
|
||||
flexWrap: 'wrap',
|
||||
})}
|
||||
>
|
||||
{getFeatureBadges(selectedAssistance).map((badge) => (
|
||||
<span
|
||||
key={badge.label}
|
||||
className={css({
|
||||
fontSize: '2xs',
|
||||
padding: '0.5 1',
|
||||
bg: isDark ? 'gray.700' : 'gray.200',
|
||||
color: isDark ? 'gray.300' : 'gray.600',
|
||||
rounded: 'sm',
|
||||
})}
|
||||
>
|
||||
{badge.icon} {badge.label}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<Select.Icon className={css({ color: isDark ? 'gray.400' : 'gray.500' })}>
|
||||
▼
|
||||
</Select.Icon>
|
||||
</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',
|
||||
color: isDark ? 'gray.100' : 'gray.900',
|
||||
fontSize: 'sm',
|
||||
})}
|
||||
>
|
||||
{level.label}
|
||||
</span>
|
||||
</Select.ItemText>
|
||||
<div
|
||||
className={css({
|
||||
fontSize: 'xs',
|
||||
color: isDark ? 'gray.400' : 'gray.500',
|
||||
lineHeight: 'tight',
|
||||
})}
|
||||
>
|
||||
{level.description}
|
||||
</div>
|
||||
{/* Feature badges */}
|
||||
{badges.length > 0 && (
|
||||
<div
|
||||
className={css({
|
||||
display: 'flex',
|
||||
gap: '1',
|
||||
marginTop: '1',
|
||||
mt: '2',
|
||||
flexWrap: 'wrap',
|
||||
})}
|
||||
>
|
||||
|
|
@ -411,55 +471,148 @@ export function SetupPhase() {
|
|||
<span
|
||||
key={badge.label}
|
||||
className={css({
|
||||
fontSize: '2xs',
|
||||
padding: '0.5 1',
|
||||
bg: isDark ? 'gray.700' : 'gray.200',
|
||||
fontSize: 'xs',
|
||||
padding: '1 2',
|
||||
bg: isDark ? 'gray.600' : 'gray.200',
|
||||
color: isDark ? 'gray.300' : 'gray.600',
|
||||
rounded: 'sm',
|
||||
rounded: 'md',
|
||||
})}
|
||||
>
|
||||
{badge.icon} {badge.label}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</Select.Item>
|
||||
)
|
||||
})}
|
||||
</Select.Viewport>
|
||||
</Select.Content>
|
||||
</Select.Portal>
|
||||
</Select.Root>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</Select.Item>
|
||||
)
|
||||
})}
|
||||
</Select.Viewport>
|
||||
</Select.Content>
|
||||
</Select.Portal>
|
||||
</Select.Root>
|
||||
|
||||
{/* Start Game Button */}
|
||||
<button
|
||||
data-action="start-game"
|
||||
onClick={startGame}
|
||||
className={css({
|
||||
width: '100%',
|
||||
padding: '4',
|
||||
fontSize: 'xl',
|
||||
fontWeight: 'bold',
|
||||
bg: 'blue.600',
|
||||
color: 'white',
|
||||
rounded: '2xl',
|
||||
cursor: 'pointer',
|
||||
boxShadow: 'lg',
|
||||
transition: 'all 0.2s',
|
||||
_hover: {
|
||||
bg: 'blue.700',
|
||||
transform: 'scale(1.02)',
|
||||
},
|
||||
_active: {
|
||||
transform: 'scale(0.98)',
|
||||
},
|
||||
})}
|
||||
>
|
||||
▶ Start Game ({contextLabel} - {totalRegionCount}{' '}
|
||||
{totalRegionCount === 1 ? 'region' : 'regions'})
|
||||
</button>
|
||||
{/* Start Button - Travel-themed, region-specific */}
|
||||
<button
|
||||
data-action="start-game"
|
||||
onClick={startGame}
|
||||
className={css({
|
||||
position: 'relative',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
gap: { base: '2', sm: '3' },
|
||||
padding: { base: '2 4', sm: '3 5' },
|
||||
width: { base: '100%', sm: 'auto' },
|
||||
minWidth: { base: 'auto', sm: '220px' },
|
||||
flex: { base: 'none', sm: 1 },
|
||||
height: { base: '56px', sm: '88px' },
|
||||
fontSize: { base: 'md', sm: 'lg' },
|
||||
fontWeight: 'bold',
|
||||
color: 'white',
|
||||
rounded: 'xl',
|
||||
cursor: 'pointer',
|
||||
transition: 'all 0.2s ease-out',
|
||||
overflow: 'hidden',
|
||||
border: '2px solid rgba(255,255,255,0.2)',
|
||||
boxShadow: '0 4px 15px rgba(0,0,0,0.2)',
|
||||
_hover: {
|
||||
transform: 'scale(1.02)',
|
||||
boxShadow: '0 6px 20px rgba(0,0,0,0.3)',
|
||||
},
|
||||
_active: {
|
||||
transform: 'scale(0.98)',
|
||||
},
|
||||
})}
|
||||
style={{
|
||||
background: regionTheme.gradient,
|
||||
}}
|
||||
onMouseEnter={(e) => {
|
||||
e.currentTarget.style.background = regionTheme.gradientHover
|
||||
}}
|
||||
onMouseLeave={(e) => {
|
||||
e.currentTarget.style.background = regionTheme.gradient
|
||||
}}
|
||||
>
|
||||
{/* Decorative flag strip at top */}
|
||||
<div
|
||||
className={css({
|
||||
position: 'absolute',
|
||||
top: '0',
|
||||
left: '0',
|
||||
right: '0',
|
||||
height: { base: '16px', sm: '20px' },
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
gap: '1',
|
||||
fontSize: { base: '2xs', sm: 'xs' },
|
||||
bg: 'rgba(0,0,0,0.15)',
|
||||
paddingTop: '1px',
|
||||
overflow: 'hidden',
|
||||
})}
|
||||
>
|
||||
{regionTheme.flagEmojis.map((flag, i) => (
|
||||
<span key={i} className={css({ opacity: 0.9 })}>
|
||||
{flag}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Main content */}
|
||||
<div
|
||||
className={css({
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: { base: '2', sm: '3' },
|
||||
marginTop: { base: '6px', sm: '8px' },
|
||||
})}
|
||||
>
|
||||
{/* Travel icons */}
|
||||
<div
|
||||
className={css({
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
fontSize: { base: 'lg', sm: '2xl' },
|
||||
lineHeight: 1,
|
||||
})}
|
||||
>
|
||||
<span>{regionTheme.icons[0]}</span>
|
||||
<span className={css({ fontSize: { base: 'xs', sm: 'sm' }, marginTop: '-2px' })}>
|
||||
{regionTheme.icons[1]}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{/* Text content */}
|
||||
<div
|
||||
className={css({
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'flex-start',
|
||||
})}
|
||||
>
|
||||
<span
|
||||
className={css({
|
||||
fontSize: { base: 'md', sm: 'lg' },
|
||||
fontWeight: 'bold',
|
||||
textShadow: '0 1px 2px rgba(0,0,0,0.2)',
|
||||
})}
|
||||
>
|
||||
Start {contextLabel}
|
||||
</span>
|
||||
<span
|
||||
className={css({
|
||||
fontSize: { base: 'xs', sm: 'sm' },
|
||||
fontWeight: 'normal',
|
||||
opacity: 0.9,
|
||||
})}
|
||||
>
|
||||
{totalRegionCount} regions
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue