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 — 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.

View File

@@ -1,7 +1,7 @@
<!-- gitnexus:start -->
# 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.

View File

@@ -470,7 +470,7 @@ export function DesktopAnimal({
) : null}
<DesktopAnimalOverlay showStopOverlay={showStopOverlay} />
<RoundBettingStartAlert />
<RoundBettingStartAlert className="z-50" />
{showStandbyState ? (
<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_MIN_STEP_MS = 180
const SETTLEMENT_REVEAL_MAX_STEP_MS = 960
const MOBILE_REVEAL_FRAME_OFFSET_PX = 2
function getRandomAnimalId(ids: number[], currentId: number | null) {
if (ids.length === 0) {
@@ -66,6 +65,7 @@ export function MobileAnimal({
const prefersReducedMotion = useReducedMotion()
const animalIds = useMemo(() => FLOWER_IMAGE_LIST.map((item) => item.id), [])
const containerRef = useRef<HTMLElement | null>(null)
const animalGridRef = useRef<HTMLDivElement | null>(null)
const cellRefs = useRef(new Map<number, HTMLButtonElement>())
const [revealCellId, setRevealCellId] = useState<number | null>(null)
const [revealFrame, setRevealFrame] = useState<{
@@ -214,7 +214,7 @@ export function MobileAnimal({
}
const syncRevealFrame = () => {
const container = containerRef.current
const container = animalGridRef.current
const cell = cellRefs.current.get(revealCellId)
if (!container || !cell) {
@@ -251,230 +251,241 @@ export function MobileAnimal({
>
<MobileStatusLine />
<div className="relative z-10 grid w-full grid-cols-4 gap-design-3">
{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')
<div ref={animalGridRef} className="relative z-10 w-full">
<div className="grid w-full grid-cols-4 gap-design-3">
{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-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"
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
? {
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={
showCellWarning
? {
duration: 0.52,
duration: 0.46,
ease: 'easeInOut',
}
: undefined
: { duration: 0.16, ease: 'easeOut' }
}
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 &&
'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 &&
'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 &&
'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 &&
'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 ? (
<span
>
<SmartImage
src={animalBorderImage}
alt=""
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}
<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 ? (
<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
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)*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"
>
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)*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
initial={{ opacity: 0, scale: 0.94 }}
animate={{
opacity: [0.72, 1, 0.92],
scale: [0.94, 1.08, 1],
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.38, ease: 'easeOut' }}
className="flex h-design-10 w-design-10 items-center justify-center"
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"
>
<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
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>
) : null}
{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}
) : null}
{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>
) : null}
</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}
/>
) : null}
</motion.button>
)
})}
</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 ? (
<button

View File

@@ -148,12 +148,12 @@ export function MobileControl() {
<SmartBackground
src={chipBg}
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
src={chipLineBg}
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) => {
const isSelected = chip.id === selectedChipId
@@ -290,7 +290,7 @@ export function MobileControl() {
<SmartBackground
src={mobileAddReduceBg}
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
type="button"

View File

@@ -8,12 +8,14 @@ import {
} from 'lucide-react'
import { motion } from 'motion/react'
import { useTranslation } from 'react-i18next'
import add from '@/assets/game/add.webp'
import avatar from '@/assets/system/avatar.webp'
import chatImage from '@/assets/system/chat.webp'
import diamond from '@/assets/system/diamond.webp'
import logo from '@/assets/system/logo.webp'
import { SmartImage } from '@/components/smart-image.tsx'
import { useHeaderClockLabel, useHeaderVm } from '@/hooks/use-header-vm'
import { cn } from '@/lib/utils'
import { useModalStore } from '@/store'
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'
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]'
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 (
<header className="relative z-30 h-design-72">
@@ -101,7 +112,7 @@ export function MobileHeader() {
/>
</div>
{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
type="button"
onClick={onOpenUserInfo}
@@ -111,10 +122,10 @@ export function MobileHeader() {
src={avatar}
alt="avatar"
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
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">
{currentUser?.username || '--'}
@@ -131,22 +142,20 @@ export function MobileHeader() {
src={diamond}
alt="diamond"
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
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">
{currentUser?.coin || '--'}
</span>
{/*<div className="common-neon-inset flex h-design-15 w-design-15 shrink-0 items-center justify-center !rounded-[3px] !p-0">*/}
{/* <Plus*/}
{/* aria-hidden="true"*/}
{/* className="h-design-10 w-design-10 shrink-0"*/}
{/* color="#57B8BF"*/}
{/* strokeWidth={2.5}*/}
{/* />*/}
{/*</div>*/}
<SmartImage
src={add}
alt="add"
priority
className="h-design-13 w-design-13 shrink-0 cursor-pointer"
/>
</div>
</button>
</div>
@@ -197,9 +206,9 @@ export function MobileHeader() {
</motion.button>
</div>
<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="flex h-design-19 w-design-43 shrink-0 items-center justify-center !rounded-[3px] !px-design-6 !py-0">
<div className="w-full px-design-10">
<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-40 shrink-0 items-center justify-center !rounded-[3px]">
<div className={signalPresentation.toneClassName}>
<SignalBars
activeBars={signalPresentation.activeBars}
@@ -224,7 +233,7 @@ export function MobileHeader() {
<button
type="button"
onClick={onOpenRules}
className={`${actionButtonClassName} !px-design-8`}
className={compactToolbarButtonClassName}
>
<Info className="h-design-8 w-design-8 shrink-0" color="#57B8BF" />
<div className="min-w-0 truncate">
@@ -235,7 +244,7 @@ export function MobileHeader() {
<button
type="button"
onClick={onOpenNotice}
className={`${actionButtonClassName} !px-design-8`}
className={wideToolbarButtonClassName}
>
<Mail className="h-design-8 w-design-8 shrink-0" color="#57B8BF" />
<div className="min-w-0 truncate">
@@ -246,7 +255,7 @@ export function MobileHeader() {
<button
type="button"
onClick={toggleSoundEnabled}
className={`${actionButtonClassName} !px-design-8`}
className={compactToolbarButtonClassName}
>
{isSoundEnabled ? (
<Volume2
@@ -266,7 +275,7 @@ export function MobileHeader() {
<button
type="button"
onClick={onOpenLanguage}
className={`${actionButtonClassName} !px-design-8 justify-between`}
className={wideToolbarButtonClassName}
>
<SmartImage
src={currentLanguageOption.icon}

View File

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

View File

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

View File

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

View File

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