Compare commits
10 Commits
6c1452be4d
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4587a59a89 | ||
|
|
d237dab098 | ||
| 6fc1da2884 | |||
| 5158b63ddd | |||
|
|
85708bb5a4 | ||
|
|
99af8da607 | ||
|
|
9235843f25 | ||
|
|
1153bce74e | ||
| e797612de6 | |||
| 3edda93691 |
@@ -5,6 +5,7 @@
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>test-ssg</title>
|
||||
<link rel="stylesheet" href="https://at.alicdn.com/t/c/font_4403230_bui5mtufs1c.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
|
||||
@@ -154,3 +154,28 @@ button[custom="black-box"] {
|
||||
--button-click-color: #fff;
|
||||
--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;
|
||||
}
|
||||
h1, h2, h3, h4, h5, h6, .products-title{
|
||||
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6,
|
||||
.products-title {
|
||||
font-family: Poppins, sans-serif;
|
||||
font-weight: 600;
|
||||
letter-spacing: 2px;
|
||||
color: #222222;
|
||||
text-transform: capitalize;
|
||||
font-weight: 600;
|
||||
letter-spacing: 2px;
|
||||
color: #222222;
|
||||
text-transform: capitalize;
|
||||
}
|
||||
|
||||
@keyframes loading {
|
||||
@@ -175,4 +182,33 @@ button[custom="black-box"] {
|
||||
--button-click-bgcolor: #979797;
|
||||
--button-click-color: #fff;
|
||||
--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',
|
||||
strokeDasharray: `${progress}, ${max}`,
|
||||
stroke: '#222',
|
||||
strokeWidth: 4,
|
||||
strokeWidth: 4
|
||||
}"
|
||||
fill="none"
|
||||
></path>
|
||||
</svg>
|
||||
<span class="iconfont icon-direction-up"></span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, onUnmounted } from "vue";
|
||||
import { ref } from "vue";
|
||||
const max = ref(98 * Math.PI);
|
||||
const progress = ref(0);
|
||||
const handleScroll = (e: number) => {
|
||||
progress.value = (e / 100) * max.value;
|
||||
};
|
||||
progress.value = (e / 100) * max.value
|
||||
}
|
||||
const handleClick = () => {
|
||||
document.documentElement.scrollTo({ top: 0, behavior: "smooth" });
|
||||
};
|
||||
document.documentElement.scrollTo({ top: 0, behavior: 'smooth' })
|
||||
}
|
||||
</script>
|
||||
<style scoped lang="less">
|
||||
.back-top {
|
||||
@@ -51,6 +52,20 @@
|
||||
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;
|
||||
&.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>
|
||||
<header class="header">
|
||||
|
||||
</header>
|
||||
<footer class="main-footer">
|
||||
<div class="left">
|
||||
<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>
|
||||
<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>
|
||||
.header{
|
||||
position: relative;
|
||||
.main-footer {
|
||||
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>
|
||||
<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="nav-item" v-for="item in navList" :key="item.path">
|
||||
<router-link class="nav-item-link" :to="item.path">
|
||||
<div class="nav-item" v-for="item in navList" :key="item.name">
|
||||
<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 }}
|
||||
<span v-show="item.children" class="iconfont icon-arrow-down-bold"></span>
|
||||
</router-link>
|
||||
</div>
|
||||
</div>
|
||||
<div class="right">
|
||||
<span>English</span>
|
||||
<span>My Account</span>
|
||||
<down-menu title="English">
|
||||
<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>
|
||||
</header>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, onUnmounted } from "vue";
|
||||
import { useRouter } from "vue-router";
|
||||
const router = useRouter();
|
||||
import { ref, onMounted, onUnmounted, computed } from 'vue'
|
||||
import DownMenu from './down-menu.vue'
|
||||
|
||||
import { useRoute } from 'vue-router'
|
||||
const route = useRoute()
|
||||
console.log(route)
|
||||
|
||||
const navList = ref([
|
||||
{
|
||||
name: "Home",
|
||||
path: "/",
|
||||
name: 'Home',
|
||||
path: '/'
|
||||
},
|
||||
{
|
||||
name: "About Us",
|
||||
path: "/about",
|
||||
name: 'About Us',
|
||||
path: '/about-us'
|
||||
},
|
||||
{
|
||||
name: "Our Solutions",
|
||||
path: "/solutions",
|
||||
name: 'Our Solutions',
|
||||
path: '',
|
||||
children: [
|
||||
{
|
||||
name: 'AiDA 3.1',
|
||||
path: '/aida'
|
||||
},
|
||||
{
|
||||
name: 'Mixi',
|
||||
path: '/mixi'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "Communities",
|
||||
path: "/communities",
|
||||
name: 'Communities',
|
||||
path: '',
|
||||
children: [
|
||||
{
|
||||
name: 'Events',
|
||||
path: '/events'
|
||||
},
|
||||
{
|
||||
name: 'User Stories',
|
||||
path: '/user-stories'
|
||||
},
|
||||
{
|
||||
name: 'Help Centre',
|
||||
path: '/help-centre'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "Media",
|
||||
path: "/media",
|
||||
name: 'Media',
|
||||
path: '/media'
|
||||
},
|
||||
{
|
||||
name: "Contact Us",
|
||||
path: "/contact",
|
||||
},
|
||||
]);
|
||||
name: 'Contact Us',
|
||||
path: '/contact-us'
|
||||
}
|
||||
])
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
.main-header {
|
||||
@@ -65,18 +106,85 @@
|
||||
> .logo {
|
||||
height: 100%;
|
||||
width: auto;
|
||||
img {
|
||||
width: auto;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
> .center-nav {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
> .nav-item {
|
||||
> .nav-item-link {
|
||||
position: relative;
|
||||
> .link {
|
||||
margin: 0 14px;
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
||||
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 {
|
||||
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 => {
|
||||
app.directive(directivesList[key].default.name, directivesList[key].default);
|
||||
});
|
||||
|
||||
@@ -33,7 +33,7 @@ export default {
|
||||
rootMap.set(obj.root, [obj])
|
||||
obj.root.addEventListener('scroll', handleScroll)
|
||||
},
|
||||
beforeUnmount(el: HTMLElement, binding: any) {
|
||||
beforeUnmount(el: HTMLElement) {
|
||||
rootMap.forEach((objs, root) => {
|
||||
if (objs.some((v: any) => v.el === el)) {
|
||||
objs = objs.filter((v_: any) => v_.el !== el)
|
||||
@@ -60,4 +60,4 @@ function handleScroll(e: any) {
|
||||
obj.el.classList.toggle('active', isActive)
|
||||
}
|
||||
})
|
||||
};
|
||||
};
|
||||
|
||||
@@ -2,7 +2,7 @@ import { gsap, } from 'gsap'
|
||||
import { ScrollTrigger } from 'gsap/ScrollTrigger'
|
||||
export default {
|
||||
name: 'tween-animation',
|
||||
mounted(el: HTMLElement, binding: any) {
|
||||
mounted(el: HTMLElement) {
|
||||
// if(!binding.value.isGsap)return
|
||||
let dom = document.querySelector('body')
|
||||
gsap.registerPlugin(ScrollTrigger);
|
||||
@@ -26,4 +26,4 @@ export default {
|
||||
// }
|
||||
});
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
@@ -9,8 +9,8 @@ export const createApp = ViteSSG(App, {
|
||||
routes,
|
||||
base: import.meta.env.BASE_URL,
|
||||
},
|
||||
({ app, router, routes, isClient, initialState }) => {
|
||||
({ app }) => {
|
||||
// 注册全局指令
|
||||
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">
|
||||
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({})
|
||||
const {} = toRefs(data);
|
||||
</script>
|
||||
<template>
|
||||
<section class="ecosystem">
|
||||
@@ -64,4 +50,4 @@ const {} = toRefs(data);
|
||||
padding: 100px 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
||||
@@ -1,19 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, onUnmounted, reactive, toRefs } from "vue";
|
||||
import Ecosystem from './ecosystem.vue'
|
||||
import Title from './title.vue'
|
||||
//const props = defineProps({
|
||||
//})
|
||||
//const emit = defineEmits([
|
||||
//])
|
||||
let data = reactive({
|
||||
})
|
||||
onMounted(()=>{
|
||||
})
|
||||
onUnmounted(()=>{
|
||||
})
|
||||
defineExpose({})
|
||||
const {} = toRefs(data);
|
||||
</script>
|
||||
<template>
|
||||
<div class="about-us">
|
||||
@@ -39,4 +27,4 @@ const {} = toRefs(data);
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
||||
@@ -1,19 +1,5 @@
|
||||
<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({})
|
||||
const {} = toRefs(data);
|
||||
</script>
|
||||
<template>
|
||||
<section class="title-section">
|
||||
@@ -42,4 +28,4 @@ const {} = toRefs(data);
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
||||
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>
|
||||
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">
|
||||
import { useHead } from '@unhead/vue'
|
||||
import { RouterLink } from 'vue-router'
|
||||
import HomeCarousel from './components/Carousel.vue'
|
||||
import { useHead } from '@unhead/vue'
|
||||
import aidaIntroBg from '@/assets/images/home/aida-intro-bg.png'
|
||||
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 = [
|
||||
{ label: 'About', to: '/about' },
|
||||
{ label: 'Products', to: '/products' },
|
||||
{ label: 'Contact', to: '/contact' },
|
||||
] as const
|
||||
const productFeatures = [
|
||||
{
|
||||
name: 'AiDA 3.1',
|
||||
title: 'Empowers fashion designers to create a collection with just a few clicks based on their creative inspirations.',
|
||||
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({
|
||||
title: 'Home | Code Create',
|
||||
meta: [
|
||||
{
|
||||
name: 'description',
|
||||
content: 'Code Create home page rendered with Vite SSG.',
|
||||
},
|
||||
],
|
||||
})
|
||||
useHead({
|
||||
title: 'Home | Code Create',
|
||||
meta: [
|
||||
{
|
||||
name: 'description',
|
||||
content:
|
||||
'Code Create revitalises the fashion ecosystem through artificial intelligence.'
|
||||
}
|
||||
]
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.home-page {
|
||||
/* width: 100%; */
|
||||
/* min-height: 100svh; */
|
||||
/* background: #ffffff; */
|
||||
}
|
||||
<template>
|
||||
<main class="home-page">
|
||||
<HomeCarousel />
|
||||
|
||||
.home-links {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
gap: 12px;
|
||||
padding: 32px 20px 48px;
|
||||
}
|
||||
<div class="home-content">
|
||||
<section
|
||||
class="ecosystem-intro"
|
||||
aria-labelledby="ecosystem-title"
|
||||
v-custom-animation.scroll
|
||||
>
|
||||
<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 {
|
||||
min-width: 112px;
|
||||
padding: 10px 16px;
|
||||
border: 1px solid #d9dde6;
|
||||
border-radius: 6px;
|
||||
color: #1d2430;
|
||||
background: #ffffff;
|
||||
font-size: 15px;
|
||||
line-height: 1.2;
|
||||
text-align: center;
|
||||
text-decoration: none;
|
||||
box-sizing: border-box;
|
||||
transition:
|
||||
border-color 0.2s,
|
||||
color 0.2s,
|
||||
box-shadow 0.2s;
|
||||
}
|
||||
<ProductFeature
|
||||
v-for="feature in productFeatures"
|
||||
:key="feature.name"
|
||||
:name="feature.name"
|
||||
:title="feature.title"
|
||||
:background-image="feature.backgroundImage"
|
||||
:background-alt="feature.backgroundAlt"
|
||||
:panel-image="feature.panelImage"
|
||||
:panel-alt="feature.panelAlt"
|
||||
:reversed="feature.reversed"
|
||||
/>
|
||||
</div>
|
||||
|
||||
.home-link:hover,
|
||||
.home-link:focus-visible,
|
||||
.home-link.router-link-active {
|
||||
border-color: #2f6df6;
|
||||
color: #2f6df6;
|
||||
box-shadow: 0 8px 24px rgba(36, 55, 92, 0.12);
|
||||
}
|
||||
<ProjectCta />
|
||||
</main>
|
||||
</template>
|
||||
|
||||
.home-link:focus-visible {
|
||||
outline: 2px solid #2f6df6;
|
||||
outline-offset: 2px;
|
||||
}
|
||||
<style scoped lang="less">
|
||||
.home-page {
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
background: #ffffff;
|
||||
}
|
||||
|
||||
@media (max-width: 640px) {
|
||||
.home-links {
|
||||
padding: 24px 16px 40px;
|
||||
}
|
||||
.home-content {
|
||||
padding: clamp(116px, 10vw, 172px) 0 clamp(92px, 8vw, 126px);
|
||||
background: #ffffff;
|
||||
}
|
||||
|
||||
.home-link {
|
||||
flex: 1 1 calc(50% - 12px);
|
||||
}
|
||||
}
|
||||
.ecosystem-intro {
|
||||
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>
|
||||
|
||||
@@ -1,33 +1,37 @@
|
||||
import type { RouteRecordRaw } from 'vue-router'
|
||||
import AboutView from './pages/about-us/index.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'
|
||||
|
||||
export const routes: RouteRecordRaw[] = [
|
||||
{
|
||||
path: '/:lang?',
|
||||
path: '/:lang(en|zh-cn|zh-tw)?',
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
component: HomeView,
|
||||
alias: ['/:lang?', '/:lang?/home'],
|
||||
|
||||
},
|
||||
{
|
||||
path: 'about',
|
||||
component: AboutView,
|
||||
component: AboutView
|
||||
},
|
||||
{
|
||||
path: 'products',
|
||||
name: 'products',
|
||||
component: ProductsView,
|
||||
component: ProductsView
|
||||
},
|
||||
{
|
||||
path: 'contact',
|
||||
name: 'contact',
|
||||
component: ContactView,
|
||||
component: ContactView
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: 'aida',
|
||||
name: 'Aida',
|
||||
component: () => import('./pages/aida/index.vue')
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
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
|
||||
}
|
||||