1449 lines
41 KiB
Vue
1449 lines
41 KiB
Vue
<template>
|
|
<div class="fullPage">
|
|
<!-- 预加载状态 -->
|
|
<div v-if="isLoading" class="loading-container">
|
|
<!-- 页面背景 -->
|
|
<BackgroundLayer :useCanvas="true" :backgroundImages="[imageUrl('bg')]" />
|
|
<!-- 页面背景 -->
|
|
|
|
<!-- 加载动画 -->
|
|
<LoadingSpinner />
|
|
<p>{{ t('loading') }}...</p>
|
|
</div>
|
|
|
|
<!-- 主要内容 -->
|
|
<div class="active-game-shell scroll">
|
|
<div class="active-game-stage">
|
|
<!-- 头部背景图 -->
|
|
<VapBg
|
|
v-if="isAndroidDevice"
|
|
class="vap-background-layer vap-background-vap-layer"
|
|
:src="bgVapUrl"
|
|
/>
|
|
<img v-else v-smart-img.noFade :src="imageUrl('bg2')" alt="" class="vap-background-layer" />
|
|
|
|
<!-- 背景层 -->
|
|
<div class="page-background-layer">
|
|
<BackgroundLayer
|
|
:useCanvas="true"
|
|
:backgroundImages="[imageUrl('bg')]"
|
|
:layerStyle="{ position: 'absolute', top: 0, left: 0, minHeight: 0, zIndex: 0 }"
|
|
/>
|
|
</div>
|
|
<!-- 页面背景 -->
|
|
|
|
<!-- 页面内容 -->
|
|
<div class="page-content">
|
|
<!-- 累计奖励 -->
|
|
<itemCenter
|
|
style="padding: 0 3vw"
|
|
:imgUrl="imageUrl('totalRewardBg')"
|
|
:contentStyle="`padding:0 7vw;gap:1vw`"
|
|
>
|
|
<img
|
|
v-smart-img
|
|
src="/src/assets/icon/Azizi/coin.png"
|
|
alt=""
|
|
style="display: block; width: 10vw; aspect-ratio: 1/1"
|
|
/>
|
|
|
|
<div
|
|
style="
|
|
flex: 1;
|
|
border-radius: 88px;
|
|
background: linear-gradient(180deg, #750a00 0%, #c71f10 48.08%, #750a00 100%);
|
|
padding: 1vw 6px;
|
|
|
|
display: flex;
|
|
justify-content: center;
|
|
align-items: center;
|
|
"
|
|
>
|
|
<div
|
|
style="
|
|
background: linear-gradient(180deg, #ffeeb0 0%, #ffcc46 100%);
|
|
background-clip: text;
|
|
-webkit-background-clip: text;
|
|
-webkit-text-fill-color: transparent;
|
|
font-size: 1.2em;
|
|
font-weight: 500;
|
|
"
|
|
>
|
|
{{ taskInfo.task1PendingCoins }}
|
|
</div>
|
|
</div>
|
|
|
|
<img
|
|
v-smart-img
|
|
:src="imageUrl(getImgName('receiveBt'))"
|
|
alt=""
|
|
style="display: block; width: 25vw"
|
|
:style="{ filter: canClaimTask1Reward ? 'none' : 'grayscale(1)' }"
|
|
@click="claimTask1Reward"
|
|
/>
|
|
</itemCenter>
|
|
|
|
<!-- 等级任务 -->
|
|
<itemCenter
|
|
style=""
|
|
:imgUrl="imageUrl('lvTaskBg')"
|
|
:contentStyle="`
|
|
flex-direction: column;
|
|
justify-content: flex-start;
|
|
padding: 10vw 8vw 5vw;
|
|
gap: 8vw;
|
|
`"
|
|
>
|
|
<!-- 标题 -->
|
|
<div style="display: flex; justify-content: center; align-items: center; height: 11vw">
|
|
<img
|
|
v-smart-img
|
|
:src="imageUrl(`titleLv${currentLevel}`)"
|
|
alt=""
|
|
style="width: 18vw"
|
|
/>
|
|
</div>
|
|
|
|
<!-- 内容 -->
|
|
<div style="width: 100%; display: flex; flex-direction: column; position: relative">
|
|
<!-- 规则弹窗展示按钮 -->
|
|
<img
|
|
v-smart-img
|
|
src="/src/assets/icon/Azizi/helpWhite.png"
|
|
alt=""
|
|
class="ruleBt"
|
|
@click="openPopup('rule')"
|
|
/>
|
|
|
|
<!-- 信息展示 -->
|
|
<div style="height: 45vw; display: flex; flex-direction: column; gap: 5vw">
|
|
<!-- 当前状态 -->
|
|
<div style="display: flex; align-items: center">
|
|
<div
|
|
style="
|
|
flex: 1;
|
|
min-width: 0;
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 2vw;
|
|
position: relative;
|
|
"
|
|
>
|
|
<div style="font-size: 1.1em">
|
|
{{
|
|
t('active_game_current_daily_recharge_amount', {
|
|
amount: displayedRechargeAmountText,
|
|
})
|
|
}}
|
|
</div>
|
|
<div style="font-size: 1.1em">
|
|
{{
|
|
t('active_game_current_daily_game_spending', {
|
|
coins: displayedSpendCoinsText,
|
|
})
|
|
}}
|
|
</div>
|
|
</div>
|
|
<!-- 展示隐藏按钮 -->
|
|
<img
|
|
v-smart-img
|
|
:src="imageUrl(amountVisible ? 'amountShowBt' : 'amountHiddenBt')"
|
|
alt=""
|
|
style="width: 5vw; min-width: 0; display: block"
|
|
@click="toggleAmountVisible"
|
|
/>
|
|
</div>
|
|
|
|
<!-- 任务详情 -->
|
|
<div style="display: flex; flex-direction: column; gap: 2vw">
|
|
<!-- 任务一 -->
|
|
<div
|
|
style="font-size: 1.1em"
|
|
:style="{ color: task1Completed ? '#1AFF00' : '#fff' }"
|
|
>
|
|
{{
|
|
t('active_game_task1_detail', {
|
|
progress: task1ProgressText,
|
|
reward: currentTask1RewardCoinsText,
|
|
})
|
|
}}
|
|
<img
|
|
v-if="task1Completed"
|
|
v-smart-img
|
|
:src="imageUrl('done')"
|
|
alt=""
|
|
style="display: inline-block; width: 1em; object-fit: contain"
|
|
/>
|
|
</div>
|
|
<!-- 任务二 -->
|
|
<div
|
|
style="font-size: 1.1em"
|
|
:style="{ color: task2Completed ? '#1AFF00' : '#fff' }"
|
|
>
|
|
{{
|
|
t('active_game_task2_detail', {
|
|
progress: task2ProgressText,
|
|
})
|
|
}}
|
|
<img
|
|
v-if="task2Completed"
|
|
v-smart-img
|
|
:src="imageUrl('done')"
|
|
alt=""
|
|
style="display: inline-block; width: 1em; object-fit: contain"
|
|
/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 宝箱展示 -->
|
|
<div style="display: flex; flex-direction: column; align-items: center">
|
|
<img
|
|
v-smart-img
|
|
:src="imageUrl(currentChestImageName)"
|
|
alt=""
|
|
style="width: 38vw; display: block; object-fit: contain"
|
|
/>
|
|
|
|
<!-- 宝箱等级 -->
|
|
<div style="font-size: 1.5em; font-weight: 700">
|
|
{{ t('active_game_level_label', { level: currentLevel }) }}
|
|
</div>
|
|
|
|
<!-- 领取按钮 -->
|
|
<img
|
|
v-smart-img
|
|
:src="imageUrl(getImgName(taskRewardButtonImageName))"
|
|
alt=""
|
|
style="width: 30vw; display: block; object-fit: contain"
|
|
:style="{ filter: taskRewardButtonDisabled ? 'grayscale(1)' : 'none' }"
|
|
@click="claimTask2Reward"
|
|
/>
|
|
</div>
|
|
</div>
|
|
</itemCenter>
|
|
|
|
<!-- 等级奖励 -->
|
|
<itemCenter
|
|
style=""
|
|
:imgUrl="imageUrl('LvRewardInfoBg')"
|
|
:contentStyle="` flex-direction: column;
|
|
justify-content: flex-start;
|
|
padding: 8vw 7.5vw 14vw;
|
|
gap: 4vw;`"
|
|
>
|
|
<!-- 标题 -->
|
|
<div style="display: flex; justify-content: center; align-items: center; height: 11vw">
|
|
<img
|
|
v-smart-img
|
|
:src="imageUrl(getImgName('LvRewardTitle'))"
|
|
alt=""
|
|
style="width: 40vw"
|
|
/>
|
|
</div>
|
|
|
|
<!-- 内容 -->
|
|
<div
|
|
style="
|
|
flex: 1;
|
|
min-height: 0;
|
|
|
|
width: 100%;
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 2.5vw;
|
|
position: relative;
|
|
"
|
|
>
|
|
<!-- 开箱进度条 -->
|
|
<div
|
|
:class="[
|
|
'scroll',
|
|
'chest-progress-scroll',
|
|
{ 'chest-progress-scroll-rtl': isRtlLayout },
|
|
]"
|
|
>
|
|
<div :class="['chest-progress-row', { 'chest-progress-row-rtl': isRtlLayout }]">
|
|
<!-- 进度条 -->
|
|
<div class="chest-progress-track" :style="progressTrackStyle">
|
|
<!-- 进度条图片 -->
|
|
<div
|
|
:class="['chest-progress-fill', { 'chest-progress-fill-rtl': isRtlLayout }]"
|
|
:style="progressFillStyle"
|
|
>
|
|
<img
|
|
v-smart-img
|
|
:src="imageUrl('progressBar')"
|
|
alt=""
|
|
:class="['chest-progress-bar', { 'chest-progress-bar-rtl': isRtlLayout }]"
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 宝箱列表 -->
|
|
<div :class="['chest-progress-list', { 'chest-progress-list-rtl': isRtlLayout }]">
|
|
<!-- 开头占位 -->
|
|
<div></div>
|
|
<div
|
|
v-for="chest in chestProgressList"
|
|
:key="chest.level"
|
|
style="width: 14vw; cursor: pointer"
|
|
@click="selectRewardChest(chest.level)"
|
|
>
|
|
<!-- 未达成条件 -->
|
|
<img
|
|
v-if="!isChestConditionReached(chest.level)"
|
|
v-smart-img
|
|
:src="imageUrl(getChestImageName(chest))"
|
|
alt=""
|
|
style="display: block; width: 100%; object-fit: contain"
|
|
/>
|
|
|
|
<!-- 达成条件 -->
|
|
<itemCenter
|
|
v-else
|
|
style="width: 100%"
|
|
:imgUrl="imageUrl('LvActive')"
|
|
:contentStyle="``"
|
|
>
|
|
<img
|
|
v-smart-img
|
|
:src="imageUrl(getChestImageName(chest))"
|
|
alt=""
|
|
style="display: block; width: 100%; object-fit: contain"
|
|
/>
|
|
</itemCenter>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 奖励展示 -->
|
|
<div class="reward-showcase">
|
|
<div v-if="mainReward" class="reward-showcase-main">
|
|
<div
|
|
style="
|
|
width: 100%;
|
|
height: 71vw;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
"
|
|
>
|
|
<div
|
|
class="reward-showcase-vap-wrap"
|
|
:key="mainRewardRenderKey"
|
|
:class="{
|
|
'reward-showcase-vap-wrap-playable': isIosDevice && mainRewardVapSourceUrl,
|
|
}"
|
|
@click="handleMainRewardPreviewClick"
|
|
>
|
|
<VapMp4Player
|
|
v-if="shouldPlayMainRewardVap"
|
|
:key="mainRewardVapRenderKey"
|
|
:model-value="true"
|
|
:src="mainRewardVapSourceUrl"
|
|
mode="inline"
|
|
fit="contain"
|
|
:loop="true"
|
|
:mute="true"
|
|
:auto-close="false"
|
|
:show-status="false"
|
|
@error="(error) => handleRewardVapError(error, mainRewardVapSourceUrl)"
|
|
/>
|
|
<img
|
|
v-else
|
|
:src="mainReward.cover || ''"
|
|
alt=""
|
|
style="display: block; width: 90%; object-fit: contain"
|
|
@error="(e) => handleRewardImageError(e, mainReward.type)"
|
|
/>
|
|
<button
|
|
v-if="isIosDevice && mainRewardVapSourceUrl"
|
|
type="button"
|
|
class="reward-showcase-play-button"
|
|
@click.stop="handleMainRewardPreviewClick"
|
|
>
|
|
<span class="reward-showcase-play-icon"></span>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<div
|
|
style="
|
|
flex: 1;
|
|
min-height: 0;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
"
|
|
>
|
|
<div>{{ getRewardDisplayText(mainReward) }}</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="reward-showcase-grid">
|
|
<div
|
|
v-for="(reward, index) in rewardGridList"
|
|
:key="getRewardRenderKey(reward, index)"
|
|
class="reward-showcase-item"
|
|
>
|
|
<div
|
|
style="
|
|
width: 100%;
|
|
aspect-ratio: 1/1;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
"
|
|
>
|
|
<img
|
|
v-if="isLocalRewardAsset(reward)"
|
|
v-smart-img.noFade
|
|
:src="reward.cover || ''"
|
|
alt=""
|
|
style="display: block; width: 90%; object-fit: contain"
|
|
@error="(e) => handleRewardImageError(e, reward.type)"
|
|
/>
|
|
<img
|
|
v-else
|
|
:src="reward.cover || ''"
|
|
alt=""
|
|
style="display: block; width: 90%; object-fit: contain"
|
|
@error="(e) => handleRewardImageError(e, reward.type)"
|
|
/>
|
|
</div>
|
|
<div
|
|
style="
|
|
flex: 1;
|
|
min-height: 0;
|
|
width: 100%;
|
|
min-width: 0;
|
|
overflow: hidden;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
"
|
|
>
|
|
<div
|
|
v-if="isLocalRewardAsset(reward)"
|
|
class="reward-extra-scroll-viewport"
|
|
:class="{ 'reward-extra-scroll-viewport-rtl': isRtlLayout }"
|
|
>
|
|
<div
|
|
class="reward-extra-scroll-wrapper"
|
|
:class="{ 'reward-extra-scroll-wrapper-rtl': isRtlLayout }"
|
|
>
|
|
<div v-for="value in 2" :key="value" class="reward-extra-scroll-content">
|
|
{{ getRewardDisplayText(reward) }}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div v-else>{{ getRewardDisplayText(reward) }}</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</itemCenter>
|
|
</div>
|
|
|
|
<!-- 弹窗遮罩层 -->
|
|
<maskLayer :maskLayerShow="maskLayerShow" @click="closedPopup">
|
|
<!-- 居中弹窗 -->
|
|
<div
|
|
style="
|
|
width: 100vw;
|
|
height: 100vh;
|
|
display: flex;
|
|
justify-content: center;
|
|
align-items: center;
|
|
"
|
|
@click="closedPopup"
|
|
>
|
|
<!-- 规则弹窗 -->
|
|
<itemCenter
|
|
style=""
|
|
:imgUrl="imageUrl('ruleInfoBg')"
|
|
:contentStyle="` flex-direction: column;
|
|
justify-content: flex-start;
|
|
padding: 8vw 7.5vw 5vw;
|
|
gap: 4vw;`"
|
|
>
|
|
<!-- 标题 -->
|
|
<div
|
|
style="display: flex; justify-content: center; align-items: center; height: 11vw"
|
|
>
|
|
<img
|
|
v-smart-img
|
|
:src="imageUrl(getImgName('ruleTitle'))"
|
|
alt=""
|
|
style="width: 18vw"
|
|
/>
|
|
</div>
|
|
|
|
<div
|
|
style="
|
|
flex: 1;
|
|
min-height: 0;
|
|
width: 100%;
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 4px;
|
|
|
|
font-size: 1.1em;
|
|
"
|
|
>
|
|
<div style="font-size: 1em; color: #fff; font-weight: 700">
|
|
{{ t('active_game_rule_1') }}
|
|
</div>
|
|
<div style="font-size: 1em; color: #fff; font-weight: 700">
|
|
{{ t('active_game_rule_2') }}
|
|
</div>
|
|
<div style="font-size: 1em; color: #fff; font-weight: 700">
|
|
{{ t('active_game_rule_3') }}
|
|
</div>
|
|
<div style="font-size: 1em; color: #fff; font-weight: 700">
|
|
{{ t('active_game_rule_4') }}
|
|
</div>
|
|
</div>
|
|
</itemCenter>
|
|
</div>
|
|
</maskLayer>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup>
|
|
import { computed, defineAsyncComponent, onBeforeUnmount, onMounted, ref } from 'vue'
|
|
import { useI18n } from 'vue-i18n'
|
|
import { setDocumentDirection } from '@/locales/i18n'
|
|
import { connectToApp } from '@/utils/appConnector.js'
|
|
import {
|
|
isAndroidDevice as detectAndroidDevice,
|
|
isIosDevice as detectIosDevice,
|
|
notifyAppPlayVap,
|
|
} from '@/utils/appBridge.js'
|
|
import { getMp4Url, getPngUrl } from '@/config/imagePaths.js'
|
|
import { preloadImages } from '@/utils/image/imagePreloader.js'
|
|
import { useLangStore } from '@/stores/lang'
|
|
import { showError } from '@/utils/toast.js'
|
|
import {
|
|
apiGetActivityResource,
|
|
claimDailyRechargeTask1,
|
|
claimDailyRechargeTask2,
|
|
getDailyRechargeInfo,
|
|
} from '@/api/activity.js'
|
|
import { handleRewardImageError } from '@/utils/image/imageHandler.js'
|
|
import { formatRewardDisplay } from '@/utils/rewardFormatter.js'
|
|
import { useThrottle } from '@/utils/useDebounce.js'
|
|
import { useDailyRechargeRewardVap } from './composables/useDailyRechargeRewardVap.js'
|
|
|
|
import BackgroundLayer from '@/components/BackgroundLayer.vue'
|
|
import itemCenter from '@/components/itemCenter.vue'
|
|
import maskLayer from '@/components/MaskLayer.vue'
|
|
|
|
const VapMp4Player = defineAsyncComponent(() => import('@/components/VapMp4Player.vue'))
|
|
const VapBg = defineAsyncComponent(() => import('./components/vapBg.vue'))
|
|
|
|
const isLoading = ref(true)
|
|
|
|
const { t, locale } = useI18n()
|
|
const langStore = useLangStore()
|
|
|
|
const currentLangType = computed(() => {
|
|
return langStore.selectedLang?.type || 'en'
|
|
})
|
|
const isRtlLayout = computed(() => {
|
|
return currentLangType.value === 'ar'
|
|
})
|
|
const DEVICE_SIMULATION_STORAGE_KEY = 'daily-recharge-device-simulation'
|
|
const DEVICE_SIMULATION_MODES = ['auto', 'android', 'ios']
|
|
const normalizeDeviceSimulationMode = (mode) => {
|
|
return DEVICE_SIMULATION_MODES.includes(mode) ? mode : 'auto'
|
|
}
|
|
const getDeviceSimulationQueryMode = () => {
|
|
if (typeof window === 'undefined') {
|
|
return ''
|
|
}
|
|
|
|
const searchMode = new URLSearchParams(window.location.search).get('dailyRechargeDevice')
|
|
if (searchMode) {
|
|
return searchMode
|
|
}
|
|
|
|
const hashQueryIndex = window.location.hash.indexOf('?')
|
|
if (hashQueryIndex === -1) {
|
|
return ''
|
|
}
|
|
|
|
return new URLSearchParams(window.location.hash.slice(hashQueryIndex + 1)).get(
|
|
'dailyRechargeDevice',
|
|
)
|
|
}
|
|
const getInitialDeviceSimulationMode = () => {
|
|
if (!import.meta.env.DEV || typeof window === 'undefined') {
|
|
return 'auto'
|
|
}
|
|
|
|
const queryMode = getDeviceSimulationQueryMode()
|
|
const storageMode = window.localStorage.getItem(DEVICE_SIMULATION_STORAGE_KEY)
|
|
return normalizeDeviceSimulationMode(queryMode || storageMode || 'auto')
|
|
}
|
|
const simulatedDeviceMode = ref(getInitialDeviceSimulationMode())
|
|
const isAndroidDevice = computed(() => {
|
|
if (import.meta.env.DEV && simulatedDeviceMode.value === 'android') {
|
|
return true
|
|
}
|
|
if (import.meta.env.DEV && simulatedDeviceMode.value === 'ios') {
|
|
return false
|
|
}
|
|
return detectAndroidDevice()
|
|
})
|
|
const isIosDevice = computed(() => {
|
|
if (import.meta.env.DEV && simulatedDeviceMode.value === 'ios') {
|
|
return true
|
|
}
|
|
if (import.meta.env.DEV && simulatedDeviceMode.value === 'android') {
|
|
return false
|
|
}
|
|
return detectIosDevice()
|
|
})
|
|
const setSimulatedDeviceMode = (mode = 'auto') => {
|
|
if (!import.meta.env.DEV) {
|
|
return
|
|
}
|
|
|
|
simulatedDeviceMode.value = normalizeDeviceSimulationMode(mode)
|
|
if (typeof window !== 'undefined') {
|
|
window.localStorage.setItem(DEVICE_SIMULATION_STORAGE_KEY, simulatedDeviceMode.value)
|
|
}
|
|
}
|
|
|
|
locale.value && setDocumentDirection(locale.value)
|
|
|
|
const imageUrl = (filename) => getPngUrl('Activities/DailyRecharge/', filename)
|
|
const mp4Url = (filename) => getMp4Url('Activities/DailyRecharge/', filename)
|
|
const bgVapUrl = mp4Url('bg')
|
|
const CHEST_PROGRESS_GAP_VW = 2
|
|
const CHEST_PROGRESS_ITEM_WIDTH_VW = 14
|
|
const CHEST_PROGRESS_FIRST_CENTER_VW = CHEST_PROGRESS_GAP_VW + CHEST_PROGRESS_ITEM_WIDTH_VW / 2
|
|
const CHEST_PROGRESS_CENTER_STEP_VW = CHEST_PROGRESS_GAP_VW + CHEST_PROGRESS_ITEM_WIDTH_VW
|
|
const CHEST_PROGRESS_TRACK_WIDTH_VW =
|
|
10 * CHEST_PROGRESS_GAP_VW + 10 * CHEST_PROGRESS_ITEM_WIDTH_VW - CHEST_PROGRESS_ITEM_WIDTH_VW / 2
|
|
|
|
// 根据语言获取图片名
|
|
const getImgName = (filename) => {
|
|
return currentLangType.value == 'en' ? filename : `${filename}_${currentLangType.value}`
|
|
}
|
|
|
|
const ruleShow = ref(false)
|
|
const amountVisible = ref(false)
|
|
const claimingTask1Reward = ref(false)
|
|
const claimingTask2Reward = ref(false)
|
|
const selectedRewardLevel = ref(1)
|
|
const activityRewards = ref([])
|
|
const createDefaultTaskInfo = () => ({
|
|
currentLevel: 1,
|
|
task1SpendCoins: 0,
|
|
task1TodayRewardCoins: 0,
|
|
task1PendingCoins: 0,
|
|
task2RechargeAmount: 0,
|
|
task2TargetAmount: 0,
|
|
task2Completed: false,
|
|
task2Claimed: false,
|
|
task2RewardCoins: 0,
|
|
levelList: [],
|
|
})
|
|
const taskInfo = ref(createDefaultTaskInfo())
|
|
const maskLayerShow = computed(() => {
|
|
return ruleShow.value
|
|
})
|
|
const normalizeLevel = (levelValue) => {
|
|
const level = Number(levelValue) || 1
|
|
return Math.min(Math.max(level, 1), 10)
|
|
}
|
|
const levelMap = computed(() => {
|
|
return new Map(taskInfo.value.levelList.map((item) => [Number(item.level), item]))
|
|
})
|
|
const currentLevel = computed(() => {
|
|
return normalizeLevel(taskInfo.value.currentLevel || 1)
|
|
})
|
|
const currentLevelInfo = computed(() => {
|
|
return levelMap.value.get(currentLevel.value) || {}
|
|
})
|
|
const currentTask2Claimed = computed(() => {
|
|
return Boolean(currentLevelInfo.value.task2Claimed)
|
|
})
|
|
const currentChestImageName = computed(() => {
|
|
return `Lv${currentLevel.value}${currentLevelInfo.value.task2Claimed ? '_open' : ''}`
|
|
})
|
|
const canClaimTask1Reward = computed(() => {
|
|
return Number(taskInfo.value.task1PendingCoins) > 0 && !claimingTask1Reward.value
|
|
})
|
|
const currentTask2RewardTop = computed(() => {
|
|
return activityRewards.value[currentLevel.value - 1] || {}
|
|
})
|
|
const canClaimTask2Reward = computed(() => {
|
|
return (
|
|
!currentTask2Claimed.value &&
|
|
task2Completed.value &&
|
|
Boolean(currentTask2RewardTop.value?.rule?.id) &&
|
|
!claimingTask2Reward.value
|
|
)
|
|
})
|
|
const taskRewardButtonDisabled = computed(() => {
|
|
return !currentTask2Claimed.value && !canClaimTask2Reward.value
|
|
})
|
|
const taskRewardButtonImageName = computed(() => {
|
|
return currentTask2Claimed.value ? 'receivedBt' : 'receiveBt'
|
|
})
|
|
const chestProgressList = computed(() => {
|
|
return Array.from({ length: 10 }, (_, index) => {
|
|
const level = index + 1
|
|
const levelInfo = levelMap.value.get(level) || {}
|
|
return {
|
|
level,
|
|
task2Claimed: Boolean(levelInfo.task2Claimed),
|
|
}
|
|
})
|
|
})
|
|
const progressBarWidth = computed(() => {
|
|
const targetLevel = Math.min(Math.max(Number(currentLevel.value) || 0, 0), 10)
|
|
const progressWidth =
|
|
targetLevel === 0
|
|
? 0
|
|
: CHEST_PROGRESS_FIRST_CENTER_VW + (targetLevel - 1) * CHEST_PROGRESS_CENTER_STEP_VW
|
|
|
|
return `${progressWidth}vw`
|
|
})
|
|
const progressTrackStyle = computed(() => {
|
|
return {
|
|
width: `${CHEST_PROGRESS_TRACK_WIDTH_VW}vw`,
|
|
left: isRtlLayout.value ? 'auto' : '0',
|
|
right: isRtlLayout.value ? '0' : 'auto',
|
|
}
|
|
})
|
|
const progressFillStyle = computed(() => {
|
|
return {
|
|
width: progressBarWidth.value,
|
|
marginLeft: isRtlLayout.value ? 'auto' : '0',
|
|
marginRight: isRtlLayout.value ? '0' : 'auto',
|
|
}
|
|
})
|
|
const task1Completed = computed(() => {
|
|
return Number(taskInfo.value.task1SpendCoins) >= Number(currentTask1TargetCoins.value)
|
|
})
|
|
const currentTask1TargetCoins = computed(() => {
|
|
return currentLevelInfo.value.spendCoinsTarget || 0
|
|
})
|
|
const currentTask2TargetAmount = computed(() => {
|
|
return currentLevelInfo.value.rechargeTarget || 0
|
|
})
|
|
const task2Completed = computed(() => {
|
|
const rechargeAmount = Number(taskInfo.value.task2RechargeAmount) || 0
|
|
const targetAmount = Number(currentTask2TargetAmount.value) || 0
|
|
return targetAmount > 0 && rechargeAmount >= targetAmount
|
|
})
|
|
const currentTask1RewardCoins = computed(() => {
|
|
return currentLevelInfo.value.rewardCoins || taskInfo.value.task1TodayRewardCoins || 0
|
|
})
|
|
const displayedSpendCoins = computed(() => {
|
|
return amountVisible.value ? taskInfo.value.task1SpendCoins : '***'
|
|
})
|
|
const displayedRechargeAmount = computed(() => {
|
|
return amountVisible.value ? taskInfo.value.task2RechargeAmount : '***'
|
|
})
|
|
const isolateLtrText = (value) => {
|
|
return `\u2066${value}\u2069`
|
|
}
|
|
const displayedSpendCoinsText = computed(() => {
|
|
return isolateLtrText(displayedSpendCoins.value)
|
|
})
|
|
const displayedRechargeAmountText = computed(() => {
|
|
return isolateLtrText(`$${displayedRechargeAmount.value}`)
|
|
})
|
|
const currentTask1RewardCoinsText = computed(() => {
|
|
return isolateLtrText(currentTask1RewardCoins.value)
|
|
})
|
|
const task1ProgressText = computed(() => {
|
|
return isolateLtrText(`${displayedSpendCoins.value}/${currentTask1TargetCoins.value}`)
|
|
})
|
|
const task2ProgressText = computed(() => {
|
|
return isolateLtrText(`$${displayedRechargeAmount.value}/$${currentTask2TargetAmount.value}`)
|
|
})
|
|
const selectedRewardTop = computed(() => {
|
|
return activityRewards.value[selectedRewardLevel.value - 1] || {}
|
|
})
|
|
const selectedRewardList = computed(() => {
|
|
return selectedRewardTop.value?.propsGroup?.activityRewardProps || []
|
|
})
|
|
const mainReward = computed(() => {
|
|
return selectedRewardList.value[0] || null
|
|
})
|
|
const {
|
|
failedRewardVapUrls,
|
|
getRewardVapRetryVersion,
|
|
getRewardVapSourceUrl,
|
|
handleRewardVapError,
|
|
preloadCriticalVap,
|
|
queueRewardVapPreload,
|
|
resetRewardVapState,
|
|
cleanupRewardVap,
|
|
} = useDailyRechargeRewardVap({
|
|
isAndroidDevice,
|
|
getCurrentMainRewardVapSourceUrl: () => mainRewardVapSourceUrl.value,
|
|
})
|
|
const mainRewardVapSourceUrl = computed(() => {
|
|
return getRewardVapSourceUrl(mainReward.value)
|
|
})
|
|
const shouldPlayMainRewardVap = computed(() => {
|
|
return (
|
|
isAndroidDevice.value &&
|
|
Boolean(mainRewardVapSourceUrl.value) &&
|
|
!failedRewardVapUrls.value.has(mainRewardVapSourceUrl.value)
|
|
)
|
|
})
|
|
const rewardGridList = computed(() => {
|
|
return selectedRewardList.value.slice(1, 7)
|
|
})
|
|
const mainRewardRenderKey = computed(() => {
|
|
return mainReward.value ? getRewardRenderKey(mainReward.value, 0) : 'empty-main-reward'
|
|
})
|
|
const mainRewardVapRetryVersion = computed(() => {
|
|
return getRewardVapRetryVersion(mainRewardVapSourceUrl.value)
|
|
})
|
|
const mainRewardVapRenderKey = computed(() => {
|
|
return `${mainRewardRenderKey.value}-vap-${mainRewardVapRetryVersion.value}`
|
|
})
|
|
let pageInitializationStarted = false
|
|
|
|
const isLocalRewardAsset = (reward) => {
|
|
return Boolean(reward?.localRewardAsset)
|
|
}
|
|
|
|
const getRewardDisplayText = (reward) => {
|
|
return formatRewardDisplay(reward.type, reward)
|
|
}
|
|
|
|
const getRewardRenderKey = (reward, index) => {
|
|
return [
|
|
selectedRewardLevel.value,
|
|
index,
|
|
reward?.type || '',
|
|
reward?.detailType || '',
|
|
reward?.cover || '',
|
|
getRewardVapSourceUrl(reward) || '',
|
|
].join('-')
|
|
}
|
|
|
|
const getExtraRewardsByLevel = (level) => {
|
|
switch (Number(level)) {
|
|
case 8:
|
|
return [
|
|
{
|
|
type: 'GIFT',
|
|
cover: imageUrl('reward_gift'),
|
|
content: t('active_game_reward_gift'),
|
|
localRewardAsset: true,
|
|
},
|
|
]
|
|
case 9:
|
|
return [
|
|
{
|
|
type: 'GIFT',
|
|
detailType: 'AVATAR_FRAME',
|
|
cover: imageUrl('reward_frame'),
|
|
content: t('active_game_reward_frame'),
|
|
localRewardAsset: true,
|
|
},
|
|
]
|
|
case 10:
|
|
return [
|
|
{
|
|
type: 'GIFT',
|
|
detailType: 'RIDE',
|
|
cover: imageUrl('reward_ride'),
|
|
content: t('active_game_reward_ride'),
|
|
localRewardAsset: true,
|
|
},
|
|
{
|
|
type: 'GIFT',
|
|
cover: imageUrl('reward_banner'),
|
|
content: t('active_game_reward_banner'),
|
|
localRewardAsset: true,
|
|
},
|
|
]
|
|
default:
|
|
return []
|
|
}
|
|
}
|
|
|
|
const appendExtraRewards = (rewardTopList) => {
|
|
return rewardTopList.map((rewardTop, index) => {
|
|
const level = Number(rewardTop?.rule?.level || index + 1)
|
|
const extraRewards = getExtraRewardsByLevel(level)
|
|
if (!extraRewards.length) {
|
|
return rewardTop
|
|
}
|
|
|
|
const propsGroup = rewardTop.propsGroup || {}
|
|
const activityRewardProps = Array.isArray(propsGroup.activityRewardProps)
|
|
? propsGroup.activityRewardProps
|
|
: []
|
|
|
|
return {
|
|
...rewardTop,
|
|
propsGroup: {
|
|
...propsGroup,
|
|
activityRewardProps: [...activityRewardProps, ...extraRewards],
|
|
},
|
|
}
|
|
})
|
|
}
|
|
|
|
const toggleAmountVisible = () => {
|
|
amountVisible.value = !amountVisible.value
|
|
}
|
|
|
|
const selectRewardChest = (level) => {
|
|
selectedRewardLevel.value = normalizeLevel(level)
|
|
}
|
|
|
|
const isChestConditionReached = (level) => {
|
|
const chestLevel = Number(level)
|
|
return chestLevel > 0 && chestLevel <= currentLevel.value
|
|
}
|
|
|
|
const getChestImageName = (chest) => {
|
|
return `Lv${chest.level}${chest.task2Claimed ? '_open' : ''}`
|
|
}
|
|
|
|
const closedPopup = () => {
|
|
ruleShow.value = false
|
|
}
|
|
|
|
const openPopup = (type) => {
|
|
closedPopup()
|
|
switch (type) {
|
|
case 'rule':
|
|
ruleShow.value = true
|
|
break
|
|
default:
|
|
break
|
|
}
|
|
}
|
|
|
|
const handleMainRewardPreviewClick = () => {
|
|
if (!mainRewardVapSourceUrl.value) {
|
|
return
|
|
}
|
|
|
|
if (isIosDevice.value) {
|
|
notifyAppPlayVap(mainRewardVapSourceUrl.value)
|
|
}
|
|
}
|
|
|
|
const claimTask1Reward = useThrottle(async () => {
|
|
if (!canClaimTask1Reward.value) {
|
|
return
|
|
}
|
|
|
|
claimingTask1Reward.value = true
|
|
try {
|
|
const res = await claimDailyRechargeTask1({})
|
|
if (res.status) {
|
|
await getDailyRechargeTaskInfo()
|
|
}
|
|
} catch (error) {
|
|
showError(error?.errorMsg || error?.message || t('active_game_claim_reward_failed'))
|
|
} finally {
|
|
claimingTask1Reward.value = false
|
|
}
|
|
}, 1000)
|
|
|
|
const claimTask2Reward = useThrottle(async () => {
|
|
if (!canClaimTask2Reward.value) {
|
|
return
|
|
}
|
|
|
|
claimingTask2Reward.value = true
|
|
try {
|
|
const res = await claimDailyRechargeTask2({
|
|
activityType: 'DAILY_RECHARGE_TASK2',
|
|
ruleId: currentTask2RewardTop.value.rule.id,
|
|
level: currentLevel.value,
|
|
})
|
|
if (res.status) {
|
|
await getDailyRechargeTaskInfo()
|
|
}
|
|
} catch (error) {
|
|
showError(error?.errorMsg || error?.message || t('active_game_claim_reward_failed'))
|
|
} finally {
|
|
claimingTask2Reward.value = false
|
|
}
|
|
}, 1000)
|
|
|
|
const getDailyRechargeTaskInfo = async () => {
|
|
const res = await getDailyRechargeInfo()
|
|
if (res.status && res.body) {
|
|
taskInfo.value = {
|
|
...createDefaultTaskInfo(),
|
|
...res.body,
|
|
levelList: Array.isArray(res.body.levelList) ? res.body.levelList : [],
|
|
}
|
|
selectedRewardLevel.value = currentLevel.value
|
|
return true
|
|
}
|
|
|
|
throw new Error('Invalid daily recharge task info response')
|
|
}
|
|
|
|
const getActivityRewards = async () => {
|
|
const res = await apiGetActivityResource('DAILY_RECHARGE_TASK2')
|
|
if (res.status && res.body) {
|
|
activityRewards.value = Array.isArray(res.body) ? appendExtraRewards(res.body) : []
|
|
resetRewardVapState()
|
|
return true
|
|
}
|
|
|
|
throw new Error('Invalid daily recharge activity reward response')
|
|
}
|
|
|
|
const delay = (duration) => {
|
|
return new Promise((resolve) => {
|
|
window.setTimeout(resolve, duration)
|
|
})
|
|
}
|
|
|
|
const runWithRetry = async (runner, retryCount = 1, retryDelay = 600) => {
|
|
let lastError = null
|
|
|
|
for (let attempt = 0; attempt <= retryCount; attempt += 1) {
|
|
try {
|
|
return await runner()
|
|
} catch (error) {
|
|
lastError = error
|
|
if (attempt < retryCount) {
|
|
await delay(retryDelay)
|
|
}
|
|
}
|
|
}
|
|
|
|
throw lastError
|
|
}
|
|
|
|
const initData = async () => {
|
|
const [taskResult, rewardResult] = await Promise.allSettled([
|
|
runWithRetry(getDailyRechargeTaskInfo, 2, 800),
|
|
runWithRetry(getActivityRewards, 1, 800),
|
|
])
|
|
|
|
if (taskResult.status === 'rejected') {
|
|
console.error('Failed to initialize daily recharge task info:', taskResult.reason)
|
|
}
|
|
|
|
if (rewardResult.status === 'rejected') {
|
|
console.error('Failed to initialize daily recharge activity rewards:', rewardResult.reason)
|
|
}
|
|
|
|
if (taskResult.status === 'rejected' && rewardResult.status === 'rejected') {
|
|
throw taskResult.reason || rewardResult.reason
|
|
}
|
|
}
|
|
|
|
const preloadCriticalImages = async () => {
|
|
const chestImages = chestProgressList.value.map((chest) => imageUrl(getChestImageName(chest)))
|
|
const criticalImages = [
|
|
imageUrl('bg2'),
|
|
imageUrl('bg'),
|
|
imageUrl('totalRewardBg'),
|
|
imageUrl('lvTaskBg'),
|
|
imageUrl('LvRewardInfoBg'),
|
|
imageUrl('ruleInfoBg'),
|
|
imageUrl(`titleLv${currentLevel.value}`),
|
|
imageUrl(currentChestImageName.value),
|
|
imageUrl('amountShowBt'),
|
|
imageUrl('amountHiddenBt'),
|
|
imageUrl('done'),
|
|
imageUrl('LvActive'),
|
|
imageUrl('reward_gift'),
|
|
imageUrl('reward_frame'),
|
|
imageUrl('reward_ride'),
|
|
imageUrl('reward_banner'),
|
|
...chestImages,
|
|
imageUrl(getImgName('LvRewardTitle')),
|
|
imageUrl(getImgName('receiveBt')),
|
|
imageUrl(getImgName('receivedBt')),
|
|
imageUrl(getImgName('ruleTitle')),
|
|
]
|
|
|
|
await Promise.all([preloadImages(criticalImages), preloadCriticalVap(bgVapUrl)])
|
|
}
|
|
|
|
const completePreloading = async () => {
|
|
try {
|
|
await initData()
|
|
await preloadCriticalImages()
|
|
} catch (error) {
|
|
console.error('棰勫姞杞借繃绋嬩腑鍙戠敓閿欒:', error)
|
|
} finally {
|
|
isLoading.value = false
|
|
queueRewardVapPreload(activityRewards.value)
|
|
}
|
|
}
|
|
|
|
const startPageInitialization = async () => {
|
|
if (pageInitializationStarted) {
|
|
return
|
|
}
|
|
|
|
pageInitializationStarted = true
|
|
await completePreloading()
|
|
}
|
|
|
|
const connectToAppHandler = async () => {
|
|
const result = await connectToApp(async () => {
|
|
await startPageInitialization()
|
|
})
|
|
|
|
if (!result?.success) {
|
|
isLoading.value = false
|
|
}
|
|
}
|
|
|
|
onMounted(() => {
|
|
if (import.meta.env.DEV && typeof window !== 'undefined') {
|
|
window.__setDailyRechargeDevice = setSimulatedDeviceMode
|
|
window.__dailyRechargeDeviceMode = simulatedDeviceMode
|
|
}
|
|
connectToAppHandler()
|
|
})
|
|
|
|
onBeforeUnmount(() => {
|
|
if (import.meta.env.DEV && typeof window !== 'undefined') {
|
|
delete window.__setDailyRechargeDevice
|
|
delete window.__dailyRechargeDeviceMode
|
|
}
|
|
cleanupRewardVap()
|
|
})
|
|
</script>
|
|
|
|
<style lang="scss" scoped>
|
|
* {
|
|
color: #fff;
|
|
}
|
|
|
|
.fullPage {
|
|
height: 100vh;
|
|
position: relative;
|
|
z-index: 0;
|
|
background: #220e08;
|
|
}
|
|
|
|
.loading-container {
|
|
width: 100vw;
|
|
height: 100vh;
|
|
position: fixed;
|
|
inset: 0;
|
|
z-index: 10;
|
|
display: flex;
|
|
flex-direction: column;
|
|
justify-content: center;
|
|
align-items: center;
|
|
overflow: hidden;
|
|
background: #220e08;
|
|
}
|
|
|
|
.active-game-shell {
|
|
width: 100vw;
|
|
height: 100vh;
|
|
overflow-y: auto;
|
|
}
|
|
|
|
.active-game-stage {
|
|
width: 100vw;
|
|
min-height: 100vh;
|
|
position: relative;
|
|
isolation: isolate;
|
|
}
|
|
|
|
.vap-background-layer {
|
|
width: 100vw;
|
|
height: auto;
|
|
position: absolute;
|
|
top: 0;
|
|
left: 0;
|
|
z-index: 1;
|
|
object-fit: cover;
|
|
pointer-events: none;
|
|
}
|
|
|
|
.vap-background-vap-layer {
|
|
height: 150vw;
|
|
}
|
|
|
|
.page-background-layer {
|
|
position: absolute;
|
|
top: 120vw;
|
|
left: 0;
|
|
right: 0;
|
|
bottom: 0;
|
|
z-index: 0;
|
|
overflow: hidden;
|
|
pointer-events: none;
|
|
}
|
|
|
|
.page-content {
|
|
position: relative;
|
|
z-index: 2;
|
|
padding: 132vw 2vw 10vw;
|
|
}
|
|
|
|
.chest-progress-scroll {
|
|
width: 100%;
|
|
overflow-x: auto;
|
|
direction: ltr;
|
|
}
|
|
|
|
.chest-progress-scroll-rtl {
|
|
direction: rtl;
|
|
}
|
|
|
|
.chest-progress-row {
|
|
position: relative;
|
|
z-index: 0;
|
|
display: flex;
|
|
align-items: center;
|
|
width: max-content;
|
|
direction: ltr;
|
|
}
|
|
|
|
.chest-progress-row-rtl {
|
|
margin-left: auto;
|
|
}
|
|
|
|
.chest-progress-track {
|
|
position: absolute;
|
|
z-index: -1;
|
|
height: 3vw;
|
|
display: flex;
|
|
align-items: center;
|
|
overflow: hidden;
|
|
background: rgba(103, 41, 0, 0.7);
|
|
}
|
|
|
|
.chest-progress-fill {
|
|
height: 3vw;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.chest-progress-fill-rtl {
|
|
direction: ltr;
|
|
}
|
|
|
|
.chest-progress-bar {
|
|
display: block;
|
|
width: 100%;
|
|
height: 3vw;
|
|
object-fit: cover;
|
|
transform-origin: center;
|
|
}
|
|
|
|
.chest-progress-bar-rtl {
|
|
transform: scaleX(-1) !important;
|
|
}
|
|
|
|
.chest-progress-list {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 2vw;
|
|
}
|
|
|
|
.chest-progress-list-rtl {
|
|
flex-direction: row-reverse;
|
|
}
|
|
|
|
.reward-showcase {
|
|
flex: 1;
|
|
min-height: 0;
|
|
width: 100%;
|
|
display: flex;
|
|
gap: 3vw;
|
|
direction: ltr;
|
|
unicode-bidi: isolate;
|
|
}
|
|
|
|
.reward-showcase-main,
|
|
.reward-showcase-item {
|
|
min-width: 0;
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
justify-content: center;
|
|
}
|
|
|
|
.reward-showcase-main {
|
|
width: 33vw;
|
|
height: 100%;
|
|
}
|
|
|
|
.reward-showcase-vap-wrap {
|
|
width: 90%;
|
|
height: 100%;
|
|
position: relative;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
}
|
|
|
|
.reward-showcase-vap-wrap-playable {
|
|
cursor: pointer;
|
|
}
|
|
|
|
.reward-showcase-play-button {
|
|
position: absolute;
|
|
left: 50%;
|
|
top: 50%;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
width: 10vw;
|
|
height: 10vw;
|
|
min-width: 36px;
|
|
min-height: 36px;
|
|
padding: 0;
|
|
border: 0;
|
|
border-radius: 50%;
|
|
background: rgba(0, 0, 0, 0.42);
|
|
transform: translate(-50%, -50%);
|
|
}
|
|
|
|
.reward-showcase-play-icon {
|
|
display: block;
|
|
width: 0;
|
|
height: 0;
|
|
margin-left: 0.4vw;
|
|
border-top: 1.8vw solid transparent;
|
|
border-bottom: 1.8vw solid transparent;
|
|
border-left: 2.8vw solid #fff;
|
|
}
|
|
|
|
.reward-showcase-grid {
|
|
flex: 1;
|
|
min-width: 0;
|
|
display: grid;
|
|
grid-template-columns: repeat(2, 1fr);
|
|
grid-template-rows: repeat(3, 1fr);
|
|
column-gap: 3vw;
|
|
direction: ltr;
|
|
}
|
|
|
|
.reward-showcase-text {
|
|
color: #fff;
|
|
font-size: 1em;
|
|
text-align: center;
|
|
}
|
|
|
|
.reward-extra-scroll-viewport {
|
|
width: 100%;
|
|
max-width: 100%;
|
|
min-width: 0;
|
|
overflow: hidden;
|
|
white-space: nowrap;
|
|
direction: ltr;
|
|
}
|
|
|
|
.reward-extra-scroll-viewport-rtl {
|
|
direction: rtl;
|
|
}
|
|
|
|
.reward-extra-scroll-wrapper {
|
|
display: flex;
|
|
width: fit-content;
|
|
animation-name: reward-scroll-left;
|
|
animation-duration: 6s;
|
|
animation-timing-function: linear;
|
|
animation-iteration-count: infinite;
|
|
will-change: transform;
|
|
direction: ltr;
|
|
}
|
|
|
|
.reward-extra-scroll-wrapper-rtl {
|
|
animation-name: reward-scroll-right;
|
|
direction: rtl;
|
|
}
|
|
|
|
.reward-extra-scroll-content {
|
|
flex: 0 0 auto;
|
|
padding: 0 2vw;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
@keyframes reward-scroll-left {
|
|
0% {
|
|
transform: translateX(0);
|
|
}
|
|
|
|
100% {
|
|
transform: translateX(-50%);
|
|
}
|
|
}
|
|
|
|
@keyframes reward-scroll-right {
|
|
0% {
|
|
transform: translateX(0);
|
|
}
|
|
|
|
100% {
|
|
transform: translateX(50%);
|
|
}
|
|
}
|
|
|
|
.scroll::-webkit-scrollbar {
|
|
display: none;
|
|
}
|
|
|
|
[dir='rtl'] .flipImg {
|
|
transform: scaleX(-1);
|
|
}
|
|
|
|
.ruleBt {
|
|
width: 5vw;
|
|
min-width: 0;
|
|
display: block;
|
|
position: absolute;
|
|
top: -5vw;
|
|
right: 0px;
|
|
}
|
|
|
|
[dir='rtl'] .ruleBt {
|
|
left: 0px;
|
|
right: auto;
|
|
}
|
|
|
|
@media screen and (max-width: 360px) {
|
|
* {
|
|
font-size: 10px;
|
|
}
|
|
}
|
|
|
|
@media screen and (min-width: 360px) {
|
|
* {
|
|
font-size: 12px;
|
|
}
|
|
}
|
|
|
|
@media screen and (min-width: 768px) {
|
|
* {
|
|
font-size: 24px;
|
|
}
|
|
}
|
|
|
|
@media screen and (min-width: 1024px) {
|
|
* {
|
|
font-size: 32px;
|
|
}
|
|
}
|
|
</style>
|