13
src/pages/Home/components/AndroidIcon.svg
Normal file
@ -0,0 +1,13 @@
|
||||
<svg viewBox="0 0 23 23" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_13954_5340)">
|
||||
<path d="M13.7436 22.7399C14.5039 22.7399 15.167 22.0768 15.167 21.3165V17.9923H16.1174C16.6877 17.9923 17.0678 17.6121 17.0678 17.0419V7.54761H5.672V17.042C5.672 17.6122 6.05216 17.9924 6.6224 17.9924H7.57288V21.3166C7.57288 22.0769 8.23577 22.7399 8.99624 22.7399C9.75649 22.7399 10.4195 22.0769 10.4195 21.3166V17.9924H12.3203V21.3166C12.3203 22.0768 12.9833 22.7399 13.7436 22.7399Z" fill="currentColor"/>
|
||||
<path d="M19.4417 17.042C20.202 17.042 20.8649 16.379 20.8649 15.6186V8.97037C20.8649 8.21407 20.202 7.54761 19.4417 7.54761C18.6813 7.54761 18.0183 8.21407 18.0183 8.97037V15.6186C18.0183 16.379 18.6812 17.042 19.4417 17.042Z" fill="currentColor"/>
|
||||
<path d="M3.29821 17.042C4.05861 17.042 4.72158 16.379 4.72158 15.6186V8.97037C4.72158 8.21407 4.05869 7.54761 3.29821 7.54761C2.53789 7.54761 1.875 8.21407 1.875 8.97037V15.6186C1.875 16.379 2.53789 17.042 3.29821 17.042Z" fill="currentColor"/>
|
||||
<path d="M15.9272 0.143414C15.7372 -0.0478047 15.4544 -0.0478047 15.2643 0.143414L13.9896 1.41388L13.9308 1.47261C13.175 1.09412 12.3275 0.905102 11.3838 0.903281C11.3792 0.903281 11.3746 0.903129 11.37 0.903129H11.3699C11.3651 0.903129 11.3607 0.903281 11.3559 0.903281C10.4122 0.905102 9.56482 1.09412 8.80905 1.47261L8.75009 1.41388L7.47545 0.143414C7.28529 -0.0478047 7.00256 -0.0478047 6.81256 0.143414C6.6224 0.333571 6.6224 0.61577 6.81256 0.805775L8.04561 2.03906C7.6483 2.30434 7.28802 2.62895 6.97471 3.00024C6.22463 3.88933 5.74621 5.04643 5.68042 6.30013C5.67981 6.31318 5.67852 6.32608 5.67792 6.33913C5.67389 6.4245 5.672 6.51032 5.672 6.59644H17.0678C17.0678 6.51032 17.0657 6.4245 17.0619 6.33913C17.0613 6.32608 17.06 6.31318 17.0592 6.30013C16.9936 5.04643 16.515 3.88925 15.7649 3.00031C15.4518 2.62903 15.0913 2.30441 14.694 2.03914L15.9272 0.805851C16.1174 0.61577 16.1174 0.333571 15.9272 0.143414ZM8.99442 4.93701C8.60121 4.93701 8.28244 4.61824 8.28244 4.22502C8.28244 3.83181 8.60121 3.51304 8.99442 3.51304C9.38763 3.51304 9.70641 3.83181 9.70641 4.22502C9.70641 4.61824 9.38763 4.93701 8.99442 4.93701ZM13.7454 4.93701C13.3522 4.93701 13.0334 4.61824 13.0334 4.22502C13.0334 3.83181 13.3522 3.51304 13.7454 3.51304C14.1386 3.51304 14.4574 3.83181 14.4574 4.22502C14.4574 4.61824 14.1386 4.93701 13.7454 4.93701Z" fill="currentColor"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_13954_5340">
|
||||
<rect width="22.7398" height="22.7398" fill="currentColor"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.5 KiB |
4
src/pages/Home/components/AppleIcon.svg
Normal file
@ -0,0 +1,4 @@
|
||||
<svg viewBox="0 0 20 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M16.7045 12.7631C16.729 10.9104 17.7481 9.15738 19.3648 8.18699C18.3449 6.76488 16.6366 5.86322 14.8592 5.80893C12.9636 5.61467 11.1258 6.91639 10.1598 6.91639C9.17508 6.91639 7.68777 5.82822 6.08619 5.86039C3.99859 5.92624 2.05242 7.08501 1.03676 8.86687C-1.14651 12.5573 0.482015 17.9809 2.5734 20.964C3.61977 22.4247 4.84267 24.0564 6.44281 23.9985C8.00865 23.9351 8.59346 23.0237 10.4836 23.0237C12.3561 23.0237 12.9048 23.9985 14.5374 23.9617C16.2176 23.9351 17.2762 22.4945 18.2859 21.02C19.0377 19.9791 19.6162 18.8288 20 17.6116C18.0254 16.7962 16.7068 14.8562 16.7045 12.7631Z" fill="currentColor"/>
|
||||
<path d="M13.6208 3.84713C14.5369 2.77343 14.9882 1.39335 14.8789 0C13.4793 0.143521 12.1864 0.796601 11.2579 1.82911C10.35 2.83793 9.87748 4.19372 9.96681 5.53382C11.3669 5.54789 12.7434 4.91252 13.6208 3.84713Z" fill="currentColor"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 935 B |
@ -1,28 +1,56 @@
|
||||
<template>
|
||||
<div class="mx-auto grid grid-cols-2 gap-[10px] md:flex md:flex-wrap">
|
||||
<template v-for="(item, index) in downloadLinks" :key="index">
|
||||
<router-link
|
||||
v-if="item.isInternal"
|
||||
:to="item.link"
|
||||
:aria-label="item.label"
|
||||
class="lucid-glass-bar flex h-[40px] w-[140px] shrink-0 items-center space-x-2 rounded-full border border-white/20 transition-transform hover:brightness-110 active:scale-95 md:h-[50px] md:w-[180px]"
|
||||
>
|
||||
<div class="flex items-center justify-center">
|
||||
<component :is="item.icon" class="h-[40px] w-[140px] md:h-[50px] md:w-[180px]" />
|
||||
</div>
|
||||
</router-link>
|
||||
<div class="flex w-full flex-col items-center justify-center md:justify-start">
|
||||
<!-- 主下载按钮 -->
|
||||
<div
|
||||
class="mx-auto h-[60px] w-[210px] rounded-full bg-[#ADFF5B] md:-ml-2 md:h-[100px] md:w-[360px]"
|
||||
>
|
||||
<a
|
||||
v-else
|
||||
:href="item.link"
|
||||
v-if="mainButton?.link"
|
||||
:href="mainButton.link"
|
||||
target="_blank"
|
||||
:aria-label="item.label"
|
||||
class="lucid-glass-bar flex h-[40px] w-[140px] shrink-0 items-center space-x-2 rounded-full transition-transform hover:brightness-110 active:scale-95 md:h-[50px] md:w-[180px]"
|
||||
:aria-label="mainButton.label"
|
||||
class="block transition-transform hover:brightness-110 active:scale-95"
|
||||
>
|
||||
<div class="flex items-center justify-center">
|
||||
<component :is="item.icon" class="h-[40px] w-[140px] md:h-[50px] md:w-[180px]" />
|
||||
</div>
|
||||
<component :is="mainButton.mainIcon" class="h-full text-black" />
|
||||
</a>
|
||||
</template>
|
||||
<div
|
||||
v-else
|
||||
:id="mainButton?.id"
|
||||
:aria-label="mainButton?.label"
|
||||
class="cursor-pointer transition-transform hover:brightness-110 active:scale-95"
|
||||
>
|
||||
<component :is="mainButton?.mainIcon" class="h-full text-black" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 其他版本下载 -->
|
||||
<div class="mt-2 w-full">
|
||||
<div class="mb-3 text-center md:text-left">
|
||||
<span class="text-xs whitespace-nowrap md:text-sm">其他版本下载</span>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-center gap-4 md:justify-start">
|
||||
<template v-for="(item, index) in otherButtons" :key="index">
|
||||
<a
|
||||
v-if="item.link"
|
||||
:href="item.link"
|
||||
target="_blank"
|
||||
:aria-label="item.label"
|
||||
class="transition-transform hover:brightness-110 active:scale-95"
|
||||
>
|
||||
<component :is="item.secondaryIcon" class="h-[24px] text-white md:h-[34px]" />
|
||||
</a>
|
||||
<div
|
||||
v-else
|
||||
:id="item.id"
|
||||
:aria-label="item.label"
|
||||
class="cursor-pointer transition-transform hover:brightness-110 active:scale-95"
|
||||
>
|
||||
<component :is="item.secondaryIcon" class="h-[24px] text-white md:h-[34px]" />
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -31,49 +59,99 @@ import Icon1 from './Group 105.svg?component'
|
||||
import Icon2 from './Group 106.svg?component'
|
||||
import Icon3 from './Group 107.svg?component'
|
||||
import Icon4 from './Group 108.svg?component'
|
||||
import { ref, computed } from 'vue'
|
||||
import request from '@/utils/request'
|
||||
import { getAllQueryString } from '@/utils/url-utils.ts'
|
||||
const downLoadWin = ref(
|
||||
'https://api.hifast.biz/v1/common/client/download/file/Hi快VPN-windows-1.0.0.exe',
|
||||
)
|
||||
const downLoadMac = ref('https://apps.apple.com/us/app/hi%E5%BF%ABvpn/id6755683167')
|
||||
const downLoadAndroid = ref('https://hifastvpn.go.link/?adj_t=1xf6e7ru')
|
||||
// request
|
||||
// .get('/api/v1/common/client/download', {
|
||||
// // invite_code: getAllQueryString('ic'),
|
||||
// platform: 'mac',
|
||||
// })
|
||||
// .then((res) => {
|
||||
// downLoadMac.value = res.url
|
||||
// })
|
||||
//
|
||||
// request
|
||||
// .get('/api/v1/common/client/download', {
|
||||
// // invite_code: getAllQueryString('ic'),
|
||||
// platform: 'windows',
|
||||
// })
|
||||
// .then((res) => {
|
||||
// downLoadWin.value = res.url
|
||||
// })
|
||||
//
|
||||
// request
|
||||
// .get('/api/v1/common/client/download', {
|
||||
// // invite_code: getAllQueryString('ic'),
|
||||
// platform: 'android',
|
||||
// })
|
||||
// .then((res) => {
|
||||
// downLoadAndroid.value = res.url
|
||||
// // console.log(downLoadAndroid.value)
|
||||
// })
|
||||
import WinIcon from './WinIcon.svg?component'
|
||||
import MacIcon from './MacIcon.svg?component'
|
||||
import AppleIcon from './AppleIcon.svg?component'
|
||||
import AndroidIcon from './AndroidIcon.svg?component'
|
||||
|
||||
// 定义下载链接数据
|
||||
const downloadLinks = computed(() => {
|
||||
return [
|
||||
{ icon: Icon2, link: '/help', isInternal: true, label: '查看帮助中心' },
|
||||
{ icon: Icon1, link: downLoadWin.value, label: '下载 Windows 版客户端' },
|
||||
{ icon: Icon4, link: downLoadAndroid.value, label: '下载 Android 版客户端' },
|
||||
{ icon: Icon3, link: downLoadMac.value, label: '下载 macOS 版客户端' },
|
||||
]
|
||||
import request from '@/utils/request'
|
||||
import { computed, ref, onMounted } from 'vue'
|
||||
import { getAllQueryString } from '@/utils/url-utils.ts'
|
||||
|
||||
const downLoadWin = ref('')
|
||||
const downLoadMac = ref('')
|
||||
const currentPlatform = ref('win')
|
||||
|
||||
onMounted(() => {
|
||||
const ua = navigator.userAgent
|
||||
const platform = navigator.platform || '' // 辅助判断
|
||||
|
||||
// 1. 先判断 Android (通常比较稳定)
|
||||
if (/Android/i.test(ua)) {
|
||||
currentPlatform.value = 'android'
|
||||
}
|
||||
// 2. 判断 iOS 设备 (iPhone, iPod)
|
||||
else if (/iPhone|iPod/i.test(ua)) {
|
||||
currentPlatform.value = 'ios'
|
||||
}
|
||||
// 3. 核心改进:区分 Mac 和 iPad
|
||||
else if (/Macintosh|Mac OS X/i.test(ua)) {
|
||||
// iPadOS 桌面模式下,支持多点触控(通常 > 1)
|
||||
// 而真正的 Mac 电脑通常 maxTouchPoints 为 0 或 undefined
|
||||
if (navigator.maxTouchPoints > 1) {
|
||||
currentPlatform.value = 'ios' // 归类为 iOS 端(iPad)
|
||||
} else {
|
||||
currentPlatform.value = 'mac'
|
||||
}
|
||||
}
|
||||
// 4. 其他情况默认为 Windows
|
||||
else {
|
||||
currentPlatform.value = 'win'
|
||||
}
|
||||
|
||||
console.log('Detected Platform:', currentPlatform.value)
|
||||
})
|
||||
|
||||
// request.get('/api/v1/common/client/download', {
|
||||
// invite_code: getAllQueryString('ic'),
|
||||
// platform: 'mac',
|
||||
// }).then((res: any) => {
|
||||
// downLoadMac.value = res.url
|
||||
// })
|
||||
|
||||
request
|
||||
.get('/api/v1/common/client/download', {
|
||||
invite_code: getAllQueryString('ic'),
|
||||
platform: 'windows',
|
||||
})
|
||||
.then((res: any) => {
|
||||
downLoadWin.value = res.url
|
||||
})
|
||||
|
||||
const allDownloadOptions = computed(() => [
|
||||
{
|
||||
key: 'win',
|
||||
mainIcon: Icon1,
|
||||
secondaryIcon: WinIcon,
|
||||
link: downLoadWin.value,
|
||||
label: 'Windows',
|
||||
id: 'downloadButton_win',
|
||||
},
|
||||
{ key: 'mac', mainIcon: Icon3, secondaryIcon: MacIcon, label: 'macOS', id: 'downloadButton_mac' },
|
||||
{
|
||||
key: 'ios',
|
||||
mainIcon: Icon2,
|
||||
secondaryIcon: AppleIcon,
|
||||
label: 'iOS',
|
||||
id: 'downloadButton_apple',
|
||||
},
|
||||
{
|
||||
key: 'android',
|
||||
mainIcon: Icon4,
|
||||
secondaryIcon: AndroidIcon,
|
||||
label: 'Android',
|
||||
id: 'downloadButton_android',
|
||||
},
|
||||
])
|
||||
|
||||
const mainButton = computed(() => {
|
||||
return (
|
||||
allDownloadOptions.value.find((opt) => opt.key === currentPlatform.value) ||
|
||||
allDownloadOptions.value[0]
|
||||
)
|
||||
})
|
||||
|
||||
const otherButtons = computed(() => {
|
||||
return allDownloadOptions.value.filter((opt) => opt.key !== currentPlatform.value)
|
||||
})
|
||||
</script>
|
||||
|
||||
|
Before Width: | Height: | Size: 6.0 KiB After Width: | Height: | Size: 6.1 KiB |
|
Before Width: | Height: | Size: 8.3 KiB After Width: | Height: | Size: 8.3 KiB |
|
Before Width: | Height: | Size: 8.5 KiB After Width: | Height: | Size: 8.6 KiB |
|
Before Width: | Height: | Size: 8.0 KiB After Width: | Height: | Size: 8.0 KiB |
5
src/pages/Home/components/MacIcon.svg
Normal file
@ -0,0 +1,5 @@
|
||||
<svg viewBox="0 0 22 22" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M10.1116 0.0118162C16.9306 -0.294218 22.1579 5.38658 21.2692 12.1411C20.6604 16.7678 17.0315 20.4574 12.4153 21.1753C5.11499 22.3107 -0.973361 16.2395 0.129739 8.96223C0.880978 4.00611 5.08396 0.237434 10.1116 0.0118162ZM6.49253 5.57971V5.15095L5.98082 5.17139V7.82566H6.51301V6.03912C6.51301 5.98384 6.63856 5.77528 6.68403 5.72991C6.88648 5.5281 7.29764 5.51339 7.4835 5.74493C7.50613 5.77314 7.57751 5.89024 7.57751 5.9166V7.82566H8.1097V5.99835C8.1097 5.94388 8.21835 5.76649 8.26341 5.72286C8.46761 5.52535 8.93703 5.50705 9.10159 5.76445C9.11408 5.78387 9.1742 5.92774 9.1742 5.93704V7.82566H9.70639V5.79408C9.70639 5.7478 9.62795 5.54057 9.6004 5.49142C9.3318 5.01168 8.57411 4.99431 8.20125 5.3545C8.14677 5.40712 8.05789 5.57378 8.01795 5.58022C7.97975 5.58798 7.99153 5.56969 7.9818 5.55365C7.95384 5.50726 7.94073 5.45402 7.905 5.4061C7.60444 5.00309 6.91567 5.00514 6.61542 5.4061C6.57978 5.45372 6.56678 5.5094 6.53851 5.55324C6.52551 5.57347 6.54087 5.58941 6.49263 5.5796L6.49253 5.57971ZM12.5518 7.82566V5.85539C12.5518 5.81707 12.4886 5.63723 12.4674 5.59248C12.1186 4.85861 10.5293 4.96621 10.4024 5.84517H10.9039C11.0519 5.48621 11.7558 5.45167 11.9549 5.75658C11.9666 5.77446 12.0196 5.88727 12.0196 5.89627V6.23316C11.4897 6.3051 10.5307 6.18166 10.3345 6.84942C10.0652 7.76599 11.284 8.18596 11.8843 7.59902C11.9319 7.55243 11.97 7.44902 12.04 7.43778V7.82566H12.5517H12.5518ZM15.5608 6.00846C15.4337 4.96539 13.8804 4.80915 13.3746 5.63478C13.0153 6.22141 13.0868 7.29166 13.7078 7.68322C14.3639 8.09685 15.4815 7.83905 15.561 6.96815H15.0595C14.9687 7.24251 14.7832 7.40059 14.4871 7.41796C13.5044 7.47579 13.4897 5.83066 14.1785 5.59932C14.5531 5.47354 14.9288 5.63356 15.0595 6.00846H15.561H15.5608ZM7.1639 9.17927C5.47873 9.29443 4.41475 10.5135 4.18239 12.1265C3.87671 14.2482 4.74347 16.4983 7.13758 16.6867C9.76558 16.8934 11.0634 14.884 10.8525 12.4504C10.6708 10.3526 9.32217 9.03182 7.1639 9.17916V9.17927ZM16.83 11.2762C16.7202 9.40591 14.6005 8.79854 13.0581 9.3822C11.3439 10.0309 11.1638 12.1881 12.9005 12.96C13.8462 13.3804 16.4376 13.3588 15.9806 14.9514C15.7548 15.7383 14.68 15.9375 13.9772 15.8676C13.2545 15.7957 12.5363 15.3986 12.429 14.6247H11.5284C11.5735 15.7605 12.4946 16.458 13.5512 16.6397C15.195 16.9222 17.1231 16.2394 16.9504 14.2701C16.7967 12.5179 14.6073 12.67 13.4325 12.1845C12.3671 11.7442 12.3516 10.5542 13.4063 10.1377C14.3723 9.75629 15.8132 10.0661 15.909 11.2762H16.8302H16.83Z" fill="currentColor"/>
|
||||
<path d="M7.26639 10.0163C8.68592 9.90412 9.63357 10.7978 9.86378 12.1503C10.179 14.0017 9.43061 16.0109 7.24008 15.8494C6.04358 15.7612 5.31119 14.8035 5.12758 13.6891C4.86675 12.1062 5.39639 10.1641 7.26639 10.0163Z" fill="currentColor"/>
|
||||
<path d="M12.0195 6.62085C12.0894 7.12757 11.7454 7.48858 11.2338 7.43514C10.9315 7.40346 10.6886 7.10938 10.8934 6.83523C11.0759 6.5906 11.737 6.64987 12.0195 6.62095V6.62085Z" fill="currentColor"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.9 KiB |
BIN
src/pages/Home/components/ReviewCarousel/avatars/avatar1.png
Normal file
|
After Width: | Height: | Size: 32 KiB |
BIN
src/pages/Home/components/ReviewCarousel/avatars/avatar2.png
Normal file
|
After Width: | Height: | Size: 32 KiB |
BIN
src/pages/Home/components/ReviewCarousel/avatars/avatar3.png
Normal file
|
After Width: | Height: | Size: 39 KiB |
BIN
src/pages/Home/components/ReviewCarousel/avatars/avatar4.png
Normal file
|
After Width: | Height: | Size: 28 KiB |
BIN
src/pages/Home/components/ReviewCarousel/avatars/avatar5.png
Normal file
|
After Width: | Height: | Size: 23 KiB |
BIN
src/pages/Home/components/ReviewCarousel/avatars/avatar6.png
Normal file
|
After Width: | Height: | Size: 33 KiB |
181
src/pages/Home/components/ReviewCarousel/index.vue
Normal file
@ -0,0 +1,181 @@
|
||||
<template>
|
||||
<div class="review-carousel-container relative">
|
||||
<transition name="fade" mode="out-in">
|
||||
<div
|
||||
v-if="currentReview"
|
||||
:key="currentIndex"
|
||||
class="review-card flex items-center lucid-glass-bar text-white rounded-2xl overflow-hidden p-4 md:p-5 relative"
|
||||
:style="{ height: isMobile ? '114px' : '130px' }"
|
||||
>
|
||||
<!-- Avatar -->
|
||||
<div class="flex-shrink-0 mr-4 md:mr-6">
|
||||
<img
|
||||
:src="currentReview.avatar"
|
||||
alt="User Avatar"
|
||||
class="w-[70px] h-[70px] md:w-[84px] md:h-[84px] rounded-full object-cover border-2 border-pink-300/30"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Content -->
|
||||
<div class="flex-grow min-w-0">
|
||||
<div class="flex justify-between items-start mb-1">
|
||||
<h3 class="text-base md:text-xl font-bold truncate pr-2">{{ currentReview.username }}</h3>
|
||||
<a
|
||||
href="https://hifastvpn.com"
|
||||
target="_blank"
|
||||
class="more-reviews text-[10px] md:text-sm text-white hover:text-[#ADFF5B] transition-colors underline decoration-white underline-offset-4"
|
||||
>
|
||||
More Reviews
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- Stars and Rating -->
|
||||
<div class="flex items-center gap-1 mb-1 md:mb-2">
|
||||
<div class="flex gap-0.5">
|
||||
<svg v-for="i in 5" :key="i" class="w-3 h-3 md:w-4 md:h-4 text-[#ADFF5B]" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z" />
|
||||
</svg>
|
||||
</div>
|
||||
<span class="text-xs md:text-sm text-white/50 font-medium ml-1">4.9</span>
|
||||
<span class="text-xs md:text-sm text-white/30 ml-2">{{ currentReview.reviewCount }}k reviews</span>
|
||||
</div>
|
||||
|
||||
<!-- Comment Text -->
|
||||
<p class="text-xs md:text-sm text-white/90 leading-[1.4] line-clamp-2 md:pr-4">
|
||||
“{{ currentReview.comment }}”
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</transition>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, onUnmounted, computed } from 'vue';
|
||||
|
||||
// Import avatars
|
||||
import avatar1 from './avatars/avatar1.png';
|
||||
import avatar2 from './avatars/avatar2.png';
|
||||
import avatar3 from './avatars/avatar3.png';
|
||||
import avatar4 from './avatars/avatar4.png';
|
||||
import avatar6 from './avatars/avatar6.png';
|
||||
|
||||
const isMobile = ref(false);
|
||||
|
||||
const reviews = [
|
||||
{
|
||||
username: '庄子不给',
|
||||
avatar: avatar1,
|
||||
comment: '真心不错,比我只前买的那个好用多了。网宿很给力。',
|
||||
reviewCount: '5.7'
|
||||
},
|
||||
{
|
||||
username: 'TechEnthusiast',
|
||||
avatar: avatar6,
|
||||
comment: 'Speed is incredible! The best VPN I have used in years for international streaming.',
|
||||
reviewCount: '12.4'
|
||||
},
|
||||
{
|
||||
username: '阿杰',
|
||||
avatar: avatar2,
|
||||
comment: '非常稳定的连接,在高峰时段也没掉过线。UI设计很现代,用着很舒服。',
|
||||
reviewCount: '3.2'
|
||||
},
|
||||
{
|
||||
username: 'Lina_Zhang',
|
||||
avatar: avatar3,
|
||||
comment: '客服响应速度很快,配置简单。推荐给需要长期稳定翻墙的朋友们。',
|
||||
reviewCount: '8.9'
|
||||
},
|
||||
{
|
||||
username: 'GlobalNomad',
|
||||
avatar: avatar4,
|
||||
comment: 'Perfect for my travels. Low latency and high security. A must-have for privacy.',
|
||||
reviewCount: '6.1'
|
||||
}
|
||||
];
|
||||
|
||||
const currentIndex = ref(0);
|
||||
const currentReview = computed(() => reviews[currentIndex.value]);
|
||||
|
||||
let timer: any = null;
|
||||
|
||||
const startCarousel = () => {
|
||||
timer = setInterval(() => {
|
||||
currentIndex.value = (currentIndex.value + 1) % reviews.length;
|
||||
}, 5000);
|
||||
};
|
||||
|
||||
const handleResize = () => {
|
||||
isMobile.value = window.innerWidth < 768;
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
handleResize();
|
||||
window.addEventListener('resize', handleResize);
|
||||
startCarousel();
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
window.removeEventListener('resize', handleResize);
|
||||
if (timer) clearInterval(timer);
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.review-carousel-container {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.review-carousel-container {
|
||||
max-width: 400px;
|
||||
}
|
||||
}
|
||||
|
||||
.fade-enter-active,
|
||||
.fade-leave-active {
|
||||
transition: opacity 0.8s cubic-bezier(0.4, 0, 0.2, 1), transform 0.8s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
}
|
||||
|
||||
.fade-enter-from {
|
||||
opacity: 0;
|
||||
transform: translateY(20px);
|
||||
}
|
||||
|
||||
.fade-leave-to {
|
||||
opacity: 0;
|
||||
transform: translateY(-20px);
|
||||
}
|
||||
|
||||
.review-card {
|
||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.5);
|
||||
transition: all 0.3s ease;
|
||||
border-radius: 16px !important;
|
||||
}
|
||||
|
||||
.review-card::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
border-radius: inherit;
|
||||
padding: 1px;
|
||||
background: linear-gradient(135deg, rgba(173, 255, 91, 0.1), transparent 50%);
|
||||
-webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
|
||||
mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
|
||||
-webkit-mask-composite: xor;
|
||||
mask-composite: exclude;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.line-clamp-2 {
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 2;
|
||||
line-clamp: 2;
|
||||
overflow: hidden;
|
||||
}
|
||||
</style>
|
||||
6
src/pages/Home/components/WinIcon.svg
Normal file
@ -0,0 +1,6 @@
|
||||
<svg viewBox="0 0 19 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect width="9.08603" height="9.1215" fill="currentColor"/>
|
||||
<rect x="9.91211" width="9.08603" height="9.1215" fill="currentColor"/>
|
||||
<rect y="9.95068" width="9.08603" height="9.1215" fill="currentColor"/>
|
||||
<rect x="9.91211" y="9.95068" width="9.08603" height="9.1215" fill="currentColor"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 369 B |
@ -35,14 +35,19 @@
|
||||
|
||||
<!-- 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">
|
||||
<main class="pt-10 md:flex md:flex-1 md:justify-between md:pt-0">
|
||||
<!-- Left Column: Text & Downloads -->
|
||||
<div class="md:flex md:w-[432px] md:flex-col md:justify-center">
|
||||
<div class="pb-4 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 class="overflow-hidden">
|
||||
<div class="float-left mt-2 rounded-full bg-white px-4 py-2 md:mt-4">
|
||||
<UserCount class="h-[29px]" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Screenshot Image -->
|
||||
@ -69,10 +74,14 @@
|
||||
|
||||
<!-- 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"
|
||||
class="mb-4 flex grid-cols-2 flex-wrap gap-[10px] px-[24px] md:mt-[124px] md:ml-[17px] md:px-0"
|
||||
>
|
||||
<DownloadButton />
|
||||
</div>
|
||||
<!-- Review Carousel -->
|
||||
<div class="mb-2 md:hidden">
|
||||
<ReviewCarousel />
|
||||
</div>
|
||||
|
||||
<!-- Features / Footer Info -->
|
||||
<div
|
||||
@ -111,7 +120,11 @@
|
||||
</div>
|
||||
|
||||
<!-- Right Column: Phone Screenshot -->
|
||||
<div class="relative hidden w-full max-w-[632px] md:mt-0 md:block">
|
||||
<div class="relative hidden w-full max-w-[582px] md:mt-0 md:block">
|
||||
<!-- Review Carousel -->
|
||||
<div class="absolute right-[2vw]">
|
||||
<ReviewCarousel />
|
||||
</div>
|
||||
<!-- Screenshot with Ripple Effect -->
|
||||
<div class="absolute right-[10%] bottom-0 z-50 aspect-square w-[58%] overflow-hidden">
|
||||
<!-- Ripple Animation (Background) -->
|
||||
@ -126,7 +139,7 @@
|
||||
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]">
|
||||
<div class="absolute right-[2vw] bottom-0 max-w-[582px]">
|
||||
<img :src="ScreenshotDesktop" alt="App Screenshot" class="relative z-10 w-full" />
|
||||
</div>
|
||||
</div>
|
||||
@ -145,6 +158,8 @@ 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 UserCount from './user-count.svg?component'
|
||||
import ReviewCarousel from './components/ReviewCarousel/index.vue'
|
||||
import MobileLogo from './mobile-logo.svg?component'
|
||||
import ScreenshotMobile from './screenshot-mobile.png'
|
||||
import ScreenshotDesktop from './screenshot-desktop.webp'
|
||||
|
||||
3
src/pages/Home/user-count.svg
Normal file
|
After Width: | Height: | Size: 13 KiB |