- 删除 src/locales/en-US/common.ts 文件中的所有国际化文案 - 删除 src/locales/id-ID/common.ts 文件中的所有国际化文案 - 移除与游戏、认证、UI组件相关的多语言配置项 - 清理导航、游戏大厅、语言选择等界面的翻译内容 - 移除登录注册表单、验证规则等认证相关文案 - 删除支付、提款、钱包记录等财务功能翻译项
550 lines
23 KiB
TypeScript
550 lines
23 KiB
TypeScript
import { TriangleAlert } from 'lucide-react'
|
|
import { motion, useReducedMotion } from 'motion/react'
|
|
import { useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react'
|
|
import { useTranslation } from 'react-i18next'
|
|
import animalBorderImage from '@/assets/game/animal-border.webp'
|
|
import diamondIcon from '@/assets/system/diamond.webp'
|
|
import { SmartImage } from '@/components/smart-image'
|
|
import { DesktopAnimalOverlay } from '@/features/game/components/desktop/desktop-animal-overlay.tsx'
|
|
import { RoundBettingStartAlert } from '@/features/game/components/shared/round-betting-start-alert.tsx'
|
|
import { useAnimalVm } from '@/features/game/hooks/use-animal-vm'
|
|
import { FLOWER_IMAGE_LIST } from '@/features/game/shared'
|
|
import { cn } from '@/lib/utils'
|
|
import { useAuthStore } from '@/store/auth'
|
|
import { useGameRoundStore } from '@/store/game'
|
|
|
|
const SETTLEMENT_REVEAL_RANDOM_DURATION_MS = 3_200
|
|
const SETTLEMENT_REVEAL_SETTLE_DURATION_MS = 800
|
|
const SETTLEMENT_REVEAL_RESULT_HOLD_MS = 1_000
|
|
const SETTLEMENT_REVEAL_MIN_STEP_MS = 180
|
|
const SETTLEMENT_REVEAL_MAX_STEP_MS = 960
|
|
|
|
function getRandomAnimalId(ids: number[], currentId: number | null) {
|
|
if (ids.length === 0) {
|
|
return null
|
|
}
|
|
|
|
if (ids.length === 1) {
|
|
return ids[0] ?? null
|
|
}
|
|
|
|
let nextId = currentId
|
|
|
|
while (nextId === currentId) {
|
|
nextId = ids[Math.floor(Math.random() * ids.length)] ?? currentId
|
|
}
|
|
|
|
return nextId
|
|
}
|
|
|
|
function getSettlementRevealStepDelay(progress: number) {
|
|
const clampedProgress = Math.min(Math.max(progress, 0), 1)
|
|
const easedProgress = clampedProgress ** 1.65
|
|
|
|
return (
|
|
SETTLEMENT_REVEAL_MIN_STEP_MS +
|
|
(SETTLEMENT_REVEAL_MAX_STEP_MS - SETTLEMENT_REVEAL_MIN_STEP_MS) *
|
|
easedProgress
|
|
)
|
|
}
|
|
|
|
interface DesktopAnimalProps {
|
|
className?: string
|
|
itemClassName?: string
|
|
imageClassName?: string
|
|
onSelect?: (animalId: number) => void
|
|
}
|
|
|
|
export function DesktopAnimal({
|
|
className,
|
|
itemClassName,
|
|
imageClassName,
|
|
onSelect,
|
|
}: DesktopAnimalProps) {
|
|
const { t } = useTranslation()
|
|
const prefersReducedMotion = useReducedMotion()
|
|
const animalIds = useMemo(() => FLOWER_IMAGE_LIST.map((item) => item.id), [])
|
|
const containerRef = useRef<HTMLElement | null>(null)
|
|
const cellRefs = useRef(new Map<number, HTMLButtonElement>())
|
|
const [revealCellId, setRevealCellId] = useState<number | null>(null)
|
|
const [revealFrame, setRevealFrame] = useState<{
|
|
height: number
|
|
left: number
|
|
top: number
|
|
width: number
|
|
} | null>(null)
|
|
const [isRevealHoldingResult, setIsRevealHoldingResult] = useState(false)
|
|
const [isRevealSettlingResult, setIsRevealSettlingResult] = useState(false)
|
|
const revealPhase = useGameRoundStore((state) => state.revealAnimation.phase)
|
|
const revealWinningCellId = useGameRoundStore(
|
|
(state) => state.revealAnimation.winningCellId,
|
|
)
|
|
const revealRewardType = useGameRoundStore(
|
|
(state) => state.revealAnimation.rewardType,
|
|
)
|
|
const roundPhase = useGameRoundStore((state) => state.round.phase)
|
|
const roundId = useGameRoundStore((state) => state.round.id)
|
|
const lastBetPeriodNo = useAuthStore(
|
|
(state) => state.currentUser?.lastBetPeriodNo,
|
|
)
|
|
const finishRevealAnimation = useGameRoundStore(
|
|
(state) => state.finishRevealAnimation,
|
|
)
|
|
const {
|
|
cellWarning,
|
|
handleSelect,
|
|
handleStart,
|
|
isRealtimeConnecting,
|
|
lockInteraction,
|
|
marqueeId,
|
|
selectionByCell,
|
|
showStandbyState,
|
|
} = useAnimalVm(animalIds, onSelect)
|
|
|
|
const isRevealRunning =
|
|
revealPhase === 'spinning' ||
|
|
(revealPhase === 'stopping' && !isRevealHoldingResult)
|
|
const isRevealResult = revealPhase === 'result'
|
|
const [hasRewardOverlayStarted, setHasRewardOverlayStarted] = useState(false)
|
|
const shouldHideRevealHighlight =
|
|
revealPhase === 'result' &&
|
|
(revealRewardType !== 'none' || hasRewardOverlayStarted)
|
|
const hasSubmittedCurrentRound =
|
|
roundPhase === 'betting' && Boolean(roundId) && lastBetPeriodNo === roundId
|
|
const showStopOverlay =
|
|
hasSubmittedCurrentRound ||
|
|
roundPhase === 'locked' ||
|
|
roundPhase === 'revealing'
|
|
|
|
useEffect(() => {
|
|
if (revealPhase === 'idle') {
|
|
setHasRewardOverlayStarted(false)
|
|
return
|
|
}
|
|
|
|
if (revealPhase === 'result' && revealRewardType !== 'none') {
|
|
setHasRewardOverlayStarted(true)
|
|
}
|
|
}, [revealPhase, revealRewardType])
|
|
|
|
useEffect(() => {
|
|
if (revealPhase === 'idle') {
|
|
setRevealCellId(null)
|
|
setIsRevealHoldingResult(false)
|
|
setIsRevealSettlingResult(false)
|
|
return
|
|
}
|
|
|
|
if (revealPhase === 'result') {
|
|
setRevealCellId(shouldHideRevealHighlight ? null : revealWinningCellId)
|
|
setIsRevealHoldingResult(false)
|
|
setIsRevealSettlingResult(false)
|
|
return
|
|
}
|
|
|
|
if (revealPhase === 'spinning') {
|
|
setIsRevealHoldingResult(false)
|
|
setIsRevealSettlingResult(false)
|
|
setRevealCellId((currentId) => getRandomAnimalId(animalIds, currentId))
|
|
|
|
const intervalId = window.setInterval(() => {
|
|
setRevealCellId((currentId) => getRandomAnimalId(animalIds, currentId))
|
|
}, 70)
|
|
|
|
return () => {
|
|
window.clearInterval(intervalId)
|
|
}
|
|
}
|
|
|
|
if (revealWinningCellId === null) {
|
|
setIsRevealHoldingResult(false)
|
|
setIsRevealSettlingResult(false)
|
|
return
|
|
}
|
|
|
|
const startedAt = performance.now()
|
|
let timeoutId = 0
|
|
setIsRevealHoldingResult(false)
|
|
setIsRevealSettlingResult(false)
|
|
|
|
const step = () => {
|
|
const elapsedMs = performance.now() - startedAt
|
|
|
|
if (elapsedMs >= SETTLEMENT_REVEAL_RANDOM_DURATION_MS) {
|
|
setIsRevealSettlingResult(true)
|
|
setRevealCellId(revealWinningCellId)
|
|
timeoutId = window.setTimeout(() => {
|
|
setIsRevealSettlingResult(false)
|
|
setIsRevealHoldingResult(true)
|
|
timeoutId = window.setTimeout(() => {
|
|
finishRevealAnimation()
|
|
}, SETTLEMENT_REVEAL_RESULT_HOLD_MS)
|
|
}, SETTLEMENT_REVEAL_SETTLE_DURATION_MS)
|
|
return
|
|
}
|
|
|
|
setRevealCellId((currentId) => getRandomAnimalId(animalIds, currentId))
|
|
|
|
const progress = elapsedMs / SETTLEMENT_REVEAL_RANDOM_DURATION_MS
|
|
const nextDelayMs = getSettlementRevealStepDelay(progress)
|
|
const remainingMs = SETTLEMENT_REVEAL_RANDOM_DURATION_MS - elapsedMs
|
|
|
|
timeoutId = window.setTimeout(step, Math.min(nextDelayMs, remainingMs))
|
|
}
|
|
|
|
setRevealCellId((currentId) => getRandomAnimalId(animalIds, currentId))
|
|
timeoutId = window.setTimeout(step, SETTLEMENT_REVEAL_MIN_STEP_MS)
|
|
|
|
return () => {
|
|
window.clearTimeout(timeoutId)
|
|
}
|
|
}, [
|
|
animalIds,
|
|
finishRevealAnimation,
|
|
revealPhase,
|
|
revealWinningCellId,
|
|
shouldHideRevealHighlight,
|
|
])
|
|
|
|
useLayoutEffect(() => {
|
|
if (revealCellId === null) {
|
|
setRevealFrame(null)
|
|
return
|
|
}
|
|
|
|
const syncRevealFrame = () => {
|
|
const container = containerRef.current
|
|
const cell = cellRefs.current.get(revealCellId)
|
|
|
|
if (!container || !cell) {
|
|
setRevealFrame(null)
|
|
return
|
|
}
|
|
|
|
const containerRect = container.getBoundingClientRect()
|
|
const cellRect = cell.getBoundingClientRect()
|
|
|
|
setRevealFrame({
|
|
height: cellRect.height,
|
|
left: cellRect.left - containerRect.left,
|
|
top: cellRect.top - containerRect.top,
|
|
width: cellRect.width,
|
|
})
|
|
}
|
|
|
|
syncRevealFrame()
|
|
window.addEventListener('resize', syncRevealFrame)
|
|
|
|
return () => {
|
|
window.removeEventListener('resize', syncRevealFrame)
|
|
}
|
|
}, [revealCellId])
|
|
|
|
return (
|
|
<section
|
|
ref={containerRef}
|
|
className={cn(
|
|
'relative grid w-full grid-cols-6 gap-design-5 overflow-hidden common-neon-inset',
|
|
className,
|
|
)}
|
|
>
|
|
{FLOWER_IMAGE_LIST.map((item) => {
|
|
const selectionMeta = selectionByCell[item.id]
|
|
const hasPlacedSelection = Boolean(selectionMeta)
|
|
const isMarqueeActive = showStandbyState && item.id === marqueeId
|
|
const isRevealWinner =
|
|
!shouldHideRevealHighlight &&
|
|
(isRevealResult || isRevealHoldingResult) &&
|
|
revealWinningCellId === item.id
|
|
const warningType =
|
|
cellWarning?.cellId === item.id ? cellWarning.type : null
|
|
const showCellWarning = warningType !== null
|
|
const warningLabel =
|
|
warningType === 'balance'
|
|
? t('gameDesktop.animal.insufficientBalanceRecharge')
|
|
: warningType === 'betLimit'
|
|
? t('gameDesktop.animal.betLimitExceeded')
|
|
: t('gameDesktop.animal.selectionLimitReached')
|
|
|
|
return (
|
|
<motion.button
|
|
key={item.id}
|
|
ref={(node) => {
|
|
if (node) {
|
|
cellRefs.current.set(item.id, node)
|
|
} else {
|
|
cellRefs.current.delete(item.id)
|
|
}
|
|
}}
|
|
type="button"
|
|
disabled={lockInteraction || showStopOverlay}
|
|
onClick={() => handleSelect(item.id)}
|
|
animate={
|
|
showCellWarning
|
|
? {
|
|
rotate: [0, -1.8, 1.4, -1, 0.6, 0],
|
|
scale: [1, 1.015, 0.992, 1.01, 1],
|
|
x: [0, -2, 2, -1, 1, 0],
|
|
}
|
|
: {
|
|
rotate: 0,
|
|
scale: 1,
|
|
x: 0,
|
|
}
|
|
}
|
|
transition={
|
|
showCellWarning
|
|
? {
|
|
duration: 0.46,
|
|
ease: 'easeInOut',
|
|
}
|
|
: { duration: 0.16, ease: 'easeOut' }
|
|
}
|
|
className={cn(
|
|
'relative flex h-design-112 flex-col items-center justify-center overflow-hidden rounded-[calc(var(--design-unit)*18)] border border-transparent transition-[transform,border-color,box-shadow,opacity] duration-150',
|
|
lockInteraction
|
|
? 'cursor-not-allowed opacity-90'
|
|
: 'cursor-pointer hover:-translate-y-[1px]',
|
|
isMarqueeActive &&
|
|
'border-[rgba(121,255,250,1)] shadow-[0_0_calc(var(--design-unit)*18)_rgba(85,255,247,0.98),0_0_calc(var(--design-unit)*34)_rgba(39,245,255,0.88),inset_0_0_calc(var(--design-unit)*26)_rgba(112,255,248,0.34)]',
|
|
isRevealRunning &&
|
|
'border-[rgba(104,255,249,0.76)] shadow-[0_0_calc(var(--design-unit)*10)_rgba(68,244,255,0.46),0_0_calc(var(--design-unit)*22)_rgba(37,214,255,0.28),inset_0_0_calc(var(--design-unit)*16)_rgba(115,255,247,0.18)] brightness-115 saturate-125',
|
|
isRevealWinner &&
|
|
'border-[rgba(121,255,250,0.72)] shadow-[0_0_calc(var(--design-unit)*12)_rgba(81,248,255,0.54),0_0_calc(var(--design-unit)*22)_rgba(30,199,255,0.32),inset_0_0_calc(var(--design-unit)*18)_rgba(125,255,249,0.24)] brightness-110 saturate-120',
|
|
showCellWarning &&
|
|
'border-[rgba(255,92,92,1)] shadow-[0_0_calc(var(--design-unit)*18)_rgba(255,88,88,0.56),0_0_calc(var(--design-unit)*28)_rgba(255,44,44,0.32),inset_0_0_calc(var(--design-unit)*18)_rgba(255,126,126,0.3)]',
|
|
!showStandbyState && !hasPlacedSelection && 'opacity-95',
|
|
itemClassName,
|
|
)}
|
|
>
|
|
<SmartImage
|
|
src={animalBorderImage}
|
|
alt=""
|
|
aria-hidden="true"
|
|
priority
|
|
showSkeleton={false}
|
|
className="pointer-events-none absolute inset-0 z-20 h-full w-full"
|
|
imgClassName="object-fill"
|
|
/>
|
|
<span className="pointer-events-none absolute left-design-24 top-design-16 z-30 text-design-32 font-bold leading-none text-[#4BFFFE]">
|
|
{String(item.id).padStart(2, '0')}
|
|
</span>
|
|
<motion.span
|
|
aria-hidden="true"
|
|
animate={
|
|
showCellWarning
|
|
? {
|
|
opacity: [0.45, 1, 0.6, 1, 0.82],
|
|
}
|
|
: undefined
|
|
}
|
|
transition={
|
|
showCellWarning
|
|
? {
|
|
duration: 0.52,
|
|
ease: 'easeInOut',
|
|
}
|
|
: undefined
|
|
}
|
|
className={cn(
|
|
'pointer-events-none absolute inset-[calc(var(--design-unit)*2)] rounded-[calc(var(--design-unit)*15)] opacity-0 transition-opacity duration-150',
|
|
isMarqueeActive &&
|
|
'bg-[radial-gradient(circle_at_center,rgba(129,255,250,0.48)_0%,rgba(94,255,247,0.18)_38%,rgba(43,236,255,0.08)_56%,transparent_76%)] opacity-100 shadow-[0_0_calc(var(--design-unit)*12)_rgba(119,255,249,0.98),0_0_calc(var(--design-unit)*28)_rgba(53,246,255,0.9),0_0_calc(var(--design-unit)*44)_rgba(37,241,255,0.58),inset_0_0_calc(var(--design-unit)*20)_rgba(163,255,250,0.52)]',
|
|
isRevealRunning &&
|
|
'bg-[radial-gradient(circle_at_center,rgba(128,255,250,0.36)_0%,rgba(77,244,255,0.16)_40%,rgba(27,183,255,0.07)_68%,transparent_88%)] opacity-100 shadow-[0_0_calc(var(--design-unit)*12)_rgba(95,249,255,0.46),inset_0_0_calc(var(--design-unit)*18)_rgba(151,255,250,0.24)]',
|
|
isRevealWinner &&
|
|
'bg-[radial-gradient(circle_at_center,rgba(128,255,250,0.34)_0%,rgba(67,226,255,0.16)_38%,rgba(25,131,255,0.07)_58%,transparent_76%)] opacity-100 shadow-[0_0_calc(var(--design-unit)*12)_rgba(92,248,255,0.42),inset_0_0_calc(var(--design-unit)*18)_rgba(126,255,250,0.24)]',
|
|
showCellWarning &&
|
|
'bg-[radial-gradient(circle_at_center,rgba(255,106,106,0.34)_0%,rgba(255,58,58,0.18)_42%,rgba(108,0,0,0.2)_78%,transparent_100%)] opacity-100',
|
|
)}
|
|
/>
|
|
{!showStandbyState && !hasPlacedSelection ? (
|
|
<span
|
|
aria-hidden="true"
|
|
className="pointer-events-none absolute inset-[calc(var(--design-unit)*2)] z-20 rounded-[calc(var(--design-unit)*15)] bg-[rgba(4,16,24,0.52)] shadow-[inset_0_0_calc(var(--design-unit)*20)_rgba(3,9,14,0.56)]"
|
|
/>
|
|
) : null}
|
|
<SmartImage
|
|
src={item.animalUrl}
|
|
alt={`animal-${item.id}`}
|
|
className={cn(
|
|
'absolute left-[1.5%] right-[1.5%] top-[2.9%] bottom-[2.9%] z-10 overflow-hidden rounded-[calc(var(--design-unit)*14)]',
|
|
isRevealRunning &&
|
|
'brightness-115 saturate-125 drop-shadow-[0_0_calc(var(--design-unit)*8)_rgba(101,250,255,0.42)]',
|
|
isRevealWinner &&
|
|
'brightness-110 saturate-120 drop-shadow-[0_0_calc(var(--design-unit)*9)_rgba(106,250,255,0.44)]',
|
|
imageClassName,
|
|
)}
|
|
imgClassName="object-fill"
|
|
/>
|
|
{showCellWarning ? (
|
|
<motion.span
|
|
initial={{ opacity: 0, scale: 0.94 }}
|
|
animate={{
|
|
opacity: [0.2, 1, 0.92],
|
|
scale: [0.96, 1.02, 1],
|
|
boxShadow: [
|
|
'inset 0 0 calc(var(--design-unit)*10) rgba(255,108,108,0.16), 0 0 calc(var(--design-unit)*8) rgba(255,60,60,0.12)',
|
|
'inset 0 0 calc(var(--design-unit)*20) rgba(255,108,108,0.28), 0 0 calc(var(--design-unit)*20) rgba(255,60,60,0.28)',
|
|
'inset 0 0 calc(var(--design-unit)*16) rgba(255,108,108,0.22), 0 0 calc(var(--design-unit)*18) rgba(255,60,60,0.2)',
|
|
],
|
|
}}
|
|
transition={{ duration: 0.5, ease: 'easeOut' }}
|
|
className="pointer-events-none absolute inset-[calc(var(--design-unit)*3)] z-30 flex flex-col items-center justify-center gap-design-8 rounded-[calc(var(--design-unit)*15)] border border-[rgba(255,126,126,0.9)] bg-[rgba(61,0,0,0.58)] px-design-10 py-design-10 text-center"
|
|
>
|
|
<motion.span
|
|
animate={{
|
|
opacity: [0.72, 1, 0.92],
|
|
scale: [0.94, 1.08, 1],
|
|
}}
|
|
transition={{ duration: 0.38, ease: 'easeOut' }}
|
|
className="flex h-design-28 w-design-28 items-center justify-center"
|
|
>
|
|
<TriangleAlert className="h-design-28 w-design-28 text-[#FFD0D0] drop-shadow-[0_0_calc(var(--design-unit)*8)_rgba(255,92,92,0.45)]" />
|
|
</motion.span>
|
|
<motion.span
|
|
animate={{
|
|
opacity: [0.7, 1, 0.96],
|
|
scale: [0.98, 1.03, 1],
|
|
}}
|
|
transition={{ duration: 0.42, ease: 'easeOut' }}
|
|
className="text-design-16 font-bold leading-tight tracking-[0.04em] text-[#FFE0E0] [text-shadow:0_0_calc(var(--design-unit)*10)_rgba(255,132,132,0.42)]"
|
|
>
|
|
{warningLabel}
|
|
</motion.span>
|
|
</motion.span>
|
|
) : null}
|
|
{hasPlacedSelection ? (
|
|
<span className="pointer-events-none absolute inset-0 z-20 flex items-center justify-center">
|
|
<span className="flex min-w-design-96 items-center justify-center gap-design-4 rounded-full border border-[rgba(162,242,255,0.48)] bg-[linear-gradient(180deg,rgba(7,23,34,0.88),rgba(5,14,22,0.96))] px-design-10 py-design-6 shadow-[0_0_calc(var(--design-unit)*18)_rgba(70,245,255,0.18)]">
|
|
<SmartImage
|
|
src={diamondIcon}
|
|
alt="diamond"
|
|
className="h-design-24 w-design-24 shrink-0 object-contain"
|
|
/>
|
|
<span className="text-design-18 font-semibold leading-none tracking-[0.06em] text-[#D8FBFF]">
|
|
{selectionMeta.amount}
|
|
</span>
|
|
</span>
|
|
</span>
|
|
) : null}
|
|
</motion.button>
|
|
)
|
|
})}
|
|
|
|
{revealFrame ? (
|
|
<div
|
|
aria-hidden="true"
|
|
className={cn(
|
|
'pointer-events-none absolute z-40 transform-gpu transition-[height,transform,width]',
|
|
prefersReducedMotion
|
|
? 'duration-0'
|
|
: isRevealSettlingResult
|
|
? 'duration-[800ms] ease-[cubic-bezier(0.16,1,0.3,1)]'
|
|
: 'duration-[160ms] ease-[cubic-bezier(0.22,1,0.36,1)]',
|
|
)}
|
|
style={{
|
|
height: revealFrame.height,
|
|
transform: `translate(${revealFrame.left}px, ${revealFrame.top}px)`,
|
|
width: revealFrame.width,
|
|
}}
|
|
>
|
|
<div
|
|
className="gold-reveal-glow rounded-[calc(var(--design-unit)*16)]"
|
|
style={
|
|
prefersReducedMotion
|
|
? {
|
|
animation: 'none',
|
|
opacity: 0.36,
|
|
transform: 'scale(1)',
|
|
}
|
|
: undefined
|
|
}
|
|
/>
|
|
<div className="gold-reveal-static-border rounded-[calc(var(--design-unit)*16)]" />
|
|
<div
|
|
className="gold-reveal-shell rounded-[calc(var(--design-unit)*16)]"
|
|
style={prefersReducedMotion ? { animation: 'none' } : undefined}
|
|
/>
|
|
</div>
|
|
) : null}
|
|
|
|
<DesktopAnimalOverlay showStopOverlay={showStopOverlay} />
|
|
<RoundBettingStartAlert />
|
|
|
|
{showStandbyState ? (
|
|
<button
|
|
type="button"
|
|
onClick={handleStart}
|
|
aria-busy={isRealtimeConnecting}
|
|
className="group absolute inset-0 z-10 flex cursor-pointer items-center justify-center overflow-hidden bg-[rgba(3,13,20,0.66)]"
|
|
>
|
|
<div className="relative flex min-w-design-260 flex-col items-center gap-design-10 rounded-[calc(var(--design-unit)*22)] border border-[rgba(111,255,247,0.56)] bg-[linear-gradient(180deg,rgba(8,30,42,0.94),rgba(4,14,20,0.96))] px-design-32 py-design-18 text-center shadow-[0_0_calc(var(--design-unit)*18)_rgba(70,245,255,0.38),0_0_calc(var(--design-unit)*42)_rgba(19,210,232,0.22),inset_0_0_calc(var(--design-unit)*18)_rgba(120,255,249,0.12)] transition-[transform,box-shadow,border-color] duration-200 group-hover:-translate-y-[1px] group-hover:border-[rgba(141,255,250,0.82)] group-hover:shadow-[0_0_calc(var(--design-unit)*24)_rgba(88,247,255,0.5),0_0_calc(var(--design-unit)*52)_rgba(32,228,255,0.32),inset_0_0_calc(var(--design-unit)*18)_rgba(145,255,251,0.16)]">
|
|
<span className="pointer-events-none absolute inset-[1px] rounded-[calc(var(--design-unit)*22)] border border-[rgba(226,255,255,0.1)]" />
|
|
<span className="pointer-events-none absolute inset-x-design-14 top-design-10 h-design-18 rounded-full bg-[linear-gradient(180deg,rgba(255,255,255,0.16),rgba(255,255,255,0))] opacity-70" />
|
|
<span className="text-design-14 uppercase tracking-[0.44em] text-[rgba(132,255,248,0.72)]">
|
|
{isRealtimeConnecting
|
|
? t('gameDesktop.animal.loading')
|
|
: t('gameDesktop.animal.tapToEnter')}
|
|
</span>
|
|
<div className="flex items-center gap-design-10">
|
|
{isRealtimeConnecting ? (
|
|
<span className="relative flex h-design-24 w-design-24 items-center justify-center">
|
|
<motion.span
|
|
animate={{ rotate: 360 }}
|
|
transition={{
|
|
duration: 1.2,
|
|
repeat: Number.POSITIVE_INFINITY,
|
|
ease: 'linear',
|
|
}}
|
|
className="absolute inset-0 rounded-full border-2 border-[rgba(119,255,250,0.22)] border-t-[rgba(119,255,250,0.98)] border-r-[rgba(119,255,250,0.56)]"
|
|
/>
|
|
<motion.span
|
|
animate={{ scale: [0.85, 1, 0.85], opacity: [0.6, 1, 0.6] }}
|
|
transition={{
|
|
duration: 1.2,
|
|
repeat: Number.POSITIVE_INFINITY,
|
|
ease: 'easeInOut',
|
|
}}
|
|
className="h-design-8 w-design-8 rounded-full bg-[#BFFFFD] shadow-[0_0_calc(var(--design-unit)*10)_rgba(114,255,249,0.72)]"
|
|
/>
|
|
</span>
|
|
) : (
|
|
<span className="h-design-10 w-design-10 rounded-full bg-[rgba(126,255,248,0.92)] shadow-[0_0_calc(var(--design-unit)*10)_rgba(114,255,249,0.62)]" />
|
|
)}
|
|
<span className="text-design-28 font-semibold tracking-[0.18em] text-[#E0FFFF]">
|
|
{isRealtimeConnecting
|
|
? t('gameDesktop.animal.loading')
|
|
: t('gameDesktop.animal.getStart')}
|
|
</span>
|
|
</div>
|
|
<div className="flex items-center gap-design-4">
|
|
{[0, 1, 2].map((index) => (
|
|
<motion.span
|
|
key={index}
|
|
animate={
|
|
isRealtimeConnecting
|
|
? { opacity: [0.28, 1, 0.28], y: [0, -2, 0] }
|
|
: { opacity: 0.7 }
|
|
}
|
|
transition={
|
|
isRealtimeConnecting
|
|
? {
|
|
duration: 0.9,
|
|
repeat: Number.POSITIVE_INFINITY,
|
|
ease: 'easeInOut',
|
|
delay: index * 0.15,
|
|
}
|
|
: undefined
|
|
}
|
|
className="h-design-4 w-design-4 rounded-full bg-[rgba(145,255,249,0.86)] shadow-[0_0_calc(var(--design-unit)*8)_rgba(114,255,249,0.48)]"
|
|
/>
|
|
))}
|
|
</div>
|
|
</div>
|
|
</button>
|
|
) : null}
|
|
</section>
|
|
)
|
|
}
|