fix(game): 修复移动端动物组件渲染和奖励数据显示问题

- 修复了GitNexus索引统计信息中的符号、关系和执行流数量
- 为桌面端动物组件的投注开始警告添加了z-index类以确保正确显示层级
- 修复了游戏回合存储中奖励金额和类型的返回逻辑,避免显示旧奖励数据
- 调整了移动端金币揭示效果的内边距样式以改善视觉效果
- 重构了移动端动物网格组件,添加了容器引用并修复了揭示动画定位问题
- 清理了未使用的移动揭示帧偏移常量并优化了网格布局结构
- 更新了移动端控制组件中的内边距和间距以改善UI布局
- 在移动端头部组件中添加了新的图像资源和CSS工具函数
- 调整了移动端头部组件中的按钮大小、间距和对齐方式
- 优化了移动端工具栏按钮的样式类以实现更好的响应式设计
This commit is contained in:
JiaJun
2026-06-05 16:10:44 +08:00
parent 7999a5d709
commit d41e8e7f1c
10 changed files with 291 additions and 257 deletions

View File

@@ -1,7 +1,7 @@
<!-- gitnexus:start --> <!-- gitnexus:start -->
# GitNexus — Code Intelligence # GitNexus — Code Intelligence
This project is indexed by GitNexus as **36-character-flower** (2879 symbols, 5735 relationships, 245 execution flows). Use the GitNexus MCP tools to understand code, assess impact, and navigate safely. This project is indexed by GitNexus as **36-character-flower** (3072 symbols, 6072 relationships, 262 execution flows). Use the GitNexus MCP tools to understand code, assess impact, and navigate safely.
> If any GitNexus tool warns the index is stale, run `npx gitnexus analyze` in terminal first. > If any GitNexus tool warns the index is stale, run `npx gitnexus analyze` in terminal first.

View File

@@ -1,7 +1,7 @@
<!-- gitnexus:start --> <!-- gitnexus:start -->
# GitNexus — Code Intelligence # GitNexus — Code Intelligence
This project is indexed by GitNexus as **36-character-flower** (2879 symbols, 5735 relationships, 245 execution flows). Use the GitNexus MCP tools to understand code, assess impact, and navigate safely. This project is indexed by GitNexus as **36-character-flower** (3072 symbols, 6072 relationships, 262 execution flows). Use the GitNexus MCP tools to understand code, assess impact, and navigate safely.
> If any GitNexus tool warns the index is stale, run `npx gitnexus analyze` in terminal first. > If any GitNexus tool warns the index is stale, run `npx gitnexus analyze` in terminal first.

View File

@@ -470,7 +470,7 @@ export function DesktopAnimal({
) : null} ) : null}
<DesktopAnimalOverlay showStopOverlay={showStopOverlay} /> <DesktopAnimalOverlay showStopOverlay={showStopOverlay} />
<RoundBettingStartAlert /> <RoundBettingStartAlert className="z-50" />
{showStandbyState ? ( {showStandbyState ? (
<button <button

View File

@@ -18,7 +18,6 @@ const SETTLEMENT_REVEAL_SETTLE_DURATION_MS = 800
const SETTLEMENT_REVEAL_RESULT_HOLD_MS = 1_000 const SETTLEMENT_REVEAL_RESULT_HOLD_MS = 1_000
const SETTLEMENT_REVEAL_MIN_STEP_MS = 180 const SETTLEMENT_REVEAL_MIN_STEP_MS = 180
const SETTLEMENT_REVEAL_MAX_STEP_MS = 960 const SETTLEMENT_REVEAL_MAX_STEP_MS = 960
const MOBILE_REVEAL_FRAME_OFFSET_PX = 2
function getRandomAnimalId(ids: number[], currentId: number | null) { function getRandomAnimalId(ids: number[], currentId: number | null) {
if (ids.length === 0) { if (ids.length === 0) {
@@ -66,6 +65,7 @@ export function MobileAnimal({
const prefersReducedMotion = useReducedMotion() const prefersReducedMotion = useReducedMotion()
const animalIds = useMemo(() => FLOWER_IMAGE_LIST.map((item) => item.id), []) const animalIds = useMemo(() => FLOWER_IMAGE_LIST.map((item) => item.id), [])
const containerRef = useRef<HTMLElement | null>(null) const containerRef = useRef<HTMLElement | null>(null)
const animalGridRef = useRef<HTMLDivElement | null>(null)
const cellRefs = useRef(new Map<number, HTMLButtonElement>()) const cellRefs = useRef(new Map<number, HTMLButtonElement>())
const [revealCellId, setRevealCellId] = useState<number | null>(null) const [revealCellId, setRevealCellId] = useState<number | null>(null)
const [revealFrame, setRevealFrame] = useState<{ const [revealFrame, setRevealFrame] = useState<{
@@ -214,7 +214,7 @@ export function MobileAnimal({
} }
const syncRevealFrame = () => { const syncRevealFrame = () => {
const container = containerRef.current const container = animalGridRef.current
const cell = cellRefs.current.get(revealCellId) const cell = cellRefs.current.get(revealCellId)
if (!container || !cell) { if (!container || !cell) {
@@ -251,230 +251,241 @@ export function MobileAnimal({
> >
<MobileStatusLine /> <MobileStatusLine />
<div className="relative z-10 grid w-full grid-cols-4 gap-design-3"> <div ref={animalGridRef} className="relative z-10 w-full">
{FLOWER_IMAGE_LIST.map((item) => { <div className="grid w-full grid-cols-4 gap-design-3">
const selectionMeta = selectionByCell[item.id] {FLOWER_IMAGE_LIST.map((item) => {
const hasPlacedSelection = Boolean(selectionMeta) const selectionMeta = selectionByCell[item.id]
const isMarqueeActive = showStandbyState && item.id === marqueeId const hasPlacedSelection = Boolean(selectionMeta)
const isRevealWinner = const isMarqueeActive = showStandbyState && item.id === marqueeId
!shouldHideRevealHighlight && const isRevealWinner =
(isRevealResult || isRevealHoldingResult) && !shouldHideRevealHighlight &&
revealWinningCellId === item.id (isRevealResult || isRevealHoldingResult) &&
const warningType = revealWinningCellId === item.id
cellWarning?.cellId === item.id ? cellWarning.type : null const warningType =
const showCellWarning = warningType !== null cellWarning?.cellId === item.id ? cellWarning.type : null
const warningLabel = const showCellWarning = warningType !== null
warningType === 'balance' const warningLabel =
? t('gameDesktop.animal.insufficientBalanceRecharge') warningType === 'balance'
: warningType === 'betLimit' ? t('gameDesktop.animal.insufficientBalanceRecharge')
? t('gameDesktop.animal.betLimitExceeded') : warningType === 'betLimit'
: t('gameDesktop.animal.selectionLimitReached') ? t('gameDesktop.animal.betLimitExceeded')
: t('gameDesktop.animal.selectionLimitReached')
return ( return (
<motion.button <motion.button
key={item.id} key={item.id}
ref={(node) => { ref={(node) => {
if (node) { if (node) {
cellRefs.current.set(item.id, node) cellRefs.current.set(item.id, node)
} else { } else {
cellRefs.current.delete(item.id) cellRefs.current.delete(item.id)
} }
}} }}
type="button" type="button"
disabled={lockInteraction || showStopOverlay} disabled={lockInteraction || showStopOverlay}
onClick={() => handleSelect(item.id)} 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-36 flex-col items-center justify-center overflow-hidden rounded-[calc(var(--design-unit)*6)] 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-6 top-design-4 z-30 text-design-10 font-bold leading-none text-[#4BFFFE]">
{String(item.id).padStart(2, '0')}
</span>
<motion.span
aria-hidden="true"
animate={ animate={
showCellWarning showCellWarning
? { ? {
opacity: [0.45, 1, 0.6, 1, 0.82], 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,
} }
: undefined
} }
transition={ transition={
showCellWarning showCellWarning
? { ? {
duration: 0.52, duration: 0.46,
ease: 'easeInOut', ease: 'easeInOut',
} }
: undefined : { duration: 0.16, ease: 'easeOut' }
} }
className={cn( className={cn(
'pointer-events-none absolute inset-[calc(var(--design-unit)*1)] rounded-[calc(var(--design-unit)*6)] opacity-0 transition-opacity duration-150', 'relative flex h-design-36 flex-col items-center justify-center overflow-hidden rounded-[calc(var(--design-unit)*6)] 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 && 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)]', '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 && 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)]', '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 && 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)]', '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 && 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', '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,
)} )}
/> >
{!showStandbyState && !hasPlacedSelection ? ( <SmartImage
<span src={animalBorderImage}
alt=""
aria-hidden="true" aria-hidden="true"
className="pointer-events-none absolute inset-[calc(var(--design-unit)*1)] z-20 rounded-[calc(var(--design-unit)*6)] bg-[rgba(4,16,24,0.52)] shadow-[inset_0_0_calc(var(--design-unit)*12)_rgba(3,9,14,0.56)]" priority
showSkeleton={false}
className="pointer-events-none absolute inset-0 z-20 h-full w-full"
imgClassName="object-fill"
/> />
) : null} <span className="pointer-events-none absolute left-design-6 top-design-4 z-30 text-design-10 font-bold leading-none text-[#4BFFFE]">
<SmartImage {String(item.id).padStart(2, '0')}
src={item.animalUrl} </span>
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)*6)]',
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 <motion.span
initial={{ opacity: 0, scale: 0.94 }} aria-hidden="true"
animate={{ animate={
opacity: [0.2, 1, 0.92], showCellWarning
scale: [0.96, 1.02, 1], ? {
boxShadow: [ opacity: [0.45, 1, 0.6, 1, 0.82],
'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)', : undefined
'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={
}} showCellWarning
transition={{ duration: 0.5, ease: 'easeOut' }} ? {
className="pointer-events-none absolute inset-[calc(var(--design-unit)*2)] z-30 flex flex-col items-center justify-center gap-design-2 rounded-[calc(var(--design-unit)*6)] border border-[rgba(255,126,126,0.9)] bg-[rgba(61,0,0,0.58)] px-design-2 py-design-2 text-center" duration: 0.52,
> ease: 'easeInOut',
}
: undefined
}
className={cn(
'pointer-events-none absolute inset-[calc(var(--design-unit)*1)] rounded-[calc(var(--design-unit)*6)] 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)*1)] z-20 rounded-[calc(var(--design-unit)*6)] bg-[rgba(4,16,24,0.52)] shadow-[inset_0_0_calc(var(--design-unit)*12)_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)*6)]',
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 <motion.span
initial={{ opacity: 0, scale: 0.94 }}
animate={{ animate={{
opacity: [0.72, 1, 0.92], opacity: [0.2, 1, 0.92],
scale: [0.94, 1.08, 1], 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.38, ease: 'easeOut' }} transition={{ duration: 0.5, ease: 'easeOut' }}
className="flex h-design-10 w-design-10 items-center justify-center" className="pointer-events-none absolute inset-[calc(var(--design-unit)*2)] z-30 flex flex-col items-center justify-center gap-design-2 rounded-[calc(var(--design-unit)*6)] border border-[rgba(255,126,126,0.9)] bg-[rgba(61,0,0,0.58)] px-design-2 py-design-2 text-center"
> >
<TriangleAlert className="h-design-10 w-design-10 text-[#FFD0D0] drop-shadow-[0_0_calc(var(--design-unit)*5)_rgba(255,92,92,0.45)]" /> <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-10 w-design-10 items-center justify-center"
>
<TriangleAlert className="h-design-10 w-design-10 text-[#FFD0D0] drop-shadow-[0_0_calc(var(--design-unit)*5)_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-6 font-bold leading-tight tracking-[0.02em] text-[#FFE0E0] [text-shadow:0_0_calc(var(--design-unit)*6)_rgba(255,132,132,0.42)]"
>
{warningLabel}
</motion.span>
</motion.span> </motion.span>
<motion.span ) : null}
animate={{ {hasPlacedSelection ? (
opacity: [0.7, 1, 0.96], <span className="pointer-events-none absolute inset-0 z-20 flex items-center justify-center">
scale: [0.98, 1.03, 1], <span className="flex min-w-design-36 items-center justify-center gap-design-1 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-3 py-design-1 shadow-[0_0_calc(var(--design-unit)*8)_rgba(70,245,255,0.18)]">
}} <SmartImage
transition={{ duration: 0.42, ease: 'easeOut' }} src={diamondIcon}
className="text-design-6 font-bold leading-tight tracking-[0.02em] text-[#FFE0E0] [text-shadow:0_0_calc(var(--design-unit)*6)_rgba(255,132,132,0.42)]" alt="diamond"
> className="h-design-9 w-design-9 shrink-0 object-contain"
{warningLabel} />
</motion.span> <span className="text-design-8 font-semibold leading-none tracking-[0.03em] text-[#D8FBFF]">
</motion.span> {selectionMeta.amount}
) : null} </span>
{hasPlacedSelection ? (
<span className="pointer-events-none absolute inset-0 z-20 flex items-center justify-center">
<span className="flex min-w-design-36 items-center justify-center gap-design-1 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-3 py-design-1 shadow-[0_0_calc(var(--design-unit)*8)_rgba(70,245,255,0.18)]">
<SmartImage
src={diamondIcon}
alt="diamond"
className="h-design-9 w-design-9 shrink-0 object-contain"
/>
<span className="text-design-8 font-semibold leading-none tracking-[0.03em] text-[#D8FBFF]">
{selectionMeta.amount}
</span> </span>
</span> </span>
</span> ) : null}
) : null} </motion.button>
</motion.button> )
) })}
})}
</div>
{revealFrame ? (
<div
aria-hidden="true"
className={cn(
'pointer-events-none absolute left-0 top-0 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 + MOBILE_REVEAL_FRAME_OFFSET_PX * 2,
transform: `translate(${revealFrame.left - MOBILE_REVEAL_FRAME_OFFSET_PX}px, ${revealFrame.top - MOBILE_REVEAL_FRAME_OFFSET_PX}px)`,
width: revealFrame.width + MOBILE_REVEAL_FRAME_OFFSET_PX * 2,
}}
>
<div
className="mobile-gold-reveal-glow rounded-[calc(var(--design-unit)*7)]"
style={
prefersReducedMotion
? {
animation: 'none',
opacity: 0.36,
transform: 'scale(1)',
}
: undefined
}
/>
<div className="mobile-gold-reveal-static-border rounded-[calc(var(--design-unit)*7)]" />
<div
className="mobile-gold-reveal-shell rounded-[calc(var(--design-unit)*7)]"
style={prefersReducedMotion ? { animation: 'none' } : undefined}
/>
</div> </div>
) : null}
<MobileAnimalOverlay showStopOverlay={showStopOverlay} /> {revealFrame ? (
<div
aria-hidden="true"
className={cn(
'pointer-events-none absolute left-0 top-0 z-30 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,
}}
>
<SmartImage
src={animalBorderImage}
alt=""
aria-hidden="true"
priority
showSkeleton={false}
className="pointer-events-none absolute inset-0 z-30 h-full w-full"
imgClassName="object-fill"
/>
<div
className="mobile-gold-reveal-glow z-10 rounded-[calc(var(--design-unit)*7)]"
style={
prefersReducedMotion
? {
animation: 'none',
opacity: 0.36,
transform: 'scale(1)',
}
: undefined
}
/>
<div className="mobile-gold-reveal-static-border z-20 rounded-[calc(var(--design-unit)*7)]" />
<div
className="mobile-gold-reveal-shell z-20 rounded-[calc(var(--design-unit)*7)]"
style={prefersReducedMotion ? { animation: 'none' } : undefined}
/>
</div>
) : null}
<MobileAnimalOverlay showStopOverlay={showStopOverlay} />
</div>
{showStandbyState ? ( {showStandbyState ? (
<button <button

View File

@@ -148,12 +148,12 @@ export function MobileControl() {
<SmartBackground <SmartBackground
src={chipBg} src={chipBg}
size="100% 100%" size="100% 100%"
className="relative z-10 flex h-full min-w-0 flex-1 items-center bg-center bg-no-repeat px-design-8" className="relative z-10 flex h-full min-w-0 flex-1 items-center bg-center bg-no-repeat pl-design-8 pr-design-1"
> >
<SmartBackground <SmartBackground
src={chipLineBg} src={chipLineBg}
size="100% 100%" size="100% 100%"
className="flex h-design-32 min-w-0 flex-1 items-center justify-between gap-design-2 overflow-visible px-design-4" className="flex h-design-32 min-w-0 items-center justify-between gap-design-7 overflow-visible"
> >
{chips.map((chip) => { {chips.map((chip) => {
const isSelected = chip.id === selectedChipId const isSelected = chip.id === selectedChipId
@@ -290,7 +290,7 @@ export function MobileControl() {
<SmartBackground <SmartBackground
src={mobileAddReduceBg} src={mobileAddReduceBg}
size="100% 100%" size="100% 100%"
className="relative z-20 -ml-design-6 flex h-full w-design-103 shrink-0 items-center justify-center gap-design-6 bg-center bg-no-repeat px-design-9 text-design-13 font-bold" className="relative z-20 !ml-[calc(var(--design-unit)*-8)] flex h-full w-design-103 shrink-0 items-center justify-center gap-design-5 bg-center bg-no-repeat px-design-7 text-design-13 font-bold"
> >
<button <button
type="button" type="button"

View File

@@ -8,12 +8,14 @@ import {
} from 'lucide-react' } from 'lucide-react'
import { motion } from 'motion/react' import { motion } from 'motion/react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import add from '@/assets/game/add.webp'
import avatar from '@/assets/system/avatar.webp' import avatar from '@/assets/system/avatar.webp'
import chatImage from '@/assets/system/chat.webp' import chatImage from '@/assets/system/chat.webp'
import diamond from '@/assets/system/diamond.webp' import diamond from '@/assets/system/diamond.webp'
import logo from '@/assets/system/logo.webp' import logo from '@/assets/system/logo.webp'
import { SmartImage } from '@/components/smart-image.tsx' import { SmartImage } from '@/components/smart-image.tsx'
import { useHeaderClockLabel, useHeaderVm } from '@/hooks/use-header-vm' import { useHeaderClockLabel, useHeaderVm } from '@/hooks/use-header-vm'
import { cn } from '@/lib/utils'
import { useModalStore } from '@/store' import { useModalStore } from '@/store'
function MobileHeaderClock() { function MobileHeaderClock() {
@@ -87,6 +89,15 @@ export function MobileHeader() {
'common-neon-inset flex h-design-19 shrink-0 cursor-pointer items-center justify-center gap-design-5 !rounded-[3px] !px-design-5 !py-0 text-design-7 leading-none transition-opacity hover:opacity-85' 'common-neon-inset flex h-design-19 shrink-0 cursor-pointer items-center justify-center gap-design-5 !rounded-[3px] !px-design-5 !py-0 text-design-7 leading-none transition-opacity hover:opacity-85'
const accountButtonClassName = const accountButtonClassName =
'common-neon-inset flex h-design-19 items-center justify-end !rounded-[3px] !py-0 text-design-7 leading-none transition-[opacity,transform] duration-150 group-hover:opacity-90 group-active:scale-[0.98]' 'common-neon-inset flex h-design-19 items-center justify-end !rounded-[3px] !py-0 text-design-7 leading-none transition-[opacity,transform] duration-150 group-hover:opacity-90 group-active:scale-[0.98]'
const toolbarButtonClassName = cn(
actionButtonClassName,
'min-w-0 shrink gap-design-3 !px-design-3',
)
const compactToolbarButtonClassName = cn(toolbarButtonClassName, 'flex-[0.8]')
const wideToolbarButtonClassName = cn(
toolbarButtonClassName,
'flex-[1.2] !px-design-4',
)
return ( return (
<header className="relative z-30 h-design-72"> <header className="relative z-30 h-design-72">
@@ -101,7 +112,7 @@ export function MobileHeader() {
/> />
</div> </div>
{authStatus === 'authenticated' ? ( {authStatus === 'authenticated' ? (
<div className="flex h-full min-w-0 flex-1 items-center justify-end gap-design-7 px-design-9"> <div className="flex h-full min-w-0 flex-1 items-center justify-end gap-design-20 px-design-9">
<button <button
type="button" type="button"
onClick={onOpenUserInfo} onClick={onOpenUserInfo}
@@ -111,10 +122,10 @@ export function MobileHeader() {
src={avatar} src={avatar}
alt="avatar" alt="avatar"
priority priority
className="absolute left-0 z-20 h-design-25 w-design-25 rounded-full" className="absolute left-0 z-20 h-design-25 w-design-25 -translate-x-1/2 rounded-full"
/> />
<div <div
className={`${accountButtonClassName} w-design-92 pl-design-28 pr-design-6`} className={`${accountButtonClassName} w-design-80 pl-design-28 pr-design-6`}
> >
<span className="truncate"> <span className="truncate">
{currentUser?.username || '--'} {currentUser?.username || '--'}
@@ -131,22 +142,20 @@ export function MobileHeader() {
src={diamond} src={diamond}
alt="diamond" alt="diamond"
priority priority
className="absolute left-0 z-20 h-design-25 w-design-25" className="absolute left-0 z-20 h-design-25 w-design-25 -translate-x-1/2"
/> />
<div <div
className={`${accountButtonClassName} w-design-90 gap-design-4 pl-design-26 pr-design-4`} className={`${accountButtonClassName} w-design-80 gap-design-4 !pr-design-4`}
> >
<span className="min-w-0 truncate"> <span className="min-w-0 truncate">
{currentUser?.coin || '--'} {currentUser?.coin || '--'}
</span> </span>
{/*<div className="common-neon-inset flex h-design-15 w-design-15 shrink-0 items-center justify-center !rounded-[3px] !p-0">*/} <SmartImage
{/* <Plus*/} src={add}
{/* aria-hidden="true"*/} alt="add"
{/* className="h-design-10 w-design-10 shrink-0"*/} priority
{/* color="#57B8BF"*/} className="h-design-13 w-design-13 shrink-0 cursor-pointer"
{/* strokeWidth={2.5}*/} />
{/* />*/}
{/*</div>*/}
</div> </div>
</button> </button>
</div> </div>
@@ -197,9 +206,9 @@ export function MobileHeader() {
</motion.button> </motion.button>
</div> </div>
<div className={'w-full px-design-10 '}> <div className="w-full px-design-10">
<div className="flex h-design-29 w-full items-center gap-design-5 my-design-5 rounded-sm border-[#0A353E] border-1"> <div className="my-design-5 flex h-design-29 w-full min-w-0 items-center gap-design-5 rounded-sm border-1 border-[#0A353E] px-design-5">
<div className="flex h-design-19 w-design-43 shrink-0 items-center justify-center !rounded-[3px] !px-design-6 !py-0"> <div className="flex h-design-19 w-design-40 shrink-0 items-center justify-center !rounded-[3px]">
<div className={signalPresentation.toneClassName}> <div className={signalPresentation.toneClassName}>
<SignalBars <SignalBars
activeBars={signalPresentation.activeBars} activeBars={signalPresentation.activeBars}
@@ -224,7 +233,7 @@ export function MobileHeader() {
<button <button
type="button" type="button"
onClick={onOpenRules} onClick={onOpenRules}
className={`${actionButtonClassName} !px-design-8`} className={compactToolbarButtonClassName}
> >
<Info className="h-design-8 w-design-8 shrink-0" color="#57B8BF" /> <Info className="h-design-8 w-design-8 shrink-0" color="#57B8BF" />
<div className="min-w-0 truncate"> <div className="min-w-0 truncate">
@@ -235,7 +244,7 @@ export function MobileHeader() {
<button <button
type="button" type="button"
onClick={onOpenNotice} onClick={onOpenNotice}
className={`${actionButtonClassName} !px-design-8`} className={wideToolbarButtonClassName}
> >
<Mail className="h-design-8 w-design-8 shrink-0" color="#57B8BF" /> <Mail className="h-design-8 w-design-8 shrink-0" color="#57B8BF" />
<div className="min-w-0 truncate"> <div className="min-w-0 truncate">
@@ -246,7 +255,7 @@ export function MobileHeader() {
<button <button
type="button" type="button"
onClick={toggleSoundEnabled} onClick={toggleSoundEnabled}
className={`${actionButtonClassName} !px-design-8`} className={compactToolbarButtonClassName}
> >
{isSoundEnabled ? ( {isSoundEnabled ? (
<Volume2 <Volume2
@@ -266,7 +275,7 @@ export function MobileHeader() {
<button <button
type="button" type="button"
onClick={onOpenLanguage} onClick={onOpenLanguage}
className={`${actionButtonClassName} !px-design-8 justify-between`} className={wideToolbarButtonClassName}
> >
<SmartImage <SmartImage
src={currentLanguageOption.icon} src={currentLanguageOption.icon}

View File

@@ -159,7 +159,8 @@ export function MobileStatusLine() {
export function MobileStatusMetrics() { export function MobileStatusMetrics() {
const { t } = useTranslation() const { t } = useTranslation()
const { limitLabel, oddsLabel, streakLabel } = useGameStatusVm() const { limitLabel, oddsLabel, streakLabel, streakValue } = useGameStatusVm()
const showStreakLimitOnly = typeof streakValue === 'number' && streakValue > 1
return ( return (
<SmartBackground <SmartBackground
@@ -168,24 +169,35 @@ export function MobileStatusMetrics() {
className="relative flex h-design-26 w-full items-center overflow-visible bg-center bg-no-repeat px-design-10 font-bold leading-none text-[#CBD3D5]" className="relative flex h-design-26 w-full items-center overflow-visible bg-center bg-no-repeat px-design-10 font-bold leading-none text-[#CBD3D5]"
style={{ fontSize: 'calc(var(--design-unit) * 7)' }} style={{ fontSize: 'calc(var(--design-unit) * 7)' }}
> >
<div {showStreakLimitOnly ? (
className="pointer-events-none absolute inset-y-design-1 left-0 w-[68%] bg-center bg-no-repeat opacity-80" <div
style={{ className="pointer-events-none absolute left-0 right-0 z-0 bg-center bg-no-repeat opacity-95"
backgroundImage: `url(${streakBg})`, style={{
backgroundSize: '118% 155%', backgroundImage: `url(${streakBg})`,
}} backgroundSize: '120% 150%',
/> bottom: 'calc(var(--design-unit)*-5)',
<div className="relative z-10 flex min-w-0 flex-1 items-center justify-center"> top: 'calc(var(--design-unit)*-6)',
{t('gameDesktop.status.odds')}: }}
<span className="ml-design-3 text-[#E3D171]">{oddsLabel}</span> />
</div> ) : (
<div className="relative z-10 flex min-w-0 flex-1 items-center justify-center gap-design-2"> <>
<SmartImage className="h-design-11 w-design-9" alt="fire" src={fire} /> <div className="relative z-10 flex min-w-0 flex-1 items-center justify-center">
<span>{t('gameDesktop.status.streak')}:</span> {t('gameDesktop.status.odds')}:
<span className="bg-gradient-to-b from-[#EBA661] to-[#FCC785] bg-clip-text text-transparent"> <span className="ml-design-3 text-[#E3D171]">{oddsLabel}</span>
{streakLabel} </div>
</span> <div className="relative z-10 flex min-w-0 flex-1 items-center justify-center gap-design-2">
</div> <SmartImage
className="h-design-11 w-design-9"
alt="fire"
src={fire}
/>
<span>{t('gameDesktop.status.streak')}:</span>
<span className="bg-gradient-to-b from-[#EBA661] to-[#FCC785] bg-clip-text text-transparent">
{streakLabel}
</span>
</div>
</>
)}
<div className="relative z-10 flex min-w-0 flex-1 items-center justify-center gap-design-2"> <div className="relative z-10 flex min-w-0 flex-1 items-center justify-center gap-design-2">
<SmartImage className="h-design-10 w-design-8" alt="lock" src={lock} /> <SmartImage className="h-design-10 w-design-8" alt="lock" src={lock} />
<span>{t('gameDesktop.status.limit')}:</span> <span>{t('gameDesktop.status.limit')}:</span>

View File

@@ -256,17 +256,19 @@ function applyBetWinMessage(message: GameSocketMessage) {
return return
} }
useGameRoundStore.getState().setPendingBetWinReward({ if (betWinData.is_win) {
isJackpot: betWinData.is_jackpot, useGameRoundStore.getState().setPendingBetWinReward({
revealKey: `${betWinData.period_no}:${betWinData.result_number ?? 'pending'}:${betWinData.total_win}`, isJackpot: betWinData.is_jackpot,
roundId: betWinData.period_no, revealKey: `${betWinData.period_no}:${betWinData.result_number ?? 'pending'}:${betWinData.total_win}`,
totalWin: betWinData.total_win, roundId: betWinData.period_no,
winningCellId: betWinData.result_number, totalWin: betWinData.total_win,
}) winningCellId: betWinData.result_number,
useGameAutoHostingStore.getState().recordBetWin({ })
isJackpot: betWinData.is_jackpot, useGameAutoHostingStore.getState().recordBetWin({
singleWinAmount: toOptionalNumber(betWinData.total_win) ?? null, isJackpot: betWinData.is_jackpot,
}) singleWinAmount: toOptionalNumber(betWinData.total_win) ?? null,
})
}
if (!currentUser) { if (!currentUser) {
return return

View File

@@ -208,10 +208,10 @@ export const useGameRoundStore = create<GameRoundStoreState>()((set) => ({
: state.revealAnimation.revealKey, : state.revealAnimation.revealKey,
rewardAmount: hasPendingReward rewardAmount: hasPendingReward
? state.revealAnimation.pendingRewardAmount ? state.revealAnimation.pendingRewardAmount
: state.revealAnimation.rewardAmount, : null,
rewardType: hasPendingReward rewardType: hasPendingReward
? state.revealAnimation.pendingRewardType ? state.revealAnimation.pendingRewardType
: state.revealAnimation.rewardType, : 'none',
roundId: hasPendingReward roundId: hasPendingReward
? state.revealAnimation.pendingRewardRoundId ? state.revealAnimation.pendingRewardRoundId
: state.revealAnimation.roundId, : state.revealAnimation.roundId,

View File

@@ -640,7 +640,7 @@
.mobile-gold-reveal-shell { .mobile-gold-reveal-shell {
--gold-angle: 0deg; --gold-angle: 0deg;
position: absolute; position: absolute;
inset: calc(var(--design-unit) * 0.2); inset: calc(var(--design-unit) * 2.2);
border-radius: calc(var(--design-unit) * 7); border-radius: calc(var(--design-unit) * 7);
overflow: hidden; overflow: hidden;
clip-path: inset(0 round calc(var(--design-unit) * 7)); clip-path: inset(0 round calc(var(--design-unit) * 7));
@@ -698,7 +698,7 @@
.mobile-gold-reveal-static-border { .mobile-gold-reveal-static-border {
position: absolute; position: absolute;
inset: calc(var(--design-unit) * 0.7); inset: calc(var(--design-unit) * 2.7);
border-radius: calc(var(--design-unit) * 7); border-radius: calc(var(--design-unit) * 7);
border: calc(var(--design-unit) * 2.8) solid rgba(181, 138, 40, 0.98); border: calc(var(--design-unit) * 2.8) solid rgba(181, 138, 40, 0.98);
box-shadow: box-shadow:
@@ -709,7 +709,7 @@
.mobile-gold-reveal-glow { .mobile-gold-reveal-glow {
position: absolute; position: absolute;
inset: 0; inset: calc(var(--design-unit) * 1.4);
border-radius: calc(var(--design-unit) * 8); border-radius: calc(var(--design-unit) * 8);
background: radial-gradient( background: radial-gradient(
circle at center, circle at center,