- 在国际化文件中添加钱包流水相关翻译项 - 在用户个人资料页面添加复制邀请链接功能 - 优化桌面端动物组件的视觉效果和动画参数 - 添加虚拟滚动功能到财务记录标签页提升性能 - 为桌面端控制面板添加投注数量调节按钮 - 更新消息模态框为通知列表和详情展示 - 在头部余额显示旁添加充值图标入口
636 lines
27 KiB
TypeScript
636 lines
27 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 enStopImage from '@/assets/game/en-stop.webp'
|
|
import hostingBg from '@/assets/game/hosting-bg.webp'
|
|
import hostingBtn from '@/assets/game/hosting-btn.webp'
|
|
import zhStopImage from '@/assets/game/zh-stop.webp'
|
|
import diamondIcon from '@/assets/system/diamond.webp'
|
|
import refreshIcon from '@/assets/system/refresh.webp'
|
|
import { SmartBackground } from '@/components/smart-background.tsx'
|
|
import { SmartImage } from '@/components/smart-image'
|
|
import { useAnimalVm } from '@/features/game/hooks/use-animal-vm'
|
|
import { cn } from '@/lib/utils'
|
|
import { useAuthStore } from '@/store/auth'
|
|
import { useGameAutoHostingStore, useGameRoundStore } from '@/store/game'
|
|
|
|
const animalModules = import.meta.glob('../../../../assets/animal/*.webp', {
|
|
eager: true,
|
|
import: 'default',
|
|
}) as Record<string, string>
|
|
|
|
const animalImageList = Object.entries(animalModules)
|
|
.map(([path, url]) => {
|
|
const match = path.match(/\/(\d+)\.webp$/)
|
|
|
|
return {
|
|
id: Number(match?.[1] ?? 0),
|
|
url,
|
|
}
|
|
})
|
|
.filter((item) => item.id > 0)
|
|
.sort((left, right) => left.id - right.id)
|
|
|
|
const SETTLEMENT_REVEAL_RANDOM_DURATION_MS = 4_000
|
|
const SETTLEMENT_REVEAL_RESULT_HOLD_MS = 1_000
|
|
const SETTLEMENT_REVEAL_MIN_STEP_MS = 90
|
|
const SETTLEMENT_REVEAL_MAX_STEP_MS = 480
|
|
|
|
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 { i18n, t } = useTranslation()
|
|
const prefersReducedMotion = useReducedMotion()
|
|
const animalIds = useMemo(() => animalImageList.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 revealPhase = useGameRoundStore((state) => state.revealAnimation.phase)
|
|
const revealWinningCellId = useGameRoundStore(
|
|
(state) => state.revealAnimation.winningCellId,
|
|
)
|
|
const roundPhase = useGameRoundStore((state) => state.round.phase)
|
|
const roundId = useGameRoundStore((state) => state.round.id)
|
|
const lastBetPeriodNo = useAuthStore(
|
|
(state) => state.currentUser?.lastBetPeriodNo,
|
|
)
|
|
const completedAutoHostingRounds = useGameAutoHostingStore(
|
|
(state) => state.completedRounds,
|
|
)
|
|
const hostingFlag = useGameAutoHostingStore((state) => state.isHosting)
|
|
const stopHosting = useGameAutoHostingStore((state) => state.stopHosting)
|
|
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 hasSubmittedCurrentRound =
|
|
roundPhase === 'betting' && Boolean(roundId) && lastBetPeriodNo === roundId
|
|
const showStopOverlay =
|
|
hasSubmittedCurrentRound ||
|
|
roundPhase === 'locked' ||
|
|
roundPhase === 'revealing'
|
|
const stopImageSrc = i18n.resolvedLanguage?.startsWith('zh')
|
|
? zhStopImage
|
|
: enStopImage
|
|
|
|
useEffect(() => {
|
|
if (revealPhase === 'idle') {
|
|
setRevealCellId(null)
|
|
setIsRevealHoldingResult(false)
|
|
return
|
|
}
|
|
|
|
if (revealPhase === 'result') {
|
|
setRevealCellId(revealWinningCellId)
|
|
setIsRevealHoldingResult(false)
|
|
return
|
|
}
|
|
|
|
if (revealPhase === 'spinning') {
|
|
setIsRevealHoldingResult(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)
|
|
return
|
|
}
|
|
|
|
const startedAt = performance.now()
|
|
let timeoutId = 0
|
|
setIsRevealHoldingResult(false)
|
|
|
|
const step = () => {
|
|
const elapsedMs = performance.now() - startedAt
|
|
|
|
if (elapsedMs >= SETTLEMENT_REVEAL_RANDOM_DURATION_MS) {
|
|
setRevealCellId(revealWinningCellId)
|
|
setIsRevealHoldingResult(true)
|
|
timeoutId = window.setTimeout(() => {
|
|
finishRevealAnimation()
|
|
}, SETTLEMENT_REVEAL_RESULT_HOLD_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])
|
|
|
|
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,
|
|
)}
|
|
>
|
|
{animalImageList.map((item) => {
|
|
const selectionMeta = selectionByCell[item.id]
|
|
const hasPlacedSelection = Boolean(selectionMeta)
|
|
const isMarqueeActive = showStandbyState && item.id === marqueeId
|
|
const isRevealWinner =
|
|
(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')
|
|
: 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.url}
|
|
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="pointer-events-none absolute z-40 transition-[height,transform,width] duration-75 ease-linear"
|
|
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}
|
|
|
|
{showStopOverlay && !hostingFlag ? (
|
|
<div
|
|
aria-hidden="true"
|
|
className="absolute inset-0 z-50 flex items-center justify-center bg-[rgba(2,8,14,0.72)] px-design-24 backdrop-blur-[2px]"
|
|
>
|
|
<SmartImage
|
|
src={stopImageSrc}
|
|
alt="stop betting"
|
|
priority
|
|
showSkeleton={false}
|
|
className="h-design-220 w-design-560 max-w-[78%] overflow-visible"
|
|
imgClassName="object-contain drop-shadow-[0_0_calc(var(--design-unit)*22)_rgba(60,235,255,0.28)]"
|
|
/>
|
|
</div>
|
|
) : null}
|
|
|
|
{hostingFlag ? (
|
|
<div className="absolute inset-0 z-50 flex flex-col items-center justify-center gap-design-22 bg-[rgba(2,8,14,0.6)] px-design-24 backdrop-blur-[1px]">
|
|
{showStopOverlay ? (
|
|
<SmartImage
|
|
src={stopImageSrc}
|
|
alt="stop betting"
|
|
priority
|
|
showSkeleton={false}
|
|
className="h-design-170 w-design-520 max-w-[72%] overflow-visible"
|
|
imgClassName="object-contain drop-shadow-[0_0_calc(var(--design-unit)*22)_rgba(60,235,255,0.28)]"
|
|
/>
|
|
) : null}
|
|
<SmartBackground
|
|
src={hostingBg}
|
|
size="100% 100%"
|
|
repeat="no-repeat"
|
|
position="center"
|
|
className="h-design-350 w-design-930 flex flex-col items-center justify-center"
|
|
>
|
|
<div className={'flex flex-col gap-design-44 items-center'}>
|
|
<div className={'flex items-center gap-design-20'}>
|
|
<motion.span
|
|
aria-hidden="true"
|
|
animate={prefersReducedMotion ? undefined : { rotate: 360 }}
|
|
transition={{
|
|
duration: 1.4,
|
|
ease: 'linear',
|
|
repeat: Number.POSITIVE_INFINITY,
|
|
}}
|
|
className="flex h-design-48 w-design-48 items-center justify-center"
|
|
>
|
|
<SmartImage
|
|
src={refreshIcon}
|
|
alt=""
|
|
priority
|
|
showSkeleton={false}
|
|
className="h-design-40 w-design-40"
|
|
imgClassName="object-contain drop-shadow-[0_0_calc(var(--design-unit)*22)_rgba(60,235,255,0.36)]"
|
|
/>
|
|
</motion.span>
|
|
<div
|
|
className={
|
|
'text-design-22 text-[#ffffff] font-bold [text-shadow:0_0_calc(var(--design-unit)*12)_rgba(76,236,255,0.45)]'
|
|
}
|
|
>
|
|
{t('game.autoSpin.runningRounds', {
|
|
count: completedAutoHostingRounds,
|
|
})}
|
|
</div>
|
|
</div>
|
|
<SmartBackground
|
|
as="button"
|
|
type="button"
|
|
onClick={stopHosting}
|
|
src={hostingBtn}
|
|
size="100% 100%"
|
|
repeat="no-repeat"
|
|
position="center"
|
|
className="h-design-70 w-design-220 flex cursor-pointer items-center justify-center pb-design-4 text-design-24 font-bold text-[#EFFFFF] [text-shadow:0_1px_0_rgba(255,255,255,0.18),0_0_calc(var(--design-unit)*10)_rgba(46,220,255,0.5)] transition-transform hover:-translate-y-[1px] active:translate-y-0"
|
|
>
|
|
{t('game.actions.stopAuto')}
|
|
</SmartBackground>
|
|
</div>
|
|
</SmartBackground>
|
|
</div>
|
|
) : null}
|
|
|
|
{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)]"
|
|
>
|
|
<motion.div
|
|
aria-hidden="true"
|
|
animate={{ rotate: 360 }}
|
|
transition={{
|
|
duration: 18,
|
|
repeat: Number.POSITIVE_INFINITY,
|
|
ease: 'linear',
|
|
}}
|
|
className="pointer-events-none absolute inset-[12%] rounded-full bg-[conic-gradient(from_0deg,rgba(129,255,250,0)_0deg,rgba(129,255,250,0.26)_60deg,rgba(129,255,250,0)_120deg,rgba(255,255,255,0)_360deg)] opacity-70 blur-[18px]"
|
|
/>
|
|
<motion.div
|
|
aria-hidden="true"
|
|
animate={{
|
|
scale: [1, 1.03, 1],
|
|
opacity: [0.42, 0.7, 0.42],
|
|
}}
|
|
transition={{
|
|
duration: 2.8,
|
|
repeat: Number.POSITIVE_INFINITY,
|
|
ease: 'easeInOut',
|
|
}}
|
|
className="pointer-events-none absolute inset-[22%] rounded-full border border-[rgba(124,255,248,0.22)] shadow-[0_0_calc(var(--design-unit)*22)_rgba(74,245,255,0.12),inset_0_0_calc(var(--design-unit)*18)_rgba(122,255,250,0.14)]"
|
|
/>
|
|
<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>
|
|
)
|
|
}
|