- 移除 useGameBoardVm 数据层实施说明文档 - 移除核心玩法与前端规则摘要文档 - 移除游戏模块数据与界面分层第一阶段实施稿文档 - 清理与数据层重构相关的技术方案说明 - 删除关于 PC 和 Mobile 界面分离的设计规划 - 移除 view-model hooks 架构设计相关内容
230 lines
8.5 KiB
TypeScript
230 lines
8.5 KiB
TypeScript
import { motion, useReducedMotion } from 'motion/react'
|
|
import { useMemo, useState } from 'react'
|
|
import { useTranslation } from 'react-i18next'
|
|
import streakBg from '@/assets/game/pc-streak.webp'
|
|
import down5Animation from '@/assets/lottie/down5.json'
|
|
import diamond from '@/assets/system/diamond.webp'
|
|
import fire from '@/assets/system/fire.webp'
|
|
import lock from '@/assets/system/lock.webp'
|
|
import statusCenter from '@/assets/system/status-center.webp'
|
|
import statusLine from '@/assets/system/status-line.webp'
|
|
import { LottiePlayer } from '@/components/lottie-player.tsx'
|
|
import { SmartBackground } from '@/components/smart-background.tsx'
|
|
import { SmartImage } from '@/components/smart-image.tsx'
|
|
import { DesktopCountdown } from '@/features/game/components/desktop/desktop-countdown.tsx'
|
|
import { DesktopTitle } from '@/features/game/components/desktop/desktop-title.tsx'
|
|
import { useGameStatusVm } from '@/hooks/use-game-status-vm.ts'
|
|
import { cn } from '@/lib/utils.ts'
|
|
|
|
export function DesktopStatusLine() {
|
|
const { t } = useTranslation()
|
|
const prefersReducedMotion = useReducedMotion()
|
|
const {
|
|
countdownMs,
|
|
limitLabel,
|
|
oddsLabel,
|
|
phaseLabel,
|
|
phaseToneClassName,
|
|
roundId,
|
|
streakLabel,
|
|
streakValue,
|
|
} = useGameStatusVm()
|
|
const [remainingMs, setRemainingMs] = useState(countdownMs)
|
|
const showWarningCountdown = remainingMs <= 5000 && remainingMs > 0
|
|
const showStreakLimitOnly = typeof streakValue === 'number' && streakValue > 1
|
|
const countdownClassName = useMemo(
|
|
() =>
|
|
showWarningCountdown
|
|
? 'text-design-64 scale-[1.2] text-[#FF5A5A] [text-shadow:0_0_calc(var(--design-unit)*10)_rgba(255,90,90,0.85),0_0_calc(var(--design-unit)*22)_rgba(255,90,90,0.32)]'
|
|
: 'text-design-64 scale-[1.2] text-[#4BFFFE] [text-shadow:0_0_calc(var(--design-unit)*10)_rgba(75,255,254,0.85),0_0_calc(var(--design-unit)*22)_rgba(75,255,254,0.32)]',
|
|
[showWarningCountdown],
|
|
)
|
|
|
|
return (
|
|
<div className={'relative w-full flex flex-col text-design-22'}>
|
|
{/*<div*/}
|
|
{/* className={*/}
|
|
{/* }*/}
|
|
{/*>*/}
|
|
{/* <DesktopTitle />*/}
|
|
{/*</div>*/}
|
|
|
|
<div className={'w-full px-design-16 mb-design-10'}>
|
|
<DesktopTitle />
|
|
</div>
|
|
<SmartBackground
|
|
src={statusLine}
|
|
size="100% 100%"
|
|
className="w-full h-design-75 bg-no-repeat bg-center flex items-center justify-center"
|
|
>
|
|
{/* 状态栏左侧 */}
|
|
<div
|
|
className={
|
|
'relative h-full flex-1 flex items-center justify-center gap-design-50 overflow-visible'
|
|
}
|
|
>
|
|
{showStreakLimitOnly && (
|
|
<div
|
|
className={'pointer-events-none absolute bg-center bg-no-repeat'}
|
|
style={{
|
|
top: 'calc(var(--design-unit)*-14)',
|
|
right: 'calc(var(--design-unit)*-190)',
|
|
bottom: 'calc(var(--design-unit)*-1)',
|
|
left: 0,
|
|
backgroundImage: `url(${streakBg})`,
|
|
backgroundPosition: 'center',
|
|
backgroundSize: '113% 150%',
|
|
}}
|
|
/>
|
|
)}
|
|
|
|
{!showStreakLimitOnly && (
|
|
<>
|
|
<div className={'text-[#CBD3D5] font-bold'}>
|
|
{t('gameDesktop.status.odds')}:{' '}
|
|
<span className={'text-[#E3D171]'}>{oddsLabel}</span>
|
|
</div>
|
|
<div
|
|
className={
|
|
'flex items-center gap-design-5 text-[#CBD3D5] font-bold'
|
|
}
|
|
>
|
|
<SmartImage
|
|
className={'w-design-37 h-design-47'}
|
|
alt={'fire'}
|
|
src={fire}
|
|
/>
|
|
<div>
|
|
{t('gameDesktop.status.streak')}:{' '}
|
|
<span
|
|
className={
|
|
'bg-gradient-to-b from-[#EBA661] to-[#FCC785] bg-clip-text text-transparent'
|
|
}
|
|
>
|
|
{streakLabel}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</>
|
|
)}
|
|
|
|
<div
|
|
className={
|
|
'relative z-20 flex items-center gap-design-5 text-[#CBD3D5] font-bold'
|
|
}
|
|
>
|
|
<SmartImage
|
|
className={'w-design-25 h-design-33'}
|
|
alt={'lock'}
|
|
src={lock}
|
|
/>
|
|
<div className={'flex items-center gap-design-10'}>
|
|
<div>{t('gameDesktop.status.limit')}:</div>
|
|
<div className={'flex items-center gap-design-5'}>
|
|
<SmartImage
|
|
className={'w-design-35 h-design-35'}
|
|
alt={'diamond'}
|
|
src={diamond}
|
|
/>
|
|
<div>{limitLabel}</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="relative z-20 flex h-[105px] w-design-360 items-center justify-center">
|
|
<SmartBackground
|
|
src={statusCenter}
|
|
className="pointer-events-none absolute inset-0 bg-no-repeat bg-center bg-contain transition-opacity duration-500 ease-out"
|
|
size="contain"
|
|
style={{
|
|
opacity: showWarningCountdown ? 0.18 : 1,
|
|
transform: showWarningCountdown ? 'scale(0.985)' : 'scale(1)',
|
|
}}
|
|
/>
|
|
<div
|
|
className="pointer-events-none absolute inset-0 overflow-visible transition-all duration-500 ease-out"
|
|
style={{
|
|
opacity: showWarningCountdown ? 1 : 0,
|
|
transform: showWarningCountdown
|
|
? 'scale(1.1) translateY(calc(var(--design-unit)*1))'
|
|
: 'scale(1.02) translateY(0)',
|
|
}}
|
|
>
|
|
<LottiePlayer
|
|
animationData={down5Animation}
|
|
className="h-full w-full"
|
|
loop
|
|
autoplay
|
|
/>
|
|
</div>
|
|
<DesktopCountdown
|
|
initialMs={countdownMs}
|
|
onRemainingMsChange={setRemainingMs}
|
|
className={countdownClassName}
|
|
/>
|
|
</div>
|
|
<div className="flex flex-1 items-center justify-center gap-design-88">
|
|
<div className="flex items-center gap-design-9">
|
|
<span className="relative flex h-design-24 w-design-24 items-center justify-center">
|
|
<motion.span
|
|
aria-hidden="true"
|
|
className="absolute h-design-24 w-design-24 rounded-full bg-[rgba(120,255,127,0.22)] blur-[calc(var(--design-unit)*3)]"
|
|
animate={
|
|
prefersReducedMotion
|
|
? undefined
|
|
: { opacity: [0.36, 0.88, 0.42], scale: [0.9, 1.28, 0.98] }
|
|
}
|
|
transition={
|
|
prefersReducedMotion
|
|
? undefined
|
|
: {
|
|
duration: 1.25,
|
|
ease: 'easeInOut',
|
|
repeat: Number.POSITIVE_INFINITY,
|
|
}
|
|
}
|
|
/>
|
|
<motion.span
|
|
aria-hidden="true"
|
|
className="relative h-design-14 w-design-14 rounded-full bg-[#78FF7F] shadow-[0_0_calc(var(--design-unit)*8)_rgba(120,255,127,0.78),0_0_calc(var(--design-unit)*16)_rgba(120,255,127,0.34)]"
|
|
animate={
|
|
prefersReducedMotion
|
|
? undefined
|
|
: { opacity: [0.78, 1, 0.86], scale: [0.96, 1.12, 1] }
|
|
}
|
|
transition={
|
|
prefersReducedMotion
|
|
? undefined
|
|
: {
|
|
duration: 1.25,
|
|
ease: 'easeInOut',
|
|
repeat: Number.POSITIVE_INFINITY,
|
|
}
|
|
}
|
|
/>
|
|
</span>
|
|
<div
|
|
className={cn(
|
|
phaseToneClassName,
|
|
'font-black tracking-[0.08em] [text-shadow:0_0_calc(var(--design-unit)*10)_rgba(120,255,127,0.44)]',
|
|
)}
|
|
>
|
|
{phaseLabel}
|
|
</div>
|
|
</div>
|
|
|
|
<div className="flex items-baseline gap-design-7 font-bold leading-none">
|
|
<span className="text-design-20 tracking-[0.06em] text-[#9FBAC0]">
|
|
{t('gameDesktop.status.roundId')}:
|
|
</span>
|
|
<span className="text-design-24 font-black tracking-[0.06em] text-[#E7FFFF] [text-shadow:0_0_calc(var(--design-unit)*9)_rgba(75,255,254,0.38)]">
|
|
{roundId}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</SmartBackground>
|
|
</div>
|
|
)
|
|
}
|