Files
dcalc/frontendNew/src/pages/DnfVp/Detail.vue
T
2026-06-12 19:05:40 +08:00

1292 lines
33 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.
<script setup lang="ts">
import { computed, nextTick, onBeforeUnmount, onMounted, ref, watch } from "vue";
import { useRoute, useRouter } from "vue-router";
import { dataList } from "./data";
interface SkillOption {
videoUrl?: string;
txt1: string;
txt2?: string;
txt3?: string;
}
interface SkillItem {
lev: number;
skillName: string;
type: "Up" | "VP" | string;
videoUrl?: string;
options: SkillOption[];
}
interface SkillGroup {
type: "Up" | "VP" | string;
skills: SkillItem[];
}
interface PersonData {
peoTit?: string;
peoName: string;
linkUrl: string;
skillsByType: SkillGroup[];
}
interface RoleData {
title: string;
data: PersonData[];
}
interface RoleOption {
roleType: string;
title: string;
}
const asset = "https://game.gtimg.cn/images/dnf/cp/a20250612vp";
const route = useRoute();
const router = useRouter();
const detailData = dataList as Record<string, RoleData>;
const roleOrder = [
"gjsNv",
"gjsNan",
"gdjNan",
"gdjNv",
"sqsNan",
"sqsNv",
"mfsNan",
"mfsNv",
"gzzNan",
"gzzNv",
"aysz",
"dzz",
"haws",
"mqs",
"qjs",
"shz",
"gongjs",
];
const roles = computed<RoleOption[]>(() =>
roleOrder
.filter((roleType) => Boolean(detailData[roleType]))
.map((roleType) => ({ roleType, title: detailData[roleType]!.title })),
);
const activeRoleType = ref("gjsNv");
const activePerson = ref(0);
const activeTab = ref<"skill" | "break">("skill");
const activeSkill = ref(0);
const activeSkillSlide = ref(0);
const activeBreakSlide = ref(0);
const isMobile = ref(false);
const mobilePanelOpen = ref(false);
const roleTrackRef = ref<HTMLElement>();
let previousTitle = "";
let previousRootFontSize = "";
const currentRole = computed<RoleOption>(() => roles.value.find((item) => item.roleType === activeRoleType.value) ?? roles.value[0]!);
const currentRoleIndex = computed(() => Math.max(0, roles.value.findIndex((item) => item.roleType === currentRole.value.roleType)));
const currentRoleData = computed<RoleData>(() => detailData[currentRole.value.roleType] ?? detailData.gjsNv!);
const currentPerson = computed<PersonData>(() => currentRoleData.value.data[activePerson.value] ?? currentRoleData.value.data[0]!);
const personIndexes = computed(() => Array.from({ length: currentRoleData.value.data.length }, (_, index) => index));
const upSkills = computed<SkillItem[]>(() => currentPerson.value.skillsByType.find((item) => item.type === "Up")?.skills ?? []);
const vpSkills = computed<SkillItem[]>(() => currentPerson.value.skillsByType.find((item) => item.type === "VP")?.skills ?? []);
const currentVpSkill = computed<SkillItem>(() => vpSkills.value[activeSkill.value] ?? vpSkills.value[0]!);
const currentOptions = computed<SkillOption[]>(() => currentVpSkill.value?.options ?? []);
const skillSlideCount = computed(() => 1 + currentOptions.value.length);
const breakSkills = computed(() => [
...upSkills.value.map((skill, index) => ({ skill, source: "up" as const, sourceIndex: index })),
...vpSkills.value.map((skill, index) => ({ skill, source: "vp" as const, sourceIndex: index })),
]);
const bgUrl = computed(() => {
const prefix = isMobile.value ? `${asset}/m` : asset;
return `url(${prefix}/${currentRole.value.roleType}/body-bg.jpg)`;
});
const personUrl = computed(() => {
const prefix = isMobile.value ? `${asset}/m` : asset;
return `${prefix}/${currentRole.value.roleType}/${activePerson.value + 1}/peo.png`;
});
const panelBgUrl = computed(() => {
const bg = activeTab.value === "skill" ? "bg1.png" : "bg2.png";
return `url(${asset}/${currentRole.value.roleType}/${bg})`;
});
const recommendUrl = computed(() => currentPerson.value.linkUrl || "https://hub.bbs.colg.cn/tools/vp/index.html");
const personKey = computed(() => `${currentRole.value.roleType}-${activePerson.value}`);
function syncRoleFromRoute() {
const roleType = typeof route.query.roleType === "string" ? route.query.roleType : "";
activeRoleType.value = roles.value.some((item) => item.roleType === roleType) ? roleType : "gjsNv";
activePerson.value = 0;
activeSkill.value = 0;
}
function changeRole(roleType: string) {
void router.replace({ name: "dnf-vp-detail", query: { roleType } });
}
function stepRole(delta: number) {
const nextIndex = (currentRoleIndex.value + delta + roles.value.length) % roles.value.length;
const role = roles.value[nextIndex];
if (role) {
changeRole(role.roleType);
}
}
async function scrollActiveRoleIntoView() {
await nextTick();
const track = roleTrackRef.value;
const active = track?.querySelector<HTMLElement>(".role-option.active");
if (!track || !active) return;
const target = active.offsetLeft - track.clientWidth / 2 + active.clientWidth / 2;
track.scrollTo({ left: Math.max(0, target), behavior: "smooth" });
}
function changeTab(tab: "skill" | "break") {
activeTab.value = tab;
mobilePanelOpen.value = true;
}
function changeSkill(index: number) {
activeSkill.value = index;
activeSkillSlide.value = 0;
}
function stepSkillSlide(step: number) {
activeSkillSlide.value = (activeSkillSlide.value + step + skillSlideCount.value) % skillSlideCount.value;
}
function stepBreakSlide(step: number) {
activeBreakSlide.value = (activeBreakSlide.value + step + breakSkills.value.length) % breakSkills.value.length;
}
function backHome() {
void router.push({ name: "dnf-vp" });
}
function syncViewport() {
const mobile = window.matchMedia("(max-width: 768px)").matches;
const width = document.documentElement.clientWidth;
const height = document.documentElement.clientHeight;
isMobile.value = mobile;
if (mobile) {
document.documentElement.style.fontSize = "";
document.documentElement.style.setProperty("--dnf-detail-rem", `${Math.min(width, 750) / 750}px`);
return;
}
const scale = Math.min(width / 1920, height / 1080, 1);
document.documentElement.style.fontSize = `${scale}px`;
document.documentElement.style.removeProperty("--dnf-detail-rem");
}
function hideBrokenImage(event: Event) {
const el = event.target;
if (el instanceof HTMLImageElement) {
el.style.visibility = "hidden";
}
}
function videoUrl(path?: string) {
if (!path) return "";
return encodeURI(`https://skillvideo.cdn.dnf.qq.com/${path}.mp4`);
}
watch(() => route.query.roleType, syncRoleFromRoute, { immediate: true });
watch(activeRoleType, scrollActiveRoleIntoView);
watch(isMobile, scrollActiveRoleIntoView);
watch(activePerson, () => {
activeSkill.value = 0;
activeSkillSlide.value = 0;
activeBreakSlide.value = 0;
});
watch(vpSkills, () => {
if (activeSkill.value >= vpSkills.value.length) {
activeSkill.value = 0;
}
activeSkillSlide.value = 0;
activeBreakSlide.value = 0;
});
watch(currentOptions, () => {
if (activeSkillSlide.value >= skillSlideCount.value) {
activeSkillSlide.value = 0;
}
});
watch(breakSkills, () => {
if (activeBreakSlide.value >= breakSkills.value.length) {
activeBreakSlide.value = 0;
}
});
onMounted(() => {
previousTitle = document.title;
previousRootFontSize = document.documentElement.style.fontSize;
document.title = "雾神武器系统站-DNF官网-腾讯游戏";
syncViewport();
window.addEventListener("resize", syncViewport);
});
onBeforeUnmount(() => {
window.removeEventListener("resize", syncViewport);
document.documentElement.style.fontSize = previousRootFontSize;
document.documentElement.style.removeProperty("--dnf-detail-rem");
document.title = previousTitle;
});
</script>
<template>
<main class="vp-detail" :class="{ mobile: isMobile }" :style="{ backgroundImage: bgUrl }">
<header class="detail-header">
<a class="detail-logo" href="https://dnf.qq.com/main.shtml" target="_blank" aria-label="DNF 官网"></a>
<button class="back-button" type="button" @click="backHome">返回首页</button>
</header>
<Transition name="person-swap" mode="out-in">
<img class="person" :key="personKey" :src="personUrl" :alt="currentPerson.peoName" @error="hideBrokenImage" />
</Transition>
<section class="mobile-info">
<Transition name="name-swap" mode="out-in">
<p class="person-name" :key="personKey">{{ currentPerson.peoName }}</p>
</Transition>
<div class="tab-buttons">
<button :class="{ active: activeTab === 'skill' }" type="button" @click="changeTab('skill')">技能进化</button>
<button :class="{ active: activeTab === 'break' }" type="button" @click="changeTab('break')">雾神突破</button>
</div>
</section>
<div class="avatar-wheel" v-if="personIndexes.length > 1">
<button
v-for="index in personIndexes"
:key="index"
class="avatar"
:class="{ active: activePerson === index, five: personIndexes.length > 4 }"
type="button"
@click="activePerson = index"
>
<img class="avatar-normal" :src="`${asset}/${currentRole.roleType}/${index + 1}/avatar1.png`" alt="" @error="hideBrokenImage" />
<img class="avatar-active" :src="`${asset}/${currentRole.roleType}/${index + 1}/avatar2.png`" alt="" @error="hideBrokenImage" />
</button>
</div>
<aside class="role-select">
<button class="mobile-role-arrow mobile-role-prev" type="button" aria-label="上一个职业" @click="stepRole(-1)"></button>
<div ref="roleTrackRef" class="role-track">
<button
v-for="(role, index) in roles"
:key="role.roleType"
class="role-option"
:class="{ active: role.roleType === currentRole.roleType }"
type="button"
@click="changeRole(role.roleType)"
>
<span class="role-icon" :style="{ '--i': index }"></span>
<span>{{ role.title }}</span>
</button>
</div>
<button class="mobile-role-arrow mobile-role-next" type="button" aria-label="下一个职业" @click="stepRole(1)"></button>
</aside>
<section class="skill-panel" :class="{ show: mobilePanelOpen || !isMobile }">
<div class="panel-inner">
<button class="panel-close" type="button" @click="mobilePanelOpen = false">关闭</button>
<div class="tab-buttons desktop-tabs">
<button :class="{ active: activeTab === 'skill' }" type="button" @click="changeTab('skill')">技能进化</button>
<button :class="{ active: activeTab === 'break' }" type="button" @click="changeTab('break')">雾神突破</button>
</div>
<p class="tips" v-if="activeTab === 'skill'">核心进化点:可在每个 VP 技能的两种进化路线中选择一种,改变技能形态与操作手感。</p>
<p class="tips" v-else>通过雾神突破强化 3 个技能,可选择攻击力增加或攻击力/冷却时间组合强化。</p>
<div v-if="activeTab === 'skill'" class="tab-main tab-skill" :style="{ backgroundImage: panelBgUrl }">
<div class="skill-tabs">
<button
v-for="(skill, index) in vpSkills"
:key="skill.skillName"
:class="{ active: activeSkill === index }"
type="button"
@click="changeSkill(index)"
>
{{ skill.lev }}
<span>{{ skill.skillName }}</span>
</button>
</div>
<div class="skill-cards">
<article class="skill-card" :class="{ active: activeSkillSlide === 0 }">
<div class="skill-title">
<img :src="`${asset}/${currentRole.roleType}/${activePerson + 1}/vp/skill${activeSkill}.png`" alt="" @error="hideBrokenImage" />
<strong>{{ currentVpSkill.skillName }}</strong>
</div>
<div class="video-thumb">
<video v-if="currentVpSkill.videoUrl" :src="videoUrl(currentVpSkill.videoUrl)" muted controls playsinline preload="metadata" />
<img v-else :src="`${asset}/img1.png`" alt="" />
</div>
</article>
<article
v-for="(option, optionIndex) in currentOptions"
:key="optionIndex"
class="skill-card"
:class="{ active: activeSkillSlide === optionIndex + 1 }"
>
<div class="skill-title">
<img
:src="`${asset}/${currentRole.roleType}/${activePerson + 1}/skillBreak/${optionIndex === 0 ? 'skill' : 'skill-'}${activeSkill}.png`"
alt=""
@error="hideBrokenImage"
/>
<strong>{{ option.txt1 }}</strong>
</div>
<p v-if="option.txt2" class="option-summary" v-html="option.txt2"></p>
<div class="video-thumb">
<video v-if="option.videoUrl" :src="videoUrl(option.videoUrl)" muted controls playsinline preload="metadata" />
<img v-else :src="`${asset}/img1.png`" alt="" />
</div>
<p v-if="option.txt3" v-html="option.txt3"></p>
</article>
</div>
<div class="mobile-slide-controls" v-if="skillSlideCount > 1">
<button class="slide-arrow slide-prev" type="button" aria-label="上一张技能卡" @click="stepSkillSlide(-1)"></button>
<span>{{ activeSkillSlide + 1 }} / {{ skillSlideCount }}</span>
<button class="slide-arrow slide-next" type="button" aria-label="下一张技能卡" @click="stepSkillSlide(1)"></button>
</div>
</div>
<div v-else class="tab-main tab-break" :style="{ backgroundImage: panelBgUrl }">
<article
v-for="({ skill, source, sourceIndex }, index) in breakSkills"
:key="`${skill.type}-${skill.skillName}-${index}`"
class="break-card"
:class="{ active: activeBreakSlide === index }"
>
<p class="level">{{ skill.lev }}级</p>
<div class="break-icon">
<img
:src="`${asset}/${currentRole.roleType}/${activePerson + 1}/${source}/skill${sourceIndex}.png`"
alt=""
@error="hideBrokenImage"
/>
<span>{{ skill.skillName }}</span>
</div>
<div class="break-info">
<p>{{ skill.lev < 35 ? "技能攻击力 +55%" : "技能攻击力 +60%" }}</p>
<p>技能攻击力 +{{ skill.lev < 35 ? "38" : "43" }}% / 冷却时间 -15%</p>
</div>
</article>
<div class="mobile-slide-controls" v-if="breakSkills.length > 1">
<button class="slide-arrow slide-prev" type="button" aria-label="上一个突破技能" @click="stepBreakSlide(-1)"></button>
<span>{{ activeBreakSlide + 1 }} / {{ breakSkills.length }}</span>
<button class="slide-arrow slide-next" type="button" aria-label="下一个突破技能" @click="stepBreakSlide(1)"></button>
</div>
</div>
<a class="recommend-link" :href="recommendUrl" target="_blank">COLG 加点推荐 >></a>
<p class="mod-tips">* 具体数据请以正式服上线后为准</p>
</div>
</section>
</main>
</template>
<style scoped>
@font-face {
font-family: dnf-vp;
src: url("https://game.gtimg.cn/images/dnf/cp/a20250612vp/font.ttf");
}
:global(body:has(.vp-detail)) {
overflow: hidden;
background: #000;
}
.vp-detail {
position: fixed;
inset: 0;
z-index: 9999;
overflow: hidden;
background-color: #242220;
background-position: 50% 50%;
background-repeat: no-repeat;
background-size: cover;
color: #fff;
font-family: "Microsoft Yahei", Arial, sans-serif;
}
.detail-header {
position: relative;
z-index: 10;
}
.detail-logo {
position: absolute;
top: 42rem;
left: 44rem;
display: block;
width: 397rem;
height: 65rem;
background: url("https://game.gtimg.cn/images/dnf/cp/a20250612vp/logo.png") center / 100% 100% no-repeat;
}
.back-button {
position: absolute;
top: 44rem;
right: 58rem;
width: 194rem;
height: 45rem;
color: #111114;
font-size: 18rem;
line-height: 45rem;
text-align: center;
cursor: pointer;
background: url("https://game.gtimg.cn/images/dnf/cp/a20250612vp/m/back-bg.png") center / 100% 100% no-repeat;
border: 0;
}
.person {
position: absolute;
right: 0;
bottom: 0;
height: 100%;
user-select: none;
will-change: opacity, transform;
}
.person-swap-enter-active,
.person-swap-leave-active {
transition: opacity 0.42s ease, transform 0.62s cubic-bezier(0.16, 1, 0.3, 1), filter 0.42s ease;
}
.person-swap-enter-from {
opacity: 0;
filter: blur(2px);
transform: translate3d(90rem, 0, 0) scale(1.018);
}
.person-swap-leave-to {
opacity: 0;
filter: blur(2px);
transform: translate3d(-42rem, 0, 0) scale(0.992);
}
.person-swap-enter-to,
.person-swap-leave-from {
opacity: 1;
filter: blur(0);
transform: translate3d(0, 0, 0) scale(1);
}
.avatar-wheel {
position: absolute;
right: 0;
bottom: 0;
width: 375rem;
height: 313rem;
background: url("https://game.gtimg.cn/images/dnf/cp/a20250612vp/zhuanpan.png") center / 100% 100% no-repeat;
}
.avatar {
position: absolute;
width: 119rem;
height: 119rem;
padding: 0;
cursor: pointer;
background: transparent;
border: 0;
border-radius: 999px;
}
.avatar img {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
transition: width 0.26s ease, height 0.26s ease, opacity 0.26s ease, transform 0.26s ease;
}
.avatar .avatar-active,
.avatar.active .avatar-normal {
width: 0;
height: 0;
opacity: 0;
}
.avatar.active .avatar-active {
width: 100%;
height: 100%;
opacity: 1;
transform: scale(1.04);
}
.avatar:nth-of-type(1) { left: -34rem; bottom: 23rem; }
.avatar:nth-of-type(2) { left: 24rem; bottom: 122rem; }
.avatar:nth-of-type(3) { left: 112rem; top: -5rem; }
.avatar:nth-of-type(4) { right: 40rem; top: -47rem; }
.avatar:nth-of-type(5) { right: 10rem; top: -55rem; }
.avatar:nth-of-type(1).five { left: -37rem; bottom: 6rem; }
.avatar:nth-of-type(2).five { left: 2rem; bottom: 95rem; }
.avatar:nth-of-type(3).five { top: auto; left: 70rem; bottom: 168rem; }
.avatar:nth-of-type(4).five { right: 110rem; top: -34rem; }
.role-select {
position: absolute;
top: 50%;
left: 45rem;
z-index: 8;
transform: translateY(-50%);
}
.role-track {
display: block;
}
.mobile-role-arrow {
display: none;
}
.role-option {
display: flex;
align-items: center;
height: 40rem;
margin: -4rem 0;
color: #fff;
font-family: dnf-vp, "Microsoft Yahei", sans-serif;
font-size: 15rem;
cursor: pointer;
background: transparent;
border: 0;
transition: color 0.22s ease, transform 0.22s ease, opacity 0.22s ease;
}
.role-option.active {
color: #20dff5;
transform: translateX(4rem);
}
.role-icon {
display: block;
width: 28rem;
height: 28rem;
margin-right: 12rem;
background: url("https://game.gtimg.cn/images/dnf/cp/a20250612vp/sp.png") 0 calc(var(--i) * -35rem) / 28rem 587rem no-repeat;
}
.role-option.active .role-icon {
width: 40rem;
height: 40rem;
margin-right: 6rem;
background-image: url("https://game.gtimg.cn/images/dnf/cp/a20250612vp/sp2.png");
background-position: 0 calc(var(--i) * -41.6rem);
background-size: 40rem 707rem;
}
.skill-panel {
position: absolute;
top: 150rem;
left: 280rem;
z-index: 9;
}
.panel-close,
.mobile-info {
display: none;
}
.tab-buttons {
display: flex;
align-items: center;
gap: 15rem;
}
.tab-buttons button {
display: block;
width: 228rem;
height: 61rem;
cursor: pointer;
background: url("https://game.gtimg.cn/images/dnf/cp/a20250612vp/tab1.png") center / 100% 100% no-repeat;
border: 0;
font-size: 0;
}
.tab-buttons button.active {
background-image: url("https://game.gtimg.cn/images/dnf/cp/a20250612vp/tab1-1.png");
}
.tab-buttons button:nth-child(2) {
background-image: url("https://game.gtimg.cn/images/dnf/cp/a20250612vp/tab2.png");
}
.tab-buttons button:nth-child(2).active {
background-image: url("https://game.gtimg.cn/images/dnf/cp/a20250612vp/tab2-1.png");
}
.tips {
display: flex;
align-items: center;
width: 1104rem;
height: 42rem;
margin: 0;
color: #fff;
font-size: 14rem;
}
.tab-main {
width: 1104rem;
height: 578rem;
overflow: hidden;
}
.tab-skill {
padding: 25rem 35rem 0;
background: url("https://game.gtimg.cn/images/dnf/cp/a20250612vp/bg1.png") 0 0 / 100% no-repeat;
}
.skill-tabs {
display: flex;
flex-wrap: wrap;
align-content: flex-start;
align-items: center;
height: 97rem;
padding: 0 15rem;
background: url("https://game.gtimg.cn/images/dnf/cp/a20250612vp/line.png") center bottom / auto 2rem no-repeat;
}
.skill-tabs button {
display: flex;
align-items: center;
justify-content: center;
width: 188rem;
height: 35rem;
margin: 0 0 9rem;
color: #f0fefd;
font-size: 15rem;
line-height: 1;
cursor: pointer;
background: url("https://game.gtimg.cn/images/dnf/cp/a20250612vp/icon1.png") center / 100% 100% no-repeat;
border: 0;
}
.skill-tabs button.active {
color: #101b38;
background-image: url("https://game.gtimg.cn/images/dnf/cp/a20250612vp/icon2.png");
}
.skill-tabs span {
max-width: 130rem;
margin-left: 5rem;
}
.skill-cards {
display: flex;
flex-wrap: wrap;
height: 425rem;
padding-left: 10rem;
overflow-y: auto;
}
.skill-card {
width: 330rem;
margin-right: 11rem;
color: #202029;
font-size: 14rem;
line-height: 1.4;
}
.skill-title {
display: flex;
align-items: center;
justify-content: center;
height: 92rem;
color: #101b38;
font-family: dnf-vp, "Microsoft Yahei", sans-serif;
font-size: 18rem;
text-align: center;
}
.skill-title img {
display: block;
height: 34rem;
margin-right: 10rem;
}
.video-thumb {
width: 328rem;
height: 195rem;
border: 1rem solid #101b38;
}
.video-thumb img,
.video-thumb video {
display: block;
width: 100%;
height: 100%;
object-fit: cover;
background: #000;
}
.skill-card p {
padding: 0 20rem;
}
.tab-break {
display: flex;
flex-wrap: wrap;
gap: 9rem 14rem;
padding: 25rem;
overflow-y: auto;
background: url("https://game.gtimg.cn/images/dnf/cp/a20250612vp/bg2.png") 0 0 / 100% 100% no-repeat;
}
.break-card {
display: flex;
width: 338rem;
height: 80rem;
color: #101b38;
font-size: 14rem;
background: #fff;
border: 1rem solid #989694;
}
.level {
display: flex;
align-items: center;
justify-content: center;
width: 44rem;
border-right: 1rem solid #989694;
}
.break-icon {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
width: 110rem;
border-right: 1rem solid #989694;
font-family: dnf-vp, "Microsoft Yahei", sans-serif;
line-height: 1;
text-align: center;
}
.break-icon img {
height: 28rem;
}
.break-info {
display: flex;
flex: 1;
flex-direction: column;
justify-content: center;
line-height: 1.2;
}
.break-info p {
margin: 0;
padding: 5rem 8rem;
}
.break-info p + p {
border-top: 1rem solid #989694;
}
.recommend-link {
display: block;
margin-top: 12rem;
color: #1ce8fb;
font-size: 18rem;
}
.mod-tips {
margin-top: 10rem;
color: #fff;
font-size: 14rem;
}
.mobile-slide-controls {
display: none;
}
@media (max-width: 768px) {
.vp-detail {
--r: var(--dnf-detail-rem, 0.52px);
overflow: hidden;
background-position: center top;
background-size: cover;
}
.detail-header {
display: flex;
justify-content: space-between;
padding: calc(60 * var(--r)) calc(26 * var(--r)) 0;
}
.detail-logo,
.back-button {
position: relative;
top: auto;
left: auto;
right: auto;
}
.detail-logo {
width: calc(357 * var(--r));
height: calc(60 * var(--r));
background-image: url("https://game.gtimg.cn/images/dnf/cp/a20250612vp/m/logo.png");
}
.back-button {
width: calc(194 * var(--r));
height: calc(45 * var(--r));
font-size: calc(18 * var(--r));
line-height: calc(45 * var(--r));
}
.person {
top: 0;
right: auto;
left: 50%;
z-index: 1;
width: calc(750 * var(--r));
height: calc(1624 * var(--r));
margin-left: calc(-375 * var(--r));
object-fit: contain;
}
.person-swap-enter-from {
transform: translate3d(calc(34 * var(--r)), 0, 0) scale(1.02);
}
.person-swap-leave-to {
transform: translate3d(calc(-24 * var(--r)), 0, 0) scale(0.99);
}
.mobile-info {
position: absolute;
bottom: calc(404 * var(--r));
left: 0;
z-index: 8;
display: block;
width: 100%;
pointer-events: none;
}
.person-name {
width: auto;
min-width: calc(313 * var(--r));
height: calc(54 * var(--r));
margin: 0 auto;
padding: 0 calc(28 * var(--r));
color: #fff;
font-family: dnf-vp, "Microsoft Yahei", sans-serif;
font-size: calc(28 * var(--r));
font-style: italic;
line-height: calc(54 * var(--r));
text-align: center;
text-shadow: 0 2px 8px rgb(0 0 0 / 75%);
background: none;
}
.name-swap-enter-active,
.name-swap-leave-active {
transition: opacity 0.28s ease, transform 0.32s ease;
}
.name-swap-enter-from {
opacity: 0;
transform: translateY(calc(14 * var(--r)));
}
.name-swap-leave-to {
opacity: 0;
transform: translateY(calc(-8 * var(--r)));
}
.name-swap-enter-to,
.name-swap-leave-from {
opacity: 1;
transform: translateY(0);
}
.mobile-info .tab-buttons {
justify-content: center;
gap: calc(15 * var(--r));
margin-top: calc(30 * var(--r));
pointer-events: auto;
}
.mobile-info .tab-buttons button {
width: calc(322 * var(--r));
height: calc(80 * var(--r));
background-image: url("https://game.gtimg.cn/images/dnf/cp/a20250612vp/m/tab1-b.png");
}
.mobile-info .tab-buttons button.active {
background-image: url("https://game.gtimg.cn/images/dnf/cp/a20250612vp/m/tab1-1-b.png");
}
.mobile-info .tab-buttons button:nth-child(2) {
background-image: url("https://game.gtimg.cn/images/dnf/cp/a20250612vp/m/tab2-b.png");
}
.mobile-info .tab-buttons button:nth-child(2).active {
background-image: url("https://game.gtimg.cn/images/dnf/cp/a20250612vp/m/tab2-1-b.png");
}
.avatar-wheel {
bottom: calc(198 * var(--r));
left: 0;
z-index: 6;
width: 100%;
height: calc(119 * var(--r));
text-align: center;
background: none;
}
.avatar-wheel::before {
position: absolute;
top: calc(58 * var(--r));
left: 0;
width: 100%;
height: calc(4 * var(--r));
content: "";
background: #13e6ff;
}
.avatar {
position: relative;
inset: auto !important;
display: inline-block;
width: calc(119 * var(--r));
height: calc(119 * var(--r));
vertical-align: top;
transition: transform 0.3s cubic-bezier(0.16, 1, 0.3, 1), filter 0.3s ease;
}
.avatar:not(.active) {
transform: scale(0.82);
filter: saturate(0.85) brightness(0.88);
}
.avatar.active {
transform: scale(1.08);
filter: none;
}
.role-select {
top: auto;
right: calc(28 * var(--r));
bottom: calc(34 * var(--r));
left: calc(28 * var(--r));
z-index: 7;
display: flex;
align-items: flex-start;
height: calc(88 * var(--r));
overflow: visible;
transform: none;
border-bottom: 1px solid rgb(255 255 255 / 42%);
scrollbar-width: none;
}
.role-select::-webkit-scrollbar {
display: none;
}
.role-track {
display: flex;
flex: 1;
height: calc(78 * var(--r));
overflow-x: auto;
overflow-y: visible;
scroll-behavior: smooth;
scroll-snap-type: x proximity;
scrollbar-width: none;
}
.role-track::-webkit-scrollbar {
display: none;
}
.role-option {
position: relative;
flex: 0 0 calc(174 * var(--r));
justify-content: center;
height: calc(62 * var(--r));
margin: 0;
padding: 0;
color: #f4f2f2;
font-size: calc(24 * var(--r));
text-shadow: 0 2px 6px rgb(0 0 0 / 80%);
background: transparent;
border: 0;
scroll-snap-align: center;
transform: none;
}
.role-option.active {
color: #101013;
text-shadow: none;
background: url("https://game.gtimg.cn/images/dnf/cp/a20250612vp/m/name-bg2.png") center / 100% 100% no-repeat;
transform: translateY(calc(-4 * var(--r)));
}
.role-option.active::after {
position: absolute;
right: calc(18 * var(--r));
bottom: calc(-20 * var(--r));
left: calc(18 * var(--r));
height: calc(7 * var(--r));
content: "";
background: #1be8fc;
}
.mobile-role-arrow {
position: relative;
z-index: 2;
display: block;
flex: 0 0 calc(36 * var(--r));
width: calc(36 * var(--r));
height: calc(62 * var(--r));
padding: 0;
cursor: pointer;
background: transparent;
border: 0;
}
.mobile-role-arrow::before {
position: absolute;
top: 50%;
width: calc(18 * var(--r));
height: calc(30 * var(--r));
margin-top: calc(-15 * var(--r));
content: "";
background: center / 100% 100% no-repeat;
}
.mobile-role-prev::before {
left: 0;
background-image: url("https://game.gtimg.cn/images/dnf/cp/a20250612vp/m/icon-left.png");
}
.mobile-role-next::before {
right: 0;
background-image: url("https://game.gtimg.cn/images/dnf/cp/a20250612vp/m/icon-right.png");
}
.role-icon {
display: none;
}
.skill-panel {
position: fixed;
inset: 0;
z-index: 20;
pointer-events: none;
opacity: 0;
transform: translateY(100%);
transition: 0.35s;
}
.skill-panel.show {
pointer-events: auto;
opacity: 1;
transform: translateY(0);
}
.panel-inner {
position: absolute;
bottom: 0;
left: 0;
width: calc(750 * var(--r));
height: calc(1100 * var(--r));
padding-top: calc(40 * var(--r));
background: url("https://game.gtimg.cn/images/dnf/cp/a20250612vp/m/dia-bg.png") top / 100% auto no-repeat;
}
.panel-close {
position: absolute;
top: calc(-83 * var(--r));
right: calc(48 * var(--r));
display: block;
width: calc(62 * var(--r));
height: calc(62 * var(--r));
overflow: hidden;
text-indent: -999px;
background: url("https://game.gtimg.cn/images/dnf/cp/a20250612vp/m/dia-close.png") center / 100% 100% no-repeat;
border: 0;
}
.desktop-tabs {
justify-content: space-between;
width: calc(676 * var(--r));
margin: 0 auto;
}
.desktop-tabs button {
width: calc(326 * var(--r));
height: calc(85 * var(--r));
background-image: url("https://game.gtimg.cn/images/dnf/cp/a20250612vp/m/tab1.png");
}
.desktop-tabs button.active {
background-image: url("https://game.gtimg.cn/images/dnf/cp/a20250612vp/m/tab1-1.png");
}
.desktop-tabs button:nth-child(2) {
background-image: url("https://game.gtimg.cn/images/dnf/cp/a20250612vp/m/tab2.png");
}
.desktop-tabs button:nth-child(2).active {
background-image: url("https://game.gtimg.cn/images/dnf/cp/a20250612vp/m/tab2-1.png");
}
.tips {
width: calc(700 * var(--r));
height: calc(85 * var(--r));
margin: 0 auto;
color: #101b38;
font-size: calc(20 * var(--r));
line-height: 1.3;
}
.tab-main {
position: relative;
width: calc(700 * var(--r));
height: calc(810 * var(--r));
margin: 0 auto;
}
.tab-skill {
padding: 0;
overflow-y: auto;
background: none;
border-top: 1px solid #777;
}
.skill-tabs {
height: calc(220 * var(--r));
padding-top: calc(8 * var(--r));
background: none;
}
.skill-tabs button {
width: calc(233 * var(--r));
height: calc(53 * var(--r));
font-size: calc(20 * var(--r));
background-image: url("https://game.gtimg.cn/images/dnf/cp/a20250612vp/m/icon1.png");
}
.skill-tabs button.active {
background-image: url("https://game.gtimg.cn/images/dnf/cp/a20250612vp/m/icon2.png");
}
.skill-cards {
display: block;
height: auto;
padding: 0;
overflow: visible;
}
.skill-card {
display: none;
width: 100%;
margin: 0;
font-size: calc(24 * var(--r));
}
.skill-card.active {
display: block;
}
.skill-title {
height: calc(100 * var(--r));
font-size: calc(26 * var(--r));
}
.skill-title img {
height: calc(45 * var(--r));
}
.video-thumb {
width: calc(586 * var(--r));
height: calc(400 * var(--r));
margin: calc(10 * var(--r)) auto 0;
}
.tab-break {
display: block;
height: calc(830 * var(--r));
padding: calc(25 * var(--r)) 0 0;
overflow-y: auto;
background: none;
border-top: 3px solid #101b38;
border-bottom: 1px solid #777;
}
.break-card {
display: none;
width: calc(594 * var(--r));
height: calc(145 * var(--r));
margin: 0 auto calc(12 * var(--r));
font-size: calc(22 * var(--r));
}
.break-card.active {
display: flex;
}
.level {
width: calc(67 * var(--r));
}
.break-icon {
width: calc(156 * var(--r));
}
.break-icon img {
height: calc(48 * var(--r));
}
.recommend-link,
.mod-tips {
display: none;
}
.mobile-slide-controls {
position: absolute;
right: 0;
bottom: calc(16 * var(--r));
left: 0;
z-index: 2;
display: flex;
align-items: center;
justify-content: center;
gap: calc(26 * var(--r));
color: #101b38;
font-family: dnf-vp, "Microsoft Yahei", sans-serif;
font-size: calc(24 * var(--r));
pointer-events: none;
}
.slide-arrow {
width: calc(52 * var(--r));
height: calc(52 * var(--r));
padding: 0;
cursor: pointer;
background: rgb(16 27 56 / 88%);
border: 1px solid rgb(28 232 251 / 70%);
pointer-events: auto;
}
.slide-arrow::before {
display: block;
width: calc(16 * var(--r));
height: calc(16 * var(--r));
margin: auto;
content: "";
border-top: calc(4 * var(--r)) solid #1ce8fb;
border-right: calc(4 * var(--r)) solid #1ce8fb;
}
.slide-prev::before {
transform: rotate(-135deg);
}
.slide-next::before {
transform: rotate(45deg);
}
}
</style>