All checks were successful
site-dist-deploy / build-and-deploy (push) Successful in 1m34s
266 lines
9.2 KiB
Vue
266 lines
9.2 KiB
Vue
<template>
|
||
<div
|
||
class="relative min-h-screen overflow-hidden bg-black bg-[url('@/pages/Home/bg-mobile.webp')] bg-cover bg-center bg-no-repeat pb-[calc(1rem+env(safe-area-inset-bottom))] font-sans text-white md:flex md:flex-col md:bg-[url('@/pages/Home/bg-desktop.webp')] md:pb-0"
|
||
>
|
||
<!-- Full Width Header -->
|
||
<div class="h-[60px] md:h-[125px]">
|
||
<div class="fixed top-[20px] z-50 w-full md:top-[45px]">
|
||
<div class="container">
|
||
<header
|
||
class="lucid-glass-bar flex h-[40px] items-center justify-between rounded-[90px] pr-[5px] pl-5 transition-all duration-300 md:h-[60px] md:pr-[10px]"
|
||
>
|
||
<router-link to="/" class="flex items-center gap-2">
|
||
<!-- Desktop Logo -->
|
||
<Logo alt="Hi快VPN" class="h-[18px] w-auto md:ml-8 md:h-[29px]" />
|
||
</router-link>
|
||
<div v-if="isLoggedIn" class="flex items-center">
|
||
<router-link
|
||
to="/user-center"
|
||
class="flex size-[30px] items-center justify-center rounded-full bg-[#78788029] text-xl font-bold text-white shadow-lg transition hover:scale-105 md:size-[40px] md:text-3xl"
|
||
>
|
||
{{ userLetter }}
|
||
</router-link>
|
||
</div>
|
||
<button
|
||
v-else
|
||
@click="openLoginModal"
|
||
class="flex h-[30px] cursor-pointer items-center justify-center rounded-full bg-[#78788029] px-6 text-sm font-bold backdrop-blur-md transition hover:brightness-110 md:h-[40px] md:w-[220px] md:text-xl"
|
||
>
|
||
登录 / 注册
|
||
</button>
|
||
</header>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Main Content Container -->
|
||
<div class="container mx-auto flex flex-1 flex-col">
|
||
<main class="pt-10 md:grid md:flex-1 md:grid-cols-2 md:pt-0">
|
||
<!-- Left Column: Text & Downloads -->
|
||
<div class="md:flex md:w-[432px] md:flex-col md:justify-center">
|
||
<div class="mb-[20px] ml-[42px] md:ml-[17px]">
|
||
<h2 class="mb-2 text-2xl font-black md:text-8xl">
|
||
<Logo class="h-[34px] md:h-[66px]" />
|
||
</h2>
|
||
<p class="font-600 text-3xl md:text-[48px]">网在我在, 网快我快</p>
|
||
</div>
|
||
|
||
<!-- Screenshot Image -->
|
||
<div class="relative z-10 mx-auto mb-9 w-full max-w-[182px] md:hidden">
|
||
<img
|
||
:src="ScreenshotMobile"
|
||
alt="App Screenshot"
|
||
class="h-auto min-h-[284px] w-full drop-shadow-2xl"
|
||
/>
|
||
<div class="absolute right-[8%] bottom-[97px] z-50 aspect-square w-[58%]">
|
||
<!-- Ripple Animation (Background) -->
|
||
<div class="ripple-container absolute inset-0 top-[7px]">
|
||
<div class="ripple ripple-outer-3"></div>
|
||
<div class="ripple ripple-outer-2"></div>
|
||
<div class="ripple ripple-outer-1"></div>
|
||
<div class="ripple ripple-core size-[80%]!"></div>
|
||
</div>
|
||
<!-- Center Image (Foreground/Top Layer) -->
|
||
<div
|
||
class="absolute inset-0 bottom-0 z-100 bg-[url('@/pages/Home/connected-mobile-bg.png')] bg-cover bg-center bg-no-repeat"
|
||
></div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Download Buttons Grid -->
|
||
<div
|
||
class="mb-6 flex grid-cols-2 flex-wrap gap-[10px] px-[24px] md:mt-[124px] md:mb-[65px] md:ml-[17px] md:px-0"
|
||
>
|
||
<DownloadButton />
|
||
</div>
|
||
|
||
<!-- Features / Footer Info -->
|
||
<div
|
||
class="mb-5 w-full text-center text-[10px] leading-5 font-[300] md:ml-[17px] md:text-left md:text-sm"
|
||
>
|
||
<p>最新加密协议-安全有保障</p>
|
||
<p>IEPL专线-纯净、稳定</p>
|
||
<p>不限速/不限流-网速多快,Hi快多快</p>
|
||
<p>极速闪连-永远快人一步</p>
|
||
<div class="flex justify-center md:justify-start">
|
||
<a
|
||
href="https://x.com/hifasttech"
|
||
target="_blank"
|
||
class="lucid-glass-bar mt-[8px] flex h-[30px] w-[100px] shrink-0 items-center justify-center space-x-2 rounded-full transition-transform hover:brightness-110 md:mt-[16px]"
|
||
>
|
||
<div class="flex items-center justify-center">
|
||
<img src="./x-logo.png" alt="X.com" class="h-[16px] w-[77px]" />
|
||
</div>
|
||
</a>
|
||
</div>
|
||
</div>
|
||
<div class="mb-[10px] hidden md:ml-[17px] md:block md:text-left">
|
||
<CompanyNameIcon class="h-[16px]" />
|
||
</div>
|
||
<div class="mx-auto mb-1 h-[20px] w-[352px] md:ml-[17px]">
|
||
<img src="./image-1.png" alt="image" />
|
||
</div>
|
||
<div class="mt-2 mb-1 md:hidden">
|
||
<CompanyNameIcon class="mx-auto h-[10px]" />
|
||
</div>
|
||
<div class="text-center text-[10px] leading-[14px] font-[300] md:ml-[17px] md:text-left">
|
||
<span class="font-[600]">Hi快VPN™</span> © All rights reserved.<br />
|
||
<router-link to="/terms" class="underline">Terms of Service</router-link>
|
||
<router-link to="/privacy" class="ml-2 underline">Privacy Policy</router-link>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Right Column: Phone Screenshot -->
|
||
<div class="relative hidden w-full max-w-[632px] md:mt-0 md:block">
|
||
<!-- Screenshot with Ripple Effect -->
|
||
<div class="absolute right-[10%] bottom-0 z-50 aspect-square w-[58%] overflow-hidden">
|
||
<!-- Ripple Animation (Background) -->
|
||
<div class="ripple-container absolute inset-0">
|
||
<div class="ripple ripple-outer-3"></div>
|
||
<div class="ripple ripple-outer-2"></div>
|
||
<div class="ripple ripple-outer-1"></div>
|
||
<div class="ripple ripple-core"></div>
|
||
</div>
|
||
<!-- Center Image (Foreground/Top Layer) -->
|
||
<div
|
||
class="absolute inset-0 bottom-[36px] z-100 bg-[url('@/pages/Home/connected-bg.png')] bg-cover bg-center bg-no-repeat"
|
||
></div>
|
||
</div>
|
||
<div class="absolute right-[2vw] bottom-0 max-w-[632px]">
|
||
<img :src="ScreenshotDesktop" alt="App Screenshot" class="relative z-10 w-full" />
|
||
</div>
|
||
</div>
|
||
</main>
|
||
</div>
|
||
|
||
<LoginFormModal ref="loginModalRef" />
|
||
</div>
|
||
</template>
|
||
|
||
<script setup lang="ts">
|
||
import { ref, onMounted, watch, computed } from 'vue'
|
||
import { useRouter, useRoute } from 'vue-router'
|
||
import { useLocalStorage } from '@vueuse/core'
|
||
import CompanyNameIcon from './company-name.svg?component'
|
||
import LoginFormModal from './components/LoginFormModal.vue'
|
||
import DownloadButton from './components/DownloadButton.vue'
|
||
import Logo from './logo.svg?component'
|
||
import MobileLogo from './mobile-logo.svg?component'
|
||
import ScreenshotMobile from './screenshot-mobile.png'
|
||
import ScreenshotDesktop from './screenshot-desktop.webp'
|
||
import request from '@/utils/request'
|
||
|
||
const route = useRoute()
|
||
const router = useRouter()
|
||
const token = useLocalStorage('Authorization', '')
|
||
const userEmail = useLocalStorage('UserEmail', '')
|
||
|
||
const isLoggedIn = computed(() => !!token.value)
|
||
const userLetter = computed(() => {
|
||
if (!userEmail.value) return '?'
|
||
return userEmail.value.charAt(0).toUpperCase()
|
||
})
|
||
|
||
const fetchUserInfo = async () => {
|
||
if (!token.value) return
|
||
|
||
try {
|
||
const res = (await request.get('/api/v1/public/user/info')) as any
|
||
const emailInfo = res.auth_methods?.find((item: any) => item.auth_type === 'email')
|
||
if (emailInfo) {
|
||
userEmail.value = emailInfo.auth_identifier
|
||
}
|
||
} catch (error: any) {
|
||
console.error('Failed to fetch user info:', error)
|
||
if (error?.code === 401 || error?.status === 401) {
|
||
token.value = ''
|
||
userEmail.value = ''
|
||
}
|
||
}
|
||
}
|
||
|
||
onMounted(() => {
|
||
fetchUserInfo()
|
||
if (route.query.login === 'true') {
|
||
openLoginModal()
|
||
router.replace({ query: { ...route.query, login: undefined } })
|
||
}
|
||
})
|
||
|
||
watch(
|
||
() => route.query.login,
|
||
(newVal) => {
|
||
if (newVal === 'true') {
|
||
openLoginModal()
|
||
router.replace({ query: { ...route.query, login: undefined } })
|
||
}
|
||
},
|
||
)
|
||
|
||
const loginModalRef = ref<InstanceType<typeof LoginFormModal> | null>(null)
|
||
|
||
const openLoginModal = () => {
|
||
loginModalRef.value?.show()
|
||
}
|
||
</script>
|
||
|
||
<style scoped>
|
||
.ripple-container {
|
||
position: relative;
|
||
width: 100%;
|
||
height: 100%;
|
||
pointer-events: none;
|
||
z-index: 50;
|
||
}
|
||
|
||
.ripple {
|
||
position: absolute;
|
||
top: 50%;
|
||
left: 50%;
|
||
transform: translate(-50%, -50%);
|
||
border-radius: 50%;
|
||
background: #a8ff53; /* Brand neon green */
|
||
will-change: width, height, opacity;
|
||
}
|
||
|
||
/* Core: Fixed minimum diameter (~57% of max) */
|
||
.ripple-core {
|
||
width: 70%;
|
||
height: 70%;
|
||
opacity: 1;
|
||
z-index: 4;
|
||
}
|
||
|
||
/* Outer Layer 1: Opacity 0.8, 5.4s */
|
||
.ripple-outer-1 {
|
||
animation: breathe 5.4s ease-in-out infinite alternate;
|
||
opacity: 0.8;
|
||
z-index: 3;
|
||
}
|
||
|
||
/* Outer Layer 2: Opacity 0.6, 4.2s */
|
||
.ripple-outer-2 {
|
||
animation: breathe 4.2s ease-in-out infinite alternate;
|
||
opacity: 0.6;
|
||
z-index: 2;
|
||
}
|
||
|
||
/* Outer Layer 3 (Outer-most): Opacity 0.4, 3s */
|
||
.ripple-outer-3 {
|
||
animation: breathe 3s ease-in-out infinite alternate;
|
||
opacity: 0.4;
|
||
z-index: 1;
|
||
}
|
||
|
||
@keyframes breathe {
|
||
0% {
|
||
width: 57%;
|
||
height: 57%;
|
||
}
|
||
100% {
|
||
width: 100%;
|
||
height: 100%;
|
||
}
|
||
}
|
||
</style>
|