|
|
|
|
<script setup lang="ts">
|
|
|
|
|
import { computed, ref, onMounted } from 'vue'
|
|
|
|
|
import { ElProgress } from 'element-plus'
|
|
|
|
|
import { useAuthStore } from '@/stores/auth'
|
|
|
|
|
import { useCharacterStore } from '@/stores/character'
|
|
|
|
|
import { useRouter } from 'vue-router'
|
|
|
|
|
import { defineAsyncComponent } from 'vue'
|
|
|
|
|
import Particles from '@/components/Particles/Particles.vue'
|
|
|
|
|
import GlareHover from '@/components/GlareHover/GlareHover.vue'
|
|
|
|
|
import ChatBox from '@/components/ChatBox.vue'
|
|
|
|
|
import ShinyText from '@/components/ShinyText/ShinyText.vue'
|
|
|
|
|
import StarBorder from '@/components/StarBorder/StarBorder.vue'
|
|
|
|
|
import meditationIcon from '@/assets/images/meditation.svg'
|
|
|
|
|
import missionIcon from '@/assets/images/mission.svg'
|
|
|
|
|
import scrapIcon from '@/assets/images/scrap.svg'
|
|
|
|
|
import bagIcon from '@/assets/images/bag.svg'
|
|
|
|
|
import monsterIcon from '@/assets/images/monster.svg'
|
|
|
|
|
import shopIcon from '@/assets/images/shop.svg'
|
|
|
|
|
import catalogIcon from '@/assets/images/catalog.svg'
|
|
|
|
|
import guidanceIcon from '@/assets/images/guidance.svg'
|
|
|
|
|
|
|
|
|
|
const DailyMissionView = defineAsyncComponent(() => import('@/views/DailyMissionView.vue'))
|
|
|
|
|
const TrainingView = defineAsyncComponent(() => import('@/views/TrainingView.vue'))
|
|
|
|
|
const BagView = defineAsyncComponent(() => import('@/views/BagView.vue'))
|
|
|
|
|
const ScrapView = defineAsyncComponent(() => import('@/views/ScrapView.vue'))
|
|
|
|
|
const ShopView = defineAsyncComponent(() => import('@/views/ShopView.vue'))
|
|
|
|
|
const MonsterListView = defineAsyncComponent(() => import('@/views/MonsterListView.vue'))
|
|
|
|
|
const CatalogView = defineAsyncComponent(() => import('@/views/CatalogView.vue'))
|
|
|
|
|
|
|
|
|
|
const authStore = useAuthStore()
|
|
|
|
|
const characterStore = useCharacterStore()
|
|
|
|
|
const router = useRouter()
|
|
|
|
|
|
|
|
|
|
const isTraining = computed(() => !!characterStore.currentCharacter?.trainingOn)
|
|
|
|
|
|
|
|
|
|
const canBreakthrough = computed(() => characterStore.currentCharacter?.canBreakthrough ?? false)
|
|
|
|
|
const currentLevelName = computed(() => characterStore.currentCharacter?.levelName ?? '')
|
|
|
|
|
const currentExp = computed(() => characterStore.currentCharacter?.currentExp ?? 0)
|
|
|
|
|
const nextLevelName = computed(() => characterStore.currentCharacter?.nextLevelName ?? '')
|
|
|
|
|
const nextLevelMinExp = computed(() => characterStore.currentCharacter?.nextLevelMinExp ?? 0)
|
|
|
|
|
const breakthroughRate = computed(() => characterStore.currentCharacter?.breakthroughRate ?? 0)
|
|
|
|
|
const requiredPillName = computed(() => characterStore.currentCharacter?.nextLevelRequiredPillName ?? '')
|
|
|
|
|
const requiredPillQuantity = computed(() => characterStore.currentCharacter?.nextLevelRequiredPillQuantity ?? 0)
|
|
|
|
|
const hasPillCost = computed(() => requiredPillQuantity.value > 0 && !!requiredPillName.value)
|
|
|
|
|
|
|
|
|
|
const expProgress = computed(() => {
|
|
|
|
|
if (!nextLevelMinExp.value) return 0
|
|
|
|
|
return Math.min(100, (currentExp.value / nextLevelMinExp.value) * 100)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
const breakthroughMessage = ref('')
|
|
|
|
|
const showBreakthroughMessage = ref(false)
|
|
|
|
|
|
|
|
|
|
//默认选中第一个
|
|
|
|
|
const activePanel = ref<string>('mission')
|
|
|
|
|
|
|
|
|
|
const panelComponents = {
|
|
|
|
|
'mission': DailyMissionView,
|
|
|
|
|
'training': TrainingView,
|
|
|
|
|
'bag': BagView,
|
|
|
|
|
'scrap': ScrapView,
|
|
|
|
|
'shop': ShopView,
|
|
|
|
|
'monster': MonsterListView,
|
|
|
|
|
'catalog': CatalogView
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const currentPanelComponent = computed(() => {
|
|
|
|
|
return panelComponents[activePanel.value as keyof typeof panelComponents] || null
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
const menuItems = computed(() => [
|
|
|
|
|
{ id: 'mission', label: '任务', icon: missionIcon, useImage: true },
|
|
|
|
|
{ id: 'training', label: isTraining.value ? '打坐中' : '打坐', icon: meditationIcon, useImage: true, isTraining: isTraining.value, isActive: activePanel.value === 'training' },
|
|
|
|
|
{ id: 'bag', label: '背包', icon: bagIcon, useImage: true },
|
|
|
|
|
{ id: 'scrap', label: '捡垃圾', icon: scrapIcon, useImage: true },
|
|
|
|
|
{ id: 'shop', label: '商店', icon: shopIcon, useImage: true },
|
|
|
|
|
{ id: 'monster', label: '挑战', icon: monsterIcon, useImage: true },
|
|
|
|
|
{ id: 'guide', label: '指南', icon: guidanceIcon, useImage: true },
|
|
|
|
|
{ id: 'catalog', label: '图鉴', icon: catalogIcon, useImage: true }
|
|
|
|
|
])
|
|
|
|
|
|
|
|
|
|
const formatNumber = (num: number) => {
|
|
|
|
|
return Math.floor(num).toLocaleString('zh-CN')
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const handleLogout = () => {
|
|
|
|
|
authStore.logout()
|
|
|
|
|
characterStore.clearCurrentCharacter()
|
|
|
|
|
window.location.href = '/login'
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const handleSwitchCharacter = () => {
|
|
|
|
|
window.location.href = '/character'
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const navigateToTutorial = () => {
|
|
|
|
|
router.push('/tutorial')
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const navigateTo = (item: { id: string; label: string }) => {
|
|
|
|
|
if (item.id === 'training') {
|
|
|
|
|
activePanel.value = 'training'
|
|
|
|
|
} else if (item.id === 'mission') {
|
|
|
|
|
activePanel.value = 'mission'
|
|
|
|
|
} else if (item.id === 'bag') {
|
|
|
|
|
activePanel.value = 'bag'
|
|
|
|
|
} else if (item.id === 'scrap') {
|
|
|
|
|
activePanel.value = 'scrap'
|
|
|
|
|
} else if (item.id === 'monster') {
|
|
|
|
|
activePanel.value = 'monster'
|
|
|
|
|
} else if (item.id === 'shop') {
|
|
|
|
|
activePanel.value = 'shop'
|
|
|
|
|
} else if (item.id === 'catalog') {
|
|
|
|
|
activePanel.value = 'catalog'
|
|
|
|
|
} else if (item.id === 'guide') {
|
|
|
|
|
router.push('/tutorial')
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const handleBreakthrough = async () => {
|
|
|
|
|
const result = await characterStore.breakthrough()
|
|
|
|
|
breakthroughMessage.value = result.message
|
|
|
|
|
showBreakthroughMessage.value = true
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
showBreakthroughMessage.value = false
|
|
|
|
|
}, 3000)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const goBack = () => {
|
|
|
|
|
activePanel.value = 'welcome'
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
onMounted(async () => {
|
|
|
|
|
await characterStore.fetchCharacters()
|
|
|
|
|
const current = characterStore.characters.find(c => c.id === characterStore.currentCharacter?.id)
|
|
|
|
|
if (current) {
|
|
|
|
|
characterStore.currentCharacter = current
|
|
|
|
|
localStorage.setItem('current_character', JSON.stringify(current))
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
<template>
|
|
|
|
|
<div class="game-page">
|
|
|
|
|
<Particles :particle-count="50" :particle-colors="['#ffffff', '#cccccc']" class="particles-bg" />
|
|
|
|
|
|
|
|
|
|
<div class="top-log">
|
|
|
|
|
<ShinyText text="✨ 欢迎来到我的世界" :speed="2" :delay="0.5" :disabled="false" :color="'#b5b5b5'" :shine-color="'#34fef1'"
|
|
|
|
|
:spread="120" :direction="'left'" :yoyo="false" :pause-on-hover="false" />
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div class="game-shell">
|
|
|
|
|
<aside class="character-sidebar">
|
|
|
|
|
<div class="sidebar-card">
|
|
|
|
|
<div class="sidebar-header">
|
|
|
|
|
<div class="sidebar-title-block">
|
|
|
|
|
<span class="sidebar-char-name">{{ characterStore.currentCharacter?.name || '未选择角色' }}</span>
|
|
|
|
|
<span class="sidebar-char-level">{{ characterStore.currentCharacter?.levelName || '' }}</span>
|
|
|
|
|
</div>
|
|
|
|
|
<button type="button" class="sidebar-switch-btn" @click="handleSwitchCharacter">
|
|
|
|
|
切换角色
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div class="detail-section">
|
|
|
|
|
<div class="section-title">基础信息</div>
|
|
|
|
|
<div class="info-row">
|
|
|
|
|
<span class="info-label">职业</span>
|
|
|
|
|
<span class="info-value">{{ characterStore.currentCharacter?.professionName || '未选择' }}</span>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="info-row">
|
|
|
|
|
<span class="info-label">灵石</span>
|
|
|
|
|
<span class="info-value">{{ formatNumber(characterStore.currentCharacter?.money || 0) }}</span>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div class="detail-section">
|
|
|
|
|
<div class="section-title">属性</div>
|
|
|
|
|
<div class="attr-row">
|
|
|
|
|
<span class="attr-label"><span class="attr-icon hp-icon">❤️</span>生命</span>
|
|
|
|
|
<span class="attr-value">
|
|
|
|
|
<span class="base-value">{{ formatNumber(characterStore.currentCharacter?.baseMaxHP || 0) }}</span>
|
|
|
|
|
<span v-if="(characterStore.currentCharacter?.bonusMaxHP || 0) > 0" class="bonus-value">+{{
|
|
|
|
|
formatNumber(characterStore.currentCharacter?.bonusMaxHP || 0) }}</span>
|
|
|
|
|
</span>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="attr-row">
|
|
|
|
|
<span class="attr-label"><span class="attr-icon atk-icon">⚔️</span>攻击</span>
|
|
|
|
|
<span class="attr-value">
|
|
|
|
|
<span class="base-value">{{ formatNumber(characterStore.currentCharacter?.baseAttack || 0) }}</span>
|
|
|
|
|
<span v-if="(characterStore.currentCharacter?.bonusAttack || 0) > 0" class="bonus-value">+{{
|
|
|
|
|
formatNumber(characterStore.currentCharacter?.bonusAttack || 0) }}</span>
|
|
|
|
|
</span>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="attr-row">
|
|
|
|
|
<span class="attr-label"><span class="attr-icon def-icon">🛡️</span>防御</span>
|
|
|
|
|
<span class="attr-value">
|
|
|
|
|
<span class="base-value">{{ formatNumber(characterStore.currentCharacter?.baseDefend || 0) }}</span>
|
|
|
|
|
<span v-if="(characterStore.currentCharacter?.bonusDefend || 0) > 0" class="bonus-value">+{{
|
|
|
|
|
formatNumber(characterStore.currentCharacter?.bonusDefend || 0) }}</span>
|
|
|
|
|
</span>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="attr-row">
|
|
|
|
|
<span class="attr-label"><span class="attr-icon crit-icon">💥</span>暴击率</span>
|
|
|
|
|
<span class="attr-value">
|
|
|
|
|
<span class="base-value">{{ (characterStore.currentCharacter?.baseCriticalRate || 0).toFixed(1)
|
|
|
|
|
}}%</span>
|
|
|
|
|
<span v-if="(characterStore.currentCharacter?.bonusCriticalRate || 0) > 0" class="bonus-value">+{{
|
|
|
|
|
(characterStore.currentCharacter?.bonusCriticalRate || 0).toFixed(1) }}%</span>
|
|
|
|
|
</span>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div v-if="nextLevelName" class="breakthrough-section">
|
|
|
|
|
<div class="breakthrough-header">
|
|
|
|
|
<span class="breakthrough-title">境界突破</span>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="breakthrough-info">
|
|
|
|
|
<div class="level-info">
|
|
|
|
|
<span class="current-level">{{ currentLevelName }}</span>
|
|
|
|
|
<span class="arrow">→</span>
|
|
|
|
|
<span class="next-level">{{ nextLevelName }}</span>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="exp-info">
|
|
|
|
|
<span class="exp-text">{{ formatNumber(currentExp) }} / {{ formatNumber(nextLevelMinExp) }} 经验</span>
|
|
|
|
|
<el-progress :percentage="Math.floor(expProgress)" color="#ff8844" :show-text="false"
|
|
|
|
|
:stroke-width="8" />
|
|
|
|
|
<span class="exp-percent">{{ Math.floor(expProgress) }}%</span>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="rate-info">
|
|
|
|
|
<span class="rate-label">突破成功率</span>
|
|
|
|
|
<el-progress :percentage="breakthroughRate" color="#22c55e" :show-text="false" :stroke-width="8" />
|
|
|
|
|
<span class="exp-percent">{{ breakthroughRate + `%` }}</span>
|
|
|
|
|
</div>
|
|
|
|
|
<div v-if="hasPillCost" class="pill-cost-info">
|
|
|
|
|
<span class="pill-cost-label">突破消耗</span>
|
|
|
|
|
<span class="pill-cost-value">{{ requiredPillName }} ×{{ requiredPillQuantity }}</span>
|
|
|
|
|
</div>
|
|
|
|
|
<button class="breakthrough-btn" :class="{ 'can-breakthrough': canBreakthrough }"
|
|
|
|
|
:disabled="!canBreakthrough" @click="handleBreakthrough">
|
|
|
|
|
{{ canBreakthrough ? '突破' : '未满足突破条件' }}
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div v-if="showBreakthroughMessage" class="toast-message">
|
|
|
|
|
{{ breakthroughMessage }}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</aside>
|
|
|
|
|
|
|
|
|
|
<div class="chat-panel">
|
|
|
|
|
<ChatBox embedded />
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div class="game-main">
|
|
|
|
|
<div class="main-panel">
|
|
|
|
|
<div v-if="activePanel === 'welcome'" class="welcome-panel">
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div v-else class="panel-content">
|
|
|
|
|
<!-- <div class="panel-header">
|
|
|
|
|
<button class="back-btn" @click="goBack" style="visibility: hidden;">← 返回</button>
|
|
|
|
|
</div> -->
|
|
|
|
|
<component :is="currentPanelComponent" />
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div class="dock-bar">
|
|
|
|
|
<div class="dock-container">
|
|
|
|
|
<div v-for="item in menuItems" :key="item.id" class="dock-item"
|
|
|
|
|
:class="{ 'dock-item--active': activePanel === item.id }" @click="navigateTo(item)">
|
|
|
|
|
<img v-if="item.useImage" :src="item.icon" class="dock-icon" />
|
|
|
|
|
<span class="dock-label">{{ item.label }}</span>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
<style scoped>
|
|
|
|
|
.game-page {
|
|
|
|
|
height: 100vh;
|
|
|
|
|
background: #000000;
|
|
|
|
|
padding: 16px;
|
|
|
|
|
position: relative;
|
|
|
|
|
overflow-x: hidden;
|
|
|
|
|
display: flex;
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
gap: 16px;
|
|
|
|
|
box-sizing: border-box;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.particles-bg {
|
|
|
|
|
position: fixed !important;
|
|
|
|
|
top: 0;
|
|
|
|
|
left: 0;
|
|
|
|
|
width: 100% !important;
|
|
|
|
|
height: 100% !important;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.top-log {
|
|
|
|
|
position: relative;
|
|
|
|
|
z-index: 10;
|
|
|
|
|
text-align: center;
|
|
|
|
|
padding: 12px;
|
|
|
|
|
background: rgba(16, 18, 24, 0.8);
|
|
|
|
|
border: 1px solid rgba(255, 255, 255, 0.1);
|
|
|
|
|
border-radius: 12px;
|
|
|
|
|
width: 100%;
|
|
|
|
|
box-sizing: border-box;
|
|
|
|
|
flex-shrink: 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.game-shell {
|
|
|
|
|
position: relative;
|
|
|
|
|
z-index: 10;
|
|
|
|
|
display: flex;
|
|
|
|
|
flex-direction: row;
|
|
|
|
|
align-items: stretch;
|
|
|
|
|
gap: 16px;
|
|
|
|
|
margin: 0;
|
|
|
|
|
flex: 1;
|
|
|
|
|
min-height: 0;
|
|
|
|
|
width: 100%;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.character-sidebar {
|
|
|
|
|
flex: 0 0 320px;
|
|
|
|
|
min-width: 0;
|
|
|
|
|
display: flex;
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
min-height: 0;
|
|
|
|
|
height: 100%;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.sidebar-card {
|
|
|
|
|
flex: 1;
|
|
|
|
|
min-height: 0;
|
|
|
|
|
overflow-y: auto;
|
|
|
|
|
background: rgba(16, 18, 24, 0.92);
|
|
|
|
|
border: 1px solid rgba(255, 255, 255, 0.1);
|
|
|
|
|
border-radius: 16px;
|
|
|
|
|
padding: 16px;
|
|
|
|
|
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.35);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.sidebar-header {
|
|
|
|
|
display: flex;
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
gap: 12px;
|
|
|
|
|
margin-bottom: 16px;
|
|
|
|
|
padding-bottom: 14px;
|
|
|
|
|
border-bottom: 1px solid rgba(255, 255, 255, 0.08);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.sidebar-title-block {
|
|
|
|
|
display: flex;
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
gap: 4px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.sidebar-char-name {
|
|
|
|
|
color: #ffffff;
|
|
|
|
|
font-size: 1.1rem;
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.sidebar-char-level {
|
|
|
|
|
color: #22c55e;
|
|
|
|
|
font-size: 0.8rem;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.sidebar-switch-btn {
|
|
|
|
|
width: 100%;
|
|
|
|
|
padding: 10px 12px;
|
|
|
|
|
background: rgba(34, 197, 94, 0.12);
|
|
|
|
|
border: 1px solid rgba(34, 197, 94, 0.35);
|
|
|
|
|
border-radius: 10px;
|
|
|
|
|
color: #86efac;
|
|
|
|
|
font-size: 0.85rem;
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
transition: all 0.2s ease;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.sidebar-switch-btn:hover {
|
|
|
|
|
background: rgba(34, 197, 94, 0.2);
|
|
|
|
|
color: #bbf7d0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.detail-section--last {
|
|
|
|
|
margin-bottom: 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.game-main {
|
|
|
|
|
flex: 0 0 480px;
|
|
|
|
|
min-width: 0;
|
|
|
|
|
height: 100%;
|
|
|
|
|
display: flex;
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
min-height: 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.game-container {
|
|
|
|
|
flex: 1;
|
|
|
|
|
min-height: 0;
|
|
|
|
|
overflow-y: auto;
|
|
|
|
|
width: 100%;
|
|
|
|
|
margin: 0;
|
|
|
|
|
padding: 16px;
|
|
|
|
|
position: relative;
|
|
|
|
|
z-index: 10;
|
|
|
|
|
background: rgba(16, 18, 24, 0.92);
|
|
|
|
|
border: 1px solid rgba(255, 255, 255, 0.1);
|
|
|
|
|
border-radius: 16px;
|
|
|
|
|
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.35);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.chat-panel {
|
|
|
|
|
flex: 1;
|
|
|
|
|
min-width: 0;
|
|
|
|
|
height: 100%;
|
|
|
|
|
display: flex;
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
min-height: 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@media (max-width: 900px) {
|
|
|
|
|
.game-shell {
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
height: auto;
|
|
|
|
|
min-height: auto;
|
|
|
|
|
gap: 16px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.character-sidebar {
|
|
|
|
|
flex: none;
|
|
|
|
|
width: 100%;
|
|
|
|
|
max-width: 560px;
|
|
|
|
|
margin: 0 auto;
|
|
|
|
|
height: auto;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.sidebar-card {
|
|
|
|
|
overflow-y: visible;
|
|
|
|
|
flex: none;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.chat-panel {
|
|
|
|
|
flex: none;
|
|
|
|
|
width: 100%;
|
|
|
|
|
max-width: none;
|
|
|
|
|
height: auto;
|
|
|
|
|
min-height: 240px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.game-main {
|
|
|
|
|
flex: none;
|
|
|
|
|
width: 100%;
|
|
|
|
|
height: auto;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.game-container {
|
|
|
|
|
flex: none;
|
|
|
|
|
overflow-y: visible;
|
|
|
|
|
max-width: 560px;
|
|
|
|
|
margin: 0 auto;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.welcome-text {
|
|
|
|
|
font-size: 1.35rem;
|
|
|
|
|
font-weight: 300;
|
|
|
|
|
text-align: center;
|
|
|
|
|
color: #ffffff;
|
|
|
|
|
letter-spacing: 0.1em;
|
|
|
|
|
margin-bottom: 20px;
|
|
|
|
|
font-weight: bold;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.star-border {
|
|
|
|
|
background: transparent;
|
|
|
|
|
border: none;
|
|
|
|
|
padding: 12px 16px;
|
|
|
|
|
display: flex;
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
align-items: center;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.menu-grid {
|
|
|
|
|
display: grid;
|
|
|
|
|
grid-template-columns: repeat(4, 1fr);
|
|
|
|
|
gap: 16px;
|
|
|
|
|
margin-bottom: 32px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.tutorial-btn {
|
|
|
|
|
padding: 8px 20px;
|
|
|
|
|
background: rgba(255, 255, 255, 0.05);
|
|
|
|
|
border: 1px solid rgba(255, 255, 255, 0.1);
|
|
|
|
|
border-radius: 8px;
|
|
|
|
|
color: #888888;
|
|
|
|
|
font-size: 0.85rem;
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
transition: all 0.2s ease;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.tutorial-btn:hover {
|
|
|
|
|
background: rgba(255, 255, 255, 0.1);
|
|
|
|
|
border-color: rgba(255, 255, 255, 0.2);
|
|
|
|
|
color: #ffffff;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.menu-card {
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.menu-card.training-active .menu-label {
|
|
|
|
|
color: #ff8844;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.menu-content {
|
|
|
|
|
display: flex;
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
align-items: center;
|
|
|
|
|
justify-content: center;
|
|
|
|
|
gap: 8px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.menu-icon {
|
|
|
|
|
font-size: 1.75rem;
|
|
|
|
|
opacity: 0.8;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.menu-icon-img {
|
|
|
|
|
width: 3rem;
|
|
|
|
|
height: 3rem;
|
|
|
|
|
object-fit: contain;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.menu-label {
|
|
|
|
|
color: #cccccc;
|
|
|
|
|
font-size: 0.875rem;
|
|
|
|
|
font-weight: 400;
|
|
|
|
|
letter-spacing: 0.1em;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.breakthrough-section {
|
|
|
|
|
background: rgba(255, 255, 255, 0.03);
|
|
|
|
|
border: 1px solid rgba(255, 255, 255, 0.08);
|
|
|
|
|
border-radius: 16px;
|
|
|
|
|
padding: 20px;
|
|
|
|
|
margin-bottom: 24px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.breakthrough-header {
|
|
|
|
|
display: flex;
|
|
|
|
|
justify-content: center;
|
|
|
|
|
margin-bottom: 16px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.breakthrough-title {
|
|
|
|
|
color: #ff8844;
|
|
|
|
|
font-size: 1rem;
|
|
|
|
|
font-weight: 500;
|
|
|
|
|
letter-spacing: 0.1em;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.breakthrough-info {
|
|
|
|
|
display: flex;
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
gap: 12px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.level-info {
|
|
|
|
|
display: flex;
|
|
|
|
|
justify-content: center;
|
|
|
|
|
align-items: center;
|
|
|
|
|
gap: 12px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.current-level {
|
|
|
|
|
color: #888888;
|
|
|
|
|
font-size: 0.9rem;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.arrow {
|
|
|
|
|
color: #555555;
|
|
|
|
|
font-size: 0.9rem;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.next-level {
|
|
|
|
|
color: #ffffff;
|
|
|
|
|
font-size: 0.9rem;
|
|
|
|
|
font-weight: 500;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.exp-info {
|
|
|
|
|
display: flex;
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
gap: 6px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.exp-text {
|
|
|
|
|
color: #666666;
|
|
|
|
|
font-size: 0.8rem;
|
|
|
|
|
text-align: center;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.exp-percent {
|
|
|
|
|
color: #888888;
|
|
|
|
|
font-size: 0.75rem;
|
|
|
|
|
text-align: center;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.rate-info {
|
|
|
|
|
display: flex;
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
gap: 6px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.rate-label {
|
|
|
|
|
color: #666666;
|
|
|
|
|
font-size: 0.75rem;
|
|
|
|
|
white-space: nowrap;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.pill-cost-info {
|
|
|
|
|
display: flex;
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
align-items: center;
|
|
|
|
|
padding: 10px 14px;
|
|
|
|
|
background: rgba(255, 68, 68, 0.1);
|
|
|
|
|
border: 1px solid rgba(255, 68, 68, 0.2);
|
|
|
|
|
border-radius: 8px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.pill-cost-label {
|
|
|
|
|
color: #888888;
|
|
|
|
|
font-size: 0.85rem;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.pill-cost-value {
|
|
|
|
|
color: #ff4444;
|
|
|
|
|
font-size: 0.9rem;
|
|
|
|
|
font-weight: 500;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.breakthrough-btn {
|
|
|
|
|
width: 100%;
|
|
|
|
|
padding: 14px;
|
|
|
|
|
border-radius: 10px;
|
|
|
|
|
font-size: 0.95rem;
|
|
|
|
|
font-weight: 500;
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
transition: all 0.3s ease;
|
|
|
|
|
border: none;
|
|
|
|
|
background: rgba(255, 255, 255, 0.05);
|
|
|
|
|
color: #555555;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.breakthrough-btn:disabled {
|
|
|
|
|
cursor: not-allowed;
|
|
|
|
|
opacity: 0.5;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.breakthrough-btn.can-breakthrough {
|
|
|
|
|
background: linear-gradient(135deg, #ff8844 0%, #ff6644 100%);
|
|
|
|
|
color: #ffffff;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.breakthrough-btn.can-breakthrough:hover {
|
|
|
|
|
transform: scale(1.02);
|
|
|
|
|
box-shadow: 0 4px 20px rgba(255, 136, 68, 0.4);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.toast-message {
|
|
|
|
|
position: fixed;
|
|
|
|
|
top: 80px;
|
|
|
|
|
left: 50%;
|
|
|
|
|
transform: translateX(-50%);
|
|
|
|
|
background: rgba(255, 255, 255, 0.9);
|
|
|
|
|
color: #000000;
|
|
|
|
|
padding: 12px 24px;
|
|
|
|
|
border-radius: 8px;
|
|
|
|
|
font-size: 0.9rem;
|
|
|
|
|
z-index: 1000;
|
|
|
|
|
animation: fadeIn 0.3s ease;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@keyframes fadeIn {
|
|
|
|
|
from {
|
|
|
|
|
opacity: 0;
|
|
|
|
|
transform: translateX(-50%) translateY(-10px);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
to {
|
|
|
|
|
opacity: 1;
|
|
|
|
|
transform: translateX(-50%) translateY(0);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.logout-button {
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
justify-content: center;
|
|
|
|
|
margin: 0 auto;
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
width: 200px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.page-footer {
|
|
|
|
|
margin-top: 32px;
|
|
|
|
|
display: flex;
|
|
|
|
|
justify-content: center;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.btn-out {
|
|
|
|
|
display: flex;
|
|
|
|
|
justify-content: center;
|
|
|
|
|
align-items: center;
|
|
|
|
|
background: rgba(255, 255, 255, 0.03);
|
|
|
|
|
border: 1px dashed red;
|
|
|
|
|
border-radius: 15px;
|
|
|
|
|
padding: 12px 16px;
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
width: 200px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.logout-text {
|
|
|
|
|
color: #888888;
|
|
|
|
|
font-size: 0.8rem;
|
|
|
|
|
font-weight: 400;
|
|
|
|
|
letter-spacing: 0.1em;
|
|
|
|
|
transition: color 0.3s ease;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.logout-button:hover .logout-text {
|
|
|
|
|
color: #ffffff;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.detail-section {
|
|
|
|
|
margin-bottom: 16px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.section-title {
|
|
|
|
|
color: #22c55e;
|
|
|
|
|
font-size: 0.9rem;
|
|
|
|
|
font-weight: 500;
|
|
|
|
|
margin-bottom: 12px;
|
|
|
|
|
letter-spacing: 0.05em;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.info-row {
|
|
|
|
|
display: flex;
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
padding: 8px 0;
|
|
|
|
|
border-bottom: 1px solid rgba(255, 255, 255, 0.05);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.info-label {
|
|
|
|
|
color: #888888;
|
|
|
|
|
font-size: 0.85rem;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.info-value {
|
|
|
|
|
color: #ffffff;
|
|
|
|
|
font-size: 0.85rem;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.attr-row {
|
|
|
|
|
display: flex;
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
align-items: center;
|
|
|
|
|
padding: 10px 0;
|
|
|
|
|
border-bottom: 1px solid rgba(255, 255, 255, 0.05);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.attr-label {
|
|
|
|
|
color: #888888;
|
|
|
|
|
font-size: 0.85rem;
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
gap: 6px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.attr-icon {
|
|
|
|
|
font-size: 0.9rem;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.hp-icon {
|
|
|
|
|
color: #ef4444;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.atk-icon {
|
|
|
|
|
color: #f97316;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.def-icon {
|
|
|
|
|
color: #3b82f6;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.crit-icon {
|
|
|
|
|
color: #eab308;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.attr-value {
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: baseline;
|
|
|
|
|
gap: 4px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.base-value {
|
|
|
|
|
color: #ffffff;
|
|
|
|
|
font-size: 1rem;
|
|
|
|
|
font-weight: 500;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.bonus-value {
|
|
|
|
|
color: #fbbf24;
|
|
|
|
|
font-size: 0.8rem;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.exp-row {
|
|
|
|
|
display: flex;
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
padding: 8px 0;
|
|
|
|
|
border-bottom: 1px solid rgba(255, 255, 255, 0.05);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.exp-label {
|
|
|
|
|
color: #888888;
|
|
|
|
|
font-size: 0.85rem;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.exp-value {
|
|
|
|
|
color: #cccccc;
|
|
|
|
|
font-size: 0.85rem;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Main Panel */
|
|
|
|
|
.main-panel {
|
|
|
|
|
flex: 1;
|
|
|
|
|
min-height: 0;
|
|
|
|
|
overflow-y: auto;
|
|
|
|
|
width: 100%;
|
|
|
|
|
margin: 0;
|
|
|
|
|
padding: 16px;
|
|
|
|
|
position: relative;
|
|
|
|
|
z-index: 10;
|
|
|
|
|
background: rgba(16, 18, 24, 0.92);
|
|
|
|
|
border: 1px solid rgba(255, 255, 255, 0.1);
|
|
|
|
|
border-radius: 16px;
|
|
|
|
|
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.35);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.welcome-panel {
|
|
|
|
|
padding: 16px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.panel-content {
|
|
|
|
|
width: 100%;
|
|
|
|
|
min-height: 100%;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.panel-header {
|
|
|
|
|
margin-bottom: 16px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.back-btn {
|
|
|
|
|
padding: 8px 16px;
|
|
|
|
|
background: rgba(255, 255, 255, 0.05);
|
|
|
|
|
border: 1px solid rgba(255, 255, 255, 0.15);
|
|
|
|
|
border-radius: 8px;
|
|
|
|
|
color: #aaaaaa;
|
|
|
|
|
font-size: 0.85rem;
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
transition: all 0.2s ease;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.back-btn:hover {
|
|
|
|
|
background: rgba(255, 255, 255, 0.1);
|
|
|
|
|
border-color: rgba(255, 255, 255, 0.25);
|
|
|
|
|
color: #ffffff;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Dock Bar */
|
|
|
|
|
.dock-bar {
|
|
|
|
|
position: relative;
|
|
|
|
|
z-index: 10;
|
|
|
|
|
width: 100%;
|
|
|
|
|
flex-shrink: 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.dock-container {
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
justify-content: center;
|
|
|
|
|
gap: 8px;
|
|
|
|
|
padding: 12px 16px;
|
|
|
|
|
background: rgba(16, 18, 24, 0.92);
|
|
|
|
|
border: 1px solid rgba(255, 255, 255, 0.1);
|
|
|
|
|
border-radius: 16px;
|
|
|
|
|
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.35);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.dock-item {
|
|
|
|
|
display: flex;
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
align-items: center;
|
|
|
|
|
gap: 4px;
|
|
|
|
|
padding: 10px 14px;
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
border-radius: 12px;
|
|
|
|
|
transition: all 0.25s cubic-bezier(0.25, 0.46, 0.45, 0.94);
|
|
|
|
|
border: 1px solid transparent;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.dock-item:hover {
|
|
|
|
|
background: rgba(255, 255, 255, 0.08);
|
|
|
|
|
transform: translateY(-4px);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.dock-item--active {
|
|
|
|
|
background: rgba(52, 254, 241, 0.12);
|
|
|
|
|
border-color: rgba(52, 254, 241, 0.3);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.dock-item--active .dock-label {
|
|
|
|
|
color: #34fef1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.dock-icon {
|
|
|
|
|
width: 2.4rem;
|
|
|
|
|
height: 2.4rem;
|
|
|
|
|
object-fit: contain;
|
|
|
|
|
opacity: 0.85;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.dock-item:hover .dock-icon {
|
|
|
|
|
opacity: 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.dock-item--active .dock-icon {
|
|
|
|
|
opacity: 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.dock-label {
|
|
|
|
|
color: #999999;
|
|
|
|
|
font-size: 0.75rem;
|
|
|
|
|
font-weight: 400;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@media (max-width: 900px) {
|
|
|
|
|
.game-shell {
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
height: auto;
|
|
|
|
|
min-height: auto;
|
|
|
|
|
gap: 16px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.character-sidebar {
|
|
|
|
|
flex: none;
|
|
|
|
|
width: 100%;
|
|
|
|
|
max-width: 560px;
|
|
|
|
|
margin: 0 auto;
|
|
|
|
|
height: auto;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.sidebar-card {
|
|
|
|
|
overflow-y: visible;
|
|
|
|
|
flex: none;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.chat-panel {
|
|
|
|
|
flex: none;
|
|
|
|
|
width: 100%;
|
|
|
|
|
max-width: none;
|
|
|
|
|
height: auto;
|
|
|
|
|
min-height: 240px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.game-main {
|
|
|
|
|
flex: none;
|
|
|
|
|
width: 100%;
|
|
|
|
|
height: auto;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.main-panel {
|
|
|
|
|
flex: none;
|
|
|
|
|
overflow-y: visible;
|
|
|
|
|
max-width: 560px;
|
|
|
|
|
margin: 0 auto;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.dock-container {
|
|
|
|
|
gap: 4px;
|
|
|
|
|
padding: 6px 8px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.dock-item {
|
|
|
|
|
padding: 8px 10px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.dock-icon {
|
|
|
|
|
width: 2rem;
|
|
|
|
|
height: 2rem;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.dock-label {
|
|
|
|
|
font-size: 0.65rem;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
</style>
|