Files
91fans_h5/src/views/lldfreeBenefits/components/task.vue
T
2026-09-15 22:40:55 +07:00

1922 lines
55 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<div class="task" id="taskBoxID">
<lazyImg :src="taskBgImg" class="task_bg" />
<Header title="每日签到" headerBg="transparent" class="isHeader" />
<div class="task_box overflow-y-auto" ref="taskBoxRef">
<van-pull-refresh v-model="loading" @refresh="onRefresh">
<div class="content_topBox">
<div class="top_content">
<div class="userinfo">
<div class="avatar" :class="{ 'avatar--vip': userStore.isVip }">
<lazyImg :src="userStore.userInfo.portrait" round :key="userStore.userInfo.portrait" />
</div>
<div class="name_box">
<div class="name">
<div>{{ userStore.userInfo.name }}</div>
<VipImg :isVip="userStore.userInfo.vipLevel" />
</div>
<div class="tip" v-if="!userStore.userInfo.isVip">剩余观看次数:{{ userStore.userInfo.watchCount }}</div>
<div class="tip" v-else>剩余AI次数:{{ state.freeCount }}</div>
</div>
</div>
<div class="vip_btn" @click="router.push('/member')" v-if="userStore.userInfo.isVip">
<div class="info">
<div class="label">VIP限时特惠 畅享全场</div>
<div class="time">
{{ userStore.userInfo.vipName }}
<span>{{ dateFormatYyyyMmDd(userStore.userInfo.vipExpireDate) }}</span>
</div>
</div>
<div v-if="userStore.userInfo.isUpgrade" class="openVip" @click="router.push('/member')">补差价升级<img src="@/assets/images/png/mine/right-icon.png" alt="" /></div>
<div v-else class="openVip" @click="router.push('/member')">会员中心<img src="@/assets/images/png/mine/right-icon.png" alt="" /></div>
</div>
<div class="btn" @click="router.push('/member')" v-else>开通VIP不限次数观看</div>
</div>
<div class="tip_text">提示:若状态未更新 请下拉刷新哦</div>
</div>
<Loading v-if="state.signLoading" />
<div class="signContent" :style="signContentStyle" v-else>
<div class="sign-header">
<p class="sign-title">每日签到领福利</p>
<span class="sign-reward-chip" v-if="allMsgObj?.checkin?.doubleReward">
{{ remainRewardDays > 0 ? `还差${remainRewardDays}天领取翻倍福利` : '已解锁7天翻倍奖励' }}
</span>
</div>
<div class="sign-progress" v-if="checkedDays < 8">
<div class="progress-track">
<div class="progress-inner-mask">
<div class="progress-inner" :style="{ width: `${progressPercent}%` }"></div>
</div>
<img
class="progress-icon"
:class="{ shake: seventhDayHighlight }"
src="@/assets/images/png/mine/progress-icon.png"
alt=""
:style="{ left: `${progressIconLeft}%` }"
/>
</div>
<span class="progress-percent">{{ progressPercent }}%</span>
</div>
<p class="sign-subtitle">
已连续签到
<span class="highlight">{{ checkedDays }}</span>
天 神秘大礼在等你
</p>
<div class="sign-sync-wrap">
<div class="free-claim-default-bg"></div>
<div class="vip-claim-default-bg"></div>
<div class="sign-sync-scroll">
<div class="sign-sync-content">
<div class="sign-days-row">
<div
v-for="col in signColumns"
:key="`sign-day-${col.id}`"
class="day-chip"
:class="{
active: checkedDays === col.dayIndex,
done: checkedDays > col.dayIndex,
}"
>
{{ getDayChipText(col) }}
</div>
</div>
<div class="sign-cards-row sign-cards-row-free">
<div v-for="col in signColumns" :key="`free-prize-${col.id}`" class="prize-card">
<div class="item-mask"></div>
<lazyImg
v-if="col.freePrize?.image"
:src="col.freePrize.image"
class="prize-img"
:class="{
'signed-gray': col.freePrize?.isCheckedIn,
'shake-prize': col.day === currentCheckinDay && seventhPrizeShaking,
}"
/>
<template v-else>
<img v-if="!col.freePrize?.isCheckedIn" class="prize-img" src="@/assets/images/png/mine/check_in_coin.png" alt="" />
<img v-else class="prize-img" src="@/assets/images/png/mine/checked_in_coin.png" alt="" />
</template>
<span class="prize-name" :class="{ 'signed-white': col.freePrize?.isCheckedIn }">
{{ col.freePrize?.prizeName || formatPrizeText(col.freePrize) }}
</span>
<span class="prize-score" v-if="hasVipTypeGte3">+2</span>
</div>
</div>
<div class="sign-cards-row sign-cards-row-vip" @click="handleVipRowClick">
<!-- <div class="vip-slash" v-if="!hasVipTypeGte3"></div> -->
<div
v-for="(col, index) in bigPrizeList"
:key="`vip-prize-${col.id || index}`"
class="prize-card"
@click.stop="handleClaimVipPrize(col)"
>
<div class="item-mask"></div>
<div class="vip-claim-badge" v-if="col.canClaim">补领</div>
<div class="prize-img-box">
<lazyImg
v-if="col?.image"
:src="col.image"
class="prize-img"
:class="{ 'signed-gray': col.isReceive || col.isExpired }"
/>
<template v-else>
<img v-if="col?.isReceive || col.isExpired" class="prize-img" src="@/assets/images/png/mine/checked_in_coin.png" alt="" />
<img v-else class="prize-img" src="@/assets/images/png/mine/check_in_coin.png" alt="" />
</template>
<img v-if="col.isExpired" class="expired-icon" src="@/assets/images/png/mine/expired.png" alt="" />
</div>
<span class="prize-name" :class="{ 'signed-white': col.isReceive }">
{{ col.prizeName || formatPrizeText(col) }}
</span>
<img
v-if="!hasVipTypeGte3 && !col.isReceive"
class="vip-lock-icon"
src="@/assets/images/png/mine/lock.png"
alt=""
/>
</div>
</div>
<div class="free-claim-bg">
<img src="@/assets/images/png/mine/qiandao-free.png" alt="" />
</div>
<div class="vip-claim-bg">
<img src="@/assets/images/png/mine/qiandao-vip.png" alt="" />
</div>
</div>
</div>
</div>
<div class="touchShowMore">
<div v-if="!allMsgObj?.checkin?.todayChecked" class="signIn" @click="handleSignTrigger">
会员打卡领双倍奖励
<img class="signInIcon" src="@/assets/images/png/signInIcon.png" alt="" />
</div>
<div v-else class="signInEnd" @click="handleSignTrigger">今日已签到</div>
<div
v-if="allMsgObj?.checkin?.todayChecked && !hasVipTypeGte3"
class="unlock-vip-tip"
@click="router.push('/member')"
>
<!-- 立即解锁可领
<span class="unlock-vip-highlight">“免费+豪奖”双重奖励</span>
<span class="unlock-vip-btn">开通会员</span> -->
<img src="@/assets/images/png/mine/vip-tips.png" alt="" />
</div>
</div>
</div>
<!-- <div class="checkin_rewards" v-if="state.signExtraPrizesList.length">
<div class="checkin_title">累计打卡白嫖会员</div>
<div class="checkin_subtitle">100%得VIP会员等你拿</div>
<div class="reward_cards">
<div class="reward_card" v-for="(item, index) in state.signExtraPrizesList" :key="index">
<div class="duration_badge" v-if="item.desc">{{ item.desc }}</div>
<div class="reward_line" />
<div class="reward_icon">
<lazyImg :src="item.image" v-if="item.image" />
</div>
<div class="reward_text">{{ item.name }}</div>
<div class="claim_btn" :class="{ active: item.extraPrizeStatus }" v-if="item.extraPrizeStatus">
已领取
</div>
<div class="claim_btn" v-else>剩余{{ item.finishCondition }}天</div>
</div>
</div>
</div> -->
</van-pull-refresh>
<div class="tabs_ltype">
<div class="record" @click="router.push('/freeRecord')">兑换记录</div>
<van-tabs
v-model:active="state.tabActive"
class="oepnX task_tabs"
title-active-color="#F68804"
title-inactive-color="#999"
animated
:scrollspy="false"
swipeable
shrink
:lazy-render="true"
>
<van-tab title="福利任务">
<div class="taskBox">
<div class="score">
我的积分
<span>{{ userStore.walletInfo.integral || 0 }}</span>
</div>
<div class="taskList" v-if="!state.loading">
<div class="taskItem flex-center-between" v-for="item in state.dailyTask" :key="item.id">
<div class="leftBox flex-vcenter">
<LazyImg :src="item.img" class="taskIcon" :key="item.img" />
<div class="taskInfo">
<div class="taskName">
{{ item.title }}
<span v-if="item.prize">+{{ item?.prize?.price }}积分</span>
<span class="countdownTag" v-if="item.countdownType == 1">限时</span>
</div>
<div class="taskDesc ellipsis1">{{ getTaskDesc(item) }}</div>
</div>
</div>
<template v-if="item.countdownType == 1">
<div class="btn receive" v-if="getTaskHongbaoPhase(item) === 'pending'" @click="todo(item, 3)">
待开启
</div>
<div class="btn" v-else-if="getTaskHongbaoPhase(item) === 'active'" @click="todo(item, 3)">
领红包
</div>
</template>
<template v-else>
<div class="btn" v-if="item.status == 0" @click="todo(item, 1)">去完成</div>
<div class="btn receive" v-else-if="item?.status == 1" @click="receive(item, 1)">待领取</div>
<div class="btn received" v-else-if="item?.status == 2">已领取</div>
</template>
</div>
<div class="taskItem flex-center-between" v-for="item in state.growTasks" :key="item.id">
<div class="leftBox flex-vcenter">
<LazyImg :src="item.img" class="taskIcon" :key="item.img" />
<div class="taskInfo">
<div class="taskName">
{{ item.title }}
<span v-if="item.prize">+{{ item?.prize?.price }}积分</span>
<span class="countdownTag" v-if="item.countdownType == 1">限时</span>
</div>
<div class="taskDesc ellipsis1">{{ getTaskDesc(item) }}</div>
</div>
</div>
<template v-if="item.countdownType == 1">
<div class="btn receive" v-if="getTaskHongbaoPhase(item) === 'pending'" @click="todo(item, 3)">
待开启
</div>
<div class="btn" v-else-if="getTaskHongbaoPhase(item) === 'active'" @click="todo(item, 3)">
领红包
</div>
</template>
<template v-else>
<div class="btn" v-if="item.status == 0" @click="todo(item, 3)">去完成</div>
<div class="btn receive" v-else-if="item.status == 1" @click="receive(item, 3)">待领取</div>
<div class="btn received" v-else-if="item.status == 2">已领取</div>
</template>
</div>
<div class="taskItem flex-center-between" v-for="item in state.onceTask" :key="item.id">
<div class="leftBox flex-vcenter">
<LazyImg :src="item.img" class="taskIcon" :key="item.img" />
<div class="taskInfo">
<div class="taskName">
{{ item.title }}
<span v-if="item.prize">+{{ item?.prize?.price }}积分</span>
<span class="countdownTag" v-if="item.countdownType == 1">限时</span>
</div>
<div class="taskDesc ellipsis1">{{ getTaskDesc(item) }}</div>
</div>
</div>
<template v-if="item.countdownType == 1">
<div class="btn receive" v-if="getTaskHongbaoPhase(item) === 'pending'" @click="todo(item, 3)">
待开启
</div>
<div class="btn" v-else-if="getTaskHongbaoPhase(item) === 'active'" @click="todo(item, 3)">
领红包
</div>
</template>
<template v-else>
<div class="btn" v-if="item.status == 0" @click="todo(item, 3)">去完成</div>
<div class="btn receive" v-else-if="item.status == 1" @click="receive(item, 2)">待领取</div>
<div class="btn received" v-else-if="item.status == 2">已领取</div>
</template>
</div>
<Empty
v-if="state.growTasks.length == 0 && state.onceTask.length == 0 && state.dailyTask.length == 0"
/>
</div>
<Loading v-else />
</div>
</van-tab>
<van-tab title="积分兑换">
<div v-if="!state.integralLoading">
<div class="score_wrap">
<div class="score">
我的积分
<span>{{ userStore.walletInfo.integral || 0 }}</span>
</div>
<div class="list_jf">
<div class="itemlist" v-for="item in state.integralList" :key="item.id">
<div class="top">
<LazyImg :src="item.img" class="itemlistimg" :key="item.img" />
</div>
<div class="bot flex-center-between">
花费{{ item.price }}积分
<div class="btn flex-center-center" @click="exchangeItem(item)">立即兑换</div>
</div>
</div>
</div>
<Empty v-if="state.integralList.length == 0" />
</div>
</div>
<Loading v-else />
</van-tab>
</van-tabs>
<!-- <div class="tabs_line" />-->
<!-- <div class="tabs_record" @click="$router.push('/freeRecord')">兑换记录</div> -->
</div>
</div>
<!-- <div class="submit-box">-->
<!-- <div class="submit flex-center-center" @click="$router.push('/memberExchange')">前往积分兑换</div>-->
<!-- </div>-->
<WorkSignPopup
v-model:show="showSignPopup"
:checked-days="checkedDays"
:remain-days="remainRewardDays"
:progress-percent="progressPercent"
:popup-target-day="DOUBLE_REWARD_TARGET_DAY"
:prize-columns="popupPrizeColumns"
:redeem-items="allMsgObj?.config?.integerExchangeList || []"
:has-vip-type-gte3="hasVipTypeGte3"
@redeem="handleRedeem"
@continue="showSignPopup = false"
/>
<SevenSignRewardPopup
v-model:show="showSevenRewardPopup"
:redeem-items="allMsgObj?.config?.integerExchangeList || []"
:reward-bg-video-url="prizeVideo || ''"
:reward-bg-h265-url="prizeH265Url || ''"
@redeem="closeSevenRewardPopup"
@continue="showSevenRewardPopup = false"
/>
<RedeemSuccessPopup
v-model:show="showRedeemSuccessPopup"
:redeem-item="currentRedeemItem"
@continue="handleContinueRedeem"
@share="handleShareFriend"
/>
</div>
</template>
<script lang="ts" setup>
import { reactive, ref, computed, nextTick, onMounted, onUnmounted } from 'vue';
import { useUserStore } from '@/stores/user';
import { dateFormatYyyyMmDd, jumpAdv } from '@/utils/index';
import HomeApi from '@/service/home';
import { showToast } from 'vant';
import { useRouter } from 'vue-router';
import useLoading from '@/hooks/loading';
import TabPageScroll from '@/utils/resetTabPageScrollTop';
import WorkSignPopup from './WorkSignPopup.vue';
import SevenSignRewardPopup from './SevenSignRewardPopup.vue';
import RedeemSuccessPopup from './RedeemSuccessPopup.vue';
import UserApi from '@/service/user';
/** 福利任务项(仅声明本次接入红包雨倒计时需要的字段,其它字段沿用原 any 兜底) */
interface BenefitTaskItem {
id: string;
title?: string;
desc?: string;
img?: string;
status?: number;
type?: number;
prize?: { price?: number };
/** 1 表示红包雨场次任务,需结合 startAt/endAt 走倒计时文案与按钮态 */
countdownType?: number;
/** 红包雨场次开始时间,countdownType=1 时有效 */
startAt?: string;
/** 红包雨场次结束时间 */
endAt?: string;
subTitle?: string;
}
const tabPageScroll = new TabPageScroll();
const DOUBLE_REWARD_TARGET_DAY = 7;
setTimeout(() => {
tabPageScroll.add(`#taskBoxID`);
}, 100);
const { openLoading, closeLoading } = useLoading();
const router = useRouter();
const userStore = useUserStore();
const prizeList = ref<any[]>([]);
const bigPrizeList = ref<any[]>([]);
const allMsgObj = ref<any>({});
const showSignPopup = ref(false);
const showSevenRewardPopup = ref(false);
const showRedeemSuccessPopup = ref(false);
const currentRedeemItem = ref<any>(null);
const prizeVideo = ref('');
const prizeH265Url = ref('');
const seventhPrizeShaking = ref(false);
const vipPrizeClaiming = ref(false);
const taskBoxRef = ref<HTMLElement | null>(null);
const state = reactive({
loading: true,
signLoading: true,
integralLoading: true,
dailyTask: <any>[],
growTasks: <any>[],
onceTask: <any>[],
integralList: <any>[],
tabActive: 0,
signExtraPrizesList: <any>[],
freeCount: 0,
});
const taskBgImg = computed(() => allMsgObj.value?.config?.backgroundImage || '');
const getList = async () => {
try {
let req: any = await HomeApi.getTaskList();
state.loading = false;
state.dailyTask = req.dailyTask || [];
state.growTasks = req.growthTasks || [];
state.onceTask = req.onceTask || [];
} catch (e) {
state.loading = false;
console.log(e);
}
};
const getIntegralList = async () => {
try {
let req: any = await HomeApi.getIntegral();
state.integralLoading = false;
state.integralList = req || [];
} catch (e) {
state.integralLoading = false;
console.log(e);
showToast('请求兑换列表失败,请稍后再试');
}
};
const todo = async (item: any, type: number) => {
jumpAdv(item);
if (type == 1 && item.countdownType !== 1) {
if (item.type == 0) {
// 广告点击,点击完即可领取
try {
let req = {
taskId: item.id,
type: 1,
};
await HomeApi.taskDo(req);
item.status = 1;
} catch (e) {
console.log(e);
}
}
} else {
if (item.type == 2 && item.countdownType !== 1) {
// 广告点击,点击完即可领取
try {
let req = {
taskId: item.id,
type: 2,
};
await HomeApi.taskDo(req);
item.status = 1;
} catch (e) {
console.log(e);
}
}
}
};
const receive = async (item: any, type: number) => {
try {
let req = {
taskId: item.id,
type: type,
};
await HomeApi.taskReceive(req);
item.status = 2;
userStore.updateWallet();
showToast('领取成功');
} catch (e) {
console.log(e);
}
};
const hasVipTypeGte3 = computed(() => Number(userStore?.vipLevel || 0) >= 1 && userStore.isVip);
const freePrizeList = computed(() =>
(prizeList.value || []).filter((item: any) => !item?.checkinType || item?.checkinType === 1),
);
const memberPrizeList = computed(() => (prizeList.value || []).filter((item: any) => item?.checkinType === 2));
const memberDisplayList = computed(() => (memberPrizeList.value.length ? memberPrizeList.value : freePrizeList.value));
const signDayList = computed(() => (freePrizeList.value.length ? freePrizeList.value : prizeList.value) || []);
const signColumns = computed(() => {
const daySource = signDayList.value || [];
return daySource.map((dayItem: any, index: number) => {
const dayValue = dayItem?.checkinDays || index + 1;
const freePrize =
freePrizeList.value.find((item: any) => (item?.checkinDays || 0) === dayValue) || freePrizeList.value[index];
const vipPrize =
memberDisplayList.value.find((item: any) => (item?.checkinDays || 0) === dayValue) ||
memberDisplayList.value[index];
return {
id: dayItem?.id ?? `day-${index}`,
day: dayValue,
freePrize,
vipPrize,
dayIndex: index,
};
});
});
const checkedDays = computed(() => allMsgObj.value?.checkin?.continuouslyDays || 0);
const checkedProgressDays = computed(() => Math.min(DOUBLE_REWARD_TARGET_DAY, checkedDays.value));
const progressPercent = computed(() => {
return Math.min(100, Math.max(0, Math.round((checkedProgressDays.value / DOUBLE_REWARD_TARGET_DAY) * 100)));
});
const progressIconLeft = computed(() => Math.min(98, Math.max(2, progressPercent.value)));
const remainRewardDays = computed(() => Math.max(0, DOUBLE_REWARD_TARGET_DAY - checkedDays.value));
const popupPrizeColumns = computed(() => signColumns.value.slice(0, DOUBLE_REWARD_TARGET_DAY - 1));
const signBackgroundImage = computed(() => allMsgObj.value?.config?.backgroundImage || '');
const currentCheckinDay = computed(() => Math.max(1, checkedDays.value));
const seventhDayHighlight = computed(() => checkedDays.value >= DOUBLE_REWARD_TARGET_DAY - 1);
const signContentStyle = computed(() => {
if (!signBackgroundImage.value) return {};
return {
backgroundImage: `url(${signBackgroundImage.value})`,
backgroundSize: '100% 100%',
backgroundRepeat: 'no-repeat',
};
});
const formatPrizeText = (item: any) => {
if (!item) return '--';
const title = item?.title?.trim?.() || item?.prizeName?.trim?.() || item?.name?.trim?.() || '';
const score = Number(item?.score ?? item?.price ?? 0);
const prizeType = item?.prizeType;
if (title && score <= 0) return title;
if (prizeType === 1) return `金币${score}`;
if (prizeType === 2) return `${score}积分`;
if (prizeType === 3) return `VIP${score}天`;
return title || (score > 0 ? `${score}积分` : '礼包奖励');
};
const getDayChipText = (col: any) => {
if (col.dayIndex === 0 && checkedDays.value > 0) return '第1天';
if (col.dayIndex === checkedDays.value && !allMsgObj.value?.checkin?.todayChecked) return '今天';
return `第${col.day}天`;
};
const handleVipRowClick = () => {
if (!hasVipTypeGte3.value) {
router.push('/member');
}
};
const checkinPage = async () => {
try {
const res: any = await HomeApi.checkinPrize({});
allMsgObj.value = res || {};
prizeList.value = res?.prizes || [];
bigPrizeList.value = res?.bigPrizes || [];
} catch (e) {
console.log(e);
} finally {
state.signLoading = false;
}
};
const fnSignIn = async () => {
if (allMsgObj.value?.checkin?.todayChecked) {
showToast('今日已签到');
return false;
}
openLoading();
try {
const res: any = await HomeApi.checkinClick({});
prizeVideo.value = res?.prizeVideo || '';
prizeH265Url.value = res?.h265Url || '';
showToast(res?.message || '签到成功');
if (res?.checkin?.doubleReward) {
showSevenRewardPopup.value = true;
} else if ((res?.checkin?.continuouslyDays || 0) < DOUBLE_REWARD_TARGET_DAY) {
showSignPopup.value = true;
}
await checkinPage();
userStore.updateWallet();
await userStore.updateUserInfo();
return true;
} catch (e) {
console.log(e);
return false;
} finally {
closeLoading();
}
};
const handleSignTrigger = () => {
if (allMsgObj.value?.checkin?.todayChecked) {
showToast('今日已签到');
return;
}
fnSignIn();
};
const handleClaimVipPrize = async (col: any) => {
if (!hasVipTypeGte3.value) {
router.push('/member');
return;
}
if (vipPrizeClaiming.value || !col?.canClaim) return;
vipPrizeClaiming.value = true;
try {
await HomeApi.checkinClaimVip({});
showToast('补领成功');
await checkinPage();
userStore.updateWallet();
await userStore.updateUserInfo();
} catch (e) {
console.log(e);
} finally {
vipPrizeClaiming.value = false;
}
};
const scrollTaskBoxToBottom = async () => {
await nextTick();
requestAnimationFrame(() => {
const taskBox = taskBoxRef.value;
if (!taskBox) return;
taskBox.scrollTo({
top: taskBox.scrollHeight,
behavior: 'smooth',
});
});
};
const handleRedeem = () => {
showSignPopup.value = false;
state.tabActive = 1;
void scrollTaskBoxToBottom();
};
const closeSevenRewardPopup = () => {
showSevenRewardPopup.value = false;
state.tabActive = 1;
void scrollTaskBoxToBottom();
};
const handleContinueRedeem = () => {
showRedeemSuccessPopup.value = false;
};
const handleShareFriend = () => {
showRedeemSuccessPopup.value = false;
router.push('/share');
};
const getSign = async () => {
try {
await checkinPage();
} catch (e) {
console.log(e);
state.signLoading = false;
}
};
const queryFreeCount = () => {
UserApi.queryWallet().then((res: any) => {
state.freeCount = res.aiUndressFreeTimes || 0;
});
};
// 签到
const init = async () => {
getList();
await getIntegralList();
await getSign();
await getSignExtraPrizesList();
queryFreeCount();
};
init();
const exchangeItem = async (data: any) => {
let req = {
id: data.id,
};
await exchange(req);
};
const exchange = async (item: any) => {
try {
let req = item;
const res: any = await HomeApi.exchangeIntegral(req);
userStore.updateWallet();
// const prize = res?.prize || res;
// if (prize?.isBigGift) {
// currentRedeemItem.value = prize;
// showRedeemSuccessPopup.value = true;
// } else {
showToast(res?.msg || '兑换成功');
// }
} catch (e) {
console.log(e);
}
};
const getSignExtraPrizesList = async () => {
try {
let req: any = await HomeApi.getSignExtraPrizesList();
state.signExtraPrizesList = req || [];
} catch (e) {
console.log(e);
}
};
const loading = ref(false);
const onRefresh = async () => {
await init();
loading.value = false;
};
// 红包雨任务倒计时:全局秒级时钟,避免每个 task 各自 setInterval
const now = ref(Date.now());
let countdownTimer: number | null = null;
/** HH:mm:ss 倒计时格式化,与首页活动浮窗保持一致 */
const formatCountdown = (remainMs: number) => {
const totalSec = Math.max(0, Math.floor(remainMs / 1000));
const hour = Math.floor(totalSec / 3600);
const minute = Math.floor((totalSec % 3600) / 60);
const second = totalSec % 60;
return `${String(hour).padStart(2, '0')}:${String(minute).padStart(2, '0')}:${String(second).padStart(2, '0')}`;
};
/**
* 福利任务描述文案:
* - countdownType !== 1 或缺少 startAt/endAt:原样展示 item.desc
* - now < startAt:下一场倒计时开启HH:mm:ss
* - startAt ≤ now < endAt:进行中 倒计时HH:mm:ss
* - now ≥ endAt:场次已结束,回退到 item.desc
*/
const getTaskDesc = (item: BenefitTaskItem) => {
if (Number(item.countdownType) !== 1) return item.desc || item.subTitle || '';
const startAt = new Date(item.startAt ?? '').getTime();
const endAt = new Date(item.endAt ?? '').getTime();
if (!startAt || !endAt) return item.desc || item.subTitle || '';
if (now.value < startAt) return `下一场倒计时开启${formatCountdown(startAt - now.value)}`;
if (now.value < endAt) return `进行中 倒计时${formatCountdown(endAt - now.value)}`;
return item.desc || item.subTitle || '';
};
/** 任务红包雨阶段:未开始 / 进行中 / 已结束(或无效) */
const getTaskHongbaoPhase = (item: BenefitTaskItem): 'pending' | 'active' | 'ended' => {
const startAt = new Date(item.startAt ?? '').getTime();
const endAt = new Date(item.endAt ?? '').getTime();
if (!startAt || !endAt) return 'ended';
if (now.value < startAt) return 'pending';
if (now.value < endAt) return 'active';
return 'ended';
};
onMounted(() => {
countdownTimer = window.setInterval(() => {
now.value = Date.now();
}, 1000);
});
onUnmounted(() => {
if (countdownTimer) {
clearInterval(countdownTimer);
countdownTimer = null;
}
});
</script>
<style lang="scss" scoped>
.task {
position: relative;
height: 100%;
padding-top: 44px;
.task_bg {
width: 100%;
height: auto;
position: absolute;
top: 0;
left: 0;
}
.task_box {
height: calc(100vh - 44px);
padding: 0 0 100px;
}
.isHeader {
position: fixed;
top: 0;
left: 0;
width: 100%;
z-index: 2;
}
.content_topBox {
width: 100%;
// height: 166px;
// background: url('@/assets/images/png/task_bg.png') left -99px no-repeat;
// background-size: 100% 240px;
}
.tip_text {
color: #999;
font-size: 12px;
font-weight: 400;
line-height: 18px;
text-align: center;
margin-top: 20px;
margin-bottom: 10px;
}
.top_content {
// display: flex;
// align-items: center;
// flex-direction: column;
padding: 6px 0;
.userinfo {
width: 100%;
height: 60px;
display: flex;
justify-content: flex-start;
align-items: center;
padding: 0 16px;
margin-bottom: 18px;
//background: url('@/assets/images/png/task_bg.png') right top no-repeat;
.avatar {
width: 60px;
height: 60px;
border-radius: 50%;
overflow: hidden;
margin-right: 12px;
box-sizing: border-box;
&--vip {
border: 2px solid #ffd84d;
}
}
.name_box {
.name {
display: flex;
align-items: center;
color: #fff;
font-size: 18px;
font-weight: 600;
line-height: 24px;
margin-bottom: 4px;
div {
max-width: 220px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
:deep(img) {
margin-left: 4px;
}
}
.tip {
color: #999;
font-size: 12px;
font-weight: 400;
line-height: 18px;
}
}
}
.vip_btn {
display: flex;
align-items: center;
justify-content: space-between;
width: 343px;
height: 53px;
margin: 0 auto;
border-radius: 7.73px;
background: url('@/assets/images/png/task_vip_bg.png') no-repeat;
background-size: 100% 100%;
padding: 6px 20px;
.label {
color: #f68804;
font-size: 14px;
font-weight: 500;
line-height: normal;
}
.time {
display: flex;
align-items: center;
color: #fff;
font-size: 12px;
font-weight: 500;
line-height: 16.028px;
span {
height: 18px;
width: 83px;
text-align: center;
line-height: 18px;
border-radius: 32px;
background: rgba(0, 0, 0, 0.5);
color: #999;
font-size: 11px;
font-weight: 400;
margin-left: 4px;
}
}
.openVip {
width: 84px;
height: 25px;
color: #fff;
font-size: 12px;
line-height: 25px;
text-align: center;
border-radius: 99px;
background: #F68804;
// background: url('@/assets/images/png/task_vip_btn.png') no-repeat;
// background-size: 100% 100%;
display: flex;
align-items: center;
justify-content: center;
gap: 4px;
img {
width: 11px;
height: 11px;
}
}
}
.btn {
display: flex;
width: 290px;
height: 40px;
justify-content: center;
align-items: center;
border-radius: 30px;
background: linear-gradient(90deg, #ff9077 0%, #ff6e4e 100%), linear-gradient(90deg, #f0927e 0%, #ea4e74 100%);
box-shadow: 0 2px 4px 0 rgba(236, 92, 94, 0.6);
color: #fff;
font-size: 16px;
font-weight: 600;
line-height: 18px;
margin: 0 auto;
}
}
.signContent {
width: 343px;
min-height: 320px;
margin: 16px auto 0;
padding: 12px 10px 16px;
border-radius: 12px;
border-top: 0.5px solid #e9d16f;
background: rgba(255, 255, 255, 0.1);
box-shadow: 0 0 12px rgba(0, 0, 0, 0.05);
overflow: hidden;
position: relative;
:deep(.loading_warp),
:deep(.loading) {
min-height: 280px;
}
.sign-header {
display: flex;
align-items: center;
min-height: 24px;
margin: 0 0 9px 0;
}
.sign-title {
color: #ffdd3a;
font-size: 16px;
font-weight: 700;
line-height: 24px;
letter-spacing: 0;
}
.sign-reward-chip {
flex-shrink: 0;
height: 18px;
border-radius: 12px;
padding: 0 10px;
line-height: 18px;
color: #fff;
font-size: 9px;
margin-left: 10px;
background: linear-gradient(180deg, #ef2a31 0%, #b51015 100%);
position: relative;
&::after {
content: '';
position: absolute;
left: 5px;
bottom: -5px;
width: 10px;
height: 7px;
background: #b51015;
clip-path: polygon(0 0, 100% 0, 0 100%);
}
}
.sign-progress {
display: flex;
align-items: center;
gap: 9px;
height: 42px;
padding: 0 12px;
border-radius: 10px;
background: rgba(255, 255, 255, 0.06);
margin-bottom: 11px;
}
.progress-track {
flex: 1;
height: 4px;
border-radius: 999px;
background: rgba(255, 255, 255, 0.1);
overflow: visible;
position: relative;
}
.progress-inner-mask {
position: absolute;
inset: 0;
border-radius: 999px;
overflow: hidden;
}
.progress-inner {
height: 100%;
border-radius: 999px;
background: #f68804;
box-shadow: 0 0 10px rgba(255, 217, 0, 0.38);
}
.progress-icon {
width: 18px;
height: 18px;
object-fit: contain;
position: absolute;
top: -10px;
transform: translateX(-50%);
pointer-events: none;
z-index: 2;
}
.progress-percent {
color: #fff;
font-size: 14px;
font-weight: 500;
line-height: 18px;
}
.sign-subtitle {
color: rgba(255, 255, 255, 0.6);
font-size: 14px;
line-height: 22px;
margin-bottom: 12px;
.highlight {
color: #f52c56;
font-weight: 700;
}
}
.sign-sync-wrap {
margin: 0 -10px;
position: relative;
overflow: hidden;
}
.sign-sync-scroll {
overflow-x: auto;
-webkit-overflow-scrolling: touch;
padding-bottom: 2px;
&::-webkit-scrollbar {
display: none;
}
}
.sign-sync-content {
width: max-content;
width: 100%;
display: flex;
flex-direction: column;
gap: 10px;
&::-webkit-scrollbar {
display: none;
}
position: relative;
}
.sign-days-row,
.sign-cards-row {
display: flex;
gap: 6px;
padding-left: 10px;
padding-right: 10px;
// overflow-x: auto;
}
.sign-cards-row-free {
height: 120px;
padding-top: 8px;
padding-bottom: 8px;
}
.free-claim-default-bg {
height: 120px;
padding-top: 8px;
padding-bottom: 8px;
// background: url('@/assets/images/png/mine/sign-free-bg.png') no-repeat left top,
// linear-gradient(to right, transparent 0, transparent 331px, rgba(255, 255, 255, 0.04) 331px);
background: rgba(255, 255, 255, 0.05);
// background-size: 331px 84px, 100% 100%;
border-radius: 12px;
position: absolute;
bottom: 134px;
left: 0;
width: 100%;
z-index: 0;
}
.sign-cards-row-vip {
position: relative;
height: 120px;
padding-top: 8px;
padding-bottom: 8px;
// background: url('@/assets/images/png/mine/sign-vip-bg.png') no-repeat left top,
// linear-gradient(to right, transparent 0, transparent 331px, rgba(255, 218, 11, 0.05) 331px);
// background: rgba(255, 218, 11, 0.08);
// background-size: 331px 84px, 100% 100%;
// border-radius: 12px;
// border-bottom: 1px solid rgba(255, 218, 11, 0.5);
// box-shadow: 0px -4px 8px 0px rgba(255, 211, 1, 0.29) inset;
}
.vip-claim-default-bg {
height: 120px;
padding-top: 8px;
padding-bottom: 8px;
background: rgba(255, 218, 11, 0.08);
// background-size: 331px 84px, 100% 100%;
border-radius: 12px;
border-bottom: 1px solid rgba(255, 218, 11, 0.5);
box-shadow: 0px -4px 8px 0px rgba(255, 211, 1, 0.29) inset;
position: absolute;
bottom: 4px;
left: 0;
width: 100%;
z-index: 0;
}
.vip-claim-badge {
position: absolute;
top: 1px;
left: 50%;
transform: translateX(-50%);
z-index: 3;
padding: 1px 4px;
border-radius: 3px;
background: #f42b2b;
color: #fff;
font-size: 9px;
line-height: 1.15;
white-space: nowrap;
}
.vip-claim-bg {
position: absolute;
bottom: 10px;
left: 50%;
transform: translateX(-50%);
width: 331px;
height: 20px;
z-index: 1;
img {
width: 331px;
height: 20px;
}
&::after {
content: '';
position: absolute;
top: -6px;
left: 50%;
transform: translateX(-50%);
width: 319px;
height: 1px;
background: rgba(255, 255, 255, 0.1);
}
}
.free-claim-bg {
position: absolute;
bottom: 140px;
left: 50%;
transform: translateX(-50%);
width: 331px;
height: 20px;
z-index: 1;
img {
width: 331px;
height: 20px;
}
&::after {
content: '';
position: absolute;
top: -6px;
left: 50%;
transform: translateX(-50%);
width: 319px;
height: 1px;
background: rgba(255, 255, 255, 0.1);
}
}
.vip-slash {
position: absolute;
left: 21px;
top: 70%;
width: 2px;
height: 35px;
background: #000;
border-radius: 1px;
transform: translateY(-50%) rotate(-70deg);
}
.day-chip {
flex: 0 0 auto;
width: 42px;
height: 20px;
border-radius: 4px;
color: #62020e;
background: #ffd900;
font-size: 12px;
line-height: 20px;
text-align: center;
opacity: 0.96;
box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.16);
&.active {
box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.48) inset;
}
&.done {
background: #4d4d4d;
color: rgba(255, 255, 255, 0.55);
box-shadow: none;
}
&.seventh {
background: linear-gradient(180deg, #ffe56f 0%, #ffc400 100%);
}
&.shake-day {
animation: day-shake 0.8s ease-in-out infinite;
transform-origin: center;
}
}
.prize-card {
flex: 0 0 auto;
width: 42px;
height: 68px;
border-radius: 6px;
padding: 7px 4px 6px;
display: flex;
flex-direction: column;
align-items: center;
background: linear-gradient(180deg, rgba(255, 255, 255, 0.14) 0%, rgba(255, 255, 255, 0.07) 100%);
position: relative;
box-sizing: border-box;
.item-mask {
width: 13px;
height: 2px;
border-radius: 20px;
background: rgba(255, 255, 255, 0.15);
margin-bottom: 8px;
}
.prize-img-box {
width: 34px;
height: 34px;
display: flex;
align-items: center;
justify-content: center;
position: relative;
overflow: hidden;
}
.prize-img {
width: 34px;
height: 34px;
overflow: hidden;
transition: filter 0.2s ease;
transform-origin: center center;
will-change: transform;
object-fit: contain;
}
.prize-img.shake-prize {
animation: prize-icon-shake 0.65s ease-in-out infinite;
}
.prize-img.signed-gray {
filter: grayscale(1) brightness(0.85);
}
.expired-icon {
width: 31px;
height: 31px;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
z-index: 2;
pointer-events: none;
}
.vip-lock-icon {
position: absolute;
top: 0;
left: 0;
width: 18px;
height: 18px;
z-index: 2;
pointer-events: none;
}
.prize-score {
position: absolute;
top: 5px;
right: 0;
color: #ffd460;
text-align: right;
font-family: Impact, sans-serif;
font-size: 10px;
font-weight: 400;
line-height: normal;
}
}
.prize-name {
margin-top: 4px;
width: 100%;
color: #ffd900;
font-size: 10px;
line-height: 14px;
text-align: center;
font-weight: 400;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
&.signed-white {
color: #fff;
}
}
.touchShowMore {
width: 100%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
margin-top: 26px;
}
.signIn {
width: 265px;
height: 38px;
border-radius: 999px;
color: #141414;
font-size: 14px;
font-weight: 500;
justify-content: center;
align-items: center;
background: #f68804;
display: inline-flex;
}
.signInIcon {
width: 22px;
height: 22px;
margin-left: 6px;
}
.signInEnd {
width: 178px;
height: 38px;
border-radius: 999px;
color: #fff;
font-size: 15px;
font-weight: 500;
justify-content: center;
align-items: center;
background: rgba(255, 255, 255, 0.3);
display: inline-flex;
}
.unlock-vip-tip {
color: #fff;
font-size: 13px;
line-height: 22px;
margin-top: 16px;
display: inline-flex;
align-items: center;
flex-wrap: wrap;
justify-content: center;
img {
width: 257px;
height: 18px;
}
}
.unlock-vip-highlight {
color: #ffda0b;
font-weight: 500;
}
.unlock-vip-btn {
height: 22px;
line-height: 22px;
border-radius: 999px;
padding: 0 10px;
color: #5b3100;
background: #ffda0b;
margin-left: 6px;
font-size: 12px;
font-weight: 500;
}
}
.checkin_rewards {
width: 343px;
margin: 20px auto 0;
// padding: 16px 12px;
padding: 16px 6px;
background: rgba(255, 255, 255, 0.05);
box-shadow: 0 0 12px 0 rgba(15, 15, 15, 0.05);
border-radius: 6px;
.checkin_title {
background: var(---, linear-gradient(135deg, #ffe580 0%, #facc15 100%));
background-clip: text;
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
font-size: 16px;
font-weight: 500;
line-height: 22px;
margin-bottom: 8px;
width: 50%;
margin-left: 6px;
}
.checkin_subtitle {
color: rgba(255, 255, 255, 0.55);
font-size: 14px;
font-weight: 400;
line-height: 20px;
margin-bottom: 14px;
margin-left: 6px;
}
.reward_cards {
display: flex;
overflow-x: auto;
-webkit-overflow-scrolling: touch;
gap: 8px;
scrollbar-width: none; /* Firefox */
&::-webkit-scrollbar {
display: none; /* Chrome, Safari, Edge */
}
.reward_card {
flex: 0 0 auto;
width: 72px;
min-width: 72px;
height: 86px;
min-height: 86px;
position: relative;
background: linear-gradient(180deg, rgba(141, 0, 2, 0.3) 0%, rgba(0, 7, 139, 0.3) 100%);
box-shadow: 0 0 12px 0 rgba(0, 0, 0, 0.05);
border-radius: 12px;
padding: 8px 4px;
box-sizing: border-box;
display: flex;
flex-direction: column;
align-items: center;
// justify-content: space-between;
// overflow: hidden;
.reward_line {
flex-shrink: 0;
width: 13px;
height: 2px;
background: rgba(249, 192, 137, 0.2);
margin: 2px 0;
}
.duration_badge {
position: absolute;
top: 0;
left: 45px;
min-width: 30px;
// max-width: 80px;
height: 16px;
line-height: 15px;
z-index: 1;
// background: url('@/assets/images/png/duration_badge.png') center center no-repeat;
// background-size: 100% 100%;
background: rgba(218, 0, 4, 1);
color: #fff;
font-size: 10px;
font-weight: 500;
white-space: nowrap;
text-align: center;
padding: 0 6px;
border-radius: 99px;
&::after {
content: '';
position: absolute;
bottom: -6.2px;
left: 3px;
width: 0;
height: 0;
border-top: 10px solid rgba(218, 0, 4, 1);
border-right: 10px solid transparent;
z-index: 0;
transform: rotate3d(1, 1, 1, 45deg);
}
}
.reward_icon {
flex-shrink: 0;
width: 24px;
height: 24px;
display: flex;
align-items: center;
justify-content: center;
margin: 4px 0;
:deep(.lazy_img) {
width: 100%;
height: 100%;
object-fit: contain;
}
}
.reward_text {
color: #fff;
font-size: 10px;
font-weight: 400;
text-align: center;
margin-bottom: 4px;
}
.claim_btn {
flex-shrink: 0;
width: 56px;
height: 20px;
border-radius: 10px;
background: rgba(255, 255, 255, 0.3);
color: rgba(255, 255, 255, 0.55);
font-size: 10px;
font-weight: 400;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: all 0.3s;
white-space: nowrap;
//&.active {
// // background: rgba(54, 112, 255, 1);
// color: #fff;
//}
//&:active {
// opacity: 0.8;
//}
}
}
}
}
.tabs_ltype {
position: relative;
width: 343px;
margin: 22px auto 0 auto;
.record {
position: absolute;
right: 0;
top: 8px;
z-index: 1;
color: #999;
font-size: 12px;
font-weight: 400;
line-height: 18px;
}
.tab_title {
img {
width: 60px;
}
.active {
width: 80px;
}
}
}
.tabs_line {
position: absolute;
top: 50px;
left: 50%;
transform: translateX(-50%);
width: calc(100% - 64px);
height: 1px;
background: #e8f2ed;
}
.tabs_record {
position: absolute;
right: 14px;
top: 20px;
font-size: 12px;
color: rgba(0, 0, 0, 0.6);
}
.task_tabs {
overflow: hidden;
:deep(> .van-tabs__wrap) {
height: 34px;
width: 100%;
margin: 0 auto;
.van-tabs__nav {
background: unset;
justify-content: flex-start;
column-gap: 18px;
padding: 0;
.van-tab {
flex: 0 0 auto;
font-size: 16px;
padding: 0;
font-weight: 400;
&.van-tab--active {
font-size: 18px;
}
}
.van-tabs__line {
height: 4px;
width: 16px;
border-radius: 3px 3px 0 0;
background: #f68804;
bottom: 0;
}
}
}
}
.taskBox {
padding: 12px 0;
.title {
padding: 0 10px;
font-size: 18px;
font-weight: 550;
span {
font-size: 12px;
color: #999;
font-weight: 300;
}
}
.subTitle {
padding: 0 10px;
font-size: 12px;
font-weight: 500;
color: #999999;
margin: 15px 0 20px;
span {
padding-right: 20px;
}
.exchangeVip {
height: 28px;
width: 67px;
background-color: $themeColor;
border-radius: 40px;
text-align: center;
line-height: 28px;
font-size: 12px;
color: #fff;
}
}
.taskList {
.total_score {
color: rgba(0, 0, 0, 0.8);
text-align: center;
font-size: 12px;
font-weight: 400;
line-height: 18px;
span {
color: #ff3d6a;
}
}
.taskItem {
margin-bottom: 12px;
padding: 12px;
border-radius: 8px;
background: rgba(255, 255, 255, 0.05);
.leftBox {
flex: 1;
.taskIcon {
height: 40px;
width: 40px;
border-radius: 50%;
overflow: hidden;
transform: translateZ(0px);
margin-right: 6px;
}
.taskInfo {
flex: 1;
.taskName {
font-size: 14px;
color: rgba(255, 255, 255, 0.9);
line-height: 20px;
display: flex;
align-items: center;
span {
color: #f54570;
font-size: 12px;
font-weight: 400;
margin-left: 8px;
}
.countdownTag {
display: inline-flex;
align-items: center;
justify-content: center;
height: 14px;
padding: 0 8px;
border-radius: 26px;
border: 0.5px solid #fff1b2;
background: #ffa800;
color: #fff !important;
font-size: 10px !important;
font-weight: 400;
line-height: 14px;
// 与任务标题文字垂直居中对齐
vertical-align: middle;
}
}
.taskDesc {
color: rgba(255, 255, 255, 0.55);
font-size: 12px;
font-weight: 400;
line-height: 18px;
}
}
}
.btn {
height: 28px;
padding: 0 12px;
text-align: center;
line-height: 28px;
font-size: 12px;
color: #fff;
border-radius: 999px;
background: #f68804;
}
.receive {
color: #692a1f;
background: #ffd5a8;
border-radius: 17.053px;
// border: 1.263px solid #e9dce0;
}
.received {
// opacity: 0.3;
background: #525252;
color: rgba(255, 255, 255, 0.9);
}
}
}
}
.score_wrap {
padding: 12px 0;
}
.score {
display: flex;
align-items: center;
color: #fff;
font-size: 14px;
font-weight: 400;
line-height: 20px;
margin-bottom: 12px;
span {
color: #f68804;
font-size: 24px;
font-weight: 700;
line-height: 32px;
margin-left: 4px;
}
}
.title {
padding: 24px 0 12px 0;
box-sizing: border-box;
font-size: 18px;
font-weight: 550;
}
.list_jf {
width: 100%;
box-sizing: border-box;
display: grid;
grid-template-columns: 1fr 1fr;
grid-row-gap: 7px;
grid-column-gap: 9px;
padding-bottom: 12px;
.itemlist {
width: 168px;
height: 134px;
padding: 12px 8px;
overflow: hidden;
border-radius: 12px;
background: #303030;
.top {
height: 78px;
padding: 15px 12px;
box-sizing: border-box;
position: relative;
p {
font-size: 16px;
margin-bottom: 5px;
position: relative;
z-index: 10;
color: #fff;
}
span {
font-size: 10px;
color: #cccccc;
position: relative;
z-index: 10;
}
.itemlistimg {
position: absolute;
width: 100%;
height: 100%;
top: 0;
left: 0;
}
}
.bot {
//height: 36px;
padding: 0 12px;
box-sizing: border-box;
font-size: 12px;
color: #989898;
margin-top: 10px;
.btn {
font-size: 10px;
width: 56px;
height: 22px;
background: #f68804;
color: #141414;
border-radius: 20px;
}
}
}
}
.submit-box {
position: absolute;
bottom: 0;
@include SafeArea('padding', 'bottom', 30px);
background: #000000;
width: 100%;
.submit {
margin: 0 auto;
width: 290px;
height: 40px;
background: linear-gradient(90deg, #f0927e 0%, #ea4e74 100%), linear-gradient(90deg, #ff9077 0%, #ff6e4e 100%);
border-radius: 99px;
color: #fff;
font-size: 16px;
}
}
}
.progress-icon.shake {
animation: icon-shake 0.72s ease-in-out infinite;
transform-origin: center bottom;
}
@keyframes icon-shake {
0% {
transform: translateX(-50%) rotate(0deg);
}
20% {
transform: translateX(-50%) rotate(-10deg);
}
40% {
transform: translateX(-50%) rotate(9deg);
}
60% {
transform: translateX(-50%) rotate(-7deg);
}
80% {
transform: translateX(-50%) rotate(6deg);
}
100% {
transform: translateX(-50%) rotate(0deg);
}
}
@keyframes day-shake {
0% {
transform: rotate(0deg);
}
25% {
transform: rotate(-6deg);
}
50% {
transform: rotate(5deg);
}
75% {
transform: rotate(-4deg);
}
100% {
transform: rotate(0deg);
}
}
@keyframes prize-icon-shake {
0% {
transform: rotate(0deg);
}
20% {
transform: rotate(-11deg) scale(1.02);
}
40% {
transform: rotate(10deg) scale(1.03);
}
60% {
transform: rotate(-8deg) scale(1.04);
}
80% {
transform: rotate(7deg) scale(1.02);
}
100% {
transform: rotate(0deg) scale(1);
}
}
</style>