1955 lines
58 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="fullPage">
<!-- 预加载状态 -->
<div v-show="isLoading" class="loading-container">
<div class="loading-spinner"></div>
<p>{{ $t('loading') }}...</p>
</div>
<!-- 主要内容 -->
<div
v-show="!isLoading"
style="width: 100vw; min-height: 100vh; overflow: hidden; position: relative"
>
<!-- 页面背景 -->
<BackgroundLayer :backgroundImages="[imageUrl('bg1'), imageUrl('bg2'), imageUrl('bg3')]" />
<!-- 页面内容 -->
<div style="position: relative; z-index: 2; margin-top: 150vw">
<!-- 弹幕 -->
<Barrage
style="display: block; width: 100vw; height: 30vw"
:barrageList="barrageList"
></Barrage>
<!-- 情书模块按钮 -->
<div style="display: flex; justify-content: center; align-items: center">
<img
v-smart-img
:src="imageUrl(getImgName('letterBg'))"
alt=""
style="width: 90%"
@click="myCoupleShow = true"
/>
</div>
<!-- 历史榜首 -->
<itemCenter
:imgUrl="imageUrl(getImgName('historyTopBg'))"
:contentStyle="`inset: 30vw 9vw 27vw;flex-direction: column;gap:2vw`"
>
<div
style="width: 100%; height: 73%; min-width: 0; display: flex; overflow: hidden"
class="scrollX"
>
<div class="topList" :style="{ left: listLeftPosition }">
<!-- 右占位 -->
<div style="width: 9vw; min-width: 0; flex-shrink: 0"></div>
<!-- top1情侣头像 -->
<itemCenter
v-for="(item, index) in 1"
style="z-index: 1; min-width: 0; flex-shrink: 0; transition: all 0.8s ease"
:style="{ width: index === centerIndex ? '60vw' : '40vw' }"
:imgUrl="imageUrl('topCoupleFrame')"
:contentStyle="``"
@click="moveToCenter(index)"
>
<!-- 头像 -->
<div style="position: relative; width: 100%; height: 100%">
<div
style="
position: relative;
z-index: -1;
top: 26%;
left: 0;
width: 100%;
height: 50%;
display: flex;
justify-content: center;
align-items: center;
gap: 16%;
"
>
<!-- 情侣1头像 -->
<img
:src="''"
alt=""
style="
display: block;
height: 100%;
object-fit: cover;
aspect-ratio: 1/1;
border-radius: 50%;
"
@error="handleAvatarImageError"
@click="viewUserInfo(rankingItem.userId)"
/>
<!-- 情侣2头像 -->
<img
:src="''"
alt=""
style="
display: block;
height: 100%;
object-fit: cover;
aspect-ratio: 1/1;
border-radius: 50%;
"
@error="handleAvatarImageError"
@click="viewUserInfo(rankingItem.cpUserId)"
/>
</div>
</div>
</itemCenter>
<!-- 左占位 -->
<div style="width: 9vw; min-width: 0; flex-shrink: 0"></div>
</div>
</div>
<!-- 奖品说明 -->
<div style="min-width: 0; display: flex; justify-content: center; align-items: center">
<div
style="
border-radius: 12px;
border: 1.325px solid #fff6e0;
background: linear-gradient(270deg, #ffcc46 0.03%, #ffdb7b 99.97%);
box-shadow:
0 -2.649px 3.974px 0 rgba(255, 241, 205, 0.48) inset,
0 5.298px 7.947px 0 #fff1cd inset;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
padding: 1vw 5vw;
"
>
<div style="font-size: 1.2em; color: #fe649b; font-weight: 700">Season1</div>
<div style="font-size: 0.9em; color: #fe649b; font-weight: 700">2026.1.20-2.18</div>
</div>
</div>
</itemCenter>
<!-- 栏目按钮 -->
<div
style="
width: 100vw;
padding: 0 10vw;
display: flex;
justify-content: center;
align-items: center;
gap: 10vw;
"
>
<!-- 排行榜栏目按钮 -->
<div style="flex: 1; min-width: 0" @click="changeTag('ranking')">
<img
v-show="RankingShow"
v-smart-img
:src="imageUrl(getImgName('rankingBtActive'))"
alt=""
style="width: 100%; display: block; object-fit: cover"
/>
<img
v-show="!RankingShow"
v-smart-img
:src="imageUrl(getImgName('rankingBt'))"
alt=""
style="width: 100%; display: block; object-fit: cover"
/>
</div>
<!-- 游戏栏目按钮 -->
<div style="flex: 1; min-width: 0" @click="changeTag('gamePlay')">
<img
v-show="GamePlayShow"
v-smart-img
:src="imageUrl(getImgName('gamePlayBtActive'))"
alt=""
style="width: 100%; display: block; object-fit: cover"
/>
<img
v-show="!GamePlayShow"
v-smart-img
:src="imageUrl(getImgName('gamePlayBt'))"
alt=""
style="width: 100%; display: block; object-fit: cover"
/>
</div>
</div>
<!-- 排行榜标签页 -->
<Ranking v-show="RankingShow" />
<!-- 游戏玩法标签页 -->
<GamePlay v-show="GamePlayShow" />
</div>
<!-- 我的排名占位 -->
<div v-show="RankingShow" style="height: 25vw"></div>
<!-- 我的排名 -->
<div v-show="RankingShow" style="width: 100vw; position: fixed; bottom: 0; z-index: 2">
<!-- 有伴侣和排名 -->
<itemCenter
style="width: 100%"
:flip="true"
v-if="myRanking?.cpUserId"
:imgUrl="imageUrl('myRankingBg')"
:contentStyle="`padding: 0 5%;justify-content: flex-start;`"
>
<!-- 排名 -->
<div style="width: 10vw; display: flex; justify-content: center; align-items: center">
<img
v-smart-img
v-for="(digit, index) in getRankDigits(myRanking.rank || 999)"
:src="getDigitImageUrl(digit)"
style="display: block; width: 3vw; object-fit: cover"
/>
</div>
<!-- 头像 -->
<div style="width: 36vw">
<div
style="
width: 100%;
padding: 3vw 5vw;
height: 80%;
display: flex;
justify-content: space-between;
align-items: center;
"
>
<!-- 情侣1头像 -->
<img
v-smart-img
:src="myRanking.userAvatar || ''"
alt=""
style="
display: block;
min-width: 0;
width: 12vw;
object-fit: cover;
aspect-ratio: 1/1;
border-radius: 50%;
border: 1px solid #fefa9f;
"
@error="handleAvatarImageError"
/>
<!-- 情侣2头像 -->
<img
v-smart-img
:src="myRanking.cpUserAvatar || ''"
alt=""
style="
display: block;
min-width: 0;
width: 12vw;
object-fit: cover;
aspect-ratio: 1/1;
border-radius: 50%;
border: 1px solid #fefa9f;
"
@error="handleAvatarImageError"
/>
</div>
</div>
<!-- 名称 -->
<div
style="
flex: 1;
min-width: 0;
height: 100%;
display: flex;
flex-direction: column;
justify-content: center;
gap: 1vw;
"
>
<div
style="width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap"
>
{{ myRanking.userNickname || '' }}
</div>
<div
style="width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap"
>
{{ myRanking.cpUserNickname || '' }}
</div>
</div>
<!-- 贡献 -->
<div style="width: auto; min-width: 0; display: flex; align-items: center">
<img
v-smart-img
:src="imageUrl('distribution')"
alt=""
style="width: 10vw; display: block; object-fit: cover"
/>
<div style="font-weight: 700">{{ myRanking.total || '0' }}</div>
</div>
</itemCenter>
<!-- 无伴侣 -->
<div
v-else
style="
border-radius: 16px 16px 0 0;
background:
radial-gradient(
171.8% 117.34% at 34.23% 104.78%,
rgba(255, 188, 221, 0) 0%,
#ffbcdd 100%
),
linear-gradient(303deg, rgba(255, 255, 255, 0) 0%, #ff4656 91.85%),
linear-gradient(292deg, #ff3bc4 0%, rgba(255, 255, 255, 0) 100.5%), #fff;
padding: 24px 12px 24px 12px;
display: flex;
justify-content: center;
align-items: center;
gap: 2vw;
"
>
<div style="font-size: 0.8em; font-weight: 700; color: #fff">
{{ t('no_relationship') }}
</div>
<div
style="
font-size: 0.8em;
color: #e14f69;
font-weight: 700;
box-sizing: border-box;
border-bottom: 1px solid #e14f69;
"
@click="viewUserInfo(getUserId())"
>
{{ t('go_add_cp') }}
</div>
</div>
</div>
<!-- 弹窗遮罩层 -->
<maskLayer :maskLayerShow="maskLayerShow" @click="closedPopup">
<!-- 我的伴侣弹窗 -->
<div v-show="myCoupleShow" style="position: fixed; bottom: 0; width: 100%">
<div
style="
border-radius: 16px 16px 0 0;
background:
radial-gradient(
171.8% 117.34% at 34.23% 104.78%,
rgba(255, 188, 221, 0) 0%,
#ffbcdd 100%
),
linear-gradient(303deg, rgba(255, 255, 255, 0) 0%, #ff4656 91.85%),
linear-gradient(292deg, #ff3bc4 0%, rgba(255, 255, 255, 0) 100.5%), #fff;
max-height: 80vh;
overflow: hidden;
display: flex;
flex-direction: column;
align-items: center;
gap: 10px;
padding: 20px;
"
@click.stop
>
<div
style="margin: 0; font-size: 1.2em; font-weight: 600; color: #fff; margin-bottom: 4vw"
>
{{ t('my_couple') }}
</div>
<!-- 有伴侣 -->
<div
v-if="myCouples.length > 0"
style="display: flex; flex-direction: column; align-items: center; gap: 4vw"
>
<!-- 伴侣列表 -->
<div
v-for="(couple, index) in myCouples"
:key="index"
style="position: relative"
@click="selectCouple(couple.cpUserProfile)"
>
<itemCenter
:imgUrl="imageUrl('coupleItem')"
:flip="true"
:contentStyle="`justify-content: space-between;`"
>
<div class="couple-item-info">
<img
v-smart-img
:src="couple?.cpUserProfile.userAvatar || ''"
alt=""
style="width: 3em; aspect-ratio: 1/1; border-radius: 50%"
@error="handleAvatarImageError"
/>
<div style="flex: 1; min-width: 0">
<div
style="
font-size: 0.9em;
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
"
>
{{ couple?.cpUserProfile.userNickname }}
</div>
<div style="font-size: 0.8em; color: rgba(255, 255, 255, 0.6)">
ID{{ couple?.cpUserProfile.account }}
</div>
</div>
</div>
<div
style="
width: 10.8%;
min-width: 0;
align-self: stretch;
display: flex;
justify-content: center;
align-items: center;
"
>
<div
v-if="selectedCouple.account === couple?.cpUserProfile.account"
style="
width: 30%;
aspect-ratio: 1/1;
border-radius: 50%;
background: linear-gradient(152deg, #f5a8b7 7.01%, #d61f3f 92.99%);
"
></div>
</div>
</itemCenter>
</div>
<!-- 发送按钮 -->
<button
style="width: 80%; padding: 12px 16px; border: none; color: #fff; font-weight: 600"
:style="
selectedCouple.account
? 'border-radius: 200px;background: linear-gradient(180deg, #FFACBB 0%, rgba(255, 172, 187, 0.00) 58.38%), #FF4768;box-shadow: 0 1px 4.5px 0 rgba(0, 0, 0, 0.10), -1px 0 5px 0 rgba(255, 255, 255, 0.70) inset, -3.5px 0 7.5px 0 rgba(255, 255, 255, 0.20) inset, 1px 0 5px 0 rgba(255, 255, 255, 0.70) inset, 3.5px 0 7.5px 0 rgba(255, 255, 255, 0.20) inset;'
: 'border-radius: 32px;background: linear-gradient(135deg, rgba(162, 162, 162, 0.50) 2.82%, rgba(100, 100, 100, 0.50) 99.15%);'
"
:disabled="!selectedCouple.account"
@click="showPopup('writeLetter')"
>
{{ t('send_love_letter') }}
</button>
</div>
<!-- 无伴侣 -->
<div
v-else
style="display: flex; flex-direction: column; align-items: center; gap: 4vw"
>
<img
v-smart-img
:src="imageUrl('noCoupleSign')"
alt=""
style="width: 50%; display: block"
/>
<p style="color: rgba(0, 0, 0, 0.4); font-size: 1em; font-weight: 700">
{{ t('no_couples') }}
</p>
</div>
</div>
</div>
<!-- 居中弹窗 -->
<div
v-show="centerPopupShow"
style="
width: 100vw;
height: 100vh;
display: flex;
justify-content: center;
align-items: center;
"
@click="closedPopup"
>
<!-- 发送情书二次确认弹窗 -->
<div
v-show="confirmShow"
style="
width: 90%;
border-radius: 12px;
background:
radial-gradient(
171.8% 117.34% at 34.23% 104.78%,
rgba(255, 188, 221, 0) 0%,
#ffbcdd 100%
),
linear-gradient(303deg, rgba(255, 255, 255, 0) 0%, #ff4656 91.85%),
linear-gradient(292deg, #ff3bc4 0%, rgba(255, 255, 255, 0) 100.5%), #fff;
padding: 20px;
display: flex;
flex-direction: column;
align-items: center;
gap: 2vw;
"
@click.stop
>
<!-- 标题 -->
<h3 style="font-size: 1.1em; font-weight: 700">
{{ t('love_letter_title') }}
</h3>
<!-- 内容 -->
<div style="font-size: 0.8em; font-weight: 500">
{{ t('spend_5000_coins_confess') }}
</div>
<!-- 按钮 -->
<div
style="width: 100%; display: flex; justify-content: space-around; align-items: center"
>
<!-- 取消按钮 -->
<div
style="
padding: 12px 20px;
border-radius: 200px;
background:
linear-gradient(180deg, #ffacbb 0%, rgba(255, 172, 187, 0) 58.38%), #ff879c;
box-shadow:
-1px 0 5px 0 rgba(255, 255, 255, 0.7) inset,
-3.5px 0 7.5px 0 rgba(255, 255, 255, 0.2) inset,
1px 0 5px 0 rgba(255, 255, 255, 0.7) inset,
3.5px 0 7.5px 0 rgba(255, 255, 255, 0.2) inset;
backdrop-filter: blur(5000px);
font-weight: 800;
"
@click="cancelSend"
>
{{ t('cancel') }}
</div>
<!-- 发送按钮 -->
<div
style="
padding: 12px 20px;
border-radius: 200px;
background:
linear-gradient(180deg, #ffacbb 0%, rgba(255, 172, 187, 0) 58.38%), #ff4768;
box-shadow:
0 1px 4.5px 0 rgba(0, 0, 0, 0.1),
-1px 0 5px 0 rgba(255, 255, 255, 0.7) inset,
-3.5px 0 7.5px 0 rgba(255, 255, 255, 0.2) inset,
1px 0 5px 0 rgba(255, 255, 255, 0.7) inset,
3.5px 0 7.5px 0 rgba(255, 255, 255, 0.2) inset;
font-weight: 800;
"
@click="sendLoveLetter"
>
{{ t('confirm') }}
</div>
</div>
</div>
<!-- 金币不足提示 -->
<div
v-show="TipsShow"
style="display: flex; justify-content: center; width: 100vw"
@click.stop
>
<div
style="
width: 60vw;
padding: 2vw;
display: flex;
justify-content: center;
align-items: center;
border-radius: 8px;
background: rgba(255, 255, 255, 0.4);
backdrop-filter: blur(32px);
"
>
<div style="font-size: 0.8em; font-weight: 700; color: #fff">
{{ t('insufficient_wallet_balance') }}
</div>
<div
style="
font-size: 0.8em;
color: #e14f69;
font-weight: 700;
box-sizing: border-box;
border-bottom: 1px solid #e14f69;
"
@click="gotoRecharge"
>
{{ t('recharge_now') }}
</div>
</div>
</div>
<!-- 需要补签提示 -->
<div
v-show="needCheckInTipsShow"
style="display: flex; justify-content: center; width: 100vw"
@click.stop
>
<div
style="
width: 60vw;
padding: 2vw;
display: flex;
justify-content: center;
align-items: center;
border-radius: 8px;
background: rgba(255, 255, 255, 0.4);
backdrop-filter: blur(32px);
"
>
<div style="font-size: 0.8em; font-weight: 700; color: #fff">
{{ t('consecutive_check_in_reward') }}
</div>
</div>
</div>
<!-- 签到成功提示 -->
<div
v-show="signInSuccessShow"
style="display: flex; justify-content: center; width: 100vw"
@click.stop
>
<div
style="
width: 60vw;
padding: 2vw;
display: flex;
justify-content: center;
align-items: center;
border-radius: 8px;
background: rgba(255, 255, 255, 0.4);
backdrop-filter: blur(32px);
"
>
<div style="font-size: 0.8em; font-weight: 700; color: #fff">
{{ t('check_in_successful') }}
</div>
</div>
</div>
<!-- 我的最新情书 -->
<itemCenter
v-if="lastLetterShow"
:imgUrl="imageUrl(getImgName('lastLetterBg'))"
:contentStyle="`inset: 30vw 17vw 27vw;
flex-direction: column;
justify-content: flex-start;
align-items: center;
gap:2vw`"
@click.stop
>
<!-- 发送者 -->
<div style="width: 100%; display: flex; align-items: center; gap: 2vw">
<img
v-smart-img
:src="latestLoveLetter.senderAvatar"
alt=""
style="
display: block;
min-width: 0;
width: 4em;
aspect-ratio: 1/1;
border-radius: 50%;
"
/>
<div
style="
flex: 1;
min-width: 0;
align-self: stretch;
display: flex;
flex-direction: column;
justify-content: center;
gap: 1vw;
"
>
<div
style="
min-width: 0;
color: #ff79a1;
font-size: 0.9em;
font-weight: 700;
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
"
>
{{ latestLoveLetter.senderNickname }}
</div>
<div style="font-size: 0.8em; font-weight: 500; color: #ff79a1">
ID{{ latestLoveLetter.senderAccount }}
</div>
</div>
</div>
<!-- 情书内容(保留换行) -->
<div
class="scrollY"
style="
flex: 1;
width: 100%;
color: #ff79a1;
font-weight: 600;
white-space: pre-wrap;
overflow-y: auto;
"
>
{{ latestLoveLetter.content }}
</div>
</itemCenter>
<!-- 抽奖结果 -->
<div v-show="resultShow" style="position: relative; width: 90%" @click.stop>
<itemCenter
:imgUrl="imageUrl('resultBg')"
:contentStyle="`inset: 37vw 15vw 16vw;
flex-direction: column;
justify-content: space-between;
gap:2vw`"
>
<div
style="
width: 100%;
display: flex;
justify-content: space-between;
align-items: center;
"
>
<!-- 上一个奖品 -->
<div
style="
width: 8vw;
align-self: stretch;
display: flex;
justify-content: center;
align-items: center;
"
>
<img
v-smart-img
class="flipImg"
v-show="result.length > 1 && resultShowIndex > 0"
:src="imageUrl('resultBack')"
alt=""
style="display: block; width: 80%; object-fit: cover"
@click="resultShowIndex--"
/>
</div>
<!-- 奖品 -->
<img
v-smart-img
:src="result[resultShowIndex]?.prize?.prizeImage"
alt=""
width="45%"
style="display: block"
/>
<!-- 下一个奖品 -->
<div
style="
width: 8vw;
align-self: stretch;
display: flex;
justify-content: center;
align-items: center;
"
>
<img
v-smart-img
class="flipImg"
v-show="result.length > 1 && resultShowIndex + 1 < result.length"
:src="imageUrl('resultNext')"
alt=""
style="display: block; width: 80%; object-fit: cover"
@click="resultShowIndex++"
/>
</div>
</div>
<!-- 奖品说明 -->
<div
style="
width: 100%;
height: 5vw;
text-align: center;
font-size: 0.9em;
font-weight: 590;
position: relative;
color: #ff79a1;
"
>
{{ t('you_get') }} {{ result[resultShowIndex]?.prize?.prizeName }}
<div v-if="result.length > 1" class="resultNum">
{{ resultShowIndex + 1 }} / {{ result.length }}
</div>
</div>
<!-- 关闭弹窗按钮 -->
<div style="width: 30vw; height: 10vw" @click="closedPopup"></div>
</itemCenter>
</div>
<!-- 中奖历史 -->
<div v-show="drawRecordShow" style="position: relative; width: 95%" @click.stop>
<itemCenter
:imgUrl="imageUrl(getImgName('recordBg'))"
:contentStyle="`padding: 28vw 12vw 25vw;`"
>
<div
style="
width: 100%;
height: 100%;
overflow-y: auto;
display: flex;
flex-direction: column;
gap: 8px;
"
class="scrollY"
>
<div
v-for="(record, index) in myRecords"
style="
border-radius: 12px;
border: 2px solid #fff;
background: linear-gradient(
264deg,
rgba(255, 220, 220, 0.4) 10.17%,
rgba(255, 151, 151, 0.4) 60.22%,
rgba(255, 220, 220, 0.4) 103.12%
);
box-shadow:
0 36.858px 13.981px 0 rgba(255, 106, 106, 0.02),
0 20.335px 12.71px 0 rgba(255, 106, 106, 0.08),
0 8.897px 8.897px 0 rgba(255, 106, 106, 0.13),
0 2.542px 5.084px 0 rgba(255, 106, 106, 0.15);
backdrop-filter: blur(4.5px);
display: flex;
padding: 12px;
justify-content: space-between;
align-items: center;
"
>
<div style="display: flex; align-items: center; gap: 4px">
<img
v-smart-img
:src="record?.prize?.prizeImage || ''"
alt=""
style="width: 10vw; min-width: 0; object-fit: cover; display: block"
/>
<div
style="font-weight: 590; font-size: 0.9em; direction: ltr; text-align: left"
>
{{ record?.prize?.prizeName }}
</div>
</div>
<div style="font-weight: 510; font-size: 0.8em; direction: ltr; text-align: left">
{{ formatUTCCustom(record.drawTime) }}
</div>
</div>
</div>
</itemCenter>
</div>
<!-- 领取历史(水晶&碎片) -->
<div v-show="receiveRecordShow" style="position: relative; width: 95%" @click.stop>
<itemCenter
:imgUrl="imageUrl('myPropsLogBg')"
:contentStyle="`padding: 28vw 12vw 25vw;`"
>
<div
style="
width: 100%;
height: 100%;
overflow-y: auto;
display: flex;
flex-direction: column;
gap: 8px;
"
class="scrollY"
>
<div
v-for="(record, index) in receiveRecord"
style="
border-radius: 12px;
border: 2px solid #fff;
background: linear-gradient(
264deg,
rgba(255, 220, 220, 0.4) 10.17%,
rgba(255, 151, 151, 0.4) 60.22%,
rgba(255, 220, 220, 0.4) 103.12%
);
box-shadow:
0 36.858px 13.981px 0 rgba(255, 106, 106, 0.02),
0 20.335px 12.71px 0 rgba(255, 106, 106, 0.08),
0 8.897px 8.897px 0 rgba(255, 106, 106, 0.13),
0 2.542px 5.084px 0 rgba(255, 106, 106, 0.15);
backdrop-filter: blur(4.5px);
display: flex;
padding: 12px;
justify-content: space-between;
align-items: center;
"
>
<div style="min-width: 0; display: flex; align-items: center; gap: 4px">
<img
v-smart-img
v-if="record.itemType == 'cp_fragment'"
:src="imageUrl('debris')"
alt=""
style="
width: 10vw;
min-width: 0;
object-fit: cover;
display: block;
"
/>
<img
v-smart-img
v-else-if="record.itemType == 'lottery_ticket'"
:src="imageUrl('crystal')"
alt=""
style="
width: 10vw;
min-width: 0;
object-fit: cover;
display: block;
"
/>
<div
style="
min-width: 0;
font-weight: 590;
font-size: 0.9em;
direction: ltr;
text-align: left;
"
>
{{ record.changeType == 2 ? '-' : '+' }}{{ record.changeAmount }}
</div>
</div>
<div
style="
min-width: 0;
font-weight: 510;
font-size: 0.8em;
direction: ltr;
text-align: left;
"
>
{{ formatUTCCustom(record.createdAt) }}
</div>
</div>
</div>
</itemCenter>
</div>
<!-- 兑换奖品确认弹窗 -->
<div
v-show="exchangeConfirmShow"
style="
border-radius: 12px;
background: #fff;
padding: 20px;
display: flex;
flex-direction: column;
align-items: center;
gap: 2vw;
"
@click.stop
>
<!-- 内容 -->
<div style="color: rgba(0, 0, 0, 0.4); font-size: 0.8em; font-weight: 500">
{{ t('confirm_item_exchange') }}
</div>
<!-- 按钮 -->
<div
style="width: 100%; display: flex; justify-content: space-around; align-items: center"
>
<!-- 取消按钮 -->
<div
style="
padding: 12px 20px;
border-radius: 24px;
border: 1px solid #e6e6e6;
color: rgba(0, 0, 0, 0.4);
font-weight: 800;
"
@click="cancelSend"
>
{{ t('cancel') }}
</div>
<!-- 发送按钮 -->
<div
style="
padding: 12px 20px;
border-radius: 24px;
background:
linear-gradient(180deg, #ffacbb 0%, rgba(255, 172, 187, 0) 58.38%), #ff4768;
box-shadow:
0 1px 4.5px 0 rgba(0, 0, 0, 0.1),
-1px 0 5px 0 rgba(255, 255, 255, 0.7) inset,
-3.5px 0 7.5px 0 rgba(255, 255, 255, 0.2) inset,
1px 0 5px 0 rgba(255, 255, 255, 0.7) inset,
3.5px 0 7.5px 0 rgba(255, 255, 255, 0.2) inset;
color: rgba(255, 255, 255, 0.8);
font-weight: 800;
"
@click="exchangeGoods"
>
{{ t('confirm') }}
</div>
</div>
</div>
<!-- 前三名奖励展示 -->
<div
v-show="topRewardShow"
style="
width: 90%;
position: relative;
border-radius: 12px;
background:
radial-gradient(
171.8% 117.34% at 34.23% 104.78%,
rgba(255, 188, 221, 0) 0%,
#ffbcdd 100%
),
linear-gradient(303deg, rgba(255, 255, 255, 0) 0%, #ff4656 91.85%),
linear-gradient(292deg, #ff3bc4 0%, rgba(255, 255, 255, 0) 100.5%), #fff;
padding: 20px;
display: flex;
flex-direction: column;
align-items: center;
gap: 2vw;
"
@click.stop
>
<!-- 关闭按钮 -->
<div
style="position: absolute; top: 2vw; right: 5vw; color: #ff4668; font-size: 3em"
@click="closedPopup"
>
x
</div>
<!-- 奖品展示 -->
<img
v-smart-img
:src="topReward.cover"
alt=""
style="width: 60%; display: block; object-fit: cover"
/>
<!-- 时长或数量 -->
<div
style="
font-size: 2em;
font-weight: 700;
background: linear-gradient(
180deg,
#ffdf6b 31.61%,
#ffe562 36.73%,
#fff 52.37%,
#ffe562 58.67%,
#ffa615 72.2%
);
background-clip: text;
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
"
>
{{ formatRewardDisplay(topReward.type, topReward) }}
</div>
</div>
<!-- help弹窗 -->
<div v-show="helpShow" @click.stop>
<itemCenter
:imgUrl="imageUrl(getImgName('helpBg'))"
:contentStyle="`inset: 30vw 18vw 28vw;overflow-y: auto;display:block;`"
>
<img
v-smart-img
:src="imageUrl(getImgName('helpInfo'))"
alt=""
style="display: block; width: 100%; object-fit: cover; margin-bottom: 6vw"
/>
</itemCenter>
</div>
<!-- 碎片不足提示 -->
<div
v-show="fragmentNotEnoughShow"
style="display: flex; justify-content: center; width: 100vw"
@click.stop
>
<div
style="
width: 60vw;
padding: 2vw;
display: flex;
justify-content: center;
align-items: center;
border-radius: 8px;
background: rgba(255, 255, 255, 0.4);
backdrop-filter: blur(32px);
"
>
<div style="font-size: 0.8em; font-weight: 700; color: #fff">
{{ t('not_enough_pieces') }}
</div>
</div>
</div>
<!-- 编写情书弹窗 -->
<div
v-show="writeLetterShow"
style="
width: 90%;
border-radius: 12px;
background: #fff;
padding: 20px;
display: flex;
flex-direction: column;
align-items: center;
gap: 2vw;
"
@click.stop
>
<!-- 标题 -->
<h3 style="color: rgba(0, 0, 0, 0.8); font-size: 1.1em; font-weight: 700">Tips</h3>
<!-- 情书输入框 -->
<div style="position: relative; width: 100%">
<textarea
v-model="writeLetterText"
@input="updateLetterText"
:placeholder="t('love_letter_placeholder')"
maxlength="80"
style="
width: 100%;
min-height: 10vh;
border-radius: 8px;
border: 1px solid #e6e6e6;
padding: 2vw 2vw 18px;
color: black;
outline: none;
resize: vertical;
"
></textarea>
<div
style="
position: absolute;
bottom: 8px;
right: 12px;
font-size: 0.8em;
color: rgba(0, 0, 0, 0.4);
font-weight: 500;
background: transparent;
z-index: 1;
"
>
{{ letterTextLength }}/80
</div>
</div>
<!-- 是否公开 -->
<div style="width: 100%; display: flex; align-items: center; gap: 1vw">
<!-- 按钮 -->
<div
style="
width: 1.5em;
aspect-ratio: 1/1;
border-radius: 50%;
border: 2px solid rgba(0, 0, 0, 0.4);
display: flex;
justify-content: center;
align-items: center;
"
@click="isPublic = !isPublic"
>
<div
v-if="isPublic"
style="
width: 0.8em;
aspect-ratio: 1/1;
border-radius: 50%;
background: linear-gradient(152deg, #f5a8b7 7.01%, #d61f3f 92.99%);
"
></div>
</div>
<!-- 说明文本 -->
<div
style="
flex: 1;
min-width: 0;
color: rgba(0, 0, 0, 0.4);
font-size: 0.8em;
font-weight: 500;
"
>
{{ t('publish_love_letter') }}
</div>
</div>
<!-- 按钮 -->
<div
style="width: 100%; display: flex; justify-content: space-around; align-items: center"
>
<!-- 取消按钮 -->
<div
style="
padding: 12px 20px;
border-radius: 24px;
border: 1px solid #e6e6e6;
color: rgba(0, 0, 0, 0.4);
font-weight: 800;
"
@click="cancelSend"
>
{{ t('cancel') }}
</div>
<!-- 发送按钮 -->
<div
style="
padding: 12px 20px;
border-radius: 24px;
background:
linear-gradient(180deg, #ffacbb 0%, rgba(255, 172, 187, 0) 58.38%), #ff4768;
box-shadow:
0 1px 4.5px 0 rgba(0, 0, 0, 0.1),
-1px 0 5px 0 rgba(255, 255, 255, 0.7) inset,
-3.5px 0 7.5px 0 rgba(255, 255, 255, 0.2) inset,
1px 0 5px 0 rgba(255, 255, 255, 0.7) inset,
3.5px 0 7.5px 0 rgba(255, 255, 255, 0.2) inset;
color: rgba(255, 255, 255, 0.8);
font-weight: 800;
"
@click="showPopup('confirm')"
>
{{ t('confirm') }}
</div>
</div>
</div>
</div>
</maskLayer>
</div>
</div>
</template>
<script setup>
import { computed, onMounted, onUnmounted, ref, watch } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { useLangStore } from '@/stores/lang'
import { useI18n } from 'vue-i18n'
import { getPngUrl } from '@/config/imagePaths.js'
import { getUserId, setUserInfo } from '@/utils/userStore.js'
import { viewUserInfo } from '@/utils/appBridge.js'
import { connectToApp } from '@/utils/appConnector.js'
import { preloadImages } from '@/utils/imagePreloader.js'
import { formatUTCCustom } from '@/utils/utcFormat.js'
import { showError, showSuccess } from '@/utils/toast.js'
import { handleAvatarImageError, handleRewardImageError } from '@/utils/imageHandler.js'
import { useCoupleStore } from '@/stores/couple'
import { storeToRefs } from 'pinia'
import { useThrottle, useDebounce } from '@/utils/useDebounce.js'
import { formatRewardDisplay } from '@/utils/rewardFormatter.js'
import { gotoAppPage } from '@/utils/appBridge.js'
import {
apiGetMyLotteryTicketCount, // 获取我的剩余抽奖券数量
apiGetUserFragmentBackpack, // 获取用户碎片背包
exchangeGood, //用碎片兑换资源
apiGetMyCpObject, // 获取我的-cp对象
apiSendLoveLetter, // 发送CP告白信封
apiGetLoveLetterWall, // 查询告白墙
apiGetLatestLoveLetter, // 查询我收到的最新告白信封
apiGetCpSeasonTop1List, // 查询赛季榜Top1列表
} from '@/api/couple.js'
import Ranking from './Ranking.vue'
import GamePlay from './GamePlay/Gameplay.vue'
import Barrage from './components/Barrage.vue'
import BackgroundLayer from '@/components/BackgroundLayer.vue'
import itemCenter from '@/components/itemCenter.vue'
import maskLayer from '@/components/MaskLayer.vue'
import { get } from '@/utils/http'
const { t } = useI18n()
const router = useRouter()
const route = useRoute()
const coupleStore = useCoupleStore()
const {
fragmentNotEnoughShow, // 碎片不足提示
helpShow, // 帮助弹窗
topRewardShow, // 前三名奖励展示
topReward, // 前三名奖励项
myRanking, // 我的排名
crystal, // 水晶数量
debris, // 碎片数量
TipsShow, // 提示弹窗
needCheckInTipsShow, // 需要补签提示
signInSuccessShow, // 签到成功提示
rewardShow, // 奖励弹窗
reward, // 奖励
resultShow, // 抽奖结果弹窗
result, // 抽奖结果
drawRecordShow, // 中奖历史弹窗
myRecords, // 我的抽奖记录
receiveRecordShow, // 领取水晶&碎片历史弹窗
receiveRecord, // 领取水晶&碎片记录
exchangeConfirmShow, // 兑换确认弹窗
exchangeItem, // 当前兑换的物品
} = storeToRefs(coupleStore)
const langStore = useLangStore()
// 当前语言类型
const currentLangType = computed(() => {
return langStore.selectedLang?.type || 'en'
})
const isLoading = ref(true) // 预加载状态
// 获取OSS图片URL的函数
const imageUrl = (filename) => getPngUrl('Ranking/Couple/', filename)
// 根据语言获取图片名
const getImgName = (filename) => {
return currentLangType.value === 'ar' ? `${filename}_ar` : filename
}
// 获取排名数字的各个位数
const getRankDigits = (rank) => {
return String(rank).split('')
}
// 获取数字图片URL
const getDigitImageUrl = (digit) => {
if (/^[0-9]$/.test(digit)) {
return imageUrl(`num${digit}`) // 假设数字图片在相同目录下
}
return ''
}
const barrageList = ref([]) // 弹幕数据
const RankingShow = ref(true) // 排行榜模块展示
const GamePlayShow = ref(false) // 游戏玩法模块展示
// 栏目切换
const changeTag = (module) => {
switch (module) {
case 'ranking':
RankingShow.value = true
GamePlayShow.value = false
break
case 'gamePlay':
RankingShow.value = false
GamePlayShow.value = true
break
}
}
const latestLoveLetter = ref({}) // 最新的一封情书
const myCouples = ref([]) // 伴侣列表
const selectedCouple = ref({}) // 选中的情书对象
const writeLetterText = ref('') // 情书内容
const letterTextLength = ref(0) // 情书内容字数
const seasonTop1List = ref([]) // 赛季榜Top1列表
const resultShowIndex = ref(0) // 抽奖结果索引
const listLeftPosition = ref('0') //top历史榜单
const centerIndex = ref(0) // 默认中间项的索引为1
// 计算移动到中心位置的函数
const moveToCenter = (targetIndex) => {
if (centerIndex.value == targetIndex) return // 如果当前索引和目标索引相同,则不需要移动
// 每个项目的宽度(包括间距)
const itemWidth = 40 // vw
const itemSpacing = 2 // vw
// 计算目标元素的偏移量
const targetOffset = (itemWidth + itemSpacing) * targetIndex
// 由于我们使用的是left属性负值向左移动
listLeftPosition.value = targetOffset + 'vw'
centerIndex.value = targetIndex
}
// 选中伴侣
const selectCouple = (couple) => {
if (selectedCouple.value.account === couple.account) {
// 如果已经选中伴侣,则取消选中
selectedCouple.value = {}
} else {
selectedCouple.value = couple
}
}
// 更新情书内容
const updateLetterText = () => {
// 情书内容不能为空
if (!writeLetterText.value.trim()) {
letterTextLength.value = 0
return
}
// 情书内容字数不得高于80字
if (writeLetterText.value.length > 80) {
writeLetterText.value = writeLetterText.value.slice(0, 80)
}
letterTextLength.value = writeLetterText.value.length
}
// 遮罩层
const lastLetterShow = ref(false) // 最新情书弹窗
const myCoupleShow = ref(false) // 我的伴侣弹窗
const writeLetterShow = ref(false) // 写信模块弹窗
const isPublic = ref(false) // 是否公开
const confirmShow = ref(false) // 二次确认发送弹窗
const maskLayerShow = computed(() => {
return (
fragmentNotEnoughShow.value ||
helpShow.value ||
topRewardShow.value ||
lastLetterShow.value ||
myCoupleShow.value ||
writeLetterShow.value ||
TipsShow.value ||
confirmShow.value ||
needCheckInTipsShow.value ||
signInSuccessShow.value ||
rewardShow.value ||
resultShow.value ||
drawRecordShow.value ||
receiveRecordShow.value ||
exchangeConfirmShow.value
)
})
const centerPopupShow = computed(() => {
return (
fragmentNotEnoughShow.value ||
helpShow.value ||
topRewardShow.value ||
lastLetterShow.value ||
writeLetterShow.value ||
TipsShow.value ||
confirmShow.value ||
needCheckInTipsShow.value ||
signInSuccessShow.value ||
rewardShow.value ||
resultShow.value ||
drawRecordShow.value ||
receiveRecordShow.value ||
exchangeConfirmShow.value
)
})
// 打开遮罩层
const showPopup = (type) => {
switch (type) {
case 'myCouple':
closedPopup() // 关闭弹窗
myCoupleShow.value = true
break
case 'writeLetter':
closedPopup() // 关闭弹窗
writeLetterShow.value = true
break
case 'confirm':
if (!writeLetterText.value.trim()) return
closedPopup() // 关闭弹窗
confirmShow.value = true
break
case 'tips':
cancelSend() // 取消发送
TipsShow.value = true
break
default:
closedPopup() // 关闭弹窗
break
}
}
// 关闭弹窗
const closedPopup = () => {
fragmentNotEnoughShow.value = false
helpShow.value = false
topRewardShow.value = false
lastLetterShow.value = false
myCoupleShow.value = false
writeLetterShow.value = false
TipsShow.value = false
confirmShow.value = false
needCheckInTipsShow.value = false
signInSuccessShow.value = false
resultShow.value = false
drawRecordShow.value = false
receiveRecordShow.value = false
exchangeConfirmShow.value = false
}
// 创建定时器引用
const tipsTimer = ref(null)
const signInSuccessTimer = ref(null)
// 监听 needCheckInTipsShow 的变化
watch(
() => needCheckInTipsShow.value,
(newValue) => {
if (newValue) {
// 如果当前是 true启动定时器
if (tipsTimer.value) {
clearTimeout(tipsTimer.value)
}
tipsTimer.value = setTimeout(() => {
needCheckInTipsShow.value = false
}, 1500) // 1.5秒后自动隐藏
}
},
)
// 监听 signInSuccessShow 的变化
watch(
() => signInSuccessShow.value,
(newValue) => {
if (newValue) {
// 如果当前是 true启动定时器
if (signInSuccessTimer.value) {
clearTimeout(signInSuccessTimer.value)
}
signInSuccessTimer.value = setTimeout(() => {
signInSuccessShow.value = false
}, 1500) // 1.5秒后自动隐藏
}
},
)
// 取消发送
const cancelSend = () => {
closedPopup()
selectedCouple.value = {}
writeLetterText.value = ''
letterTextLength.value = 0
isPublic.value = false
}
// 发送情书
const sendLoveLetter = useDebounce(() => {
sendLetter() //发送信件
}, 1000)
const sendLetter = async () => {
if (!selectedCouple.value.account) return
try {
await apiSendLoveLetter({
receiverId: selectedCouple.value.id,
content: writeLetterText.value,
isPublic: isPublic.value ? 1 : 0,
})
cancelSend()
} catch (error) {
// 余额不足
if (error.response.errorCode == 5000) {
cancelSend()
showPopup('tips')
}
}
}
// 跳转到充值页面
const gotoRecharge = () => {
router.push('/recharge')
}
// 查询告白墙信息列表
const getLoveLetterWall = async () => {
const resLoveLetterWall = await apiGetLoveLetterWall()
if (resLoveLetterWall.status && resLoveLetterWall.body) {
barrageList.value = resLoveLetterWall.body
}
}
// 查询赛季榜Top1列表
const getCpSeasonTop1List = async () => {
const resCpSeasonTop1List = await apiGetCpSeasonTop1List()
if (resCpSeasonTop1List.status && resCpSeasonTop1List.body) {
seasonTop1List.value = resCpSeasonTop1List.body
}
}
// 查询我收到的最新告白信封
const getLatestLoveLetter = async () => {
const resLatestLoveLetter = await apiGetLatestLoveLetter()
if (resLatestLoveLetter.status && resLatestLoveLetter.body) {
latestLoveLetter.value = resLatestLoveLetter.body
if (latestLoveLetter.value.id) {
lastLetterShow.value = true
}
}
}
// 查询我的CP对象
const getMyCpObject = async () => {
const resMyCpObject = await apiGetMyCpObject()
if (resMyCpObject.status && resMyCpObject.body) {
myCouples.value = resMyCpObject.body
// myCouples.value = Array.from({ length: 4 }, () => {
// return { ...resMyCpObject.body[0] }
// })
}
}
// 获取抽奖券
const getMyLotteryTicketCount = async () => {
try {
const response = await apiGetMyLotteryTicketCount('2004471533988167125')
if (response.status) {
crystal.value = response.body || 0
}
} catch (error) {
showError(error.response.errorMsg)
}
}
// 获取用户碎片背包
const getUserFragmentBackpack = async () => {
try {
const response = await apiGetUserFragmentBackpack({
userId: getUserId(),
// userId: '1957345312961527809',
fragmentsIds: ['2011710323414081538'],
})
if (response.status) {
debris.value = response.body?.[0]?.quantity || 0
}
} catch (error) {
showError(error.response.errorMsg)
}
}
// 更新数据
const updateNumbers = () => {
getMyLotteryTicketCount() // 获取抽奖券数量
getUserFragmentBackpack() // 获取碎片数量
}
// 用碎片兑换资源
const exchangeGoods = async () => {
try {
const resExchangeGood = await exchangeGood(exchangeItem.value)
if (resExchangeGood.status) {
updateNumbers() // 更新数据
exchangeItem.value = null // 清空当前兑换的物品
exchangeConfirmShow.value = false // 关闭兑换确认弹窗
}
} catch (error) {
console.log('error:', error)
// 信息提示id有误
showWarning(error.response.errorMsg)
}
}
// 页面初始化数据
const initData = async () => {
await Promise.all([
getLoveLetterWall(), // 查询告白墙信息列表
getLatestLoveLetter(), // 查询我收到的最新告白信封
getMyCpObject(), // 查询我的CP对象
getCpSeasonTop1List(), // 查询赛季榜Top1列表
])
}
// 预加载关键图片
const preloadCriticalImages = async () => {
const criticalImages = [
// 背景
imageUrl('bg1'),
imageUrl('bg2'),
imageUrl('bg3'),
imageUrl('bg3'),
// 弹幕
imageUrl('coupleSign'),
// 情书
imageUrl(getImgName('letterBg')),
// 历史榜首
imageUrl(getImgName('historyTopBg')),
imageUrl('topCoupleFrame'),
// 按钮
imageUrl(getImgName('rankingBtActive')),
imageUrl(getImgName('rankingBt')),
imageUrl(getImgName('gamePlayBtActive')),
imageUrl(getImgName('gamePlayBt')),
imageUrl(getImgName('rankingWeeklyBtActive')),
imageUrl(getImgName('rankingWeeklyBt')),
imageUrl(getImgName('rankingSeasonBtActive')),
imageUrl(getImgName('rankingSeasonBt')),
imageUrl(getImgName('gameLotteryBtActive')),
imageUrl(getImgName('gameLotteryBt')),
imageUrl(getImgName('gameSignInBtActive')),
imageUrl(getImgName('gameSignInBt')),
imageUrl(getImgName('gameExchangeBtActive')),
imageUrl(getImgName('gameExchangeBt')),
// 排行榜
imageUrl(`timeBg`),
imageUrl('helpBt'),
imageUrl(`top1`),
imageUrl(`top2-3`),
imageUrl(`top2RankingBg`),
imageUrl(`top2Frame`),
imageUrl(`top3RankingBg`),
imageUrl(`top3Frame`),
imageUrl(`rankingCoupleSign`),
imageUrl(`rankingItemBg`),
imageUrl('distribution'),
imageUrl(`num0`),
imageUrl(`num1`),
imageUrl(`num2`),
imageUrl(`num3`),
imageUrl(`num4`),
imageUrl(`num5`),
imageUrl(`num6`),
imageUrl(`num7`),
imageUrl(`num8`),
imageUrl(`num9`),
imageUrl('myRankingBg'),
imageUrl('distribution'),
imageUrl(getImgName('lastLetterBg')),
]
await preloadImages(criticalImages)
}
// 完成预加载
const completePreloading = async () => {
try {
// 执行所有初始化操作
await Promise.all([initData(), preloadCriticalImages()])
console.log('预加载完成,初始化数据成功')
} catch (error) {
console.error('预加载过程中发生错误:', error)
} finally {
// 无论成功或失败都结束加载状态
isLoading.value = false
}
}
// 预加载其他图片
const preloadOtherImages = async () => {
const criticalImages = []
await preloadImages(criticalImages)
}
// 使用工具函数连接APP
const connectToAppHandler = async () => {
await connectToApp(() => {
// 连接成功回调
completePreloading() // 完成预加载
})
}
// 组件挂载时检测环境
onMounted(() => {
connectToAppHandler()
preloadOtherImages() // 预加载其他图片
})
onUnmounted(() => {
if (tipsTimer.value) {
clearTimeout(tipsTimer.value)
}
if (signInSuccessTimer.value) {
clearTimeout(signInSuccessTimer.value)
}
})
</script>
<style lang="scss" scoped>
* {
color: #fff;
}
.fullPage {
width: 100vw;
min-height: 100vh;
background: #ff79a1;
position: relative;
}
/* 添加加载动画样式 */
.loading-container {
width: 100vw;
height: 100vh;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
color: #666;
}
.loading-spinner {
width: 32px;
height: 32px;
border: 3px solid #f3f3f3;
border-top: 3px solid #f59e0b;
border-radius: 50%;
animation: spin 1s linear infinite;
margin-bottom: 12px;
}
.scrollX::-webkit-scrollbar {
display: none;
}
.topList {
display: flex;
flex-direction: row-reverse;
align-items: center;
gap: 2vw;
width: 100%;
position: relative;
transition: all 0.8s ease; // 添加平滑过渡效果
left: 0;
}
@keyframes spin {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
textarea::placeholder {
font-size: 0.8em;
font-weight: bold;
color: rgba(0, 0, 0, 0.4);
}
.scrollY::-webkit-scrollbar {
display: none;
}
.resultNum {
position: absolute;
z-index: 9999;
inset: 0;
padding-right: 8%;
display: flex;
justify-content: flex-end;
align-items: center;
font-size: 0.8em;
font-weight: 590;
color: #ff79a1;
}
.couple-item-info {
flex: 1;
min-width: 0;
padding-left: 4vw;
display: flex;
align-items: center;
gap: 4vw;
}
@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;
}
}
[dir='rtl'] .flipImg {
transform: scaleX(-1);
}
[dir='rtl'] .resultNum {
direction: ltr;
justify-content: flex-start;
}
[dir='rtl'] .couple-item-info {
padding-left: 0;
padding-right: 4vw;
}
</style>