refactor(frontend): 代码格式化与移动端响应式优化

- 修正 Vue 组件 defineProps/defineEmits 声明顺序
- 优化首页移动端布局,调整 Logo 位置与透明度
- 为 Aether 标题添加打字机动画效果
- 修复 HTML 属性中引号的实体转义
This commit is contained in:
fawney19
2026-01-29 11:06:30 +08:00
parent 557e64c77f
commit 407fcfff3e
10 changed files with 201 additions and 60 deletions

View File

@@ -1,9 +1,9 @@
<template>
<section
ref="sectionRef"
class="min-h-screen snap-start flex items-center px-4 sm:px-8 md:px-16 lg:px-20 py-20"
class="min-h-screen snap-start flex items-center justify-center px-4 sm:px-8 md:px-16 lg:px-20 py-20"
>
<div class="max-w-7xl mx-auto grid md:grid-cols-2 gap-12 items-center">
<div class="max-w-7xl mx-auto grid md:grid-cols-2 gap-12 items-center w-full">
<!-- Content column -->
<div :class="contentOrder">
<!-- Badge -->
@@ -105,10 +105,10 @@
</div>
</div>
<!-- Logo placeholder column -->
<!-- Logo placeholder column - hidden on mobile since logo is fixed positioned -->
<div
:class="logoOrder"
class="flex items-center justify-center h-full min-h-[300px] relative"
class="hidden md:flex items-center justify-center h-full min-h-[300px] relative"
>
<slot name="logo" />
</div>

View File

@@ -116,6 +116,17 @@
<main class="relative z-10">
<!-- Fixed Logo Container -->
<div class="fixed top-0 left-0 right-0 bottom-0 z-20 pointer-events-none flex items-center justify-center overflow-hidden">
<!-- Gemini Star Cluster - positioned behind logo -->
<Transition name="fade">
<GeminiStarCluster
v-if="currentSection === SECTIONS.GEMINI"
:is-visible="sectionVisibility[SECTIONS.GEMINI] > 0.05"
class="absolute gemini-stars"
:class="windowWidth < 768 ? 'scale-75 opacity-60' : ''"
:style="fixedLogoStyle"
/>
</Transition>
<div
class="transform-gpu logo-container"
:class="[currentSection === SECTIONS.HOME ? 'home-section' : '', `logo-transition-${scrollDirection}`]"
@@ -126,14 +137,14 @@
v-if="currentSection === SECTIONS.HOME"
ref="aetherLogoRef"
key="aether-logo"
:size="400"
:size="homeLogoSize"
:line-delay="50"
:stroke-duration="1200"
:fill-duration="1500"
:auto-start="false"
:loop="true"
:loop-pause="800"
:stroke-width="3.5"
:stroke-width="windowWidth < 768 ? 2.5 : 3.5"
:cycle-colors="true"
:is-dark="isDark"
/>
@@ -145,7 +156,7 @@
<RippleLogo
ref="rippleLogoRef"
:type="currentLogoType"
:size="320"
:size="windowWidth < 768 ? 200 : 320"
:use-adaptive="false"
:disable-ripple="currentSection === SECTIONS.GEMINI || currentSection === SECTIONS.FEATURES"
:anim-delay="logoTransitionDelay"
@@ -164,12 +175,12 @@
class="min-h-screen snap-start flex items-center justify-center px-4 sm:px-8 md:px-16 lg:px-20 py-20"
>
<div class="max-w-4xl mx-auto text-center">
<div class="h-48 sm:h-64 md:h-80 w-full mb-8 sm:mb-12 md:mb-16 mt-4 sm:mt-8" />
<div class="h-64 sm:h-80 md:h-[26rem] w-full mb-12 sm:mb-8 md:mb-10 mt-8 sm:mt-12" />
<h1
class="mb-6 text-3xl sm:text-5xl md:text-7xl font-bold text-[#191919] dark:text-white leading-tight transition-all duration-700"
:style="getTitleStyle(SECTIONS.HOME)"
>
欢迎使用 <span class="text-primary">Aether</span>
欢迎使用 <span class="text-primary typewriter">{{ aetherText }}<span class="cursor" :class="{ 'cursor-hidden': !showCursor }">_</span></span>
</h1>
<p
class="mb-8 text-base sm:text-lg md:text-xl text-[#666663] dark:text-gray-300 max-w-2xl mx-auto transition-all duration-700"
@@ -252,69 +263,65 @@
:card-style-fn="(idx) => getCardStyle(SECTIONS.GEMINI, idx)"
content-position="right"
@copy="copyToClipboard"
>
<template #logo>
<GeminiStarCluster :is-visible="currentSection === SECTIONS.GEMINI && sectionVisibility[SECTIONS.GEMINI] > 0.05" />
</template>
</CliSection>
/>
<!-- Section 4: Features -->
<section
ref="section4"
class="min-h-screen snap-start flex items-center justify-center px-4 sm:px-8 md:px-16 lg:px-20 py-20 relative overflow-hidden"
class="min-h-screen snap-start flex items-center justify-center px-4 sm:px-8 md:px-16 lg:px-20 py-12 md:py-20 relative overflow-hidden"
>
<div class="max-w-4xl mx-auto text-center relative z-10">
<div
class="inline-flex items-center gap-2 rounded-full bg-[#cc785c]/10 dark:bg-purple-500/20 border border-[#cc785c]/20 dark:border-purple-500/40 px-4 py-2 text-sm font-medium text-[#cc785c] dark:text-purple-300 mb-6 backdrop-blur-sm transition-all duration-500"
class="inline-flex items-center gap-1.5 md:gap-2 rounded-full bg-[#cc785c]/10 dark:bg-purple-500/20 border border-[#cc785c]/20 dark:border-purple-500/40 px-3 md:px-4 py-1.5 md:py-2 text-xs md:text-sm font-medium text-[#cc785c] dark:text-purple-300 mb-4 md:mb-6 backdrop-blur-sm transition-all duration-500"
:style="getBadgeStyle(SECTIONS.FEATURES)"
>
<Sparkles class="h-4 w-4" />
<Sparkles class="h-3.5 w-3.5 md:h-4 md:w-4" />
项目进度
</div>
<h2
class="text-4xl md:text-5xl font-bold text-[#191919] dark:text-white mb-6 transition-all duration-700"
class="text-2xl md:text-5xl font-bold text-[#191919] dark:text-white mb-3 md:mb-6 transition-all duration-700"
:style="getTitleStyle(SECTIONS.FEATURES)"
>
功能开发进度
</h2>
<p
class="text-lg text-[#666663] dark:text-gray-300 mb-12 max-w-2xl mx-auto transition-all duration-700"
class="text-base md:text-lg text-[#666663] dark:text-gray-300 mb-6 md:mb-12 max-w-2xl mx-auto transition-all duration-700"
:style="getDescStyle(SECTIONS.FEATURES)"
>
核心 API 代理功能已完成,正在载入更多功能
</p>
<div class="grid md:grid-cols-3 gap-6">
<div class="grid md:grid-cols-3 gap-3 md:gap-6">
<div
v-for="(feature, idx) in featureCards"
:key="idx"
class="bg-white/70 dark:bg-[#262624]/80 backdrop-blur-sm rounded-2xl p-6 border border-[#e5e4df] dark:border-[rgba(227,224,211,0.16)] hover:border-[#cc785c]/30 dark:hover:border-[#d4a27f]/40 transition-all duration-700"
class="bg-white/70 dark:bg-[#262624]/80 backdrop-blur-sm rounded-xl md:rounded-2xl p-4 md:p-6 border border-[#e5e4df] dark:border-[rgba(227,224,211,0.16)] hover:border-[#cc785c]/30 dark:hover:border-[#d4a27f]/40 transition-all duration-700"
:style="getFeatureCardStyle(SECTIONS.FEATURES, idx)"
>
<div
class="flex h-12 w-12 items-center justify-center rounded-xl mb-4 mx-auto"
class="flex h-10 w-10 md:h-12 md:w-12 items-center justify-center rounded-lg md:rounded-xl mb-2 md:mb-4 mx-auto"
:class="feature.status === 'completed'
? 'bg-emerald-500/10 dark:bg-emerald-500/15'
: 'bg-[#cc785c]/10 dark:bg-[#cc785c]/15'"
>
<component
:is="feature.icon"
class="h-6 w-6"
class="h-5 w-5 md:h-6 md:w-6"
:class="feature.status === 'completed'
? 'text-emerald-500 dark:text-emerald-400'
: 'text-[#cc785c] dark:text-[#d4a27f] animate-spin'"
/>
</div>
<h3 class="text-lg font-bold text-[#191919] dark:text-white mb-2">
<h3 class="text-base md:text-lg font-bold text-[#191919] dark:text-white mb-1 md:mb-2">
{{ feature.title }}
</h3>
<p class="text-sm text-[#666663] dark:text-[#c9c3b4]">
<p class="text-xs md:text-sm text-[#666663] dark:text-[#c9c3b4]">
{{ feature.desc }}
</p>
<div
class="mt-3 inline-flex items-center gap-1.5 px-2.5 py-1 rounded-full text-xs font-medium"
class="mt-2 md:mt-3 inline-flex items-center gap-1.5 px-2 md:px-2.5 py-0.5 md:py-1 rounded-full text-xs font-medium"
:class="feature.status === 'completed'
? 'bg-emerald-500/10 text-emerald-600 dark:text-emerald-400'
: 'bg-amber-500/10 text-amber-600 dark:text-amber-400'"
@@ -325,7 +332,7 @@
</div>
<div
class="mt-12 transition-all duration-700"
class="mt-6 md:mt-12 transition-all duration-700"
:style="getButtonsStyle(SECTIONS.FEATURES)"
>
<RouterLink
@@ -452,6 +459,9 @@ const { logoTransitionName } = useLogoTransition(currentSection, previousSection
// Logo computed
const currentLogoType = computed(() => getLogoType(currentSection.value))
const currentLogoClass = computed(() => getLogoClass(currentSection.value))
// Responsive logo size - matches .logo-container.home-section CSS
const homeLogoSize = computed(() => windowWidth.value < 768 ? 280 : 400)
const logoTransitionDelay = computed(() => {
if (currentSection.value === SECTIONS.FEATURES) return 0
if (previousSection.value === SECTIONS.FEATURES) return 200
@@ -475,6 +485,57 @@ const { claudeConfig, codexConfig, codexAuthConfig, geminiEnvConfig, geminiSetti
// Dialog state
const showLoginDialog = ref(false)
// Typewriter effect for "Aether"
const aetherText = ref('')
const showCursor = ref(true)
const typewriterFullText = 'Aether'
let typewriterTimer: ReturnType<typeof setTimeout> | null = null
let hasTypewriterStarted = ref(false)
const startTypewriter = () => {
if (hasTypewriterStarted.value) return
hasTypewriterStarted.value = true
aetherText.value = ''
showCursor.value = true
const typeSpeed = 200
const deleteSpeed = 120
const pauseAfterType = 3500
const pauseAfterDelete = 1000
const typeLoop = () => {
let index = 0
// Type phase
const typeNextChar = () => {
if (index < typewriterFullText.length) {
aetherText.value = typewriterFullText.slice(0, index + 1)
index++
typewriterTimer = setTimeout(typeNextChar, typeSpeed)
} else {
// Pause then start deleting
typewriterTimer = setTimeout(deleteChars, pauseAfterType)
}
}
// Delete phase
const deleteChars = () => {
if (aetherText.value.length > 0) {
aetherText.value = aetherText.value.slice(0, -1)
typewriterTimer = setTimeout(deleteChars, deleteSpeed)
} else {
// Pause then restart typing
typewriterTimer = setTimeout(typeLoop, pauseAfterDelete)
}
}
typeNextChar()
}
// Start typing after a short delay
typewriterTimer = setTimeout(typeLoop, 400)
}
// Scroll handling
let scrollEndTimer: ReturnType<typeof setTimeout> | null = null
@@ -525,6 +586,7 @@ const handleScroll = () => {
if (currentSection.value === SECTIONS.HOME && !hasLogoAnimationStarted.value) {
hasLogoAnimationStarted.value = true
setTimeout(() => aetherLogoRef.value?.startAnimation(), 100)
startTypewriter()
}
}, 150)
}
@@ -560,6 +622,7 @@ onMounted(() => {
if (currentSection.value === SECTIONS.HOME && !hasLogoAnimationStarted.value) {
hasLogoAnimationStarted.value = true
setTimeout(() => aetherLogoRef.value?.startAnimation(), 100)
startTypewriter()
}
}, 300)
})
@@ -568,6 +631,7 @@ onUnmounted(() => {
scrollContainer.value?.removeEventListener('scroll', handleScroll)
window.removeEventListener('resize', handleResize)
if (scrollEndTimer) clearTimeout(scrollEndTimer)
if (typewriterTimer) clearTimeout(typewriterTimer)
})
</script>
@@ -777,4 +841,42 @@ h1, h2, p {
28% { transform: scale(1); }
42% { transform: scale(1.1); }
}
/* Gemini star cluster positioning */
.gemini-stars {
z-index: -1;
}
/* Fade transition */
.fade-enter-active,
.fade-leave-active {
transition: opacity 0.6s ease;
}
.fade-enter-from,
.fade-leave-to {
opacity: 0;
}
/* Typewriter cursor */
.typewriter {
display: inline;
}
.typewriter .cursor {
font-weight: 400;
opacity: 1;
animation: cursor-blink 1s ease-in-out infinite;
margin-left: 1px;
}
.typewriter .cursor.cursor-hidden {
opacity: 0;
animation: none;
}
@keyframes cursor-blink {
0%, 45% { opacity: 1; }
50%, 100% { opacity: 0; }
}
</style>

View File

@@ -157,14 +157,18 @@ export function useLogoPosition(
let opacity = 1
if (section === SECTIONS.HOME) {
transform = 'scale(1.1) translateY(-18vh)'
transform = 'scale(1.1) translateY(-12vh)'
opacity = 0.25
} else if (section === SECTIONS.CLAUDE) {
transform = isDesktop ? 'translateX(-25vw) scale(1)' : 'translateY(-20vh) scale(0.8)'
// Mobile: move logo higher and reduce opacity to avoid blocking content
transform = isDesktop ? 'translateX(-25vw) scale(1)' : 'translateY(-32vh) scale(0.6)'
opacity = isDesktop ? 1 : 0.2
} else if (section === SECTIONS.CODEX) {
transform = isDesktop ? 'translateX(25vw) scale(1)' : 'translateY(-20vh) scale(0.8)'
transform = isDesktop ? 'translateX(25vw) scale(1)' : 'translateY(-32vh) scale(0.6)'
opacity = isDesktop ? 1 : 0.2
} else if (section === SECTIONS.GEMINI) {
transform = isDesktop ? 'translateX(-25vw) scale(1)' : 'translateY(-20vh) scale(0.8)'
transform = isDesktop ? 'translateX(-25vw) scale(1)' : 'translateY(-32vh) scale(0.6)'
opacity = isDesktop ? 1 : 0.2
} else {
transform = isDesktop ? 'translateX(0) scale(1)' : 'translateY(-20vh) scale(0.8)'
opacity = 0.15