Compare commits
12 Commits
1de788ccb7
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4587a59a89 | ||
|
|
d237dab098 | ||
| 6fc1da2884 | |||
| 5158b63ddd | |||
|
|
85708bb5a4 | ||
|
|
99af8da607 | ||
|
|
9235843f25 | ||
|
|
1153bce74e | ||
| e797612de6 | |||
| 3edda93691 | |||
| 6c1452be4d | |||
| 9733a0dcd6 |
@@ -5,6 +5,7 @@
|
|||||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>test-ssg</title>
|
<title>test-ssg</title>
|
||||||
|
<link rel="stylesheet" href="https://at.alicdn.com/t/c/font_4403230_bui5mtufs1c.css" />
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="app"></div>
|
<div id="app"></div>
|
||||||
|
|||||||
23
pnpm-lock.yaml
generated
@@ -14,9 +14,18 @@ importers:
|
|||||||
'@unhead/vue':
|
'@unhead/vue':
|
||||||
specifier: ^2.1.15
|
specifier: ^2.1.15
|
||||||
version: 2.1.15(vue@3.5.34(typescript@6.0.3))
|
version: 2.1.15(vue@3.5.34(typescript@6.0.3))
|
||||||
|
gsap:
|
||||||
|
specifier: ^3.15.0
|
||||||
|
version: 3.15.0
|
||||||
|
less:
|
||||||
|
specifier: ^4.6.4
|
||||||
|
version: 4.6.4
|
||||||
|
unhead:
|
||||||
|
specifier: 2.1.15
|
||||||
|
version: 2.1.15
|
||||||
vite-ssg:
|
vite-ssg:
|
||||||
specifier: ^28.3.0
|
specifier: ^28.3.0
|
||||||
version: 28.3.0(unhead@3.1.0(vite@8.0.12(@types/node@24.12.4)(terser@5.47.1)))(vite@8.0.12(@types/node@24.12.4)(terser@5.47.1))(vue-router@4.6.4(vue@3.5.34(typescript@6.0.3)))(vue@3.5.34(typescript@6.0.3))
|
version: 28.3.0(unhead@2.1.15)(vite@8.0.12(@types/node@24.12.4)(less@4.6.4)(terser@5.47.1))(vue-router@4.6.4(vue@3.5.34(typescript@6.0.3)))(vue@3.5.34(typescript@6.0.3))
|
||||||
vue:
|
vue:
|
||||||
specifier: ^3.5.34
|
specifier: ^3.5.34
|
||||||
version: 3.5.34(typescript@6.0.3)
|
version: 3.5.34(typescript@6.0.3)
|
||||||
@@ -203,42 +212,36 @@ packages:
|
|||||||
engines: {node: ^20.19.0 || >=22.12.0}
|
engines: {node: ^20.19.0 || >=22.12.0}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [glibc]
|
|
||||||
|
|
||||||
'@rolldown/binding-linux-arm64-musl@1.0.0':
|
'@rolldown/binding-linux-arm64-musl@1.0.0':
|
||||||
resolution: {integrity: sha512-EIVjy2cgd7uuMMo94FVkBp7F6DhcZAUwNURkSG3RwUmvAXR6s0ISxM81U+IydcZByPG0pZIHsf1b6kTxoFDgJA==}
|
resolution: {integrity: sha512-EIVjy2cgd7uuMMo94FVkBp7F6DhcZAUwNURkSG3RwUmvAXR6s0ISxM81U+IydcZByPG0pZIHsf1b6kTxoFDgJA==}
|
||||||
engines: {node: ^20.19.0 || >=22.12.0}
|
engines: {node: ^20.19.0 || >=22.12.0}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [musl]
|
|
||||||
|
|
||||||
'@rolldown/binding-linux-ppc64-gnu@1.0.0':
|
'@rolldown/binding-linux-ppc64-gnu@1.0.0':
|
||||||
resolution: {integrity: sha512-JEwwOPcwTLAcpDQlqSmjEmfs63xJnSiUNIGvLcDLUHCWK4XowpS/7c7tUsUH6uT/ct6bMUTdXKfI8967FYj6mg==}
|
resolution: {integrity: sha512-JEwwOPcwTLAcpDQlqSmjEmfs63xJnSiUNIGvLcDLUHCWK4XowpS/7c7tUsUH6uT/ct6bMUTdXKfI8967FYj6mg==}
|
||||||
engines: {node: ^20.19.0 || >=22.12.0}
|
engines: {node: ^20.19.0 || >=22.12.0}
|
||||||
cpu: [ppc64]
|
cpu: [ppc64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [glibc]
|
|
||||||
|
|
||||||
'@rolldown/binding-linux-s390x-gnu@1.0.0':
|
'@rolldown/binding-linux-s390x-gnu@1.0.0':
|
||||||
resolution: {integrity: sha512-0wjCFhLrihtAubnT9iA0N++0pSV0z5Hg7tNGdNJ4RFaINceHadoF+kiFGyY1qSSNVIAZtLotG8Ju1bgDPkjnFA==}
|
resolution: {integrity: sha512-0wjCFhLrihtAubnT9iA0N++0pSV0z5Hg7tNGdNJ4RFaINceHadoF+kiFGyY1qSSNVIAZtLotG8Ju1bgDPkjnFA==}
|
||||||
engines: {node: ^20.19.0 || >=22.12.0}
|
engines: {node: ^20.19.0 || >=22.12.0}
|
||||||
cpu: [s390x]
|
cpu: [s390x]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [glibc]
|
|
||||||
|
|
||||||
'@rolldown/binding-linux-x64-gnu@1.0.0':
|
'@rolldown/binding-linux-x64-gnu@1.0.0':
|
||||||
resolution: {integrity: sha512-Dfn7iak9BcMMePxcoJfpSbWqnEyrp/dRF63/8qW/eHBdOZov6x5aShLLEYGYdIeSJ6vMLK/XCVB+lGIxm41bQA==}
|
resolution: {integrity: sha512-Dfn7iak9BcMMePxcoJfpSbWqnEyrp/dRF63/8qW/eHBdOZov6x5aShLLEYGYdIeSJ6vMLK/XCVB+lGIxm41bQA==}
|
||||||
engines: {node: ^20.19.0 || >=22.12.0}
|
engines: {node: ^20.19.0 || >=22.12.0}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [glibc]
|
|
||||||
|
|
||||||
'@rolldown/binding-linux-x64-musl@1.0.0':
|
'@rolldown/binding-linux-x64-musl@1.0.0':
|
||||||
resolution: {integrity: sha512-5/utzzDmD/pD/bmuaUcbTf/sZYy0aztwIVlfpoW1fTjCZ0BaPOMVWGZL1zvgxyi7ZIVYWlxKONHmSbHuiOh8Jw==}
|
resolution: {integrity: sha512-5/utzzDmD/pD/bmuaUcbTf/sZYy0aztwIVlfpoW1fTjCZ0BaPOMVWGZL1zvgxyi7ZIVYWlxKONHmSbHuiOh8Jw==}
|
||||||
engines: {node: ^20.19.0 || >=22.12.0}
|
engines: {node: ^20.19.0 || >=22.12.0}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [musl]
|
|
||||||
|
|
||||||
'@rolldown/binding-openharmony-arm64@1.0.0':
|
'@rolldown/binding-openharmony-arm64@1.0.0':
|
||||||
resolution: {integrity: sha512-ouJs8VcUomfLfpbUECqFMRqdV4x6aeAK3MA4m6vTrJJjKyWTV5KnxZx7Jd9G+GlDaQQxubcba00x16OyJ1meig==}
|
resolution: {integrity: sha512-ouJs8VcUomfLfpbUECqFMRqdV4x6aeAK3MA4m6vTrJJjKyWTV5KnxZx7Jd9G+GlDaQQxubcba00x16OyJ1meig==}
|
||||||
@@ -562,28 +565,24 @@ packages:
|
|||||||
engines: {node: '>= 12.0.0'}
|
engines: {node: '>= 12.0.0'}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [glibc]
|
|
||||||
|
|
||||||
lightningcss-linux-arm64-musl@1.32.0:
|
lightningcss-linux-arm64-musl@1.32.0:
|
||||||
resolution: {integrity: sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==}
|
resolution: {integrity: sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==}
|
||||||
engines: {node: '>= 12.0.0'}
|
engines: {node: '>= 12.0.0'}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [musl]
|
|
||||||
|
|
||||||
lightningcss-linux-x64-gnu@1.32.0:
|
lightningcss-linux-x64-gnu@1.32.0:
|
||||||
resolution: {integrity: sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==}
|
resolution: {integrity: sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==}
|
||||||
engines: {node: '>= 12.0.0'}
|
engines: {node: '>= 12.0.0'}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [glibc]
|
|
||||||
|
|
||||||
lightningcss-linux-x64-musl@1.32.0:
|
lightningcss-linux-x64-musl@1.32.0:
|
||||||
resolution: {integrity: sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==}
|
resolution: {integrity: sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==}
|
||||||
engines: {node: '>= 12.0.0'}
|
engines: {node: '>= 12.0.0'}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
libc: [musl]
|
|
||||||
|
|
||||||
lightningcss-win32-arm64-msvc@1.32.0:
|
lightningcss-win32-arm64-msvc@1.32.0:
|
||||||
resolution: {integrity: sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==}
|
resolution: {integrity: sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==}
|
||||||
@@ -1556,7 +1555,7 @@ snapshots:
|
|||||||
|
|
||||||
vite-ssg-sitemap@0.10.0: {}
|
vite-ssg-sitemap@0.10.0: {}
|
||||||
|
|
||||||
vite-ssg@28.3.0(unhead@3.1.0(vite@8.0.12(@types/node@24.12.4)(terser@5.47.1)))(vite@8.0.12(@types/node@24.12.4)(terser@5.47.1))(vue-router@4.6.4(vue@3.5.34(typescript@6.0.3)))(vue@3.5.34(typescript@6.0.3)):
|
vite-ssg@28.3.0(unhead@2.1.15)(vite@8.0.12(@types/node@24.12.4)(less@4.6.4)(terser@5.47.1))(vue-router@4.6.4(vue@3.5.34(typescript@6.0.3)))(vue@3.5.34(typescript@6.0.3)):
|
||||||
dependencies:
|
dependencies:
|
||||||
'@unhead/dom': 2.1.15(unhead@2.1.15)
|
'@unhead/dom': 2.1.15(unhead@2.1.15)
|
||||||
'@unhead/vue': 2.1.15(vue@3.5.34(typescript@6.0.3))
|
'@unhead/vue': 2.1.15(vue@3.5.34(typescript@6.0.3))
|
||||||
|
|||||||
@@ -154,3 +154,28 @@ button[custom="black-box"] {
|
|||||||
--button-click-color: #fff;
|
--button-click-color: #fff;
|
||||||
--button-font-size: 1.6rem;
|
--button-font-size: 1.6rem;
|
||||||
}
|
}
|
||||||
|
.hover-bottom-animation {
|
||||||
|
position: relative;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.hover-bottom-animation::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
height: 2px;
|
||||||
|
width: 0;
|
||||||
|
right: 0;
|
||||||
|
left: auto;
|
||||||
|
bottom: 0;
|
||||||
|
transition: width 0.2s ease-in-out;
|
||||||
|
-webkit-transition: width 0.2s ease-in-out;
|
||||||
|
background-color: #fff;
|
||||||
|
}
|
||||||
|
.hover-bottom-animation:hover::before {
|
||||||
|
width: 100%;
|
||||||
|
left: 0;
|
||||||
|
right: auto;
|
||||||
|
}
|
||||||
|
.hover-bottom-animation.active:before,
|
||||||
|
.hover-bottom-animation.router-link-exact-active:before {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|||||||
@@ -15,12 +15,19 @@ p {
|
|||||||
* {
|
* {
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
h1, h2, h3, h4, h5, h6, .products-title{
|
|
||||||
|
h1,
|
||||||
|
h2,
|
||||||
|
h3,
|
||||||
|
h4,
|
||||||
|
h5,
|
||||||
|
h6,
|
||||||
|
.products-title {
|
||||||
font-family: Poppins, sans-serif;
|
font-family: Poppins, sans-serif;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
letter-spacing: 2px;
|
letter-spacing: 2px;
|
||||||
color: #222222;
|
color: #222222;
|
||||||
text-transform: capitalize;
|
text-transform: capitalize;
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes loading {
|
@keyframes loading {
|
||||||
@@ -176,3 +183,32 @@ button[custom="black-box"] {
|
|||||||
--button-click-color: #fff;
|
--button-click-color: #fff;
|
||||||
--button-font-size: 1.6rem;
|
--button-font-size: 1.6rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.hover-bottom-animation {
|
||||||
|
position: relative;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
height: 2px;
|
||||||
|
width: 0;
|
||||||
|
right: 0;
|
||||||
|
left: auto;
|
||||||
|
bottom: 0;
|
||||||
|
transition: width 0.2s ease-in-out;
|
||||||
|
-webkit-transition: width 0.2s ease-in-out;
|
||||||
|
background-color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover::before {
|
||||||
|
width: 100%;
|
||||||
|
left: 0;
|
||||||
|
right: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.active:before,
|
||||||
|
&.router-link-exact-active:before {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
3
src/assets/images/aida/diamond.svg
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512">
|
||||||
|
<path fill="#666" d="M464 0H112c-4 0-7.8 2-10 5.4L2 152.6c-2.9 4.4-2.6 10.2.7 14.2l276 340.8c4.8 5.9 13.8 5.9 18.6 0l276-340.8c3.3-4.1 3.6-9.8.7-14.2L474.1 5.4C471.8 2 468.1 0 464 0zm-19.3 48l63.3 96h-68.4l-51.7-96h56.8zm-202.1 0h90.7l51.7 96H191l51.6-96zm-111.3 0h56.8l-51.7 96H68l63.3-96zm-43 144h51.4L208 352 88.3 192zm102.9 0h193.6L288 435.3 191.2 192zM368 352l68.2-160h51.4L368 352z"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 462 B |
|
Before Width: | Height: | Size: 6.0 KiB After Width: | Height: | Size: 6.0 KiB |
|
Before Width: | Height: | Size: 8.2 KiB After Width: | Height: | Size: 8.2 KiB |
|
Before Width: | Height: | Size: 6.5 KiB After Width: | Height: | Size: 6.5 KiB |
8
src/assets/images/aida/time.svg
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024">
|
||||||
|
<g transform="translate(1024 0) scale(-1 1)">
|
||||||
|
<path
|
||||||
|
fill="#666"
|
||||||
|
d="M512 64C264.96 64 64 264.96 64 512s200.96 448 448 448 448-200.96 448-448S759.04 64 512 64zM512 895.712c-211.584 0-383.712-172.16-383.712-383.712C128.288 300.416 300.416 128.288 512 128.288c211.552 0 383.712 172.128 383.712 383.712C895.712 723.552 723.552 895.712 512 895.712zM671.968 512 512 512 512 288.064c0-17.76-14.24-32.128-32-32.128s-32 14.4-32 32.128L448 544c0 17.76 14.272 32 32 32l191.968 0c17.76 0 32.128-14.24 32.128-32S689.728 512 671.968 512z"
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 627 B |
@@ -7,24 +7,25 @@
|
|||||||
transition: 'stroke-dashoffset 10ms linear',
|
transition: 'stroke-dashoffset 10ms linear',
|
||||||
strokeDasharray: `${progress}, ${max}`,
|
strokeDasharray: `${progress}, ${max}`,
|
||||||
stroke: '#222',
|
stroke: '#222',
|
||||||
strokeWidth: 4,
|
strokeWidth: 4
|
||||||
}"
|
}"
|
||||||
fill="none"
|
fill="none"
|
||||||
></path>
|
></path>
|
||||||
</svg>
|
</svg>
|
||||||
|
<span class="iconfont icon-direction-up"></span>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, onMounted, onUnmounted } from "vue";
|
import { ref } from "vue";
|
||||||
const max = ref(98 * Math.PI);
|
const max = ref(98 * Math.PI);
|
||||||
const progress = ref(0);
|
const progress = ref(0);
|
||||||
const handleScroll = (e: number) => {
|
const handleScroll = (e: number) => {
|
||||||
progress.value = (e / 100) * max.value;
|
progress.value = (e / 100) * max.value
|
||||||
};
|
}
|
||||||
const handleClick = () => {
|
const handleClick = () => {
|
||||||
document.documentElement.scrollTo({ top: 0, behavior: "smooth" });
|
document.documentElement.scrollTo({ top: 0, behavior: 'smooth' })
|
||||||
};
|
}
|
||||||
</script>
|
</script>
|
||||||
<style scoped lang="less">
|
<style scoped lang="less">
|
||||||
.back-top {
|
.back-top {
|
||||||
@@ -51,6 +52,20 @@
|
|||||||
transition: stroke-dashoffset 10ms linear;
|
transition: stroke-dashoffset 10ms linear;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
&:hover > .iconfont {
|
||||||
|
animation: animArrow 1s infinite;
|
||||||
|
}
|
||||||
|
@keyframes animArrow {
|
||||||
|
0% {
|
||||||
|
transform: rotate(0deg);
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
transform: translateY(-3px);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: rotate(0deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
animation: back-top-hidden 0.2s linear both;
|
animation: back-top-hidden 0.2s linear both;
|
||||||
&.active {
|
&.active {
|
||||||
|
|||||||
100
src/components/down-menu.vue
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
<template>
|
||||||
|
<div class="down-menu">
|
||||||
|
<div class="title hover-bottom-animation" to="#">
|
||||||
|
{{ title }}
|
||||||
|
<span class="iconfont icon-arrow-down-bold"></span>
|
||||||
|
</div>
|
||||||
|
<div class="child">
|
||||||
|
<slot></slot>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, onMounted, onUnmounted } from 'vue'
|
||||||
|
const props = defineProps({
|
||||||
|
title: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
<style scoped lang="less">
|
||||||
|
.down-menu {
|
||||||
|
position: relative;
|
||||||
|
> .title {
|
||||||
|
margin: 0 14px;
|
||||||
|
color: #fff;
|
||||||
|
font-size: 15px;
|
||||||
|
text-decoration: none;
|
||||||
|
line-height: 37px;
|
||||||
|
display: inline-block;
|
||||||
|
position: relative;
|
||||||
|
cursor: pointer;
|
||||||
|
&::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
height: 2px;
|
||||||
|
width: 0;
|
||||||
|
right: 0;
|
||||||
|
left: auto;
|
||||||
|
bottom: 0;
|
||||||
|
transition: width 0.2s ease-in-out;
|
||||||
|
-webkit-transition: width 0.2s ease-in-out;
|
||||||
|
background-color: #fff;
|
||||||
|
}
|
||||||
|
&:hover::before {
|
||||||
|
width: 100%;
|
||||||
|
left: 0;
|
||||||
|
right: auto;
|
||||||
|
}
|
||||||
|
&.router-link-exact-active:not(.parent):before {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
> .iconfont {
|
||||||
|
opacity: 0.5;
|
||||||
|
font-size: 10px;
|
||||||
|
margin-left: 5px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
> .child {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
visibility: hidden;
|
||||||
|
width: 250px;
|
||||||
|
height: auto;
|
||||||
|
padding: 10px 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
border: 1px solid #e1e1e1;
|
||||||
|
background-color: #fff;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
transform: translateY(calc(100% + 5px));
|
||||||
|
> * {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 10px 15px;
|
||||||
|
color: #000;
|
||||||
|
font-size: 15px;
|
||||||
|
text-decoration: none;
|
||||||
|
text-align: left;
|
||||||
|
&:hover {
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&:hover > .child {
|
||||||
|
animation: child-show 0.2s linear both;
|
||||||
|
}
|
||||||
|
@keyframes child-show {
|
||||||
|
0% {
|
||||||
|
visibility: hidden;
|
||||||
|
// opacity: 0;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
// opacity: 1;
|
||||||
|
transform: translateY(100%);
|
||||||
|
visibility: visible;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -1,25 +1,78 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import { ref, onMounted, onUnmounted, reactive, toRefs } from "vue";
|
|
||||||
//const props = defineProps({
|
|
||||||
//})
|
|
||||||
//const emit = defineEmits([
|
|
||||||
//])
|
|
||||||
let data = reactive({
|
|
||||||
})
|
|
||||||
onMounted(()=>{
|
|
||||||
})
|
|
||||||
onUnmounted(()=>{
|
|
||||||
})
|
|
||||||
defineExpose({})
|
|
||||||
const {} = toRefs(data);
|
|
||||||
</script>
|
|
||||||
<template>
|
<template>
|
||||||
<header class="header">
|
<footer class="main-footer">
|
||||||
|
<div class="left">
|
||||||
</header>
|
<span>©2025 Code-Create Limited</span>
|
||||||
|
</div>
|
||||||
|
<div class="right">
|
||||||
|
<div v-for="item in nav" :key="item.path">
|
||||||
|
<router-link class="link" :to="item.path">{{ item.name }}</router-link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
</template>
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, onMounted, onUnmounted, reactive, toRefs } from 'vue'
|
||||||
|
const nav = ref([
|
||||||
|
{
|
||||||
|
name: 'Privacy Policy',
|
||||||
|
path: '/privacy-policy'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Terms of Use',
|
||||||
|
path: '/terms-of-use'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Disclaimer',
|
||||||
|
path: '/disclaimer'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Site Map',
|
||||||
|
path: '/site-map'
|
||||||
|
}
|
||||||
|
])
|
||||||
|
</script>
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
.header{
|
.main-footer {
|
||||||
position: relative;
|
width: 100%;
|
||||||
|
max-width: 1200px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 15px 0;
|
||||||
|
min-height: 140px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
font-family: Poppins, sans-serif;
|
||||||
|
> .left {
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: normal;
|
||||||
|
line-height: 24px;
|
||||||
|
color: #000000b3;
|
||||||
|
}
|
||||||
|
> .right {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
> div {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding-left: 20px;
|
||||||
|
border-left: 1px solid #0a0a0a;
|
||||||
|
margin-left: 20px;
|
||||||
|
&:first-child {
|
||||||
|
border-left: none;
|
||||||
|
padding-left: 0;
|
||||||
|
margin-left: 0;
|
||||||
|
}
|
||||||
|
> .link {
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 12px;
|
||||||
|
transition: all 0.2s ease-out;
|
||||||
|
box-sizing: border-box;
|
||||||
|
box-shadow: none;
|
||||||
|
text-decoration: none;
|
||||||
|
display: inline-block;
|
||||||
|
color: #0a0a0a;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
@@ -1,49 +1,90 @@
|
|||||||
<template>
|
<template>
|
||||||
<header class="main-header" v-scroll-progress>
|
<header class="main-header" v-scroll-progress>
|
||||||
<img class="logo" src="../assets/logo-full.png" alt="code-create" />
|
<a href="/" class="logo"><img src="../assets/logo-full.png" alt="code-create" /></a>
|
||||||
<div class="center-nav">
|
<div class="center-nav">
|
||||||
<div class="nav-item" v-for="item in navList" :key="item.path">
|
<div class="nav-item" v-for="item in navList" :key="item.name">
|
||||||
<router-link class="nav-item-link" :to="item.path">
|
<down-menu :title="item.name" v-if="item.children">
|
||||||
|
<router-link :to="child.path" v-for="child in item.children" :key="child.path">
|
||||||
|
{{ child.name }}
|
||||||
|
</router-link>
|
||||||
|
</down-menu>
|
||||||
|
<router-link class="link hover-bottom-animation" :to="item.path" v-else>
|
||||||
{{ item.name }}
|
{{ item.name }}
|
||||||
|
<span v-show="item.children" class="iconfont icon-arrow-down-bold"></span>
|
||||||
</router-link>
|
</router-link>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="right">
|
<div class="right">
|
||||||
<span>English</span>
|
<down-menu title="English">
|
||||||
<span>My Account</span>
|
<router-link class="link" to="/">English</router-link>
|
||||||
|
<router-link class="link" to="/zh-cn">简体中文</router-link>
|
||||||
|
<router-link class="link" to="/zh-tw">繁體中文</router-link>
|
||||||
|
</down-menu>
|
||||||
|
<span class="">
|
||||||
|
<span class="iconfont icon-wode"></span>
|
||||||
|
<span>My Account</span>
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
</template>
|
</template>
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, onMounted, onUnmounted } from "vue";
|
import { ref, onMounted, onUnmounted, computed } from 'vue'
|
||||||
import { useRouter } from "vue-router";
|
import DownMenu from './down-menu.vue'
|
||||||
const router = useRouter();
|
|
||||||
|
import { useRoute } from 'vue-router'
|
||||||
|
const route = useRoute()
|
||||||
|
console.log(route)
|
||||||
|
|
||||||
const navList = ref([
|
const navList = ref([
|
||||||
{
|
{
|
||||||
name: "Home",
|
name: 'Home',
|
||||||
path: "/",
|
path: '/'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "About Us",
|
name: 'About Us',
|
||||||
path: "/about",
|
path: '/about-us'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Our Solutions",
|
name: 'Our Solutions',
|
||||||
path: "/solutions",
|
path: '',
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
name: 'AiDA 3.1',
|
||||||
|
path: '/aida'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Mixi',
|
||||||
|
path: '/mixi'
|
||||||
|
}
|
||||||
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Communities",
|
name: 'Communities',
|
||||||
path: "/communities",
|
path: '',
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
name: 'Events',
|
||||||
|
path: '/events'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'User Stories',
|
||||||
|
path: '/user-stories'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Help Centre',
|
||||||
|
path: '/help-centre'
|
||||||
|
}
|
||||||
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Media",
|
name: 'Media',
|
||||||
path: "/media",
|
path: '/media'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Contact Us",
|
name: 'Contact Us',
|
||||||
path: "/contact",
|
path: '/contact-us'
|
||||||
},
|
}
|
||||||
]);
|
])
|
||||||
</script>
|
</script>
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
.main-header {
|
.main-header {
|
||||||
@@ -65,17 +106,84 @@
|
|||||||
> .logo {
|
> .logo {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
width: auto;
|
width: auto;
|
||||||
|
img {
|
||||||
|
width: auto;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
> .center-nav {
|
> .center-nav {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
> .nav-item {
|
> .nav-item {
|
||||||
> .nav-item-link {
|
position: relative;
|
||||||
|
> .link {
|
||||||
margin: 0 14px;
|
margin: 0 14px;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
font-size: 16px;
|
font-size: 15px;
|
||||||
|
text-decoration: none;
|
||||||
|
line-height: 37px;
|
||||||
|
display: inline-block;
|
||||||
|
|
||||||
|
> .iconfont {
|
||||||
|
opacity: 0.5;
|
||||||
|
font-size: 10px;
|
||||||
|
margin-left: 5px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
> .child {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
visibility: hidden;
|
||||||
|
width: 250px;
|
||||||
|
height: auto;
|
||||||
|
padding: 10px 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
border: 1px solid #e1e1e1;
|
||||||
|
background-color: #fff;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
transform: translateY(calc(100% + 5px));
|
||||||
|
> .child-link {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 10px 15px;
|
||||||
|
color: #000;
|
||||||
|
font-size: 15px;
|
||||||
|
text-decoration: none;
|
||||||
|
text-align: left;
|
||||||
|
&:hover {
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&:hover > .child {
|
||||||
|
animation: child-show 0.2s linear both;
|
||||||
|
}
|
||||||
|
@keyframes child-show {
|
||||||
|
0% {
|
||||||
|
visibility: hidden;
|
||||||
|
// opacity: 0;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
// opacity: 1;
|
||||||
|
transform: translateY(100%);
|
||||||
|
visibility: visible;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
> .right {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
> span {
|
||||||
|
margin: 0 14px;
|
||||||
|
color: #fff;
|
||||||
|
font-size: 15px;
|
||||||
|
text-decoration: none;
|
||||||
|
line-height: 37px;
|
||||||
|
display: inline-block;
|
||||||
|
position: relative;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
177
src/directives/custom-animation.js
Normal file
@@ -0,0 +1,177 @@
|
|||||||
|
/**
|
||||||
|
* 自定义动画指令
|
||||||
|
* v-custom-animation.scroll.once.parent="{GetRoot, activeClass}"
|
||||||
|
* 修饰符
|
||||||
|
* scroll: 是否监听滚动事件
|
||||||
|
* once: 是否只执行一次
|
||||||
|
* parent: 是否监听父元素滚动事件-优先级(GetRoot > parent > document)
|
||||||
|
* 参数
|
||||||
|
* GetRoot: 获取根元素函数-优先级(GetRoot > parent > document)
|
||||||
|
* activeClass: 激活类名-默认值(active)
|
||||||
|
*
|
||||||
|
* 子元素动画
|
||||||
|
* <div translate-x-s="-100" translate-x="100"></div>
|
||||||
|
* 添加动画属性
|
||||||
|
* translate-x-s: 水平方向移动开始位置
|
||||||
|
* translate-x: 水平方向移动结束位置
|
||||||
|
* ......(属性支持查看 T 对象)
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
const roots = new Map()
|
||||||
|
const els = new Map()
|
||||||
|
const T = {
|
||||||
|
translateX_s: 'translate-x-s',
|
||||||
|
translateX: 'translate-x',
|
||||||
|
translateY_s: 'translate-y-s',
|
||||||
|
translateY: 'translate-y',
|
||||||
|
scale_s: 'scale-s',
|
||||||
|
scale: 'scale',
|
||||||
|
scaleX_s: 'scale-x-s',
|
||||||
|
scaleX: 'scale-x',
|
||||||
|
scaleY_s: 'scale-y-s',
|
||||||
|
scaleY: 'scale-y',
|
||||||
|
rotate_s: 'rotate-s',
|
||||||
|
rotate: 'rotate',
|
||||||
|
rotateX_s: 'rotate-x-s',
|
||||||
|
rotateX: 'rotate-x',
|
||||||
|
rotateY_s: 'rotate-y-s',
|
||||||
|
rotateY: 'rotate-y',
|
||||||
|
rotateZ_s: 'rotate-z-s',
|
||||||
|
rotateZ: 'rotate-z',
|
||||||
|
opacity_s: 'opacity-s',
|
||||||
|
opacity: 'opacity',
|
||||||
|
}
|
||||||
|
const types = Object.values(T)
|
||||||
|
const typesStr = types.map(v => `[${v}]`).join(',')
|
||||||
|
const resize = new ResizeObserver((e) => {
|
||||||
|
e.forEach(({ target }) => {
|
||||||
|
requestAnimationFrame(() => handleScroll({ target }))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'custom-animation',
|
||||||
|
mounted(el, binding) {
|
||||||
|
const { value, modifiers } = binding
|
||||||
|
const {
|
||||||
|
GetRoot,// 获取根元素函数
|
||||||
|
activeClass = 'active'// 激活类名
|
||||||
|
} = value || {}
|
||||||
|
const {
|
||||||
|
scroll = false,// 是否监听滚动事件
|
||||||
|
once = false,// 是否只执行一次
|
||||||
|
parent = false,// 是否监听父元素滚动事件
|
||||||
|
} = modifiers
|
||||||
|
const root = GetRoot ? GetRoot() : parent ? el.parentElement : document;
|
||||||
|
if (el === root) return;
|
||||||
|
add(el, root)
|
||||||
|
els.set(el, {
|
||||||
|
root,// 根元素
|
||||||
|
scroll,
|
||||||
|
once,
|
||||||
|
activeClass,
|
||||||
|
isActive: false,
|
||||||
|
})
|
||||||
|
},
|
||||||
|
beforeUnmount(el, binding) {
|
||||||
|
remove(el)
|
||||||
|
els.delete(el)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
function add(el, root = document) {
|
||||||
|
requestAnimationFrame(() => handleScroll({ target: root }))
|
||||||
|
resize.observe(el)
|
||||||
|
if (roots.has(root)) {
|
||||||
|
let obj = roots.get(root)
|
||||||
|
obj.els.push(el)
|
||||||
|
obj.observer.observe(el)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
resize.observe(isDocumentRoot(root))
|
||||||
|
root.addEventListener('scroll', handleScroll)
|
||||||
|
const observer = new IntersectionObserver((entries) => {
|
||||||
|
entries.forEach((entry) => {
|
||||||
|
const { target } = entry
|
||||||
|
const obj = els.get(target)
|
||||||
|
if (!obj) return
|
||||||
|
if (obj.once && obj.isActive) return;// 只执行一次,且已可见,不执行
|
||||||
|
obj.isActive = entry.isIntersecting;
|
||||||
|
target.classList.toggle(obj.activeClass, obj.isActive)
|
||||||
|
})
|
||||||
|
}, { root })
|
||||||
|
observer.observe(el)
|
||||||
|
roots.set(root, { els: [el], observer, resize })
|
||||||
|
}
|
||||||
|
function remove(el, root = document) {
|
||||||
|
if (!roots.has(root)) return
|
||||||
|
const obj = roots.get(root)
|
||||||
|
if (obj.els.includes(el)) {
|
||||||
|
obj.observer.unobserve(el)
|
||||||
|
obj.resize.unobserve(el)
|
||||||
|
obj.els.splice(obj.els.indexOf(el), 1)
|
||||||
|
}
|
||||||
|
if (obj.els.length === 0) {
|
||||||
|
obj.observer.disconnect()
|
||||||
|
resize.unobserve(isDocumentRoot(root))
|
||||||
|
root.removeEventListener('scroll', handleScroll)
|
||||||
|
roots.delete(root)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var timer = null
|
||||||
|
async function handleScroll({ target: root }) {
|
||||||
|
clearTimeout(timer)
|
||||||
|
timer = await new Promise(resolve => setTimeout(resolve, 10))
|
||||||
|
const obj = roots.get(root)
|
||||||
|
if (!obj) return
|
||||||
|
obj.els.forEach((el) => {
|
||||||
|
const item = els.get(el)
|
||||||
|
if (!item) return
|
||||||
|
if (!item.scroll) return
|
||||||
|
const children = Array.from(el.querySelectorAll(typesStr))
|
||||||
|
if (Object.values(T).some(v => hasAttr(el, v))) children.push(el)
|
||||||
|
if (children.length === 0) return
|
||||||
|
const rootEl = isDocumentRoot(root)
|
||||||
|
const offsetHeight = root === document ? window.innerHeight : rootEl.offsetHeight
|
||||||
|
const offsetTop = rootEl.offsetTop
|
||||||
|
const scrollTop = rootEl.scrollTop
|
||||||
|
const elTop_bottom = offsetHeight - (el.offsetTop - offsetTop - rootEl.scrollTop)
|
||||||
|
const maxHeight = offsetHeight + el.offsetHeight
|
||||||
|
const p = Math.min(1, Math.max(0, elTop_bottom / maxHeight))
|
||||||
|
children.forEach((item) => {
|
||||||
|
item.style.transition = 'transform 0.5s ease-out'
|
||||||
|
|
||||||
|
const tX = getCurrentValue(item, T.translateX_s, T.translateX, p)
|
||||||
|
const tY = getCurrentValue(item, T.translateY_s, T.translateY, p)
|
||||||
|
const sx = getCurrentValue(item, T.scaleX_s, T.scaleX, p, T.scale_s, T.scale, 1)
|
||||||
|
const sy = getCurrentValue(item, T.scaleY_s, T.scaleY, p, T.scale_s, T.scale, 1)
|
||||||
|
const r = getCurrentValue(item, T.rotate_s, T.rotate, p)
|
||||||
|
const rx = getCurrentValue(item, T.rotateX_s, T.rotateX, p)
|
||||||
|
const ry = getCurrentValue(item, T.rotateY_s, T.rotateY, p)
|
||||||
|
const rz = getCurrentValue(item, T.rotateZ_s, T.rotateZ, p)
|
||||||
|
const transform = `translate(${tX}px, ${tY}px) scale(${sx}, ${sy}) rotate(${r}deg) rotateX(${rx}deg) rotateY(${ry}deg) rotateZ(${rz}deg)`
|
||||||
|
item.style.transform = transform
|
||||||
|
if (hasAttr(item, [T.opacity_s, T.opacity])) {
|
||||||
|
item.style.opacity = getCurrentValue(item, T.opacity_s, T.opacity, p, T.opacity_s, T.opacity, 1)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
function getCurrentValue(el, start, end, progress, bStart, bEnd, defaultValue = 0) {
|
||||||
|
// const startNum = Number(el.getAttribute(start) || el.getAttribute(bStart)) || defaultValue
|
||||||
|
// const endNum = Number(el.getAttribute(end) || el.getAttribute(bEnd)) || defaultValue
|
||||||
|
const startNum = hasAttr(el, start) ? Number(el.getAttribute(start)) : hasAttr(el, bStart) ? Number(el.getAttribute(bStart)) : defaultValue
|
||||||
|
const endNum = hasAttr(el, end) ? Number(el.getAttribute(end)) : hasAttr(el, bEnd) ? Number(el.getAttribute(bEnd)) : defaultValue
|
||||||
|
return startNum + (endNum - startNum) * progress
|
||||||
|
}
|
||||||
|
function hasAttr(el, attr) {
|
||||||
|
if (Array.isArray(attr)) {
|
||||||
|
return attr.some(v => el.hasAttribute(v))
|
||||||
|
} else {
|
||||||
|
return el.hasAttribute(attr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 检查document是否为根元素
|
||||||
|
function isDocumentRoot(root) {
|
||||||
|
return root === document ? document.documentElement : root
|
||||||
|
}
|
||||||
@@ -2,7 +2,9 @@ import type { App } from 'vue'
|
|||||||
|
|
||||||
export default {
|
export default {
|
||||||
install(app: App) {
|
install(app: App) {
|
||||||
const directivesList = import.meta.glob('./*.ts', { eager: true }) as any;
|
const directivesList1 = import.meta.glob('./*.ts', { eager: true }) as any;
|
||||||
|
const directivesList2 = import.meta.glob('./*.js', { eager: true }) as any;
|
||||||
|
const directivesList = { ...directivesList1, ...directivesList2 };
|
||||||
Object.keys(directivesList).forEach(key => {
|
Object.keys(directivesList).forEach(key => {
|
||||||
app.directive(directivesList[key].default.name, directivesList[key].default);
|
app.directive(directivesList[key].default.name, directivesList[key].default);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ export default {
|
|||||||
rootMap.set(obj.root, [obj])
|
rootMap.set(obj.root, [obj])
|
||||||
obj.root.addEventListener('scroll', handleScroll)
|
obj.root.addEventListener('scroll', handleScroll)
|
||||||
},
|
},
|
||||||
beforeUnmount(el: HTMLElement, binding: any) {
|
beforeUnmount(el: HTMLElement) {
|
||||||
rootMap.forEach((objs, root) => {
|
rootMap.forEach((objs, root) => {
|
||||||
if (objs.some((v: any) => v.el === el)) {
|
if (objs.some((v: any) => v.el === el)) {
|
||||||
objs = objs.filter((v_: any) => v_.el !== el)
|
objs = objs.filter((v_: any) => v_.el !== el)
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { gsap, } from 'gsap'
|
|||||||
import { ScrollTrigger } from 'gsap/ScrollTrigger'
|
import { ScrollTrigger } from 'gsap/ScrollTrigger'
|
||||||
export default {
|
export default {
|
||||||
name: 'tween-animation',
|
name: 'tween-animation',
|
||||||
mounted(el: HTMLElement, binding: any) {
|
mounted(el: HTMLElement) {
|
||||||
// if(!binding.value.isGsap)return
|
// if(!binding.value.isGsap)return
|
||||||
let dom = document.querySelector('body')
|
let dom = document.querySelector('body')
|
||||||
gsap.registerPlugin(ScrollTrigger);
|
gsap.registerPlugin(ScrollTrigger);
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ export const createApp = ViteSSG(App, {
|
|||||||
routes,
|
routes,
|
||||||
base: import.meta.env.BASE_URL,
|
base: import.meta.env.BASE_URL,
|
||||||
},
|
},
|
||||||
({ app, router, routes, isClient, initialState }) => {
|
({ app }) => {
|
||||||
// 注册全局指令
|
// 注册全局指令
|
||||||
app.use(directives)
|
app.use(directives)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,60 +0,0 @@
|
|||||||
<template>
|
|
||||||
<section id="center">
|
|
||||||
<div class="hero">
|
|
||||||
<img :src="heroImg" class="base" width="170" height="179" alt="" />
|
|
||||||
<img :src="vueLogo" class="framework" alt="Vue logo" />
|
|
||||||
<img :src="viteLogo" class="vite" alt="Vite logo" />
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<h1>Get started</h1>
|
|
||||||
<p>Edit <code>src/pages/HomeView.vue</code> and save to test <code>HMR</code></p>
|
|
||||||
</div>
|
|
||||||
<button type="button" class="counter" @click="count++">
|
|
||||||
Count is {{ count }}
|
|
||||||
</button>
|
|
||||||
<nav class="site-pages" aria-label="Site pages">
|
|
||||||
<RouterLink
|
|
||||||
v-for="page in pageLinks"
|
|
||||||
:key="page.to"
|
|
||||||
class="site-page-link"
|
|
||||||
:to="page.to"
|
|
||||||
>
|
|
||||||
{{ page.label }}
|
|
||||||
</RouterLink>
|
|
||||||
</nav>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<div class="ticks"></div>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<div class="ticks"></div>
|
|
||||||
<section id="spacer"></section>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import { useHead } from '@unhead/vue'
|
|
||||||
import { shallowRef } from 'vue'
|
|
||||||
import { RouterLink } from 'vue-router'
|
|
||||||
import heroImg from '../assets/hero.png'
|
|
||||||
import viteLogo from '../assets/vite.svg'
|
|
||||||
import vueLogo from '../assets/vue.svg'
|
|
||||||
|
|
||||||
const count = shallowRef(0)
|
|
||||||
const pageLinks = [
|
|
||||||
{ label: 'Home', to: '/' },
|
|
||||||
{ label: 'About', to: '/about' },
|
|
||||||
{ label: 'Products', to: '/products' },
|
|
||||||
{ label: 'Contact', to: '/contact' },
|
|
||||||
] as const
|
|
||||||
|
|
||||||
useHead({
|
|
||||||
title: 'Home | test-ssg',
|
|
||||||
meta: [
|
|
||||||
{
|
|
||||||
name: 'description',
|
|
||||||
content: 'Static generated home page for the Vite SSG test site.',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
@@ -1,19 +1,5 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, onMounted, onUnmounted, reactive, toRefs } from "vue";
|
|
||||||
import { gsap, TweenMax, TweenLite } from 'gsap'
|
|
||||||
import { ScrollTrigger } from 'gsap/ScrollTrigger'
|
|
||||||
//const props = defineProps({
|
|
||||||
//})
|
|
||||||
//const emit = defineEmits([
|
|
||||||
//])
|
|
||||||
let data = reactive({
|
|
||||||
})
|
|
||||||
onMounted(()=>{
|
|
||||||
})
|
|
||||||
onUnmounted(()=>{
|
|
||||||
})
|
|
||||||
defineExpose({})
|
defineExpose({})
|
||||||
const {} = toRefs(data);
|
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<section class="ecosystem">
|
<section class="ecosystem">
|
||||||
|
|||||||
@@ -1,19 +1,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, onMounted, onUnmounted, reactive, toRefs } from "vue";
|
|
||||||
import Ecosystem from './ecosystem.vue'
|
import Ecosystem from './ecosystem.vue'
|
||||||
import Title from './title.vue'
|
import Title from './title.vue'
|
||||||
//const props = defineProps({
|
|
||||||
//})
|
|
||||||
//const emit = defineEmits([
|
|
||||||
//])
|
|
||||||
let data = reactive({
|
|
||||||
})
|
|
||||||
onMounted(()=>{
|
|
||||||
})
|
|
||||||
onUnmounted(()=>{
|
|
||||||
})
|
|
||||||
defineExpose({})
|
defineExpose({})
|
||||||
const {} = toRefs(data);
|
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<div class="about-us">
|
<div class="about-us">
|
||||||
|
|||||||
@@ -1,19 +1,5 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, onMounted, onUnmounted, reactive, toRefs } from "vue";
|
|
||||||
import { gsap, TweenMax, TweenLite } from 'gsap'
|
|
||||||
import { ScrollTrigger } from 'gsap/ScrollTrigger'
|
|
||||||
//const props = defineProps({
|
|
||||||
//})
|
|
||||||
//const emit = defineEmits([
|
|
||||||
//])
|
|
||||||
let data = reactive({
|
|
||||||
})
|
|
||||||
onMounted(()=>{
|
|
||||||
})
|
|
||||||
onUnmounted(()=>{
|
|
||||||
})
|
|
||||||
defineExpose({})
|
defineExpose({})
|
||||||
const {} = toRefs(data);
|
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<section class="title-section">
|
<section class="title-section">
|
||||||
|
|||||||
819
src/pages/aida/index.vue
Normal file
@@ -0,0 +1,819 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { useHead } from '@unhead/vue'
|
||||||
|
import { shallowRef, useTemplateRef } from 'vue'
|
||||||
|
import aidaIntroBg from '@/assets/images/home/aida-intro-bg.png'
|
||||||
|
import aidaPanel from '@/assets/images/home/aida-panel.png'
|
||||||
|
import aidaBanner from '@/assets/images/aida/aida-banner.jpg'
|
||||||
|
import demoVideo from '@/assets/images/aida/aida-demo-video.mp4'
|
||||||
|
import diamondIcon from '@/assets/images/aida/diamond.svg'
|
||||||
|
import industryOne from '@/assets/images/aida/industry-1.png'
|
||||||
|
import industryTwo from '@/assets/images/aida/industry-2.png'
|
||||||
|
import industryThree from '@/assets/images/aida/industry-3.png'
|
||||||
|
import timeIcon from '@/assets/images/aida/time.svg'
|
||||||
|
|
||||||
|
const keyFeatures = [
|
||||||
|
"The world's first AI system in fashion design emphasizing user control, ensuring AI-generated designs align seamlessly with the designer's unique vision and brand identity.",
|
||||||
|
'Excels in synthesizing diverse inputs, such as moodboards, fabric prints, color choices, and sketches, into a cohesive collection quickly, harmoniously, and efficiently.',
|
||||||
|
'Significantly speeds up the design process by over 60%, quickly generating unlimited designs based on user input.',
|
||||||
|
'Incorporates cutting-edge AIGC technology to generate innovative designs and provide comprehensive assistance in the creative process.'
|
||||||
|
] as const
|
||||||
|
|
||||||
|
const benefits = [
|
||||||
|
{
|
||||||
|
image: industryOne,
|
||||||
|
alt: 'Light bulb icon',
|
||||||
|
text: 'Provides speedy ideation for fashion brands and individual designers'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
image: industryTwo,
|
||||||
|
alt: 'Stopwatch icon',
|
||||||
|
text: 'Speeds up the whole fashion design process to strive for the goal of sustainability and cost-saving'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
image: industryThree,
|
||||||
|
alt: 'Drawing brush icon',
|
||||||
|
text: 'Allows fashion novices who do not have drawing or sketching skills to create their own designs in a simple and easy mode'
|
||||||
|
}
|
||||||
|
] as const
|
||||||
|
|
||||||
|
const subscriptionHighlights = [
|
||||||
|
'Easily create your fashion collections in around 10 seconds based on your creative inspirations and Brand DNA',
|
||||||
|
'Upload mood boards, colour choices, fabric prints and sketches for generating unlimited design proposals',
|
||||||
|
'Save and retrieve your own designs with just a few clicks',
|
||||||
|
'A Cloud-based system by subscription for accessing anytime and anywhere',
|
||||||
|
'Easy to use, can learn in 10 minutes',
|
||||||
|
'Contact us for AiDA trial at info@code-create.com.hk'
|
||||||
|
] as const
|
||||||
|
|
||||||
|
const plans = [
|
||||||
|
{
|
||||||
|
icon: timeIcon,
|
||||||
|
name: 'Trial',
|
||||||
|
description: '7 days free trial',
|
||||||
|
action: 'Start Trial',
|
||||||
|
href: 'mailto:info@code-create.com.hk?subject=AiDA%203.1%20Trial'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: diamondIcon,
|
||||||
|
name: 'Corporate',
|
||||||
|
description: 'Customised plan',
|
||||||
|
action: 'Contact Us',
|
||||||
|
href: 'mailto:info@code-create.com.hk?subject=AiDA%203.1%20Corporate%20Plan'
|
||||||
|
}
|
||||||
|
] as const
|
||||||
|
|
||||||
|
const demoVideoRef = useTemplateRef<HTMLVideoElement>('demoVideoRef')
|
||||||
|
const isVideoPlaying = shallowRef(false)
|
||||||
|
|
||||||
|
async function toggleDemoVideo() {
|
||||||
|
const video = demoVideoRef.value
|
||||||
|
|
||||||
|
if (!video) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (video.paused) {
|
||||||
|
await video.play()
|
||||||
|
isVideoPlaying.value = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
video.pause()
|
||||||
|
isVideoPlaying.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleVideoStateChange() {
|
||||||
|
const video = demoVideoRef.value
|
||||||
|
isVideoPlaying.value = Boolean(video && !video.paused && !video.ended)
|
||||||
|
}
|
||||||
|
|
||||||
|
useHead({
|
||||||
|
title: 'AiDA 3.1 | Code Create',
|
||||||
|
meta: [
|
||||||
|
{
|
||||||
|
name: 'description',
|
||||||
|
content:
|
||||||
|
'AiDA 3.1 is an AI-based interactive design assistant for fashion designers.'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<main class="aida-page">
|
||||||
|
<section class="aida-hero" aria-labelledby="aida-title">
|
||||||
|
<img class="aida-hero-image" :src="aidaBanner" alt="AiDA 3.1 purple silk banner" />
|
||||||
|
<h1 id="aida-title" class="aida-hero-title">AiDA 3.1</h1>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="intro-section" aria-labelledby="intro-title">
|
||||||
|
<div class="intro-inner" v-custom-animation.scroll>
|
||||||
|
<h2 id="intro-title" class="intro-title">
|
||||||
|
AI-Based Interactive Design Assistant For Fashion
|
||||||
|
</h2>
|
||||||
|
<p class="intro-copy">
|
||||||
|
AiDA 3.1, a first-to-market technology that empowers fashion designers, based on
|
||||||
|
their creative inspirations, to work with AI to create original designs. With
|
||||||
|
just a few clicks, designers can choose or refine options to develop fashion
|
||||||
|
collections, bringing agility, efficiency and flexibility to conventional and
|
||||||
|
intensive studio processes.
|
||||||
|
</p>
|
||||||
|
<p class="pricing-copy">
|
||||||
|
Annual Subscription Fee: $5,000 HKD / Year (50000 Credits)<br />
|
||||||
|
Monthly Subscription Fee: $500 HKD / Month (3500 Credits)<br />
|
||||||
|
Monthly Subscription Fee: $100 HKD / Month (500 Credits)
|
||||||
|
</p>
|
||||||
|
<p class="academic-copy">
|
||||||
|
Special Academic rate available, please contact us for details.
|
||||||
|
</p>
|
||||||
|
<a
|
||||||
|
class="primary-button"
|
||||||
|
href="mailto:info@code-create.com.hk?subject=AiDA%203.1%20Subscription"
|
||||||
|
>
|
||||||
|
Subscribe Now
|
||||||
|
</a>
|
||||||
|
<a class="manual-link" href="#subscription">User Manual</a>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="features-section" aria-labelledby="features-title">
|
||||||
|
<div class="features-inner" v-custom-animation.scroll>
|
||||||
|
<div class="feature-art">
|
||||||
|
<img
|
||||||
|
class="feature-bg"
|
||||||
|
:src="aidaIntroBg"
|
||||||
|
alt="Fashion design sketches on paper"
|
||||||
|
loading="lazy"
|
||||||
|
/>
|
||||||
|
<img
|
||||||
|
class="feature-panel"
|
||||||
|
:src="aidaPanel"
|
||||||
|
alt="AiDA design workspace preview"
|
||||||
|
loading="lazy"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="feature-copy">
|
||||||
|
<h2 id="features-title" class="section-title">Key Features</h2>
|
||||||
|
<ul class="feature-list">
|
||||||
|
<li v-for="feature in keyFeatures" :key="feature">
|
||||||
|
{{ feature }}
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="benefits-section" aria-labelledby="benefits-title">
|
||||||
|
<div class="benefits-inner">
|
||||||
|
<h2 id="benefits-title" class="section-title benefits-title">
|
||||||
|
Benefits to Industry
|
||||||
|
</h2>
|
||||||
|
<div class="benefits-grid">
|
||||||
|
<article v-for="benefit in benefits" :key="benefit.text" class="benefit-card">
|
||||||
|
<img
|
||||||
|
class="benefit-icon"
|
||||||
|
:src="benefit.image"
|
||||||
|
:alt="benefit.alt"
|
||||||
|
loading="lazy"
|
||||||
|
/>
|
||||||
|
<p class="benefit-copy">{{ benefit.text }}</p>
|
||||||
|
</article>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="demo-section" aria-labelledby="demo-title">
|
||||||
|
<div class="demo-inner">
|
||||||
|
<h2 id="demo-title" class="demo-title">Demo</h2>
|
||||||
|
<div class="demo-video-wrap">
|
||||||
|
<video
|
||||||
|
ref="demoVideoRef"
|
||||||
|
class="demo-video"
|
||||||
|
:class="{ 'is-playing': isVideoPlaying }"
|
||||||
|
preload="metadata"
|
||||||
|
playsinline
|
||||||
|
@click="toggleDemoVideo"
|
||||||
|
@pause="handleVideoStateChange"
|
||||||
|
@play="handleVideoStateChange"
|
||||||
|
@ended="handleVideoStateChange"
|
||||||
|
>
|
||||||
|
<source :src="demoVideo" type="video/mp4" />
|
||||||
|
</video>
|
||||||
|
<button
|
||||||
|
v-show="!isVideoPlaying"
|
||||||
|
class="video-play-button"
|
||||||
|
type="button"
|
||||||
|
aria-label="Play AiDA demo video"
|
||||||
|
@click="toggleDemoVideo"
|
||||||
|
>
|
||||||
|
Click to play
|
||||||
|
<span class="play-dot" aria-hidden="true"></span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section
|
||||||
|
id="subscription"
|
||||||
|
class="subscription-section"
|
||||||
|
aria-labelledby="subscription-title"
|
||||||
|
>
|
||||||
|
<div class="subscription-inner">
|
||||||
|
<h2 id="subscription-title" class="section-title subscription-title">
|
||||||
|
Choose Your Subscription Plan
|
||||||
|
</h2>
|
||||||
|
<ul class="subscription-list">
|
||||||
|
<li v-for="item in subscriptionHighlights" :key="item">
|
||||||
|
{{ item }}
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<div class="plans-grid">
|
||||||
|
<article v-for="plan in plans" :key="plan.name" class="plan-card">
|
||||||
|
<img
|
||||||
|
class="plan-icon"
|
||||||
|
:src="plan.icon"
|
||||||
|
:alt="`${plan.name} plan icon`"
|
||||||
|
loading="lazy"
|
||||||
|
/>
|
||||||
|
<h3 class="plan-title">{{ plan.name }}</h3>
|
||||||
|
<p class="plan-copy">{{ plan.description }}</p>
|
||||||
|
<a class="plan-button" :href="plan.href">{{ plan.action }}</a>
|
||||||
|
</article>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="legal-links">
|
||||||
|
<a href="#subscription">Terms & Conditions</a>
|
||||||
|
<a href="#subscription">Subscription Agreement</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped lang="less">
|
||||||
|
.aida-page {
|
||||||
|
width: 100%;
|
||||||
|
min-height: 100vh;
|
||||||
|
overflow: hidden;
|
||||||
|
background: #f0f0f0;
|
||||||
|
color: #333333;
|
||||||
|
font-family: Poppins, Arial, sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
.aida-hero {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
height: clamp(280px, 36vw, 520px);
|
||||||
|
min-height: 280px;
|
||||||
|
background: #241023;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.aida-hero-image {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: cover;
|
||||||
|
object-position: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.aida-hero-title {
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
|
margin: 0;
|
||||||
|
padding-top: 34px;
|
||||||
|
color: #ffffff;
|
||||||
|
font-size: clamp(40px, 5.2vw, 72px);
|
||||||
|
font-weight: 700;
|
||||||
|
line-height: 1;
|
||||||
|
letter-spacing: 0;
|
||||||
|
text-transform: none;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.intro-section {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
min-height: 735px;
|
||||||
|
padding: 110px 24px 122px;
|
||||||
|
background: #ebebeb;
|
||||||
|
}
|
||||||
|
|
||||||
|
.intro-inner {
|
||||||
|
width: min(100%, 720px);
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.intro-title,
|
||||||
|
.section-title,
|
||||||
|
.demo-title,
|
||||||
|
.subscription-title,
|
||||||
|
.plan-title {
|
||||||
|
margin: 0;
|
||||||
|
color: #333333;
|
||||||
|
font-family: Poppins, Arial, sans-serif;
|
||||||
|
font-weight: 700;
|
||||||
|
letter-spacing: 1.4px;
|
||||||
|
text-transform: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.intro-title {
|
||||||
|
margin-bottom: 32px;
|
||||||
|
font-size: clamp(18px, 1.55vw, 24px);
|
||||||
|
line-height: 1.35;
|
||||||
|
}
|
||||||
|
|
||||||
|
.intro-copy,
|
||||||
|
.pricing-copy,
|
||||||
|
.academic-copy {
|
||||||
|
margin: 0 auto;
|
||||||
|
color: #555555;
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 1.5;
|
||||||
|
letter-spacing: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.intro-copy {
|
||||||
|
max-width: 610px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pricing-copy {
|
||||||
|
max-width: 520px;
|
||||||
|
margin-bottom: 14px;
|
||||||
|
color: #3f3f3f;
|
||||||
|
font-weight: 700;
|
||||||
|
line-height: 1.32;
|
||||||
|
letter-spacing: 1.1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.academic-copy {
|
||||||
|
margin-bottom: 25px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.primary-button,
|
||||||
|
.plan-button {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
min-height: 43px;
|
||||||
|
padding: 0 34px;
|
||||||
|
border-radius: 999px;
|
||||||
|
background: #a51f28;
|
||||||
|
color: #ffffff;
|
||||||
|
font-size: 10px;
|
||||||
|
font-weight: 700;
|
||||||
|
line-height: 1;
|
||||||
|
letter-spacing: 1.8px;
|
||||||
|
text-decoration: none;
|
||||||
|
text-transform: uppercase;
|
||||||
|
transition:
|
||||||
|
background-color 0.2s ease,
|
||||||
|
transform 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.primary-button:hover,
|
||||||
|
.primary-button:focus-visible,
|
||||||
|
.plan-button:hover,
|
||||||
|
.plan-button:focus-visible {
|
||||||
|
background: #8f1720;
|
||||||
|
transform: translateY(-1px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.primary-button:focus-visible,
|
||||||
|
.plan-button:focus-visible,
|
||||||
|
.manual-link:focus-visible,
|
||||||
|
.legal-links a:focus-visible,
|
||||||
|
.video-play-button:focus-visible {
|
||||||
|
outline: 2px solid #a51f28;
|
||||||
|
outline-offset: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.manual-link {
|
||||||
|
display: table;
|
||||||
|
margin: 23px auto 0;
|
||||||
|
color: #333333;
|
||||||
|
font-size: 10px;
|
||||||
|
font-weight: 700;
|
||||||
|
line-height: 1.4;
|
||||||
|
text-decoration: underline;
|
||||||
|
text-underline-offset: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.features-section,
|
||||||
|
.benefits-section,
|
||||||
|
.subscription-section {
|
||||||
|
background: #f4f4f4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.features-section {
|
||||||
|
padding: clamp(82px, 9vw, 132px) 24px 70px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.features-inner {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: minmax(300px, 520px) minmax(320px, 620px);
|
||||||
|
align-items: center;
|
||||||
|
gap: clamp(56px, 7vw, 112px);
|
||||||
|
width: min(100%, 1180px);
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.feature-art {
|
||||||
|
position: relative;
|
||||||
|
min-height: clamp(440px, 38vw, 565px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.feature-bg {
|
||||||
|
display: block;
|
||||||
|
width: min(75%, 430px);
|
||||||
|
border-radius: 24px;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.feature-panel {
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
bottom: 32px;
|
||||||
|
width: min(75%, 445px);
|
||||||
|
filter: drop-shadow(0 12px 18px rgba(0, 0, 0, 0.12));
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.feature-copy {
|
||||||
|
max-width: 610px;
|
||||||
|
padding-top: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-title {
|
||||||
|
font-size: clamp(26px, 2.35vw, 42px);
|
||||||
|
line-height: 1.18;
|
||||||
|
}
|
||||||
|
|
||||||
|
.feature-list {
|
||||||
|
margin: 32px 0 0;
|
||||||
|
padding-left: 18px;
|
||||||
|
color: #444444;
|
||||||
|
font-size: 13px;
|
||||||
|
line-height: 1.55;
|
||||||
|
}
|
||||||
|
|
||||||
|
.feature-list li + li {
|
||||||
|
margin-top: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.benefits-section {
|
||||||
|
padding: 78px 24px 112px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.benefits-inner {
|
||||||
|
width: min(100%, 1160px);
|
||||||
|
margin: 0 auto;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.benefits-title {
|
||||||
|
margin-bottom: clamp(56px, 7vw, 95px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.benefits-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||||
|
gap: clamp(40px, 8vw, 118px);
|
||||||
|
align-items: start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.benefit-card {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.benefit-icon {
|
||||||
|
width: 84px;
|
||||||
|
height: 84px;
|
||||||
|
object-fit: contain;
|
||||||
|
margin-bottom: 48px;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.benefit-copy {
|
||||||
|
max-width: 248px;
|
||||||
|
margin: 0;
|
||||||
|
color: #555555;
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 1.45;
|
||||||
|
letter-spacing: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.demo-section {
|
||||||
|
padding: 78px 24px 104px;
|
||||||
|
background: #372b28;
|
||||||
|
}
|
||||||
|
|
||||||
|
.demo-inner {
|
||||||
|
width: min(100%, 1060px);
|
||||||
|
margin: 0 auto;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.demo-title {
|
||||||
|
margin-bottom: 14px;
|
||||||
|
color: #ffffff;
|
||||||
|
font-size: clamp(24px, 2.15vw, 34px);
|
||||||
|
line-height: 1.2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.demo-video-wrap {
|
||||||
|
position: relative;
|
||||||
|
width: min(100%, 960px);
|
||||||
|
margin: 0 auto;
|
||||||
|
background: #202020;
|
||||||
|
aspect-ratio: 16 / 9;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.demo-video {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: cover;
|
||||||
|
cursor: pointer;
|
||||||
|
filter: grayscale(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.demo-video.is-playing {
|
||||||
|
filter: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.video-play-button {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
min-height: 32px;
|
||||||
|
padding: 0 16px;
|
||||||
|
border: 0;
|
||||||
|
border-radius: 999px;
|
||||||
|
background: #a51f28;
|
||||||
|
color: #ffffff;
|
||||||
|
font-family: Poppins, Arial, sans-serif;
|
||||||
|
font-size: 10px;
|
||||||
|
font-weight: 700;
|
||||||
|
line-height: 1;
|
||||||
|
letter-spacing: 0;
|
||||||
|
cursor: pointer;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
transition:
|
||||||
|
background-color 0.2s ease,
|
||||||
|
transform 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.video-play-button:hover {
|
||||||
|
background: #8f1720;
|
||||||
|
transform: translate(-50%, calc(-50% - 1px));
|
||||||
|
}
|
||||||
|
|
||||||
|
.play-dot {
|
||||||
|
display: block;
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
border-top: 5px solid transparent;
|
||||||
|
border-bottom: 5px solid transparent;
|
||||||
|
border-left: 8px solid #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.subscription-section {
|
||||||
|
padding: clamp(76px, 8vw, 104px) 24px 86px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.subscription-inner {
|
||||||
|
width: min(100%, 930px);
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.subscription-title {
|
||||||
|
margin-bottom: 27px;
|
||||||
|
font-size: clamp(28px, 3vw, 43px);
|
||||||
|
line-height: 1.2;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.subscription-list {
|
||||||
|
margin: 0 auto 52px;
|
||||||
|
padding: 0;
|
||||||
|
list-style: none;
|
||||||
|
color: #333333;
|
||||||
|
font-size: 13px;
|
||||||
|
line-height: 1.48;
|
||||||
|
}
|
||||||
|
|
||||||
|
.subscription-list li {
|
||||||
|
position: relative;
|
||||||
|
padding-left: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.subscription-list li + li {
|
||||||
|
margin-top: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.subscription-list li::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: 0.55em;
|
||||||
|
left: 0;
|
||||||
|
width: 10px;
|
||||||
|
height: 5px;
|
||||||
|
border-left: 2px solid #333333;
|
||||||
|
border-bottom: 2px solid #333333;
|
||||||
|
transform: rotate(-45deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.plans-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||||
|
gap: 36px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.plan-card {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
min-height: 324px;
|
||||||
|
padding: 34px 28px 30px;
|
||||||
|
border-radius: 12px;
|
||||||
|
background: #ffffff;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.plan-icon {
|
||||||
|
width: 86px;
|
||||||
|
height: 86px;
|
||||||
|
object-fit: contain;
|
||||||
|
margin-bottom: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.plan-title {
|
||||||
|
margin-bottom: 34px;
|
||||||
|
color: #a51f28;
|
||||||
|
font-size: 22px;
|
||||||
|
line-height: 1.2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.plan-copy {
|
||||||
|
margin: 0 0 44px;
|
||||||
|
color: #555555;
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 1.45;
|
||||||
|
}
|
||||||
|
|
||||||
|
.plan-button {
|
||||||
|
min-height: 39px;
|
||||||
|
margin-top: auto;
|
||||||
|
padding: 0 27px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.legal-links {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
gap: 18px;
|
||||||
|
margin-top: 45px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.legal-links a {
|
||||||
|
color: #333333;
|
||||||
|
font-size: 10px;
|
||||||
|
font-weight: 700;
|
||||||
|
line-height: 1.2;
|
||||||
|
text-decoration: underline;
|
||||||
|
text-underline-offset: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 980px) {
|
||||||
|
.intro-section {
|
||||||
|
min-height: 620px;
|
||||||
|
padding: 84px 24px 94px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.features-inner {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
gap: 34px;
|
||||||
|
width: min(100%, 680px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.feature-art {
|
||||||
|
min-height: clamp(390px, 78vw, 560px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.feature-copy {
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.benefits-grid {
|
||||||
|
gap: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.subscription-inner {
|
||||||
|
width: min(100%, 760px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 720px) {
|
||||||
|
.aida-hero {
|
||||||
|
height: 292px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.aida-hero-title {
|
||||||
|
padding-top: 24px;
|
||||||
|
font-size: 42px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.intro-section {
|
||||||
|
min-height: 520px;
|
||||||
|
padding: 72px 20px 78px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.intro-title {
|
||||||
|
margin-bottom: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.features-section {
|
||||||
|
padding: 64px 20px 44px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.feature-bg {
|
||||||
|
width: 72%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.feature-panel {
|
||||||
|
width: 78%;
|
||||||
|
bottom: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.benefits-section {
|
||||||
|
padding: 52px 20px 76px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.benefits-grid,
|
||||||
|
.plans-grid {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.benefit-icon {
|
||||||
|
margin-bottom: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.demo-section {
|
||||||
|
padding: 58px 20px 76px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.subscription-section {
|
||||||
|
padding: 62px 20px 70px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.subscription-title {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.subscription-list {
|
||||||
|
margin-bottom: 40px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 480px) {
|
||||||
|
.aida-hero {
|
||||||
|
height: 250px;
|
||||||
|
min-height: 250px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.aida-hero-title {
|
||||||
|
font-size: 34px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.intro-copy,
|
||||||
|
.pricing-copy,
|
||||||
|
.academic-copy,
|
||||||
|
.feature-list,
|
||||||
|
.subscription-list {
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.feature-art {
|
||||||
|
min-height: 320px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.plan-card {
|
||||||
|
min-height: 292px;
|
||||||
|
padding: 30px 22px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -1,13 +1,53 @@
|
|||||||
<template>
|
<template>
|
||||||
<section class="carousel-container" aria-label="Featured content">
|
<section
|
||||||
|
class="carousel-container"
|
||||||
|
aria-label="Featured content"
|
||||||
|
@mouseenter="pauseAutoplay"
|
||||||
|
@mouseleave="resumeAutoplay"
|
||||||
|
>
|
||||||
<KagolCarousel
|
<KagolCarousel
|
||||||
v-model="activePage"
|
v-model="activePage"
|
||||||
class="home-carousel"
|
class="home-carousel"
|
||||||
:autoplay="isAutoplayEnabled"
|
:autoplay="false"
|
||||||
:interval="5000"
|
:interval="1000"
|
||||||
>
|
>
|
||||||
<article v-for="slide in slides" :key="slide.id" class="carousel-slide">
|
<article
|
||||||
<img class="carousel-image" :src="slide.image" :alt="slide.alt" />
|
v-for="slide in slides"
|
||||||
|
:key="slide.id"
|
||||||
|
ref="slideEls"
|
||||||
|
class="carousel-slide"
|
||||||
|
>
|
||||||
|
<div class="mask"></div>
|
||||||
|
<div class="banner-title" v-if="slide.title">{{ slide.title }}</div>
|
||||||
|
<img
|
||||||
|
v-if="!slide.video"
|
||||||
|
class="carousel-banner image"
|
||||||
|
:src="slide.image"
|
||||||
|
:alt="slide.alt"
|
||||||
|
/>
|
||||||
|
<video
|
||||||
|
v-else
|
||||||
|
class="carousel-banner video"
|
||||||
|
:alt="slide.alt"
|
||||||
|
:controls="false"
|
||||||
|
autoplay
|
||||||
|
muted
|
||||||
|
loop
|
||||||
|
>
|
||||||
|
<source :src="slide.video" type="video/mp4" />
|
||||||
|
</video>
|
||||||
|
|
||||||
|
<div class="desc flex flex-center" v-if="slide.description">
|
||||||
|
<span class="desc-fill" aria-hidden="true"></span>
|
||||||
|
<span class="desc-index-group">
|
||||||
|
<span class="desc-line" aria-hidden="true"></span>
|
||||||
|
<span class="desc-index-frame">
|
||||||
|
<span class="desc-index">{{ slide.number }}</span>
|
||||||
|
<span class="desc-index-cover" aria-hidden="true"></span>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
<p class="desc-copy">{{ slide.description }}</p>
|
||||||
|
</div>
|
||||||
</article>
|
</article>
|
||||||
|
|
||||||
<template #pagination="{ prevPage, nextPage }">
|
<template #pagination="{ prevPage, nextPage }">
|
||||||
@@ -43,123 +83,355 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { Carousel as KagolCarousel } from '@kagol/vue-carousel'
|
import { Carousel as KagolCarousel } from '@kagol/vue-carousel'
|
||||||
import '@kagol/vue-carousel/dist/style.css'
|
import '@kagol/vue-carousel/dist/style.css'
|
||||||
import { onMounted, shallowRef } from 'vue'
|
import { gsap } from 'gsap'
|
||||||
|
import {
|
||||||
|
nextTick,
|
||||||
|
onBeforeUnmount,
|
||||||
|
onMounted,
|
||||||
|
shallowRef,
|
||||||
|
useTemplateRef,
|
||||||
|
watch
|
||||||
|
} from 'vue'
|
||||||
import mainBanner01 from '../../../assets/images/home/mainbanner01.jpg'
|
import mainBanner01 from '../../../assets/images/home/mainbanner01.jpg'
|
||||||
import mainBanner02 from '../../../assets/images/home/mainbanner02.jpg'
|
import mainBanner02 from '../../../assets/images/home/mainbanner02.jpg'
|
||||||
|
import Video from '@/assets/images/home/hero-desktop.mp4'
|
||||||
|
|
||||||
|
type HomeSlide = {
|
||||||
|
id: string
|
||||||
|
image: string
|
||||||
|
video: string
|
||||||
|
alt: string
|
||||||
|
title?: string
|
||||||
|
number?: string
|
||||||
|
description?: string
|
||||||
|
}
|
||||||
|
|
||||||
const activePage = shallowRef(1)
|
const activePage = shallowRef(1)
|
||||||
const isAutoplayEnabled = shallowRef(false)
|
const isAutoplayEnabled = shallowRef(false)
|
||||||
const slides = [
|
const slideEls = useTemplateRef<HTMLElement[]>('slideEls')
|
||||||
|
const slides: readonly HomeSlide[] = [
|
||||||
{
|
{
|
||||||
id: 'aida',
|
id: 'aida',
|
||||||
image: mainBanner01,
|
image: mainBanner01,
|
||||||
alt: 'AiDA product banner'
|
video: '',
|
||||||
|
alt: 'Code Create product banner',
|
||||||
|
title: 'Shaping the future\nof fashion design',
|
||||||
|
number: '01',
|
||||||
|
description:
|
||||||
|
"World's first and only designer-led AI system that streamlines ideation from hours to seconds"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'mixi',
|
id: 'mixi',
|
||||||
image: mainBanner02,
|
image: mainBanner02,
|
||||||
alt: 'Mixi product banner'
|
video: '',
|
||||||
|
alt: 'Code Create product banner',
|
||||||
|
title: 'Be the game changer,\n subscribe now!',
|
||||||
|
number: '02',
|
||||||
|
description: 'Make the first move to streamline and facilitate your inspiration process'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'video',
|
||||||
|
image: '',
|
||||||
|
video: Video,
|
||||||
|
alt: 'Code Create product video banner'
|
||||||
}
|
}
|
||||||
] as const
|
]
|
||||||
|
const descAnimationDelay = 1
|
||||||
|
let activeSlideIndex: number | null = null
|
||||||
|
let descAnimationFrame = 0
|
||||||
|
let descTimeline: ReturnType<typeof gsap.timeline> | null = null
|
||||||
|
|
||||||
|
function getActiveSlideIndex() {
|
||||||
|
const slideCount = slides.length
|
||||||
|
|
||||||
|
return ((activePage.value - 1) % slideCount + slideCount) % slideCount
|
||||||
|
}
|
||||||
|
|
||||||
|
function prefersReducedMotion() {
|
||||||
|
return window.matchMedia?.('(prefers-reduced-motion: reduce)').matches ?? false
|
||||||
|
}
|
||||||
|
|
||||||
|
function playDescAnimation(slideIndex: number) {
|
||||||
|
const activeSlide = slideEls.value?.[slideIndex]
|
||||||
|
const desc = activeSlide?.querySelector<HTMLElement>('.desc')
|
||||||
|
const fill = desc?.querySelector<HTMLElement>('.desc-fill')
|
||||||
|
const line = desc?.querySelector<HTMLElement>('.desc-line')
|
||||||
|
const index = desc?.querySelector<HTMLElement>('.desc-index')
|
||||||
|
const cover = desc?.querySelector<HTMLElement>('.desc-index-cover')
|
||||||
|
const copy = desc?.querySelector<HTMLElement>('.desc-copy')
|
||||||
|
|
||||||
|
descTimeline?.kill()
|
||||||
|
descTimeline = null
|
||||||
|
|
||||||
|
if (!fill || !line || !index || !cover || !copy) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
gsap.killTweensOf([fill, line, index, cover, copy])
|
||||||
|
|
||||||
|
if (prefersReducedMotion()) {
|
||||||
|
gsap.set(fill, { width: '100%' })
|
||||||
|
gsap.set(line, { autoAlpha: 1, width: 1, x: 0 })
|
||||||
|
gsap.set(index, { autoAlpha: 1, x: 0 })
|
||||||
|
gsap.set(cover, { autoAlpha: 0, xPercent: 110 })
|
||||||
|
gsap.set(copy, { autoAlpha: 1, x: 0 })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
gsap.set(fill, { width: 0 })
|
||||||
|
gsap.set(line, { autoAlpha: 0, width: 5, x: 18 })
|
||||||
|
gsap.set(index, { autoAlpha: 0, x: -34 })
|
||||||
|
gsap.set(cover, { autoAlpha: 0, xPercent: 0 })
|
||||||
|
gsap.set(copy, { autoAlpha: 0, x: 22 })
|
||||||
|
|
||||||
|
descTimeline = gsap
|
||||||
|
.timeline({
|
||||||
|
delay: descAnimationDelay,
|
||||||
|
defaults: {
|
||||||
|
ease: 'power3.out'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.addLabel('panel', 0)
|
||||||
|
.to(fill, { duration: 1.05, ease: 'power2.out', width: '100%' }, 'panel')
|
||||||
|
.to(line, { autoAlpha: 1, duration: 0.9, width: 1, x: 0 }, 'panel+=0.12')
|
||||||
|
.addLabel('number', 1.18)
|
||||||
|
.set(index, { autoAlpha: 1 }, 'number')
|
||||||
|
.set(cover, { autoAlpha: 1 }, 'number')
|
||||||
|
.to(index, { duration: 0.7, x: 0 }, 'number')
|
||||||
|
.to(cover, { duration: 0.72, ease: 'power2.inOut', xPercent: 110 }, 'number+=0.08')
|
||||||
|
.to(copy, { autoAlpha: 1, duration: 0.72, x: 0 }, '>')
|
||||||
|
}
|
||||||
|
|
||||||
|
function queueDescAnimation() {
|
||||||
|
const slideIndex = getActiveSlideIndex()
|
||||||
|
|
||||||
|
if (slideIndex === activeSlideIndex) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
activeSlideIndex = slideIndex
|
||||||
|
nextTick(() => {
|
||||||
|
if (descAnimationFrame) {
|
||||||
|
window.cancelAnimationFrame(descAnimationFrame)
|
||||||
|
}
|
||||||
|
|
||||||
|
descAnimationFrame = window.requestAnimationFrame(() => {
|
||||||
|
descAnimationFrame = 0
|
||||||
|
playDescAnimation(slideIndex)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function pauseAutoplay() {
|
||||||
|
isAutoplayEnabled.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
function resumeAutoplay() {
|
||||||
|
isAutoplayEnabled.value = true
|
||||||
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
isAutoplayEnabled.value = true
|
resumeAutoplay()
|
||||||
|
queueDescAnimation()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
if (descAnimationFrame) {
|
||||||
|
window.cancelAnimationFrame(descAnimationFrame)
|
||||||
|
}
|
||||||
|
|
||||||
|
descTimeline?.kill()
|
||||||
|
})
|
||||||
|
|
||||||
|
watch(activePage, queueDescAnimation)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped lang="less">
|
||||||
.carousel-container {
|
.carousel-container {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
background: #070b14;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.home-carousel {
|
.home-carousel {
|
||||||
width: 100%;
|
.carousel-slide {
|
||||||
}
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
aspect-ratio: 16 / 9;
|
||||||
|
height: 960px;
|
||||||
|
overflow: hidden;
|
||||||
|
.mask {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background-color: rgba(0, 0, 0, 0.35);
|
||||||
|
z-index: 2;
|
||||||
|
}
|
||||||
|
.carousel-banner {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: cover;
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
.banner-title {
|
||||||
|
position: absolute;
|
||||||
|
right: 50%;
|
||||||
|
top: 50%;
|
||||||
|
white-space: pre-line;
|
||||||
|
color: #fff;
|
||||||
|
font-size: 64px;
|
||||||
|
font-family: 'Poppins';
|
||||||
|
transform: translateY(-70%);
|
||||||
|
z-index: 3;
|
||||||
|
}
|
||||||
|
.desc {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
bottom: 0;
|
||||||
|
z-index: 3;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 24px;
|
||||||
|
height: 140px;
|
||||||
|
width: 656px;
|
||||||
|
color: #ffffff;
|
||||||
|
background: transparent;
|
||||||
|
isolation: isolate;
|
||||||
|
}
|
||||||
|
.desc-fill {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0 auto 0 0;
|
||||||
|
z-index: 0;
|
||||||
|
display: block;
|
||||||
|
width: 0;
|
||||||
|
background: #a51f24;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
.carousel-slide {
|
.desc-index-group {
|
||||||
position: relative;
|
position: relative;
|
||||||
width: 100%;
|
z-index: 1;
|
||||||
aspect-ratio: 16 / 9;
|
flex: 0 0 auto;
|
||||||
min-height: 360px;
|
}
|
||||||
max-height: 680px;
|
.desc-line {
|
||||||
overflow: hidden;
|
position: absolute;
|
||||||
background: #070b14;
|
top: -160%;
|
||||||
}
|
left: 50%;
|
||||||
|
display: block;
|
||||||
|
width: 1px;
|
||||||
|
height: 65px;
|
||||||
|
background: rgba(255, 255, 255, 0.72);
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
will-change: transform, width, opacity;
|
||||||
|
}
|
||||||
|
.desc-index-frame {
|
||||||
|
position: relative;
|
||||||
|
display: block;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
.desc-index {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
|
display: block;
|
||||||
|
font-family: 'Poppins';
|
||||||
|
font-size: 52px;
|
||||||
|
font-weight: 700;
|
||||||
|
line-height: 1;
|
||||||
|
opacity: 0;
|
||||||
|
will-change: transform, opacity;
|
||||||
|
}
|
||||||
|
.desc-index-cover {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
z-index: 2;
|
||||||
|
display: block;
|
||||||
|
background: #ffffff;
|
||||||
|
opacity: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
will-change: transform;
|
||||||
|
}
|
||||||
|
.desc-copy {
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
|
max-width: 440px;
|
||||||
|
margin: 0;
|
||||||
|
font-family: 'Poppins';
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
line-height: 1.45;
|
||||||
|
opacity: 0;
|
||||||
|
will-change: transform, opacity;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.carousel-pagination {
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
z-index: 2;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
width: 70px;
|
||||||
|
height: 140px;
|
||||||
|
transform: translateX(100%);
|
||||||
|
transition: transform 0.28s ease;
|
||||||
|
will-change: transform;
|
||||||
|
}
|
||||||
|
.carousel-pagination-button {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
flex: 1 1 0;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 100%;
|
||||||
|
padding: 0;
|
||||||
|
border: 0;
|
||||||
|
color: #ffffff;
|
||||||
|
background: #65090c;
|
||||||
|
cursor: pointer;
|
||||||
|
appearance: none;
|
||||||
|
transition: background-color 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
.carousel-image {
|
.carousel-pagination-button:hover {
|
||||||
display: block;
|
background: rgba(255, 255, 255, 0.75);
|
||||||
width: 100%;
|
}
|
||||||
height: 100%;
|
|
||||||
object-fit: cover;
|
|
||||||
}
|
|
||||||
|
|
||||||
.carousel-pagination {
|
.carousel-pagination-button:focus-visible {
|
||||||
position: absolute;
|
position: relative;
|
||||||
right: 0;
|
z-index: 1;
|
||||||
bottom: 0;
|
outline: 2px solid #ffffff;
|
||||||
z-index: 2;
|
outline-offset: -6px;
|
||||||
display: flex;
|
}
|
||||||
flex-direction: column;
|
|
||||||
width: 70px;
|
.carousel-pagination-arrow {
|
||||||
height: 140px;
|
position: absolute;
|
||||||
transform: translateX(100%);
|
top: 50%;
|
||||||
transition: transform 0.28s ease;
|
left: 50%;
|
||||||
will-change: transform;
|
display: block;
|
||||||
|
box-sizing: border-box;
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
border-top: 2.5px solid currentColor;
|
||||||
|
border-right: 2.5px solid currentColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
.carousel-pagination-arrow-prev {
|
||||||
|
transform: translate(-50%, -50%) rotate(225deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.carousel-pagination-arrow-next {
|
||||||
|
transform: translate(-50%, -50%) rotate(45deg);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.carousel-container:hover .carousel-pagination {
|
.carousel-container:hover .carousel-pagination {
|
||||||
transform: translateX(0);
|
transform: translateX(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
.carousel-pagination-button {
|
|
||||||
position: relative;
|
|
||||||
display: flex;
|
|
||||||
flex: 1 1 0;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
width: 100%;
|
|
||||||
padding: 0;
|
|
||||||
border: 0;
|
|
||||||
color: #ffffff;
|
|
||||||
background: #65090c;
|
|
||||||
cursor: pointer;
|
|
||||||
appearance: none;
|
|
||||||
transition: background-color 0.2s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.carousel-pagination-button:hover {
|
|
||||||
background: rgba(255, 255, 255, 0.75);
|
|
||||||
}
|
|
||||||
|
|
||||||
.carousel-pagination-button:focus-visible {
|
|
||||||
position: relative;
|
|
||||||
z-index: 1;
|
|
||||||
outline: 2px solid #ffffff;
|
|
||||||
outline-offset: -6px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.carousel-pagination-arrow {
|
|
||||||
position: absolute;
|
|
||||||
top: 50%;
|
|
||||||
left: 50%;
|
|
||||||
display: block;
|
|
||||||
box-sizing: border-box;
|
|
||||||
width: 20px;
|
|
||||||
height: 20px;
|
|
||||||
border-top: 2.5px solid currentColor;
|
|
||||||
border-right: 2.5px solid currentColor;
|
|
||||||
}
|
|
||||||
|
|
||||||
.carousel-pagination-arrow-prev {
|
|
||||||
transform: translate(-50%, -50%) rotate(225deg);
|
|
||||||
}
|
|
||||||
|
|
||||||
.carousel-pagination-arrow-next {
|
|
||||||
transform: translate(-50%, -50%) rotate(45deg);
|
|
||||||
}
|
|
||||||
|
|
||||||
:deep(.xui-carousel) {
|
:deep(.xui-carousel) {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
@@ -173,6 +445,29 @@
|
|||||||
min-height: 320px;
|
min-height: 320px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.home-carousel {
|
||||||
|
.carousel-slide {
|
||||||
|
.desc {
|
||||||
|
width: 100%;
|
||||||
|
min-width: 0;
|
||||||
|
height: auto;
|
||||||
|
min-height: 92px;
|
||||||
|
padding: 16px 20px;
|
||||||
|
}
|
||||||
|
.desc-line {
|
||||||
|
top: -28px;
|
||||||
|
height: 42px;
|
||||||
|
}
|
||||||
|
.desc-index {
|
||||||
|
font-size: 40px;
|
||||||
|
}
|
||||||
|
.desc-copy {
|
||||||
|
max-width: none;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.carousel-pagination-arrow {
|
.carousel-pagination-arrow {
|
||||||
width: 18px;
|
width: 18px;
|
||||||
height: 18px;
|
height: 18px;
|
||||||
|
|||||||
269
src/pages/home/components/ProductFeature.vue
Normal file
@@ -0,0 +1,269 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { RouterLink } from 'vue-router'
|
||||||
|
|
||||||
|
withDefaults(
|
||||||
|
defineProps<{
|
||||||
|
name: string
|
||||||
|
title: string
|
||||||
|
backgroundImage: string
|
||||||
|
backgroundAlt: string
|
||||||
|
panelImage: string
|
||||||
|
panelAlt: string
|
||||||
|
reversed?: boolean
|
||||||
|
}>(),
|
||||||
|
{
|
||||||
|
reversed: false
|
||||||
|
}
|
||||||
|
)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<section
|
||||||
|
class="product-feature"
|
||||||
|
:class="{ 'product-feature-reversed': reversed }"
|
||||||
|
v-custom-animation.scroll
|
||||||
|
>
|
||||||
|
<div class="product-feature-art">
|
||||||
|
<img
|
||||||
|
class="product-feature-bg"
|
||||||
|
:src="backgroundImage"
|
||||||
|
:alt="backgroundAlt"
|
||||||
|
loading="lazy"
|
||||||
|
translate-x-s="-100"
|
||||||
|
translate-x="100"
|
||||||
|
/>
|
||||||
|
<img
|
||||||
|
class="product-feature-panel"
|
||||||
|
:src="panelImage"
|
||||||
|
:alt="panelAlt"
|
||||||
|
loading="lazy"
|
||||||
|
translate-y-s="-100"
|
||||||
|
translate-y="20"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="product-feature-copy">
|
||||||
|
<p class="product-feature-name">{{ name }}</p>
|
||||||
|
<h2 class="product-feature-title">{{ title }}</h2>
|
||||||
|
<RouterLink
|
||||||
|
class="product-feature-link"
|
||||||
|
to="/products"
|
||||||
|
translate-y-s="100"
|
||||||
|
translate-y="0"
|
||||||
|
>
|
||||||
|
View More
|
||||||
|
</RouterLink>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped lang="less">
|
||||||
|
.product-feature {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: clamp(48px, 5.5vw, 76px);
|
||||||
|
width: 1440px;
|
||||||
|
min-width: 0;
|
||||||
|
min-height: 690px;
|
||||||
|
margin: 0 auto;
|
||||||
|
& > div {
|
||||||
|
width: 50%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-feature-reversed {
|
||||||
|
grid-template-columns: minmax(420px, 0.95fr) minmax(0, 1.05fr);
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-feature-reversed .product-feature-art {
|
||||||
|
order: 2;
|
||||||
|
justify-self: end;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-feature-reversed .product-feature-copy {
|
||||||
|
order: 1;
|
||||||
|
justify-self: start;
|
||||||
|
max-width: 510px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-feature-art {
|
||||||
|
position: relative;
|
||||||
|
width: min(100%, 560px);
|
||||||
|
min-width: 0;
|
||||||
|
min-height: 560px;
|
||||||
|
justify-self: start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-feature-bg {
|
||||||
|
display: block;
|
||||||
|
width: min(100%, 480px);
|
||||||
|
max-width: 100%;
|
||||||
|
height: auto;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-feature-panel {
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
bottom: 94px;
|
||||||
|
width: min(72%, 380px);
|
||||||
|
max-width: 100%;
|
||||||
|
height: auto;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-feature-reversed .product-feature-bg {
|
||||||
|
width: min(100%, 460px);
|
||||||
|
margin-left: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-feature-reversed .product-feature-panel {
|
||||||
|
right: auto;
|
||||||
|
left: -88px;
|
||||||
|
bottom: 70px;
|
||||||
|
width: min(78%, 430px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-feature-copy {
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
|
max-width: 590px;
|
||||||
|
min-width: 0;
|
||||||
|
justify-self: start;
|
||||||
|
padding-bottom: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-feature-name {
|
||||||
|
margin: 0 0 20px;
|
||||||
|
color: #6e6e6e;
|
||||||
|
font-family: Poppins, sans-serif;
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 700;
|
||||||
|
line-height: 1;
|
||||||
|
letter-spacing: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-feature-title {
|
||||||
|
margin: 0 0 28px;
|
||||||
|
color: #252525;
|
||||||
|
font-family: Poppins, sans-serif;
|
||||||
|
font-size: clamp(30px, 2.8vw, 38px);
|
||||||
|
font-weight: 700;
|
||||||
|
line-height: 1.16;
|
||||||
|
letter-spacing: 2px;
|
||||||
|
text-transform: none;
|
||||||
|
overflow-wrap: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-feature-link {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
min-width: 152px;
|
||||||
|
min-height: 48px;
|
||||||
|
padding: 0 28px;
|
||||||
|
border-radius: 999px;
|
||||||
|
color: #ffffff;
|
||||||
|
background: #a72125;
|
||||||
|
font-family: Poppins, sans-serif;
|
||||||
|
font-size: 10px;
|
||||||
|
font-weight: 700;
|
||||||
|
line-height: 1;
|
||||||
|
letter-spacing: 2px;
|
||||||
|
text-decoration: none;
|
||||||
|
text-transform: uppercase;
|
||||||
|
transition:
|
||||||
|
background-color 0.2s ease,
|
||||||
|
transform 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-feature-link:hover,
|
||||||
|
.product-feature-link:focus-visible {
|
||||||
|
background: #8e171b;
|
||||||
|
transform: translateY(-1px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-feature-link:focus-visible {
|
||||||
|
outline: 2px solid #a72125;
|
||||||
|
outline-offset: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 980px) {
|
||||||
|
.product-feature,
|
||||||
|
.product-feature-reversed {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
gap: 24px;
|
||||||
|
width: min(680px, calc(100% - 40px));
|
||||||
|
min-height: 0;
|
||||||
|
padding: 70px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-feature-reversed .product-feature-art,
|
||||||
|
.product-feature-reversed .product-feature-copy {
|
||||||
|
order: initial;
|
||||||
|
justify-self: start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-feature-art {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 100%;
|
||||||
|
min-height: clamp(390px, 82vw, 560px);
|
||||||
|
justify-self: stretch;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-feature-copy {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 640px;
|
||||||
|
justify-self: stretch;
|
||||||
|
padding-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-feature-reversed .product-feature-panel {
|
||||||
|
left: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 640px) {
|
||||||
|
.product-feature,
|
||||||
|
.product-feature-reversed {
|
||||||
|
width: calc(100% - 32px);
|
||||||
|
padding: 52px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-feature-art {
|
||||||
|
min-height: clamp(300px, 82vw, 430px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-feature-copy,
|
||||||
|
.product-feature-reversed .product-feature-copy {
|
||||||
|
width: 100% !important;
|
||||||
|
max-width: 100% !important;
|
||||||
|
justify-self: stretch !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-feature-bg {
|
||||||
|
width: 78%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-feature-panel,
|
||||||
|
.product-feature-reversed .product-feature-panel {
|
||||||
|
left: auto;
|
||||||
|
right: 0;
|
||||||
|
bottom: 42px;
|
||||||
|
width: 68%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-feature-reversed .product-feature-bg {
|
||||||
|
width: 72%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-feature-title {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 100%;
|
||||||
|
font-size: clamp(26px, 7.2vw, 30px);
|
||||||
|
line-height: 1.2;
|
||||||
|
letter-spacing: 1.2px;
|
||||||
|
overflow-wrap: anywhere;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
83
src/pages/home/components/ProjectCta.vue
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { RouterLink } from 'vue-router'
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<section class="project-cta">
|
||||||
|
<div class="project-cta-inner">
|
||||||
|
<h2 class="project-cta-title">Talk To Us About Your Next Project</h2>
|
||||||
|
<RouterLink class="project-cta-link" to="/contact">
|
||||||
|
Contact Us
|
||||||
|
</RouterLink>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped lang="less">
|
||||||
|
.project-cta {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
min-height: 435px;
|
||||||
|
padding: 72px 20px;
|
||||||
|
background: #473935;
|
||||||
|
}
|
||||||
|
|
||||||
|
.project-cta-inner {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
gap: 38px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.project-cta-title {
|
||||||
|
margin: 0;
|
||||||
|
color: #ffffff;
|
||||||
|
font-family: Poppins, sans-serif;
|
||||||
|
font-size: clamp(22px, 2vw, 27px);
|
||||||
|
font-weight: 700;
|
||||||
|
line-height: 1.35;
|
||||||
|
letter-spacing: 2px;
|
||||||
|
text-transform: capitalize;
|
||||||
|
}
|
||||||
|
|
||||||
|
.project-cta-link {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
min-width: 158px;
|
||||||
|
min-height: 48px;
|
||||||
|
padding: 0 28px;
|
||||||
|
border-radius: 999px;
|
||||||
|
color: #ffffff;
|
||||||
|
background: #ad2228;
|
||||||
|
font-family: Poppins, sans-serif;
|
||||||
|
font-size: 10px;
|
||||||
|
font-weight: 700;
|
||||||
|
line-height: 1;
|
||||||
|
letter-spacing: 2px;
|
||||||
|
text-decoration: none;
|
||||||
|
text-transform: uppercase;
|
||||||
|
transition:
|
||||||
|
background-color 0.2s ease,
|
||||||
|
transform 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.project-cta-link:hover,
|
||||||
|
.project-cta-link:focus-visible {
|
||||||
|
background: #93191f;
|
||||||
|
transform: translateY(-1px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.project-cta-link:focus-visible {
|
||||||
|
outline: 2px solid #ffffff;
|
||||||
|
outline-offset: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 640px) {
|
||||||
|
.project-cta {
|
||||||
|
min-height: 340px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -1,95 +1,181 @@
|
|||||||
<template>
|
|
||||||
<main class="home-page">
|
|
||||||
<HomeCarousel />
|
|
||||||
|
|
||||||
<section class="home-links" aria-label="Site pages">
|
|
||||||
<RouterLink
|
|
||||||
v-for="page in pageLinks"
|
|
||||||
:key="page.to"
|
|
||||||
class="home-link"
|
|
||||||
:to="page.to"
|
|
||||||
>
|
|
||||||
{{ page.label }}
|
|
||||||
</RouterLink>
|
|
||||||
</section>
|
|
||||||
</main>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useHead } from '@unhead/vue'
|
import { useHead } from '@unhead/vue'
|
||||||
import { RouterLink } from 'vue-router'
|
import aidaIntroBg from '@/assets/images/home/aida-intro-bg.png'
|
||||||
import HomeCarousel from './components/Carousel.vue'
|
import aidaPanel from '@/assets/images/home/aida-panel.png'
|
||||||
|
import homeAiLogo from '@/assets/images/home/home-ai-logo.png'
|
||||||
|
import mixiIntroBg from '@/assets/images/home/mixi-intro-bg.png'
|
||||||
|
import mixiPanel from '@/assets/images/home/mixi-panel.png'
|
||||||
|
import HomeCarousel from './components/Carousel.vue'
|
||||||
|
import ProductFeature from './components/ProductFeature.vue'
|
||||||
|
import ProjectCta from './components/ProjectCta.vue'
|
||||||
|
|
||||||
const pageLinks = [
|
const productFeatures = [
|
||||||
{ label: 'About', to: '/about' },
|
{
|
||||||
{ label: 'Products', to: '/products' },
|
name: 'AiDA 3.1',
|
||||||
{ label: 'Contact', to: '/contact' },
|
title: 'Empowers fashion designers to create a collection with just a few clicks based on their creative inspirations.',
|
||||||
] as const
|
backgroundImage: aidaIntroBg,
|
||||||
|
backgroundAlt: 'Fashion design sketches on paper',
|
||||||
|
panelImage: aidaPanel,
|
||||||
|
panelAlt: 'AiDA design workspace preview',
|
||||||
|
reversed: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Mixi',
|
||||||
|
title: "Drives sales by improving shoppers' experience through precise and fast search.",
|
||||||
|
backgroundImage: mixiIntroBg,
|
||||||
|
backgroundAlt: 'Layered fabric texture',
|
||||||
|
panelImage: mixiPanel,
|
||||||
|
panelAlt: 'Mixi visual search interface preview',
|
||||||
|
reversed: true
|
||||||
|
}
|
||||||
|
] as const
|
||||||
|
|
||||||
useHead({
|
useHead({
|
||||||
title: 'Home | Code Create',
|
title: 'Home | Code Create',
|
||||||
meta: [
|
meta: [
|
||||||
{
|
{
|
||||||
name: 'description',
|
name: 'description',
|
||||||
content: 'Code Create home page rendered with Vite SSG.',
|
content:
|
||||||
},
|
'Code Create revitalises the fashion ecosystem through artificial intelligence.'
|
||||||
],
|
}
|
||||||
})
|
]
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<template>
|
||||||
.home-page {
|
<main class="home-page">
|
||||||
/* width: 100%; */
|
<HomeCarousel />
|
||||||
/* min-height: 100svh; */
|
|
||||||
/* background: #ffffff; */
|
|
||||||
}
|
|
||||||
|
|
||||||
.home-links {
|
<div class="home-content">
|
||||||
display: flex;
|
<section
|
||||||
flex-wrap: wrap;
|
class="ecosystem-intro"
|
||||||
justify-content: center;
|
aria-labelledby="ecosystem-title"
|
||||||
gap: 12px;
|
v-custom-animation.scroll
|
||||||
padding: 32px 20px 48px;
|
>
|
||||||
}
|
<img
|
||||||
|
class="ecosystem-logo"
|
||||||
|
:src="homeAiLogo"
|
||||||
|
alt="Code Create"
|
||||||
|
loading="lazy"
|
||||||
|
translate-y-s="80"
|
||||||
|
translate-y="0"
|
||||||
|
/>
|
||||||
|
<h1
|
||||||
|
id="ecosystem-title"
|
||||||
|
class="ecosystem-title"
|
||||||
|
translate-y-s="-60"
|
||||||
|
translate-y="0"
|
||||||
|
>
|
||||||
|
Revitalise The Fashion Ecosystem
|
||||||
|
</h1>
|
||||||
|
<p translate-y-s="-60" translate-y="0" class="ecosystem-subtitle">
|
||||||
|
Through Artificial Intelligence (AI)
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
.home-link {
|
<ProductFeature
|
||||||
min-width: 112px;
|
v-for="feature in productFeatures"
|
||||||
padding: 10px 16px;
|
:key="feature.name"
|
||||||
border: 1px solid #d9dde6;
|
:name="feature.name"
|
||||||
border-radius: 6px;
|
:title="feature.title"
|
||||||
color: #1d2430;
|
:background-image="feature.backgroundImage"
|
||||||
background: #ffffff;
|
:background-alt="feature.backgroundAlt"
|
||||||
font-size: 15px;
|
:panel-image="feature.panelImage"
|
||||||
line-height: 1.2;
|
:panel-alt="feature.panelAlt"
|
||||||
text-align: center;
|
:reversed="feature.reversed"
|
||||||
text-decoration: none;
|
/>
|
||||||
box-sizing: border-box;
|
</div>
|
||||||
transition:
|
|
||||||
border-color 0.2s,
|
|
||||||
color 0.2s,
|
|
||||||
box-shadow 0.2s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.home-link:hover,
|
<ProjectCta />
|
||||||
.home-link:focus-visible,
|
</main>
|
||||||
.home-link.router-link-active {
|
</template>
|
||||||
border-color: #2f6df6;
|
|
||||||
color: #2f6df6;
|
|
||||||
box-shadow: 0 8px 24px rgba(36, 55, 92, 0.12);
|
|
||||||
}
|
|
||||||
|
|
||||||
.home-link:focus-visible {
|
<style scoped lang="less">
|
||||||
outline: 2px solid #2f6df6;
|
.home-page {
|
||||||
outline-offset: 2px;
|
width: 100%;
|
||||||
}
|
overflow: hidden;
|
||||||
|
background: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
@media (max-width: 640px) {
|
.home-content {
|
||||||
.home-links {
|
padding: clamp(116px, 10vw, 172px) 0 clamp(92px, 8vw, 126px);
|
||||||
padding: 24px 16px 40px;
|
background: #ffffff;
|
||||||
}
|
}
|
||||||
|
|
||||||
.home-link {
|
.ecosystem-intro {
|
||||||
flex: 1 1 calc(50% - 12px);
|
display: flex;
|
||||||
}
|
flex-direction: column;
|
||||||
}
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 0 20px clamp(92px, 10vw, 150px);
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ecosystem-logo {
|
||||||
|
display: block;
|
||||||
|
width: clamp(146px, 15vw, 184px);
|
||||||
|
height: auto;
|
||||||
|
margin-bottom: 30px;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ecosystem-title {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 100%;
|
||||||
|
margin: 0 0 9px;
|
||||||
|
color: #101010;
|
||||||
|
font-family: Poppins, sans-serif;
|
||||||
|
font-size: clamp(18px, 1.65vw, 22px);
|
||||||
|
font-weight: 700;
|
||||||
|
line-height: 1.25;
|
||||||
|
letter-spacing: 4px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
overflow-wrap: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ecosystem-subtitle {
|
||||||
|
width: 100%;
|
||||||
|
margin: 0;
|
||||||
|
color: #101010;
|
||||||
|
font-family: Poppins, sans-serif;
|
||||||
|
font-size: clamp(13px, 1.2vw, 16px);
|
||||||
|
font-weight: 600;
|
||||||
|
line-height: 1.35;
|
||||||
|
letter-spacing: 2.5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 980px) {
|
||||||
|
.home-content {
|
||||||
|
padding: 96px 0 64px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ecosystem-intro {
|
||||||
|
padding-bottom: 50px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 640px) {
|
||||||
|
.home-content {
|
||||||
|
padding: 72px 0 36px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ecosystem-logo {
|
||||||
|
width: 138px;
|
||||||
|
margin-bottom: 22px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ecosystem-title {
|
||||||
|
width: min(100%, 330px);
|
||||||
|
max-width: none;
|
||||||
|
font-size: 14px;
|
||||||
|
letter-spacing: 1.5px;
|
||||||
|
line-height: 1.45;
|
||||||
|
overflow-wrap: anywhere;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ecosystem-subtitle {
|
||||||
|
font-size: 12px;
|
||||||
|
letter-spacing: 1.5px;
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,33 +1,37 @@
|
|||||||
import type { RouteRecordRaw } from 'vue-router'
|
import type { RouteRecordRaw } from 'vue-router'
|
||||||
import AboutView from './pages/about-us/index.vue'
|
import AboutView from './pages/about-us/index.vue'
|
||||||
import ContactView from './pages/ContactView.vue'
|
import ContactView from './pages/ContactView.vue'
|
||||||
import HomeView from './pages/HomeView.vue'
|
import HomeView from './pages/home/index.vue'
|
||||||
import ProductsView from './pages/ProductsView.vue'
|
import ProductsView from './pages/ProductsView.vue'
|
||||||
|
|
||||||
export const routes: RouteRecordRaw[] = [
|
export const routes: RouteRecordRaw[] = [
|
||||||
{
|
{
|
||||||
path: '/:lang?',
|
path: '/:lang(en|zh-cn|zh-tw)?',
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
path: '',
|
path: '',
|
||||||
component: HomeView,
|
component: HomeView,
|
||||||
alias: ['/:lang?', '/:lang?/home'],
|
|
||||||
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'about',
|
path: 'about',
|
||||||
component: AboutView,
|
component: AboutView
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'products',
|
path: 'products',
|
||||||
name: 'products',
|
name: 'products',
|
||||||
component: ProductsView,
|
component: ProductsView
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'contact',
|
path: 'contact',
|
||||||
name: 'contact',
|
name: 'contact',
|
||||||
component: ContactView,
|
component: ContactView
|
||||||
},
|
},
|
||||||
],
|
{
|
||||||
},
|
path: 'aida',
|
||||||
|
name: 'Aida',
|
||||||
|
component: () => import('./pages/aida/index.vue')
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,34 @@
|
|||||||
html,body{
|
html,
|
||||||
margin: 0;
|
body {
|
||||||
padding: 0;
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.flex {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flex-col {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flex-center {
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flex-1 {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.space-between {
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.justify-center {
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.align-center {
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|||||||
8
src/vite-env.d.ts
vendored
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
/// <reference types="vite/client" />
|
||||||
|
|
||||||
|
declare module '*.vue' {
|
||||||
|
import type { DefineComponent } from 'vue'
|
||||||
|
|
||||||
|
const component: DefineComponent<Record<string, never>, Record<string, never>, unknown>
|
||||||
|
export default component
|
||||||
|
}
|
||||||