feat(know-your-world): add region shape silhouette to learning takeover
- Display SVG silhouette of target region during takeover animation - Use CSS centering for takeover text (more robust than position animation) - Restructure takeover overlay as parent container to fix stacking context - Region shape shows between scrim and text with semi-transparent blue fill 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
3fd8472e68
commit
ebe07e358f
|
|
@ -3,10 +3,11 @@
|
||||||
import * as DropdownMenu from '@radix-ui/react-dropdown-menu'
|
import * as DropdownMenu from '@radix-ui/react-dropdown-menu'
|
||||||
import { css } from '@styled/css'
|
import { css } from '@styled/css'
|
||||||
import { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react'
|
import { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react'
|
||||||
import { useSpring, animated, to } from '@react-spring/web'
|
import { useSpring, animated } from '@react-spring/web'
|
||||||
import { useViewerId } from '@/lib/arcade/game-sdk'
|
import { useViewerId } from '@/lib/arcade/game-sdk'
|
||||||
import { useTheme } from '@/contexts/ThemeContext'
|
import { useTheme } from '@/contexts/ThemeContext'
|
||||||
import {
|
import {
|
||||||
|
calculateBoundingBox,
|
||||||
DEFAULT_DIFFICULTY_CONFIG,
|
DEFAULT_DIFFICULTY_CONFIG,
|
||||||
getAssistanceLevel,
|
getAssistanceLevel,
|
||||||
getCountryFlagEmoji,
|
getCountryFlagEmoji,
|
||||||
|
|
@ -145,6 +146,23 @@ export function GameInfoPanel({
|
||||||
const displayFlagEmoji =
|
const displayFlagEmoji =
|
||||||
selectedMap === 'world' && displayRegionId ? getCountryFlagEmoji(displayRegionId) : ''
|
selectedMap === 'world' && displayRegionId ? getCountryFlagEmoji(displayRegionId) : ''
|
||||||
|
|
||||||
|
// Get the region's SVG path for the takeover shape display
|
||||||
|
const displayRegionShape = useMemo(() => {
|
||||||
|
if (!displayRegionId) return null
|
||||||
|
const region = mapData.regions.find((r) => r.id === displayRegionId)
|
||||||
|
if (!region?.path) return null
|
||||||
|
|
||||||
|
// Calculate bounding box with padding for the viewBox
|
||||||
|
const bbox = calculateBoundingBox([region.path])
|
||||||
|
const padding = Math.max(bbox.width, bbox.height) * 0.1 // 10% padding
|
||||||
|
const viewBox = `${bbox.minX - padding} ${bbox.minY - padding} ${bbox.width + padding * 2} ${bbox.height + padding * 2}`
|
||||||
|
|
||||||
|
return {
|
||||||
|
path: region.path,
|
||||||
|
viewBox,
|
||||||
|
}
|
||||||
|
}, [displayRegionId, mapData.regions])
|
||||||
|
|
||||||
// Track if animation is in progress (local state based on timestamp)
|
// Track if animation is in progress (local state based on timestamp)
|
||||||
const [isAnimating, setIsAnimating] = useState(false)
|
const [isAnimating, setIsAnimating] = useState(false)
|
||||||
|
|
||||||
|
|
@ -167,17 +185,14 @@ export function GameInfoPanel({
|
||||||
// Ref to measure the takeover container (region name + instructions)
|
// Ref to measure the takeover container (region name + instructions)
|
||||||
const takeoverContainerRef = useRef<HTMLDivElement>(null)
|
const takeoverContainerRef = useRef<HTMLDivElement>(null)
|
||||||
|
|
||||||
// Calculate the safe scale factor and offsets for perfect centering
|
// Calculate the safe scale factor based on viewport size
|
||||||
const [safeScale, setSafeScale] = useState(2.5)
|
const [safeScale, setSafeScale] = useState(2.5)
|
||||||
const [centerXOffset, setCenterXOffset] = useState(0)
|
|
||||||
const [centerYOffset, setCenterYOffset] = useState(0)
|
|
||||||
|
|
||||||
// Measure container and calculate safe scale + centering offsets when region changes
|
// Measure container and calculate safe scale when region changes or window resizes
|
||||||
useLayoutEffect(() => {
|
useLayoutEffect(() => {
|
||||||
if (!currentRegionName || !isLearningMode) return
|
if (!currentRegionName || !isLearningMode) return
|
||||||
|
|
||||||
// Use requestAnimationFrame to ensure text has rendered
|
const measureAndUpdate = () => {
|
||||||
const rafId = requestAnimationFrame(() => {
|
|
||||||
if (takeoverContainerRef.current) {
|
if (takeoverContainerRef.current) {
|
||||||
const rect = takeoverContainerRef.current.getBoundingClientRect()
|
const rect = takeoverContainerRef.current.getBoundingClientRect()
|
||||||
|
|
||||||
|
|
@ -188,20 +203,19 @@ export function GameInfoPanel({
|
||||||
// Use the smaller of width/height constraints, clamped between 1.5 and 3.5
|
// Use the smaller of width/height constraints, clamped between 1.5 and 3.5
|
||||||
const calculatedScale = Math.min(maxWidthScale, maxHeightScale)
|
const calculatedScale = Math.min(maxWidthScale, maxHeightScale)
|
||||||
setSafeScale(Math.max(1.5, Math.min(3.5, calculatedScale)))
|
setSafeScale(Math.max(1.5, Math.min(3.5, calculatedScale)))
|
||||||
|
|
||||||
// Calculate X offset to center horizontally on screen
|
|
||||||
const elementCenterX = rect.left + rect.width / 2
|
|
||||||
const screenCenterX = window.innerWidth / 2
|
|
||||||
setCenterXOffset(screenCenterX - elementCenterX)
|
|
||||||
|
|
||||||
// Calculate Y offset to center vertically on screen
|
|
||||||
const elementCenterY = rect.top + rect.height / 2
|
|
||||||
const screenCenterY = window.innerHeight / 2
|
|
||||||
setCenterYOffset(screenCenterY - elementCenterY)
|
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
|
|
||||||
return () => cancelAnimationFrame(rafId)
|
// Use requestAnimationFrame to ensure text has rendered
|
||||||
|
const rafId = requestAnimationFrame(measureAndUpdate)
|
||||||
|
|
||||||
|
// Also update on resize
|
||||||
|
window.addEventListener('resize', measureAndUpdate)
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
cancelAnimationFrame(rafId)
|
||||||
|
window.removeEventListener('resize', measureAndUpdate)
|
||||||
|
}
|
||||||
}, [currentRegionName, isLearningMode])
|
}, [currentRegionName, isLearningMode])
|
||||||
|
|
||||||
// Calculate takeover progress based on letters typed (0 = full takeover, 1 = complete)
|
// Calculate takeover progress based on letters typed (0 = full takeover, 1 = complete)
|
||||||
|
|
@ -213,33 +227,12 @@ export function GameInfoPanel({
|
||||||
return Math.min(1, confirmedLetterCount / requiresNameConfirmation)
|
return Math.min(1, confirmedLetterCount / requiresNameConfirmation)
|
||||||
}, [isLearningMode, requiresNameConfirmation, confirmedLetterCount, isGiveUpAnimating])
|
}, [isLearningMode, requiresNameConfirmation, confirmedLetterCount, isGiveUpAnimating])
|
||||||
|
|
||||||
// Memoize spring target values to avoid recalculating on every render
|
// Spring animation for scale only - position is handled by CSS centering
|
||||||
const springTargets = useMemo(
|
|
||||||
() => ({
|
|
||||||
scale: safeScale - (safeScale - 1) * takeoverProgress,
|
|
||||||
x: centerXOffset * (1 - takeoverProgress),
|
|
||||||
y: centerYOffset * (1 - takeoverProgress),
|
|
||||||
}),
|
|
||||||
[safeScale, centerXOffset, centerYOffset, takeoverProgress]
|
|
||||||
)
|
|
||||||
|
|
||||||
// React-spring animation for takeover transition
|
|
||||||
const takeoverSpring = useSpring({
|
const takeoverSpring = useSpring({
|
||||||
...springTargets,
|
scale: safeScale - (safeScale - 1) * takeoverProgress,
|
||||||
config: TAKEOVER_ANIMATION_CONFIG,
|
config: TAKEOVER_ANIMATION_CONFIG,
|
||||||
})
|
})
|
||||||
|
|
||||||
// Memoize the transform interpolation to avoid recreating on every render
|
|
||||||
const transformStyle = useMemo(
|
|
||||||
() => ({
|
|
||||||
transform: to(
|
|
||||||
[takeoverSpring.scale, takeoverSpring.x, takeoverSpring.y],
|
|
||||||
(s, x, y) => `translate(${x}px, ${y}px) scale(${s})`
|
|
||||||
),
|
|
||||||
}),
|
|
||||||
[takeoverSpring.scale, takeoverSpring.x, takeoverSpring.y]
|
|
||||||
)
|
|
||||||
|
|
||||||
// Memoize whether we're in active takeover mode
|
// Memoize whether we're in active takeover mode
|
||||||
const isInTakeover = isLearningMode && takeoverProgress < 1
|
const isInTakeover = isLearningMode && takeoverProgress < 1
|
||||||
const showPulseAnimation = isLearningMode && takeoverProgress < 0.5
|
const showPulseAnimation = isLearningMode && takeoverProgress < 0.5
|
||||||
|
|
@ -422,31 +415,159 @@ export function GameInfoPanel({
|
||||||
}
|
}
|
||||||
`}</style>
|
`}</style>
|
||||||
|
|
||||||
{/* Takeover backdrop scrim - blurs background but leaves nav accessible */}
|
{/* Takeover overlay - contains scrim backdrop, region shape, and takeover text */}
|
||||||
{/* Always rendered but uses CSS transitions for smooth fade in/out */}
|
{/* All children share the same stacking context */}
|
||||||
<div
|
<div
|
||||||
data-element="takeover-scrim"
|
data-element="takeover-overlay"
|
||||||
className={css({
|
className={css({
|
||||||
position: 'fixed',
|
position: 'fixed',
|
||||||
// Start below the nav bar to keep it accessible
|
top: 0,
|
||||||
top: '60px',
|
|
||||||
left: 0,
|
left: 0,
|
||||||
right: 0,
|
right: 0,
|
||||||
bottom: 0,
|
bottom: 0,
|
||||||
// Below takeover content (z-index 200) but above map
|
|
||||||
zIndex: 150,
|
zIndex: 150,
|
||||||
pointerEvents: 'none',
|
pointerEvents: 'none',
|
||||||
// Smooth transitions for opacity and blur
|
// Smooth transition for the whole overlay
|
||||||
transition: 'opacity 0.3s ease-out, backdrop-filter 0.3s ease-out',
|
transition: 'opacity 0.3s ease-out',
|
||||||
})}
|
})}
|
||||||
style={{
|
style={{
|
||||||
backgroundColor: isDark ? 'rgba(0, 0, 0, 0.5)' : 'rgba(255, 255, 255, 0.5)',
|
|
||||||
// Animate between blurred (in takeover) and clear (not in takeover)
|
|
||||||
backdropFilter: isInTakeover ? 'blur(8px)' : 'blur(0px)',
|
|
||||||
WebkitBackdropFilter: isInTakeover ? 'blur(8px)' : 'blur(0px)',
|
|
||||||
opacity: isInTakeover ? 1 : 0,
|
opacity: isInTakeover ? 1 : 0,
|
||||||
}}
|
}}
|
||||||
/>
|
>
|
||||||
|
{/* Backdrop scrim with blur */}
|
||||||
|
<div
|
||||||
|
data-element="takeover-scrim"
|
||||||
|
className={css({
|
||||||
|
position: 'absolute',
|
||||||
|
top: 0,
|
||||||
|
left: 0,
|
||||||
|
right: 0,
|
||||||
|
bottom: 0,
|
||||||
|
})}
|
||||||
|
style={{
|
||||||
|
backgroundColor: isDark ? 'rgba(0, 0, 0, 0.6)' : 'rgba(255, 255, 255, 0.6)',
|
||||||
|
backdropFilter: 'blur(8px)',
|
||||||
|
WebkitBackdropFilter: 'blur(8px)',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Region shape silhouette */}
|
||||||
|
{displayRegionShape && (
|
||||||
|
<svg
|
||||||
|
data-element="takeover-region-shape"
|
||||||
|
viewBox={displayRegionShape.viewBox}
|
||||||
|
preserveAspectRatio="xMidYMid meet"
|
||||||
|
className={css({
|
||||||
|
position: 'absolute',
|
||||||
|
top: 0,
|
||||||
|
left: 0,
|
||||||
|
width: '100%',
|
||||||
|
height: '100%',
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d={displayRegionShape.path}
|
||||||
|
fill={isDark ? 'rgba(59, 130, 246, 0.5)' : 'rgba(59, 130, 246, 0.35)'}
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Takeover text - CSS centered, only scale is animated */}
|
||||||
|
<animated.div
|
||||||
|
data-element="takeover-content"
|
||||||
|
className={css({
|
||||||
|
position: 'absolute',
|
||||||
|
// CSS centering
|
||||||
|
top: '50%',
|
||||||
|
left: '50%',
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
alignItems: 'center',
|
||||||
|
transformOrigin: 'center center',
|
||||||
|
})}
|
||||||
|
style={{
|
||||||
|
// Combine centering translation with animated scale
|
||||||
|
transform: takeoverSpring.scale.to((s) => `translate(-50%, -50%) scale(${s})`),
|
||||||
|
animation: showPulseAnimation ? 'takeoverPulse 0.8s ease-in-out infinite' : 'none',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{/* Region name display */}
|
||||||
|
<div
|
||||||
|
data-element="takeover-region-name"
|
||||||
|
className={css({
|
||||||
|
fontSize: { base: 'lg', sm: 'xl', md: '2xl' },
|
||||||
|
fontWeight: 'bold',
|
||||||
|
color: isDark ? 'white' : 'blue.900',
|
||||||
|
whiteSpace: 'nowrap',
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
gap: { base: '1', sm: '2' },
|
||||||
|
})}
|
||||||
|
style={{
|
||||||
|
textShadow: isDark ? '0 2px 4px rgba(0,0,0,0.5)' : '0 2px 4px rgba(0,0,0,0.2)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{displayFlagEmoji && (
|
||||||
|
<span className={css({ fontSize: { base: 'lg', sm: 'xl', md: '2xl' } })}>
|
||||||
|
{displayFlagEmoji}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
<span>
|
||||||
|
{displayRegionName
|
||||||
|
? displayRegionName.split('').map((char, index) => {
|
||||||
|
// During takeover, show all letters fully visible (no confirmation styling)
|
||||||
|
const needsConfirmation =
|
||||||
|
!isGiveUpAnimating &&
|
||||||
|
requiresNameConfirmation > 0 &&
|
||||||
|
!nameConfirmed &&
|
||||||
|
index < requiresNameConfirmation
|
||||||
|
const isConfirmed = index < confirmedLetterCount
|
||||||
|
const isNextToConfirm = index === confirmedLetterCount && needsConfirmation
|
||||||
|
|
||||||
|
return (
|
||||||
|
<span
|
||||||
|
key={index}
|
||||||
|
className={css({ transition: 'all 0.15s ease-out' })}
|
||||||
|
style={{
|
||||||
|
opacity: needsConfirmation && !isConfirmed ? 0.4 : 1,
|
||||||
|
textDecoration: isNextToConfirm ? 'underline' : 'none',
|
||||||
|
textDecorationColor: isNextToConfirm
|
||||||
|
? isDark
|
||||||
|
? '#60a5fa'
|
||||||
|
: '#3b82f6'
|
||||||
|
: undefined,
|
||||||
|
textUnderlineOffset: isNextToConfirm ? '4px' : undefined,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{char}
|
||||||
|
</span>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
: '...'}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Type-to-unlock instruction */}
|
||||||
|
{!isGiveUpAnimating && requiresNameConfirmation > 0 && !nameConfirmed && (
|
||||||
|
<div
|
||||||
|
data-element="takeover-type-instruction"
|
||||||
|
className={css({
|
||||||
|
marginTop: '2',
|
||||||
|
fontSize: { base: 'sm', sm: 'md' },
|
||||||
|
color: isDark ? 'amber.300' : 'amber.700',
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
gap: '1.5',
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<span>⌨️</span>
|
||||||
|
<span>Type the underlined letter{requiresNameConfirmation > 1 ? 's' : ''}</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</animated.div>
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* TOP-CENTER: Prompt Display - positioned below game nav (~150px) */}
|
{/* TOP-CENTER: Prompt Display - positioned below game nav (~150px) */}
|
||||||
{/* Background fills left-to-right as progress increases */}
|
{/* Background fills left-to-right as progress increases */}
|
||||||
|
|
@ -469,8 +590,7 @@ export function GameInfoPanel({
|
||||||
style={{
|
style={{
|
||||||
animation: 'glowPulse 2s ease-in-out infinite',
|
animation: 'glowPulse 2s ease-in-out infinite',
|
||||||
overflow: isInTakeover ? 'visible' : 'hidden',
|
overflow: isInTakeover ? 'visible' : 'hidden',
|
||||||
// Raise z-index above scrim (150) during takeover
|
// Prompt pane stays behind scrim; only takeover-container elevates above
|
||||||
zIndex: isInTakeover ? 200 : undefined,
|
|
||||||
background: isDark
|
background: isDark
|
||||||
? `linear-gradient(to right, rgba(22, 78, 99, 0.3) ${progress}%, rgba(30, 58, 138, 0.25) ${progress}%)`
|
? `linear-gradient(to right, rgba(22, 78, 99, 0.3) ${progress}%, rgba(30, 58, 138, 0.25) ${progress}%)`
|
||||||
: `linear-gradient(to right, rgba(34, 197, 94, 0.25) ${progress}%, rgba(59, 130, 246, 0.2) ${progress}%)`,
|
: `linear-gradient(to right, rgba(34, 197, 94, 0.25) ${progress}%, rgba(59, 130, 246, 0.2) ${progress}%)`,
|
||||||
|
|
@ -814,25 +934,20 @@ export function GameInfoPanel({
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{/* Animated container for takeover - includes region name and instructions */}
|
{/* Region name container - ref used for measuring position for takeover */}
|
||||||
<animated.div
|
{/* Content hidden during takeover (shown in overlay instead) */}
|
||||||
|
<div
|
||||||
ref={takeoverContainerRef}
|
ref={takeoverContainerRef}
|
||||||
key={currentRegionId || 'empty'} // Re-trigger animation on change
|
data-element="region-name-container"
|
||||||
data-element="takeover-container"
|
|
||||||
className={css({
|
className={css({
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
flexDirection: 'column',
|
flexDirection: 'column',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
position: 'relative',
|
position: 'relative',
|
||||||
transformOrigin: 'center center',
|
|
||||||
})}
|
})}
|
||||||
style={{
|
style={{
|
||||||
// Dynamic z-index moved to inline style to avoid css() recalculation
|
// Hide during takeover since it's shown in the overlay
|
||||||
zIndex: isInTakeover ? 200 : 'auto',
|
visibility: isInTakeover ? 'hidden' : 'visible',
|
||||||
// React-spring animated transform
|
|
||||||
...transformStyle,
|
|
||||||
// Pulse animation during takeover (fades after halfway)
|
|
||||||
animation: showPulseAnimation ? 'takeoverPulse 0.8s ease-in-out infinite' : 'none',
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{/* Region name display */}
|
{/* Region name display */}
|
||||||
|
|
@ -853,20 +968,13 @@ export function GameInfoPanel({
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{displayFlagEmoji && (
|
{displayFlagEmoji && (
|
||||||
<span
|
<span className={css({ fontSize: { base: 'lg', sm: 'xl', md: '2xl' } })}>
|
||||||
className={css({
|
|
||||||
fontSize: { base: 'lg', sm: 'xl', md: '2xl' },
|
|
||||||
})}
|
|
||||||
>
|
|
||||||
{displayFlagEmoji}
|
{displayFlagEmoji}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
{/* Inline letter highlighting for name confirmation */}
|
|
||||||
<span>
|
<span>
|
||||||
{displayRegionName
|
{displayRegionName
|
||||||
? displayRegionName.split('').map((char, index) => {
|
? displayRegionName.split('').map((char, index) => {
|
||||||
// Determine if this letter needs confirmation styling
|
|
||||||
// Disable during give-up animation (showing the given-up region)
|
|
||||||
const needsConfirmation =
|
const needsConfirmation =
|
||||||
!isGiveUpAnimating &&
|
!isGiveUpAnimating &&
|
||||||
requiresNameConfirmation > 0 &&
|
requiresNameConfirmation > 0 &&
|
||||||
|
|
@ -878,13 +986,9 @@ export function GameInfoPanel({
|
||||||
return (
|
return (
|
||||||
<span
|
<span
|
||||||
key={index}
|
key={index}
|
||||||
className={css({
|
className={css({ transition: 'all 0.15s ease-out' })}
|
||||||
transition: 'all 0.15s ease-out',
|
|
||||||
})}
|
|
||||||
style={{
|
style={{
|
||||||
// Dim unconfirmed letters that need confirmation
|
|
||||||
opacity: needsConfirmation && !isConfirmed ? 0.4 : 1,
|
opacity: needsConfirmation && !isConfirmed ? 0.4 : 1,
|
||||||
// Highlight the next letter to type
|
|
||||||
textDecoration: isNextToConfirm ? 'underline' : 'none',
|
textDecoration: isNextToConfirm ? 'underline' : 'none',
|
||||||
textDecorationColor: isNextToConfirm
|
textDecorationColor: isNextToConfirm
|
||||||
? isDark
|
? isDark
|
||||||
|
|
@ -902,8 +1006,7 @@ export function GameInfoPanel({
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Type-to-unlock instruction - included in takeover container */}
|
{/* Type-to-unlock instruction */}
|
||||||
{/* Hide during give-up animation since we're showing the given-up region */}
|
|
||||||
{!isGiveUpAnimating && requiresNameConfirmation > 0 && !nameConfirmed && (
|
{!isGiveUpAnimating && requiresNameConfirmation > 0 && !nameConfirmed && (
|
||||||
<div
|
<div
|
||||||
data-element="type-to-unlock"
|
data-element="type-to-unlock"
|
||||||
|
|
@ -921,7 +1024,7 @@ export function GameInfoPanel({
|
||||||
<span>Type the underlined letter{requiresNameConfirmation > 1 ? 's' : ''}</span>
|
<span>Type the underlined letter{requiresNameConfirmation > 1 ? 's' : ''}</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</animated.div>
|
</div>
|
||||||
|
|
||||||
{/* Inline hint - shown after name is confirmed (or always in non-learning modes) */}
|
{/* Inline hint - shown after name is confirmed (or always in non-learning modes) */}
|
||||||
{currentHint && (requiresNameConfirmation === 0 || nameConfirmed) && (
|
{currentHint && (requiresNameConfirmation === 0 || nameConfirmed) && (
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue