feat(app): 添加应用启动资源预加载功能
- 实现了 AppBootResourceGate 组件用于管理应用启动时的资源加载 - 集成了图片和字体资源的预加载机制 - 添加了启动时的加载进度指示器和动画效果 - 创建了 DataLoadingIndicator 组件用于显示数据加载状态 - 在多个组件中替换原有的加载提示为新的 DataLoadingIndicator - 更新了样式文件以支持新的加载动画和视觉效果
This commit is contained in:
@@ -1,7 +1,7 @@
|
|||||||
<!-- gitnexus:start -->
|
<!-- gitnexus:start -->
|
||||||
# GitNexus — Code Intelligence
|
# GitNexus — Code Intelligence
|
||||||
|
|
||||||
This project is indexed by GitNexus as **36-character-flower** (2527 symbols, 4819 relationships, 217 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** (2566 symbols, 4898 relationships, 220 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.
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<!-- gitnexus:start -->
|
<!-- gitnexus:start -->
|
||||||
# GitNexus — Code Intelligence
|
# GitNexus — Code Intelligence
|
||||||
|
|
||||||
This project is indexed by GitNexus as **36-character-flower** (2527 symbols, 4819 relationships, 217 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** (2566 symbols, 4898 relationships, 220 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.
|
||||||
|
|
||||||
|
|||||||
159
src/components/app-boot-resource-gate.tsx
Normal file
159
src/components/app-boot-resource-gate.tsx
Normal file
@@ -0,0 +1,159 @@
|
|||||||
|
import { type PropsWithChildren, useEffect, useMemo, useState } from 'react'
|
||||||
|
|
||||||
|
const bootImageModules = import.meta.glob(
|
||||||
|
'../assets/**/*.{jpg,jpeg,png,svg,webp}',
|
||||||
|
{
|
||||||
|
eager: true,
|
||||||
|
import: 'default',
|
||||||
|
},
|
||||||
|
) as Record<string, string>
|
||||||
|
|
||||||
|
const BOOT_MIN_VISIBLE_MS = 900
|
||||||
|
const particleLayout = Array.from({ length: 36 }, (_, index) => ({
|
||||||
|
delayMs: (index % 9) * 180,
|
||||||
|
durationMs: 3200 + (index % 7) * 260,
|
||||||
|
left: `${(index * 17 + 9) % 100}%`,
|
||||||
|
size: 2 + (index % 4),
|
||||||
|
top: `${(index * 29 + 13) % 100}%`,
|
||||||
|
}))
|
||||||
|
|
||||||
|
function preloadImage(url: string) {
|
||||||
|
return new Promise<void>((resolve) => {
|
||||||
|
const image = new Image()
|
||||||
|
|
||||||
|
image.onload = () => resolve()
|
||||||
|
image.onerror = () => resolve()
|
||||||
|
image.src = url
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function waitForFonts() {
|
||||||
|
if (typeof document === 'undefined' || !('fonts' in document)) {
|
||||||
|
return Promise.resolve()
|
||||||
|
}
|
||||||
|
|
||||||
|
return document.fonts.ready.then(() => undefined)
|
||||||
|
}
|
||||||
|
|
||||||
|
function useBootResourceLoader() {
|
||||||
|
const imageUrls = useMemo(
|
||||||
|
() => Array.from(new Set(Object.values(bootImageModules))).filter(Boolean),
|
||||||
|
[],
|
||||||
|
)
|
||||||
|
const [progress, setProgress] = useState(0)
|
||||||
|
const [isReady, setIsReady] = useState(false)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
let cancelled = false
|
||||||
|
const startedAt = performance.now()
|
||||||
|
const totalSteps = imageUrls.length + 1
|
||||||
|
let completedSteps = 0
|
||||||
|
|
||||||
|
const syncProgress = () => {
|
||||||
|
completedSteps += 1
|
||||||
|
if (!cancelled) {
|
||||||
|
setProgress(
|
||||||
|
Math.min(100, Math.round((completedSteps / totalSteps) * 100)),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const waitForMinimumDuration = async () => {
|
||||||
|
const elapsedMs = performance.now() - startedAt
|
||||||
|
const remainingMs = Math.max(0, BOOT_MIN_VISIBLE_MS - elapsedMs)
|
||||||
|
|
||||||
|
if (remainingMs <= 0) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
await new Promise((resolve) => window.setTimeout(resolve, remainingMs))
|
||||||
|
}
|
||||||
|
|
||||||
|
void Promise.all([
|
||||||
|
waitForFonts().finally(syncProgress),
|
||||||
|
...imageUrls.map((url) => preloadImage(url).finally(syncProgress)),
|
||||||
|
])
|
||||||
|
.then(waitForMinimumDuration)
|
||||||
|
.then(() => {
|
||||||
|
if (!cancelled) {
|
||||||
|
setProgress(100)
|
||||||
|
setIsReady(true)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
cancelled = true
|
||||||
|
}
|
||||||
|
}, [imageUrls])
|
||||||
|
|
||||||
|
return { isReady, progress }
|
||||||
|
}
|
||||||
|
|
||||||
|
function AppLoadingOverlay({ progress }: { progress: number }) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
role="status"
|
||||||
|
aria-live="polite"
|
||||||
|
aria-label="Loading"
|
||||||
|
className="fixed inset-0 z-[9999] flex items-center justify-center overflow-hidden bg-[#020913] text-[#D5FBFF]"
|
||||||
|
>
|
||||||
|
<div className="absolute inset-0 bg-[radial-gradient(circle_at_50%_38%,rgba(35,221,255,0.18),transparent_30%),linear-gradient(180deg,#061827_0%,#020913_54%,#01050C_100%)]" />
|
||||||
|
<div className="boot-grid absolute inset-0 opacity-50" />
|
||||||
|
<div className="boot-scanline absolute inset-0 opacity-35" />
|
||||||
|
|
||||||
|
<div className="pointer-events-none absolute inset-0">
|
||||||
|
{particleLayout.map((particle) => (
|
||||||
|
<span
|
||||||
|
key={`${particle.left}-${particle.top}`}
|
||||||
|
className="boot-particle absolute rounded-full bg-[#71FFF7] shadow-[0_0_12px_rgba(113,255,247,0.88)]"
|
||||||
|
style={{
|
||||||
|
animationDelay: `${particle.delayMs}ms`,
|
||||||
|
animationDuration: `${particle.durationMs}ms`,
|
||||||
|
height: `calc(var(--design-unit) * ${particle.size})`,
|
||||||
|
left: particle.left,
|
||||||
|
top: particle.top,
|
||||||
|
width: `calc(var(--design-unit) * ${particle.size})`,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="relative flex w-[min(calc(var(--design-unit)*720),86vw)] flex-col items-center gap-design-24 px-design-24">
|
||||||
|
<div className="relative flex h-design-190 w-design-190 min-h-32 min-w-32 items-center justify-center md:h-design-260 md:w-design-260">
|
||||||
|
<div className="boot-ring absolute inset-0 rounded-full border border-[rgba(105,255,250,0.38)]" />
|
||||||
|
<div className="boot-ring boot-ring-delay absolute inset-[calc(var(--design-unit)*18)] rounded-full border border-[rgba(255,224,117,0.34)] md:inset-[calc(var(--design-unit)*24)]" />
|
||||||
|
<div className="absolute h-[64%] w-[64%] rounded-full border border-[rgba(113,255,247,0.26)] bg-[radial-gradient(circle,rgba(18,95,113,0.68),rgba(2,13,22,0.96))] shadow-[inset_0_0_calc(var(--design-unit)*32)_rgba(113,255,247,0.18),0_0_calc(var(--design-unit)*52)_rgba(65,232,255,0.22)]" />
|
||||||
|
<span className="relative text-design-34 font-bold leading-none text-[#FEE889] [text-shadow:0_0_calc(var(--design-unit)*16)_rgba(254,232,137,0.46)] md:text-design-50 md:[text-shadow:0_0_calc(var(--design-unit)*22)_rgba(254,232,137,0.56)]">
|
||||||
|
{progress}%
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="w-full overflow-hidden rounded-[calc(var(--design-unit)*10)] border border-[rgba(113,255,247,0.38)] bg-[rgba(4,20,31,0.82)] p-design-2 shadow-[0_0_calc(var(--design-unit)*22)_rgba(65,232,255,0.16),inset_0_0_calc(var(--design-unit)*10)_rgba(113,255,247,0.1)]">
|
||||||
|
<div
|
||||||
|
className="h-design-10 rounded-[calc(var(--design-unit)*8)] bg-[linear-gradient(90deg,#34E7FF,#7BFFF6,#FEE889)] shadow-[0_0_calc(var(--design-unit)*16)_rgba(113,255,247,0.58)] transition-[width] duration-300 ease-out"
|
||||||
|
style={{ width: `${progress}%` }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex flex-col items-center gap-design-8 text-center">
|
||||||
|
<div className="text-design-24 font-bold text-[#E8FFFF] [text-shadow:0_0_calc(var(--design-unit)*14)_rgba(113,255,247,0.38)]">
|
||||||
|
资源加载中
|
||||||
|
</div>
|
||||||
|
<div className="text-design-14 text-[rgba(181,242,247,0.72)]">
|
||||||
|
正在同步字花图鉴与游戏界面
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function AppBootResourceGate({ children }: PropsWithChildren) {
|
||||||
|
const { isReady, progress } = useBootResourceLoader()
|
||||||
|
|
||||||
|
if (!isReady) {
|
||||||
|
return <AppLoadingOverlay progress={progress} />
|
||||||
|
}
|
||||||
|
|
||||||
|
return children
|
||||||
|
}
|
||||||
53
src/components/ui/data-loading-indicator.tsx
Normal file
53
src/components/ui/data-loading-indicator.tsx
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
import type { ReactNode } from 'react'
|
||||||
|
import { cn } from '@/lib/utils'
|
||||||
|
|
||||||
|
interface DataLoadingIndicatorProps {
|
||||||
|
className?: string
|
||||||
|
compact?: boolean
|
||||||
|
label?: ReactNode
|
||||||
|
}
|
||||||
|
|
||||||
|
export function DataLoadingIndicator({
|
||||||
|
className,
|
||||||
|
compact = false,
|
||||||
|
label,
|
||||||
|
}: DataLoadingIndicatorProps) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
role="status"
|
||||||
|
aria-live="polite"
|
||||||
|
className={cn(
|
||||||
|
'flex w-full flex-col items-center justify-center text-center text-[#9CE8F2]',
|
||||||
|
compact ? 'gap-design-8 py-design-8' : 'gap-design-14 py-design-30',
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
'data-loading-core relative flex items-center justify-center rounded-full border border-[rgba(113,255,247,0.34)] bg-[radial-gradient(circle,rgba(13,75,92,0.68),rgba(2,14,22,0.92))] shadow-[inset_0_0_calc(var(--design-unit)*14)_rgba(113,255,247,0.14),0_0_calc(var(--design-unit)*18)_rgba(65,232,255,0.2)]',
|
||||||
|
compact ? 'h-design-34 w-design-34' : 'h-design-58 w-design-58',
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<span className="data-loading-orbit absolute inset-[-18%] rounded-full border border-[rgba(113,255,247,0.24)]" />
|
||||||
|
<span className="data-loading-orbit data-loading-orbit-delay absolute inset-[12%] rounded-full border border-[rgba(255,232,137,0.24)]" />
|
||||||
|
<span
|
||||||
|
className={cn(
|
||||||
|
'rounded-full bg-[#FEE889] shadow-[0_0_calc(var(--design-unit)*12)_rgba(254,232,137,0.76)]',
|
||||||
|
compact ? 'h-design-6 w-design-6' : 'h-design-9 w-design-9',
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{label ? (
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
'font-medium text-[#BFFBFF] [text-shadow:0_0_calc(var(--design-unit)*10)_rgba(113,255,247,0.28)]',
|
||||||
|
compact ? 'text-design-13' : 'text-design-16',
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{label}
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -3,6 +3,7 @@ import { useTranslation } from 'react-i18next'
|
|||||||
import historyBg from '@/assets/system/history-bg.png'
|
import historyBg from '@/assets/system/history-bg.png'
|
||||||
import { SmartBackground } from '@/components/smart-background.tsx'
|
import { SmartBackground } from '@/components/smart-background.tsx'
|
||||||
import { SmartImage } from '@/components/smart-image'
|
import { SmartImage } from '@/components/smart-image'
|
||||||
|
import { DataLoadingIndicator } from '@/components/ui/data-loading-indicator'
|
||||||
import { useGameHistoryVm } from '@/features/game/hooks/use-game-history-vm.ts'
|
import { useGameHistoryVm } from '@/features/game/hooks/use-game-history-vm.ts'
|
||||||
import { FLOWER_IMAGE_BY_ID } from '@/features/game/shared'
|
import { FLOWER_IMAGE_BY_ID } from '@/features/game/shared'
|
||||||
|
|
||||||
@@ -17,12 +18,18 @@ function HistoryRewardNumber({
|
|||||||
const label = String(number).padStart(2, '0')
|
const label = String(number).padStart(2, '0')
|
||||||
|
|
||||||
if (!image?.rewardUrl) {
|
if (!image?.rewardUrl) {
|
||||||
return <span className={className}>{label}</span>
|
return (
|
||||||
|
<span
|
||||||
|
className={`inline-flex h-design-38 min-w-design-38 shrink-0 items-center justify-center px-design-5 text-design-15 font-bold text-[#D5FBFF] shadow-[inset_0_0_calc(var(--design-unit)*10)_rgba(75,233,255,0.12),0_0_calc(var(--design-unit)*10)_rgba(75,233,255,0.1)] align-middle ${className ?? ''}`}
|
||||||
|
>
|
||||||
|
{label}
|
||||||
|
</span>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<span
|
<span
|
||||||
className={`inline-flex h-design-36 w-design-36 shrink-0 items-center justify-center align-middle ${className ?? ''}`}
|
className={`inline-flex h-design-38 w-design-38 shrink-0 items-center justify-center p-design-1 align-middle ${className ?? ''}`}
|
||||||
title={label}
|
title={label}
|
||||||
>
|
>
|
||||||
<SmartImage
|
<SmartImage
|
||||||
@@ -74,7 +81,7 @@ export function DesktopGameHistory() {
|
|||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className={
|
className={
|
||||||
'relative z-20 flex h-design-50 shrink-0 items-center justify-center text-design-30 text-[#D5FBFF]'
|
'relative z-20 flex h-design-50 shrink-0 items-center justify-center text-design-28 font-bold text-[#D5FBFF]'
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{t('gameDesktop.history.title')}
|
{t('gameDesktop.history.title')}
|
||||||
@@ -87,13 +94,10 @@ export function DesktopGameHistory() {
|
|||||||
}
|
}
|
||||||
>
|
>
|
||||||
{isInitialLoading ? (
|
{isInitialLoading ? (
|
||||||
<div
|
<DataLoadingIndicator
|
||||||
className={
|
label={loadingText}
|
||||||
'flex w-full flex-1 items-center justify-center text-design-18 text-[#84A2A2]'
|
className="min-h-full flex-1"
|
||||||
}
|
/>
|
||||||
>
|
|
||||||
{loadingText}
|
|
||||||
</div>
|
|
||||||
) : isEmpty ? (
|
) : isEmpty ? (
|
||||||
<div
|
<div
|
||||||
className={
|
className={
|
||||||
@@ -124,44 +128,94 @@ export function DesktopGameHistory() {
|
|||||||
: isWin
|
: isWin
|
||||||
? '0 0 calc(var(--design-unit)*10) #FFE375, 0 0 calc(var(--design-unit)*22) rgba(255,227,117,0.48)'
|
? '0 0 calc(var(--design-unit)*10) #FFE375, 0 0 calc(var(--design-unit)*22) rgba(255,227,117,0.48)'
|
||||||
: '0 0 calc(var(--design-unit)*10) #8DFF98, 0 0 calc(var(--design-unit)*22) rgba(141,255,152,0.48)'
|
: '0 0 calc(var(--design-unit)*10) #8DFF98, 0 0 calc(var(--design-unit)*22) rgba(141,255,152,0.48)'
|
||||||
|
const statusBorderColor =
|
||||||
|
item.resultState === 'pending'
|
||||||
|
? 'rgba(143,241,255,0.34)'
|
||||||
|
: isWin
|
||||||
|
? 'rgba(255,227,117,0.5)'
|
||||||
|
: 'rgba(141,255,152,0.36)'
|
||||||
|
const statusBg =
|
||||||
|
item.resultState === 'pending'
|
||||||
|
? 'linear-gradient(180deg,rgba(28,106,126,0.38),rgba(5,25,36,0.9))'
|
||||||
|
: isWin
|
||||||
|
? 'linear-gradient(180deg,rgba(127,92,14,0.5),rgba(29,22,8,0.92))'
|
||||||
|
: 'linear-gradient(180deg,rgba(31,111,54,0.38),rgba(6,28,20,0.92))'
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div key={item.id} className="w-full pb-design-12 last:pb-0">
|
<div key={item.id} className="w-full pb-design-12 last:pb-0">
|
||||||
<div
|
<div
|
||||||
className={
|
className={
|
||||||
'common-neon-inset flex w-full flex-col items-center !p-0 text-[#FFE375]'
|
'relative isolate flex w-full flex-col items-center overflow-hidden rounded-[calc(var(--design-unit)*14)] border bg-[linear-gradient(180deg,rgba(6,33,45,0.97),rgba(3,14,23,0.94))] text-[#FFE375] shadow-[0_0_calc(var(--design-unit)*16)_rgba(63,226,255,0.14),inset_0_1px_0_rgba(218,255,255,0.12)] transition-colors duration-200'
|
||||||
}
|
}
|
||||||
|
style={{
|
||||||
|
borderColor: statusBorderColor,
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<div
|
<span
|
||||||
className="common-neon-inset w-full !rounded-b-none text-center text-design-20 font-bold tracking-[0.08em]"
|
aria-hidden="true"
|
||||||
|
className="pointer-events-none absolute inset-x-design-10 top-0 h-px opacity-80"
|
||||||
style={{
|
style={{
|
||||||
color: statusColor,
|
background: `linear-gradient(90deg, transparent, ${statusColor}, transparent)`,
|
||||||
textShadow: statusTextShadow,
|
}}
|
||||||
|
/>
|
||||||
|
<span
|
||||||
|
aria-hidden="true"
|
||||||
|
className="pointer-events-none absolute -right-design-28 -top-design-30 h-design-90 w-design-90 rounded-full blur-[28px]"
|
||||||
|
style={{
|
||||||
|
background: statusColor,
|
||||||
|
opacity: isWin ? 0.14 : 0.08,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
className="relative z-10 flex min-h-design-42 w-full items-center justify-center border-b px-design-12 py-design-7"
|
||||||
|
style={{
|
||||||
|
background: statusBg,
|
||||||
|
borderColor: statusBorderColor,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{statusLabel}
|
<span
|
||||||
|
className="inline-flex min-w-design-118 items-center justify-center gap-design-8 px-design-14 py-design-4 text-design-18 font-bold"
|
||||||
|
style={{
|
||||||
|
borderColor: statusBorderColor,
|
||||||
|
color: statusColor,
|
||||||
|
textShadow: statusTextShadow,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
aria-hidden="true"
|
||||||
|
className="h-design-8 w-design-8 rounded-full shadow-[0_0_calc(var(--design-unit)*10)_currentColor]"
|
||||||
|
style={{ backgroundColor: statusColor }}
|
||||||
|
/>
|
||||||
|
{statusLabel}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
className={
|
className={
|
||||||
'flex w-full flex-col gap-design-5 px-design-10 py-design-10 text-design-16'
|
'relative z-10 flex w-full flex-col gap-design-7 px-design-10 py-design-10 text-design-16'
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<div>
|
<div className="flex min-h-design-34 items-center justify-between gap-design-10 rounded-[calc(var(--design-unit)*8)] border border-[rgba(94,212,230,0.14)] bg-[rgba(5,23,33,0.58)] px-design-10 py-design-6">
|
||||||
<span className={'text-[#84A2A2]'}>
|
<span className={'shrink-0 text-[#84A2A2]'}>
|
||||||
{t('gameDesktop.history.roundId')}:{' '}
|
{t('gameDesktop.history.roundId')}:{' '}
|
||||||
</span>
|
</span>
|
||||||
<span className={'text-[#C0E7EB]'}>
|
<span
|
||||||
|
className={
|
||||||
|
'min-w-0 text-right text-design-15 font-medium text-[#C0E7EB]'
|
||||||
|
}
|
||||||
|
>
|
||||||
{item.periodNo}
|
{item.periodNo}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div className="flex min-h-design-42 items-start justify-between gap-design-10 rounded-[calc(var(--design-unit)*8)] border border-[rgba(94,212,230,0.14)] bg-[rgba(5,23,33,0.58)] px-design-10 py-design-6">
|
||||||
<span className={'text-[#84A2A2]'}>
|
<span className={'shrink-0 pt-design-7 text-[#84A2A2]'}>
|
||||||
{t('gameDesktop.history.numbers')}:{' '}
|
{t('gameDesktop.history.numbers')}:{' '}
|
||||||
</span>
|
</span>
|
||||||
{item.numbers.length === 0 ? (
|
{item.numbers.length === 0 ? (
|
||||||
<span>{item.numbersLabel}</span>
|
<span className="pt-design-7 text-right">
|
||||||
|
{item.numbersLabel}
|
||||||
|
</span>
|
||||||
) : (
|
) : (
|
||||||
<span className="inline-flex flex-wrap items-center gap-design-4 align-middle">
|
<span className="inline-flex min-w-0 flex-1 flex-wrap items-center justify-end gap-design-5 align-middle">
|
||||||
{item.numbers.map((number) => (
|
{item.numbers.map((number) => (
|
||||||
<HistoryRewardNumber
|
<HistoryRewardNumber
|
||||||
key={`${item.id}-${number}`}
|
key={`${item.id}-${number}`}
|
||||||
@@ -171,20 +225,28 @@ export function DesktopGameHistory() {
|
|||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div className="flex min-h-design-34 items-center justify-between gap-design-10 rounded-[calc(var(--design-unit)*8)] border border-[rgba(255,227,117,0.16)] bg-[linear-gradient(90deg,rgba(5,23,33,0.58),rgba(84,57,8,0.16))] px-design-10 py-design-6">
|
||||||
<span className={'text-[#84A2A2]'}>
|
<span className={'shrink-0 text-[#84A2A2]'}>
|
||||||
{t('gameDesktop.history.totalPoolAmount')}:{' '}
|
{t('gameDesktop.history.totalPoolAmount')}:{' '}
|
||||||
</span>
|
</span>
|
||||||
<span className={'text-[#FFE375]'}>
|
<span
|
||||||
|
className={
|
||||||
|
'text-right text-design-17 font-bold text-[#FFE375] [text-shadow:0_0_calc(var(--design-unit)*10)_rgba(255,227,117,0.22)]'
|
||||||
|
}
|
||||||
|
>
|
||||||
{item.amountLabel}
|
{item.amountLabel}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div className="flex min-h-design-42 items-center justify-between gap-design-10 rounded-[calc(var(--design-unit)*8)] border border-[rgba(255,117,117,0.18)] bg-[linear-gradient(90deg,rgba(5,23,33,0.58),rgba(88,20,28,0.16))] px-design-10 py-design-6">
|
||||||
<span className={'text-[#84A2A2]'}>
|
<span className={'shrink-0 text-[#84A2A2]'}>
|
||||||
{t('gameDesktop.history.winningResult')}:{' '}
|
{t('gameDesktop.history.winningResult')}:{' '}
|
||||||
</span>
|
</span>
|
||||||
{item.resultNumber === null ? (
|
{item.resultNumber === null ? (
|
||||||
<span className={'text-[#FF7575]'}>
|
<span
|
||||||
|
className={
|
||||||
|
'text-right font-bold text-[#FF7575] [text-shadow:0_0_calc(var(--design-unit)*10)_rgba(255,117,117,0.28)]'
|
||||||
|
}
|
||||||
|
>
|
||||||
{item.resultNumberLabel}
|
{item.resultNumberLabel}
|
||||||
</span>
|
</span>
|
||||||
) : (
|
) : (
|
||||||
@@ -200,7 +262,13 @@ export function DesktopGameHistory() {
|
|||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
<div className="flex min-h-[calc(var(--design-unit)*40)] items-center justify-center text-design-16 text-[#84A2A2]">
|
<div className="flex min-h-[calc(var(--design-unit)*40)] items-center justify-center text-design-16 text-[#84A2A2]">
|
||||||
{isFetchingNextPage ? loadingText : hasNextPage ? '' : endText}
|
{isFetchingNextPage ? (
|
||||||
|
<DataLoadingIndicator compact label={loadingText} />
|
||||||
|
) : hasNextPage ? (
|
||||||
|
''
|
||||||
|
) : (
|
||||||
|
endText
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { useMutation } from '@tanstack/react-query'
|
import { useMutation } from '@tanstack/react-query'
|
||||||
import { useRef } from 'react'
|
import { useRef } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import { DataLoadingIndicator } from '@/components/ui/data-loading-indicator'
|
||||||
import { createDeposit, type DepositTierItem } from '@/features/game/api'
|
import { createDeposit, type DepositTierItem } from '@/features/game/api'
|
||||||
import { useDepositTierList } from '@/features/game/hooks/use-deposit-tier-list'
|
import { useDepositTierList } from '@/features/game/hooks/use-deposit-tier-list'
|
||||||
import { notify } from '@/lib/notify'
|
import { notify } from '@/lib/notify'
|
||||||
@@ -104,9 +105,10 @@ function DesktopTopup() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{tierListQuery.isLoading ? (
|
{tierListQuery.isLoading ? (
|
||||||
<div className="flex h-full min-h-0 items-center justify-center rounded-[calc(var(--design-unit)*6)] border border-[rgba(103,227,239,0.18)] bg-[rgba(6,24,35,0.52)] text-design-16 text-[#8FDDE6]">
|
<DataLoadingIndicator
|
||||||
{t('gameDesktop.topup.tier.loading')}
|
label={t('gameDesktop.topup.tier.loading')}
|
||||||
</div>
|
className="h-full min-h-0 rounded-[calc(var(--design-unit)*6)] border border-[rgba(103,227,239,0.18)] bg-[rgba(6,24,35,0.52)]"
|
||||||
|
/>
|
||||||
) : tierListQuery.isError ? (
|
) : tierListQuery.isError ? (
|
||||||
<div className="flex h-full min-h-0 items-center justify-center rounded-[calc(var(--design-unit)*6)] border border-[rgba(185,63,68,0.28)] bg-[rgba(34,13,16,0.42)] text-design-16 text-[#F4A9AE]">
|
<div className="flex h-full min-h-0 items-center justify-center rounded-[calc(var(--design-unit)*6)] border border-[rgba(185,63,68,0.28)] bg-[rgba(34,13,16,0.42)] text-design-16 text-[#F4A9AE]">
|
||||||
{t('gameDesktop.topup.tier.failed')}
|
{t('gameDesktop.topup.tier.failed')}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import checkIcon from '@/assets/system/right.webp'
|
|||||||
import { CenterModal } from '@/components/center-modal.tsx'
|
import { CenterModal } from '@/components/center-modal.tsx'
|
||||||
import { SmartBackground } from '@/components/smart-background.tsx'
|
import { SmartBackground } from '@/components/smart-background.tsx'
|
||||||
import { SmartImage } from '@/components/smart-image.tsx'
|
import { SmartImage } from '@/components/smart-image.tsx'
|
||||||
|
import { DataLoadingIndicator } from '@/components/ui/data-loading-indicator.tsx'
|
||||||
import {
|
import {
|
||||||
ENTRY_NOTICE_CONFIRM_INTERVAL_MS,
|
ENTRY_NOTICE_CONFIRM_INTERVAL_MS,
|
||||||
ENTRY_NOTICE_LAST_CONFIRMED_AT_KEY,
|
ENTRY_NOTICE_LAST_CONFIRMED_AT_KEY,
|
||||||
@@ -117,9 +118,10 @@ export function EntryNoticeGateModal() {
|
|||||||
<div className="flex h-full w-full flex-col gap-design-20 px-design-14 pb-design-30 pt-design-8">
|
<div className="flex h-full w-full flex-col gap-design-20 px-design-14 pb-design-30 pt-design-8">
|
||||||
<div className="min-h-0 flex-1 overflow-y-auto rounded-md border border-[#2B8CA3]/45 bg-[#001B24]/70 p-design-18 shadow-[inset_0_0_calc(var(--design-unit)*18)_rgba(39,175,205,0.1)]">
|
<div className="min-h-0 flex-1 overflow-y-auto rounded-md border border-[#2B8CA3]/45 bg-[#001B24]/70 p-design-18 shadow-[inset_0_0_calc(var(--design-unit)*18)_rgba(39,175,205,0.1)]">
|
||||||
{noticeListQuery.isPending ? (
|
{noticeListQuery.isPending ? (
|
||||||
<div className="flex h-full min-h-[calc(var(--design-unit)*320)] items-center justify-center text-design-22 text-[#9CE8F2]">
|
<DataLoadingIndicator
|
||||||
{t('game.modals.entryNotice.loading')}
|
label={t('game.modals.entryNotice.loading')}
|
||||||
</div>
|
className="h-full min-h-[calc(var(--design-unit)*320)]"
|
||||||
|
/>
|
||||||
) : noticeListQuery.isError ? (
|
) : noticeListQuery.isError ? (
|
||||||
<div className="flex h-full min-h-[calc(var(--design-unit)*320)] flex-col items-center justify-center gap-design-18 text-center text-[#9CE8F2]">
|
<div className="flex h-full min-h-[calc(var(--design-unit)*320)] flex-col items-center justify-center gap-design-18 text-center text-[#9CE8F2]">
|
||||||
<div className="text-design-22">
|
<div className="text-design-22">
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { useVirtualizer } from '@tanstack/react-virtual'
|
|||||||
import { motion } from 'motion/react'
|
import { motion } from 'motion/react'
|
||||||
import { useEffect, useRef } from 'react'
|
import { useEffect, useRef } from 'react'
|
||||||
|
|
||||||
|
import { DataLoadingIndicator } from '@/components/ui/data-loading-indicator'
|
||||||
import { useFinanceRecordsVm } from '@/features/game/hooks/use-finance-records-vm'
|
import { useFinanceRecordsVm } from '@/features/game/hooks/use-finance-records-vm'
|
||||||
import { cn } from '@/lib/utils'
|
import { cn } from '@/lib/utils'
|
||||||
|
|
||||||
@@ -108,9 +109,7 @@ function DesktopFinanceRecordsTab({ enabled }: { enabled: boolean }) {
|
|||||||
}
|
}
|
||||||
>
|
>
|
||||||
{vm.isLoading ? (
|
{vm.isLoading ? (
|
||||||
<div className={'py-design-30 text-center text-[#6CCDCF]'}>
|
<DataLoadingIndicator label={vm.loadingText} />
|
||||||
{vm.loadingText}
|
|
||||||
</div>
|
|
||||||
) : vm.isError ? (
|
) : vm.isError ? (
|
||||||
<div className={'py-design-30 text-center text-[#6CCDCF]'}>
|
<div className={'py-design-30 text-center text-[#6CCDCF]'}>
|
||||||
{vm.loadFailedText}
|
{vm.loadFailedText}
|
||||||
@@ -159,13 +158,11 @@ function DesktopFinanceRecordsTab({ enabled }: { enabled: boolean }) {
|
|||||||
</div>
|
</div>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
) : (
|
) : (
|
||||||
<div
|
<DataLoadingIndicator
|
||||||
className={
|
compact
|
||||||
'flex h-[calc(var(--design-unit)*62)] items-center justify-center rounded-md bg-[#0A4252]/60 text-design-16 text-[#6CCDCF]'
|
label={vm.loadingText}
|
||||||
}
|
className="h-[calc(var(--design-unit)*62)] rounded-md bg-[#0A4252]/60"
|
||||||
>
|
/>
|
||||||
{vm.loadingText}
|
|
||||||
</div>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { useTranslation } from 'react-i18next'
|
|||||||
import blueBtnBg from '@/assets/system/blue-btn.webp'
|
import blueBtnBg from '@/assets/system/blue-btn.webp'
|
||||||
import { CenterModal } from '@/components/center-modal.tsx'
|
import { CenterModal } from '@/components/center-modal.tsx'
|
||||||
import { SmartBackground } from '@/components/smart-background.tsx'
|
import { SmartBackground } from '@/components/smart-background.tsx'
|
||||||
|
import { DataLoadingIndicator } from '@/components/ui/data-loading-indicator'
|
||||||
import { getNoticeDetail, getNoticeList } from '@/features/game/api'
|
import { getNoticeDetail, getNoticeList } from '@/features/game/api'
|
||||||
import { cn } from '@/lib/utils'
|
import { cn } from '@/lib/utils'
|
||||||
import { useModalStore } from '@/store'
|
import { useModalStore } from '@/store'
|
||||||
@@ -104,9 +105,9 @@ function DesktopNoticeModal() {
|
|||||||
}
|
}
|
||||||
>
|
>
|
||||||
{noticeListQuery.isLoading ? (
|
{noticeListQuery.isLoading ? (
|
||||||
<div className={'py-design-30 text-center text-[#6CCDCF]'}>
|
<DataLoadingIndicator
|
||||||
{t('game.modals.userInfo.message.loading')}
|
label={t('game.modals.userInfo.message.loading')}
|
||||||
</div>
|
/>
|
||||||
) : noticeListQuery.isError ? (
|
) : noticeListQuery.isError ? (
|
||||||
<div className={'py-design-30 text-center text-[#6CCDCF]'}>
|
<div className={'py-design-30 text-center text-[#6CCDCF]'}>
|
||||||
{t('game.modals.userInfo.message.loadFailed')}
|
{t('game.modals.userInfo.message.loadFailed')}
|
||||||
@@ -186,9 +187,9 @@ function DesktopNoticeModal() {
|
|||||||
}
|
}
|
||||||
>
|
>
|
||||||
{noticeDetailQuery.isLoading ? (
|
{noticeDetailQuery.isLoading ? (
|
||||||
<div className={'py-design-30 text-center text-[#6CCDCF]'}>
|
<DataLoadingIndicator
|
||||||
{t('game.modals.userInfo.message.loading')}
|
label={t('game.modals.userInfo.message.loading')}
|
||||||
</div>
|
/>
|
||||||
) : noticeDetailQuery.isError ? (
|
) : noticeDetailQuery.isError ? (
|
||||||
<div className={'py-design-30 text-center text-[#6CCDCF]'}>
|
<div className={'py-design-30 text-center text-[#6CCDCF]'}>
|
||||||
{t('game.modals.userInfo.message.loadFailed')}
|
{t('game.modals.userInfo.message.loadFailed')}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { useVirtualizer } from '@tanstack/react-virtual'
|
|||||||
import { motion } from 'motion/react'
|
import { motion } from 'motion/react'
|
||||||
import { useEffect, useRef } from 'react'
|
import { useEffect, useRef } from 'react'
|
||||||
|
|
||||||
|
import { DataLoadingIndicator } from '@/components/ui/data-loading-indicator'
|
||||||
import { useWalletRecordsVm } from '@/features/game/hooks/use-wallet-records-vm'
|
import { useWalletRecordsVm } from '@/features/game/hooks/use-wallet-records-vm'
|
||||||
|
|
||||||
function DesktopWalletRecordsTab({ enabled }: { enabled: boolean }) {
|
function DesktopWalletRecordsTab({ enabled }: { enabled: boolean }) {
|
||||||
@@ -69,9 +70,7 @@ function DesktopWalletRecordsTab({ enabled }: { enabled: boolean }) {
|
|||||||
}
|
}
|
||||||
>
|
>
|
||||||
{vm.isLoading ? (
|
{vm.isLoading ? (
|
||||||
<div className={'py-design-30 text-center text-[#6CCDCF]'}>
|
<DataLoadingIndicator label={vm.loadingText} />
|
||||||
{vm.loadingText}
|
|
||||||
</div>
|
|
||||||
) : vm.isError ? (
|
) : vm.isError ? (
|
||||||
<div className={'py-design-30 text-center text-[#6CCDCF]'}>
|
<div className={'py-design-30 text-center text-[#6CCDCF]'}>
|
||||||
{vm.loadFailedText}
|
{vm.loadFailedText}
|
||||||
@@ -126,13 +125,11 @@ function DesktopWalletRecordsTab({ enabled }: { enabled: boolean }) {
|
|||||||
</div>
|
</div>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
) : (
|
) : (
|
||||||
<div
|
<DataLoadingIndicator
|
||||||
className={
|
compact
|
||||||
'flex h-[calc(var(--design-unit)*62)] items-center justify-center rounded-md bg-[#0A4252]/60 text-design-16 text-[#6CCDCF]'
|
label={vm.loadingText}
|
||||||
}
|
className="h-[calc(var(--design-unit)*62)] rounded-md bg-[#0A4252]/60"
|
||||||
>
|
/>
|
||||||
{vm.loadingText}
|
|
||||||
</div>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
13
src/main.tsx
13
src/main.tsx
@@ -3,6 +3,7 @@ import { ReactQueryDevtools } from '@tanstack/react-query-devtools'
|
|||||||
import { RouterProvider } from '@tanstack/react-router'
|
import { RouterProvider } from '@tanstack/react-router'
|
||||||
import { StrictMode } from 'react'
|
import { StrictMode } from 'react'
|
||||||
import { createRoot } from 'react-dom/client'
|
import { createRoot } from 'react-dom/client'
|
||||||
|
import { AppBootResourceGate } from '@/components/app-boot-resource-gate'
|
||||||
import { AppNotificationAlert } from '@/components/ui/notification-alert'
|
import { AppNotificationAlert } from '@/components/ui/notification-alert'
|
||||||
import { APP_ROOT_ELEMENT_ID } from '@/constants'
|
import { APP_ROOT_ELEMENT_ID } from '@/constants'
|
||||||
import {
|
import {
|
||||||
@@ -44,10 +45,14 @@ void initializeAuthSession().then(async () => {
|
|||||||
createRoot(rootElement).render(
|
createRoot(rootElement).render(
|
||||||
<StrictMode>
|
<StrictMode>
|
||||||
<QueryClientProvider client={queryClient}>
|
<QueryClientProvider client={queryClient}>
|
||||||
<GlobalAudioController />
|
<AppBootResourceGate>
|
||||||
<RouterProvider router={router} />
|
<GlobalAudioController />
|
||||||
<AppNotificationAlert />
|
<RouterProvider router={router} />
|
||||||
{shouldShowQueryDevtools && <ReactQueryDevtools initialIsOpen={false} />}
|
<AppNotificationAlert />
|
||||||
|
{shouldShowQueryDevtools && (
|
||||||
|
<ReactQueryDevtools initialIsOpen={false} />
|
||||||
|
)}
|
||||||
|
</AppBootResourceGate>
|
||||||
</QueryClientProvider>
|
</QueryClientProvider>
|
||||||
</StrictMode>,
|
</StrictMode>,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -260,6 +260,169 @@
|
|||||||
color: rgba(138, 214, 221, 0.58);
|
color: rgba(138, 214, 221, 0.58);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.boot-grid {
|
||||||
|
background-image:
|
||||||
|
linear-gradient(rgba(112, 255, 247, 0.12) 1px, transparent 1px),
|
||||||
|
linear-gradient(90deg, rgba(112, 255, 247, 0.12) 1px, transparent 1px);
|
||||||
|
background-size:
|
||||||
|
calc(var(--design-unit) * 64) calc(var(--design-unit) * 64),
|
||||||
|
calc(var(--design-unit) * 64) calc(var(--design-unit) * 64);
|
||||||
|
mask-image: radial-gradient(circle at center, black 0%, transparent 72%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.boot-scanline {
|
||||||
|
background: linear-gradient(
|
||||||
|
180deg,
|
||||||
|
transparent 0%,
|
||||||
|
rgba(117, 255, 250, 0.08) 50%,
|
||||||
|
transparent 100%
|
||||||
|
);
|
||||||
|
animation: boot-scanline 2.6s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.boot-particle {
|
||||||
|
animation: boot-particle-float 3.6s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.boot-ring {
|
||||||
|
animation: boot-ring-spin 4.8s cubic-bezier(0.45, 0, 0.2, 1) infinite;
|
||||||
|
box-shadow:
|
||||||
|
0 0 calc(var(--design-unit) * 24) rgba(113, 255, 247, 0.24),
|
||||||
|
inset 0 0 calc(var(--design-unit) * 18) rgba(113, 255, 247, 0.12);
|
||||||
|
}
|
||||||
|
|
||||||
|
.boot-ring::before,
|
||||||
|
.boot-ring::after {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
left: 50%;
|
||||||
|
height: calc(var(--design-unit) * 10);
|
||||||
|
width: calc(var(--design-unit) * 10);
|
||||||
|
border-radius: 9999px;
|
||||||
|
background: #77fff8;
|
||||||
|
box-shadow: 0 0 calc(var(--design-unit) * 18) rgba(119, 255, 248, 0.85);
|
||||||
|
transform: translateX(-50%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.boot-ring::before {
|
||||||
|
top: calc(var(--design-unit) * -5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.boot-ring::after {
|
||||||
|
bottom: calc(var(--design-unit) * -5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.boot-ring-delay {
|
||||||
|
animation-direction: reverse;
|
||||||
|
animation-duration: 6.4s;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes boot-particle-float {
|
||||||
|
0%,
|
||||||
|
100% {
|
||||||
|
opacity: 0.18;
|
||||||
|
transform: translate3d(0, 0, 0) scale(0.72);
|
||||||
|
}
|
||||||
|
|
||||||
|
45% {
|
||||||
|
opacity: 0.95;
|
||||||
|
transform: translate3d(
|
||||||
|
calc(var(--design-unit) * 26),
|
||||||
|
calc(var(--design-unit) * -38),
|
||||||
|
0
|
||||||
|
)
|
||||||
|
scale(1.24);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes boot-ring-spin {
|
||||||
|
0% {
|
||||||
|
transform: rotate(0deg) scale(0.96);
|
||||||
|
}
|
||||||
|
|
||||||
|
50% {
|
||||||
|
transform: rotate(185deg) scale(1.04);
|
||||||
|
}
|
||||||
|
|
||||||
|
100% {
|
||||||
|
transform: rotate(360deg) scale(0.96);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes boot-scanline {
|
||||||
|
0% {
|
||||||
|
transform: translateY(-100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
100% {
|
||||||
|
transform: translateY(100%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-reduced-motion: reduce) {
|
||||||
|
.boot-particle,
|
||||||
|
.boot-ring,
|
||||||
|
.boot-scanline {
|
||||||
|
animation: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.data-loading-core::before,
|
||||||
|
.data-loading-core::after {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
border-radius: 9999px;
|
||||||
|
background: #77fff8;
|
||||||
|
box-shadow: 0 0 calc(var(--design-unit) * 12) rgba(119, 255, 248, 0.8);
|
||||||
|
}
|
||||||
|
|
||||||
|
.data-loading-core::before {
|
||||||
|
top: 6%;
|
||||||
|
height: calc(var(--design-unit) * 5);
|
||||||
|
width: calc(var(--design-unit) * 5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.data-loading-core::after {
|
||||||
|
bottom: 10%;
|
||||||
|
height: calc(var(--design-unit) * 4);
|
||||||
|
width: calc(var(--design-unit) * 4);
|
||||||
|
background: #fee889;
|
||||||
|
box-shadow: 0 0 calc(var(--design-unit) * 12) rgba(254, 232, 137, 0.72);
|
||||||
|
}
|
||||||
|
|
||||||
|
.data-loading-orbit {
|
||||||
|
animation: data-loading-orbit 1.25s cubic-bezier(0.45, 0, 0.2, 1) infinite;
|
||||||
|
border-top-color: rgba(113, 255, 247, 0.96);
|
||||||
|
border-right-color: rgba(113, 255, 247, 0.08);
|
||||||
|
}
|
||||||
|
|
||||||
|
.data-loading-orbit-delay {
|
||||||
|
animation-duration: 1.7s;
|
||||||
|
animation-direction: reverse;
|
||||||
|
border-top-color: rgba(255, 232, 137, 0.86);
|
||||||
|
border-left-color: rgba(255, 232, 137, 0.08);
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes data-loading-orbit {
|
||||||
|
0% {
|
||||||
|
transform: rotate(0deg) scale(0.98);
|
||||||
|
}
|
||||||
|
|
||||||
|
50% {
|
||||||
|
transform: rotate(185deg) scale(1.04);
|
||||||
|
}
|
||||||
|
|
||||||
|
100% {
|
||||||
|
transform: rotate(360deg) scale(0.98);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-reduced-motion: reduce) {
|
||||||
|
.data-loading-orbit {
|
||||||
|
animation: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.game-setting-input:focus,
|
.game-setting-input:focus,
|
||||||
.game-setting-input:focus-visible {
|
.game-setting-input:focus-visible {
|
||||||
border: none;
|
border: none;
|
||||||
|
|||||||
Reference in New Issue
Block a user