Compare commits
28 Commits
d47d9535ee
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4254f291f4 | ||
|
|
35df75d4ea | ||
|
|
d327797236 | ||
|
|
6523c30d9f | ||
|
|
4af58134fd | ||
|
|
e36b17642f | ||
|
|
57835e6416 | ||
|
|
892c568efc | ||
|
|
a92ceff8e4 | ||
|
|
2cf20d1b81 | ||
|
|
27a280ab46 | ||
|
|
1c84d3949f | ||
|
|
f282adfd87 | ||
|
|
09af83050a | ||
|
|
4587a59a89 | ||
|
|
d237dab098 | ||
| 6fc1da2884 | |||
| 5158b63ddd | |||
|
|
85708bb5a4 | ||
|
|
99af8da607 | ||
|
|
9235843f25 | ||
|
|
1153bce74e | ||
| e797612de6 | |||
| 3edda93691 | |||
| 6c1452be4d | |||
| 9733a0dcd6 | |||
|
|
1de788ccb7 | ||
|
|
dfd566e389 |
@@ -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_70xz405a1wg.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
|
||||
@@ -11,11 +11,14 @@
|
||||
"dependencies": {
|
||||
"@kagol/vue-carousel": "^0.1.2",
|
||||
"@unhead/vue": "^2.1.15",
|
||||
"unhead": "2.1.15",
|
||||
"gsap": "^3.15.0",
|
||||
"less": "^4.6.4",
|
||||
"pinia": "^3.0.4",
|
||||
"pinia-persistedstate-plugin": "^0.1.0",
|
||||
"unhead": "2.1.15",
|
||||
"vite-ssg": "^28.3.0",
|
||||
"vue": "^3.5.34",
|
||||
"vue-i18n": "^11.4.4",
|
||||
"vue-router": "^4.6.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
218
pnpm-lock.yaml
generated
@@ -14,12 +14,30 @@ importers:
|
||||
'@unhead/vue':
|
||||
specifier: ^2.1.15
|
||||
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
|
||||
pinia:
|
||||
specifier: ^3.0.4
|
||||
version: 3.0.4(typescript@6.0.3)(vue@3.5.34(typescript@6.0.3))
|
||||
pinia-persistedstate-plugin:
|
||||
specifier: ^0.1.0
|
||||
version: 0.1.0(typescript@6.0.3)(vue@3.5.34(typescript@6.0.3))
|
||||
unhead:
|
||||
specifier: 2.1.15
|
||||
version: 2.1.15
|
||||
vite-ssg:
|
||||
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:
|
||||
specifier: ^3.5.34
|
||||
version: 3.5.34(typescript@6.0.3)
|
||||
vue-i18n:
|
||||
specifier: ^11.4.4
|
||||
version: 11.4.4(vue@3.5.34(typescript@6.0.3))
|
||||
vue-router:
|
||||
specifier: ^4.6.4
|
||||
version: 4.6.4(vue@3.5.34(typescript@6.0.3))
|
||||
@@ -140,6 +158,22 @@ packages:
|
||||
'@noble/hashes':
|
||||
optional: true
|
||||
|
||||
'@intlify/core-base@11.4.4':
|
||||
resolution: {integrity: sha512-w/vItlylrAmhebkIbVl5YY8XMCtj8Mb2g70ttxktMYuf5AuRahgEHL2iLgLIsZBIbTSgs4hkUo7ucCL0uTJvOg==}
|
||||
engines: {node: '>= 22'}
|
||||
|
||||
'@intlify/devtools-types@11.4.4':
|
||||
resolution: {integrity: sha512-PcBLmGmDQsTSVV911P8upzpcLJO1CNVYi/IH6bGnLR2nA+0L963+kXN1ZrisTEnbtw2ewN6HMMSldqzjronA0Q==}
|
||||
engines: {node: '>= 22'}
|
||||
|
||||
'@intlify/message-compiler@11.4.4':
|
||||
resolution: {integrity: sha512-vn0OAV9pYkJlPPmgnsSm5eAG3mL0+9C/oaded2JY9jmxBbhmUXT3TcAUY8WRgLY9Hte7lkUJKpXrVlYjMXBD2w==}
|
||||
engines: {node: '>= 22'}
|
||||
|
||||
'@intlify/shared@11.4.4':
|
||||
resolution: {integrity: sha512-QRUCHqda1U6aR14FR0vvXD4+4gj6+fm0AhAozvSuRCw0fCvrmCugWpgiR4xH2NI6s8am6N9p5OhirplsX8ZS3g==}
|
||||
engines: {node: '>= 22'}
|
||||
|
||||
'@jridgewell/gen-mapping@0.3.13':
|
||||
resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==}
|
||||
|
||||
@@ -278,10 +312,14 @@ packages:
|
||||
resolution: {integrity: sha512-9Q7DGjZN+hTdJomaQ3Iub4m6VPu1r94bmK2z3UeWP3dGUecRC54tmVu9vKHTm1bOt3ASoYtEz6JSRLFzrysKlA==}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@tybys/wasm-util@0.10.2':
|
||||
resolution: {integrity: sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==}
|
||||
|
||||
'@types/js-cookie@3.0.6':
|
||||
resolution: {integrity: sha512-wkw9yd1kEXOPnvEeEV1Go1MmxtBJL0RR79aOTAApecWFVu7w0NNXNqhcWgvw2YgZDYadliXkl14pa3WXw5jlCQ==}
|
||||
|
||||
'@types/node@24.12.4':
|
||||
resolution: {integrity: sha512-GUUEShf+PBCGW2KaXwcIt3Yk+e3pkKwWKb9GSyM9WQVE+ep2jzmHdGsHzu4wgcZy5fN9FBdVzjpBQsYlpfpgLA==}
|
||||
|
||||
@@ -326,6 +364,15 @@ packages:
|
||||
'@vue/devtools-api@6.6.4':
|
||||
resolution: {integrity: sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==}
|
||||
|
||||
'@vue/devtools-api@7.7.9':
|
||||
resolution: {integrity: sha512-kIE8wvwlcZ6TJTbNeU2HQNtaxLx3a84aotTITUuL/4bzfPxzajGBOoqjMhwZJ8L9qFYDU/lAYMEEm11dnZOD6g==}
|
||||
|
||||
'@vue/devtools-kit@7.7.9':
|
||||
resolution: {integrity: sha512-PyQ6odHSgiDVd4hnTP+aDk2X4gl2HmLDfiyEnn3/oV+ckFDuswRs4IbBT7vacMuGdwY/XemxBoh302ctbsptuA==}
|
||||
|
||||
'@vue/devtools-shared@7.7.9':
|
||||
resolution: {integrity: sha512-iWAb0v2WYf0QWmxCGy0seZNDPdO3Sp5+u78ORnyeonS6MT4PC7VPrryX2BpMJrwlDeaZ6BD4vP4XKjK0SZqaeA==}
|
||||
|
||||
'@vue/language-core@3.2.8':
|
||||
resolution: {integrity: sha512-9OiSPQFiAAWNVnXb0d2dcTmcKnFQamhuNES6ayyISrb/mwPWVgoGdAqSfCWqKhQpa3D5gDTcYD+w7ObiheZ81g==}
|
||||
|
||||
@@ -376,6 +423,9 @@ packages:
|
||||
bidi-js@1.0.3:
|
||||
resolution: {integrity: sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==}
|
||||
|
||||
birpc@2.9.0:
|
||||
resolution: {integrity: sha512-KrayHS5pBi69Xi9JmvoqrIgYGDkD6mcSe/i6YKi3w5kekCLzrX4+nawcXqrj2tIp50Kw/mT/s3p+GVK0A0sKxw==}
|
||||
|
||||
buffer-from@1.1.2:
|
||||
resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==}
|
||||
|
||||
@@ -401,6 +451,10 @@ packages:
|
||||
resolution: {integrity: sha512-yCEafptTtb4bk7GLEQoM8KVJpxAfdBJYaXyzQEgQQQgYrZiDp8SJmGKlYza6CYjEDNstAdNdKA3UuoULlEbS6w==}
|
||||
engines: {node: '>=12.13'}
|
||||
|
||||
copy-anything@4.0.5:
|
||||
resolution: {integrity: sha512-7Vv6asjS4gMOuILabD3l739tsaxFQmC+a7pLZm02zyvs8p977bL3zEgq3yDk5rn9B0PbYgIv++jmHcuUab4RhA==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
css-tree@3.2.1:
|
||||
resolution: {integrity: sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA==}
|
||||
engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0}
|
||||
@@ -474,6 +528,9 @@ packages:
|
||||
gsap@3.15.0:
|
||||
resolution: {integrity: sha512-dMW4CWBTUK1AEEDeZc1g4xpPGIrSf9fJF960qbTZmN/QwZIWY5wgliS6JWl9/25fpTGJrMRtSjGtOmPnfjZB+A==}
|
||||
|
||||
hookable@5.5.3:
|
||||
resolution: {integrity: sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==}
|
||||
|
||||
hookable@6.1.1:
|
||||
resolution: {integrity: sha512-U9LYDy1CwhMCnprUfeAZWZGByVbhd54hwepegYTK7Pi5NvqEj63ifz5z+xukznehT7i6NIZRu89Ay1AZmRsLEQ==}
|
||||
|
||||
@@ -513,6 +570,10 @@ packages:
|
||||
resolution: {integrity: sha512-ZhMwEosbFJkA0YhFnNDgTM4ZxDRsS6HqTo7qsZM08fehyRYIYa0yHu5R6mgo1n/8MgaPBXiPimPD77baVFYg+A==}
|
||||
engines: {node: '>=12.13'}
|
||||
|
||||
is-what@5.5.0:
|
||||
resolution: {integrity: sha512-oG7cgbmg5kLYae2N5IVd3jm2s+vldjxJzK1pcu9LfpGuQ93MQSzo0okvRna+7y5ifrD+20FE8FvjusyGaz14fw==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
jsdom@28.1.0:
|
||||
resolution: {integrity: sha512-0+MoQNYyr2rBHqO1xilltfDjV9G7ymYGlAUazgcDLQaUf8JDHbuGwsxN6U9qWaElZ4w1B2r7yEGIL3GdeW3Rug==}
|
||||
engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0}
|
||||
@@ -623,6 +684,9 @@ packages:
|
||||
engines: {node: '>=4'}
|
||||
hasBin: true
|
||||
|
||||
mitt@3.0.1:
|
||||
resolution: {integrity: sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==}
|
||||
|
||||
ms@2.1.3:
|
||||
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
|
||||
|
||||
@@ -658,6 +722,9 @@ packages:
|
||||
path-browserify@1.0.1:
|
||||
resolution: {integrity: sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==}
|
||||
|
||||
perfect-debounce@1.0.0:
|
||||
resolution: {integrity: sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==}
|
||||
|
||||
picocolors@1.1.1:
|
||||
resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
|
||||
|
||||
@@ -669,6 +736,27 @@ packages:
|
||||
resolution: {integrity: sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
pinia-persistedstate-plugin@0.1.0:
|
||||
resolution: {integrity: sha512-ToKR/EJzhhXElQ5YL8PVVY4CqLJjywxszAJjOCgprjmIVkTrPBsEOY4b/ATOzHQc1TtuaJs/3MJuoCpA3pv8Ew==}
|
||||
|
||||
pinia@2.3.1:
|
||||
resolution: {integrity: sha512-khUlZSwt9xXCaTbbxFYBKDc/bWAGWJjOgvxETwkTN7KRm66EeT1ZdZj6i2ceh9sP2Pzqsbc704r2yngBrxBVug==}
|
||||
peerDependencies:
|
||||
typescript: '>=4.4.4'
|
||||
vue: ^2.7.0 || ^3.5.11
|
||||
peerDependenciesMeta:
|
||||
typescript:
|
||||
optional: true
|
||||
|
||||
pinia@3.0.4:
|
||||
resolution: {integrity: sha512-l7pqLUFTI/+ESXn6k3nu30ZIzW5E2WZF/LaHJEpoq6ElcLD+wduZoB2kBN19du6K/4FDpPMazY2wJr+IndBtQw==}
|
||||
peerDependencies:
|
||||
typescript: '>=4.5.0'
|
||||
vue: ^3.5.11
|
||||
peerDependenciesMeta:
|
||||
typescript:
|
||||
optional: true
|
||||
|
||||
postcss@8.5.14:
|
||||
resolution: {integrity: sha512-SoSL4+OSEtR99LHFZQiJLkT59C5B1amGO1NzTwj7TT1qCUgUO6hxOvzkOYxD+vMrXBM3XJIKzokoERdqQq/Zmg==}
|
||||
engines: {node: ^10 || ^12 || >=14}
|
||||
@@ -688,6 +776,9 @@ packages:
|
||||
resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
rfdc@1.4.1:
|
||||
resolution: {integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==}
|
||||
|
||||
rolldown@1.0.0:
|
||||
resolution: {integrity: sha512-yD986aXDESFGS95spT1LAv0jssywP4npMEjmMHyN2/5+eE8qQJUype2AaKkRiLgBgyD0LFlubwAht7VmY8rGoA==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
@@ -719,6 +810,14 @@ packages:
|
||||
resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
speakingurl@14.0.1:
|
||||
resolution: {integrity: sha512-1POYv7uv2gXoyGFpBCmpDVSNV74IfsWlDW216UPjbWufNf+bSU6GdbDsxdcxtfwb4xlI3yxzOTKClUosxARYrQ==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
superjson@2.2.6:
|
||||
resolution: {integrity: sha512-H+ue8Zo4vJmV2nRjpx86P35lzwDT3nItnIsocgumgr0hHMQ+ZGq5vrERg9kJBo5AWGmxZDhzDo+WVIJqkB0cGA==}
|
||||
engines: {node: '>=16'}
|
||||
|
||||
symbol-tree@3.2.4:
|
||||
resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==}
|
||||
|
||||
@@ -831,6 +930,23 @@ packages:
|
||||
vscode-uri@3.1.0:
|
||||
resolution: {integrity: sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==}
|
||||
|
||||
vue-demi@0.14.10:
|
||||
resolution: {integrity: sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==}
|
||||
engines: {node: '>=12'}
|
||||
hasBin: true
|
||||
peerDependencies:
|
||||
'@vue/composition-api': ^1.0.0-rc.1
|
||||
vue: ^3.0.0-0 || ^2.6.0
|
||||
peerDependenciesMeta:
|
||||
'@vue/composition-api':
|
||||
optional: true
|
||||
|
||||
vue-i18n@11.4.4:
|
||||
resolution: {integrity: sha512-gIbXVSFQV4jcSJxfwdZ5zSZmZ+12CnX0K3vBkRSd6Zn+HSzCp+QwUgPwpD/uN0oKNKI9RzlUXPKVedEuMgNG0A==}
|
||||
engines: {node: '>= 22'}
|
||||
peerDependencies:
|
||||
vue: ^3.0.0
|
||||
|
||||
vue-router@4.6.4:
|
||||
resolution: {integrity: sha512-Hz9q5sa33Yhduglwz6g9skT8OBPii+4bFn88w6J+J4MfEo4KRRpmiNG/hHHkdbRFlLBOqxN8y8gf2Fb0MTUgVg==}
|
||||
peerDependencies:
|
||||
@@ -959,6 +1075,24 @@ snapshots:
|
||||
|
||||
'@exodus/bytes@1.15.0': {}
|
||||
|
||||
'@intlify/core-base@11.4.4':
|
||||
dependencies:
|
||||
'@intlify/devtools-types': 11.4.4
|
||||
'@intlify/message-compiler': 11.4.4
|
||||
'@intlify/shared': 11.4.4
|
||||
|
||||
'@intlify/devtools-types@11.4.4':
|
||||
dependencies:
|
||||
'@intlify/core-base': 11.4.4
|
||||
'@intlify/shared': 11.4.4
|
||||
|
||||
'@intlify/message-compiler@11.4.4':
|
||||
dependencies:
|
||||
'@intlify/shared': 11.4.4
|
||||
source-map-js: 1.2.1
|
||||
|
||||
'@intlify/shared@11.4.4': {}
|
||||
|
||||
'@jridgewell/gen-mapping@0.3.13':
|
||||
dependencies:
|
||||
'@jridgewell/sourcemap-codec': 1.5.5
|
||||
@@ -1061,6 +1195,8 @@ snapshots:
|
||||
tslib: 2.8.1
|
||||
optional: true
|
||||
|
||||
'@types/js-cookie@3.0.6': {}
|
||||
|
||||
'@types/node@24.12.4':
|
||||
dependencies:
|
||||
undici-types: 7.16.0
|
||||
@@ -1125,6 +1261,24 @@ snapshots:
|
||||
|
||||
'@vue/devtools-api@6.6.4': {}
|
||||
|
||||
'@vue/devtools-api@7.7.9':
|
||||
dependencies:
|
||||
'@vue/devtools-kit': 7.7.9
|
||||
|
||||
'@vue/devtools-kit@7.7.9':
|
||||
dependencies:
|
||||
'@vue/devtools-shared': 7.7.9
|
||||
birpc: 2.9.0
|
||||
hookable: 5.5.3
|
||||
mitt: 3.0.1
|
||||
perfect-debounce: 1.0.0
|
||||
speakingurl: 14.0.1
|
||||
superjson: 2.2.6
|
||||
|
||||
'@vue/devtools-shared@7.7.9':
|
||||
dependencies:
|
||||
rfdc: 1.4.1
|
||||
|
||||
'@vue/language-core@3.2.8':
|
||||
dependencies:
|
||||
'@volar/language-core': 2.4.28
|
||||
@@ -1176,6 +1330,8 @@ snapshots:
|
||||
dependencies:
|
||||
require-from-string: 2.0.2
|
||||
|
||||
birpc@2.9.0: {}
|
||||
|
||||
buffer-from@1.1.2: {}
|
||||
|
||||
cac@6.7.14: {}
|
||||
@@ -1197,6 +1353,10 @@ snapshots:
|
||||
dependencies:
|
||||
is-what: 4.1.16
|
||||
|
||||
copy-anything@4.0.5:
|
||||
dependencies:
|
||||
is-what: 5.5.0
|
||||
|
||||
css-tree@3.2.1:
|
||||
dependencies:
|
||||
mdn-data: 2.27.1
|
||||
@@ -1256,6 +1416,8 @@ snapshots:
|
||||
|
||||
gsap@3.15.0: {}
|
||||
|
||||
hookable@5.5.3: {}
|
||||
|
||||
hookable@6.1.1: {}
|
||||
|
||||
html-encoding-sniffer@6.0.0:
|
||||
@@ -1304,6 +1466,8 @@ snapshots:
|
||||
|
||||
is-what@4.1.16: {}
|
||||
|
||||
is-what@5.5.0: {}
|
||||
|
||||
jsdom@28.1.0:
|
||||
dependencies:
|
||||
'@acemir/cssom': 0.9.31
|
||||
@@ -1414,6 +1578,8 @@ snapshots:
|
||||
mime@1.6.0:
|
||||
optional: true
|
||||
|
||||
mitt@3.0.1: {}
|
||||
|
||||
ms@2.1.3: {}
|
||||
|
||||
muggle-string@0.4.1: {}
|
||||
@@ -1449,6 +1615,8 @@ snapshots:
|
||||
|
||||
path-browserify@1.0.1: {}
|
||||
|
||||
perfect-debounce@1.0.0: {}
|
||||
|
||||
picocolors@1.1.1: {}
|
||||
|
||||
picomatch@4.0.4: {}
|
||||
@@ -1456,6 +1624,32 @@ snapshots:
|
||||
pify@4.0.1:
|
||||
optional: true
|
||||
|
||||
pinia-persistedstate-plugin@0.1.0(typescript@6.0.3)(vue@3.5.34(typescript@6.0.3)):
|
||||
dependencies:
|
||||
'@types/js-cookie': 3.0.6
|
||||
pinia: 2.3.1(typescript@6.0.3)(vue@3.5.34(typescript@6.0.3))
|
||||
transitivePeerDependencies:
|
||||
- '@vue/composition-api'
|
||||
- typescript
|
||||
- vue
|
||||
|
||||
pinia@2.3.1(typescript@6.0.3)(vue@3.5.34(typescript@6.0.3)):
|
||||
dependencies:
|
||||
'@vue/devtools-api': 6.6.4
|
||||
vue: 3.5.34(typescript@6.0.3)
|
||||
vue-demi: 0.14.10(vue@3.5.34(typescript@6.0.3))
|
||||
optionalDependencies:
|
||||
typescript: 6.0.3
|
||||
transitivePeerDependencies:
|
||||
- '@vue/composition-api'
|
||||
|
||||
pinia@3.0.4(typescript@6.0.3)(vue@3.5.34(typescript@6.0.3)):
|
||||
dependencies:
|
||||
'@vue/devtools-api': 7.7.9
|
||||
vue: 3.5.34(typescript@6.0.3)
|
||||
optionalDependencies:
|
||||
typescript: 6.0.3
|
||||
|
||||
postcss@8.5.14:
|
||||
dependencies:
|
||||
nanoid: 3.3.12
|
||||
@@ -1471,6 +1665,8 @@ snapshots:
|
||||
|
||||
require-from-string@2.0.2: {}
|
||||
|
||||
rfdc@1.4.1: {}
|
||||
|
||||
rolldown@1.0.0:
|
||||
dependencies:
|
||||
'@oxc-project/types': 0.129.0
|
||||
@@ -1514,6 +1710,12 @@ snapshots:
|
||||
|
||||
source-map@0.6.1: {}
|
||||
|
||||
speakingurl@14.0.1: {}
|
||||
|
||||
superjson@2.2.6:
|
||||
dependencies:
|
||||
copy-anything: 4.0.5
|
||||
|
||||
symbol-tree@3.2.4: {}
|
||||
|
||||
terser@5.47.1:
|
||||
@@ -1556,7 +1758,7 @@ snapshots:
|
||||
|
||||
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:
|
||||
'@unhead/dom': 2.1.15(unhead@2.1.15)
|
||||
'@unhead/vue': 2.1.15(vue@3.5.34(typescript@6.0.3))
|
||||
@@ -1590,6 +1792,18 @@ snapshots:
|
||||
|
||||
vscode-uri@3.1.0: {}
|
||||
|
||||
vue-demi@0.14.10(vue@3.5.34(typescript@6.0.3)):
|
||||
dependencies:
|
||||
vue: 3.5.34(typescript@6.0.3)
|
||||
|
||||
vue-i18n@11.4.4(vue@3.5.34(typescript@6.0.3)):
|
||||
dependencies:
|
||||
'@intlify/core-base': 11.4.4
|
||||
'@intlify/devtools-types': 11.4.4
|
||||
'@intlify/shared': 11.4.4
|
||||
'@vue/devtools-api': 6.6.4
|
||||
vue: 3.5.34(typescript@6.0.3)
|
||||
|
||||
vue-router@4.6.4(vue@3.5.34(typescript@6.0.3)):
|
||||
dependencies:
|
||||
'@vue/devtools-api': 6.6.4
|
||||
|
||||
39
src/App.vue
@@ -3,13 +3,22 @@
|
||||
<RouterView />
|
||||
<MainFooter />
|
||||
<BackTop />
|
||||
<VideoModel />
|
||||
<div v-show="loading" class="main-loading">
|
||||
<span class="iconfont icon-loading"></span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { RouterView } from "vue-router";
|
||||
import MainHeader from "./components/main-header.vue";
|
||||
import MainFooter from "./components/main-footer.vue";
|
||||
import BackTop from "./components/back-top.vue";
|
||||
import { ref, computed } from 'vue'
|
||||
import { RouterView } from 'vue-router'
|
||||
import MainHeader from './components/main-header.vue'
|
||||
import MainFooter from './components/main-footer.vue'
|
||||
import BackTop from './components/back-top.vue'
|
||||
import VideoModel from './components/video-model.vue'
|
||||
import { useGlobalStore } from './stores/global'
|
||||
const globalStore = useGlobalStore()
|
||||
const loading = computed(() => globalStore.state.loading)
|
||||
</script>
|
||||
<style scoped lang="less">
|
||||
// .main {
|
||||
@@ -22,4 +31,26 @@
|
||||
// height: auto;
|
||||
// }
|
||||
// }
|
||||
.main-loading {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 9999999999;
|
||||
> .iconfont {
|
||||
font-size: 80px;
|
||||
color: #fff;
|
||||
animation: loading 2s linear infinite;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<style lang="less">
|
||||
html:root {
|
||||
--main-header-height: 85px;
|
||||
}
|
||||
</style>
|
||||
@@ -6,6 +6,7 @@ li,
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
p {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
@@ -13,14 +14,17 @@ p {
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
html,
|
||||
body,
|
||||
#app {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
font-family: 'KaiseiOpti-Medium';
|
||||
color: #232323;
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6,
|
||||
.products-title {
|
||||
font-family: Poppins, sans-serif;
|
||||
font-weight: 600;
|
||||
letter-spacing: 2px;
|
||||
text-transform: capitalize;
|
||||
}
|
||||
@keyframes loading {
|
||||
0% {
|
||||
@@ -117,35 +121,56 @@ body,
|
||||
background-size: var(--mosaic-bg-size) var(--mosaic-bg-size);
|
||||
}
|
||||
button[custom],
|
||||
button[custom="white"] {
|
||||
min-width: 19.4rem;
|
||||
height: 5rem;
|
||||
padding: 0 1rem;
|
||||
border-radius: 0;
|
||||
font-family: KaiseiOpti-Bold;
|
||||
font-size: var(--button-font-size, 2rem);
|
||||
color: var(--button-color, #232323);
|
||||
background: var(--button-bgcolor, #fff);
|
||||
border: var(--button-border, none);
|
||||
button[custom="red"] {
|
||||
width: 100%;
|
||||
height: 38px;
|
||||
border-radius: 4px;
|
||||
border: none;
|
||||
background-color: #9a2125;
|
||||
color: #fff;
|
||||
transition: all 0.3s ease-in-out;
|
||||
box-shadow: 0 2px 5px rgba(7, 129, 105, 0.3);
|
||||
cursor: pointer;
|
||||
font-size: 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
button[custom]:hover,
|
||||
button[custom="red"]:hover {
|
||||
background-color: #99494c;
|
||||
}
|
||||
button[custom] > .iconfont,
|
||||
button[custom="red"] > .iconfont {
|
||||
margin-right: 4px;
|
||||
font-size: 16px;
|
||||
}
|
||||
button[custom] > .label,
|
||||
button[custom="red"] > .label {
|
||||
font-size: 14px;
|
||||
}
|
||||
.hover-bottom-animation {
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
}
|
||||
button[custom]:active,
|
||||
button[custom="white"]:active {
|
||||
background: var(--button-click-bgcolor, #e4e4e4);
|
||||
color: var(--button-click-color, #232323);
|
||||
.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;
|
||||
}
|
||||
button[custom="black"] {
|
||||
--button-bgcolor: #232323;
|
||||
--button-color: #fff;
|
||||
--button-click-bgcolor: #333;
|
||||
--button-click-color: #fff;
|
||||
--button-font-size: 1.6rem;
|
||||
.hover-bottom-animation:hover::before {
|
||||
width: 100%;
|
||||
left: 0;
|
||||
right: auto;
|
||||
}
|
||||
button[custom="black-box"] {
|
||||
--button-bgcolor: transparent;
|
||||
--button-color: #232323;
|
||||
--button-border: 0.2rem solid #979797;
|
||||
--button-click-bgcolor: #979797;
|
||||
--button-click-color: #fff;
|
||||
--button-font-size: 1.6rem;
|
||||
.hover-bottom-animation.active:before,
|
||||
.hover-bottom-animation.router-link-exact-active:before {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ li,
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
p {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
@@ -15,14 +16,17 @@ p {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
html,
|
||||
body,
|
||||
#app {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
font-family: 'KaiseiOpti-Medium';
|
||||
color: #232323;
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6,
|
||||
.products-title {
|
||||
font-family: Poppins, sans-serif;
|
||||
font-weight: 600;
|
||||
letter-spacing: 2px;
|
||||
text-transform: capitalize;
|
||||
}
|
||||
|
||||
@keyframes loading {
|
||||
@@ -144,37 +148,63 @@ body,
|
||||
|
||||
// 自定义button按钮
|
||||
button[custom],
|
||||
button[custom="white"] {
|
||||
min-width: 19.4rem;
|
||||
height: 5rem;
|
||||
padding: 0 1rem;
|
||||
border-radius: 0;
|
||||
font-family: KaiseiOpti-Bold;
|
||||
font-size: var(--button-font-size, 2rem);
|
||||
color: var(--button-color, #232323);
|
||||
background: var(--button-bgcolor, #fff);
|
||||
border: var(--button-border, none);
|
||||
button[custom="red"] {
|
||||
width: 100%;
|
||||
height: 38px;
|
||||
border-radius: 4px;
|
||||
border: none;
|
||||
background-color: #9a2125;
|
||||
color: #fff;
|
||||
transition: all 0.3s ease-in-out;
|
||||
box-shadow: 0 2px 5px rgba(7, 129, 105, 0.3);
|
||||
cursor: pointer;
|
||||
font-size: 12px;
|
||||
|
||||
&:hover {
|
||||
background-color: #99494c;
|
||||
}
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
>.iconfont {
|
||||
margin-right: 4px;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
>.label {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
.hover-bottom-animation {
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
|
||||
&:active {
|
||||
background: var(--button-click-bgcolor, #e4e4e4);
|
||||
color: var(--button-click-color, #232323);
|
||||
}
|
||||
&::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;
|
||||
}
|
||||
|
||||
button[custom="black"] {
|
||||
--button-bgcolor: #232323;
|
||||
--button-color: #fff;
|
||||
--button-click-bgcolor: #333;
|
||||
--button-click-color: #fff;
|
||||
--button-font-size: 1.6rem;
|
||||
&:hover::before {
|
||||
width: 100%;
|
||||
left: 0;
|
||||
right: auto;
|
||||
}
|
||||
|
||||
button[custom="black-box"] {
|
||||
--button-bgcolor: transparent;
|
||||
--button-color: #232323;
|
||||
--button-border: 0.2rem solid #979797;
|
||||
--button-click-bgcolor: #979797;
|
||||
--button-click-color: #fff;
|
||||
--button-font-size: 1.6rem;
|
||||
&.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: 14px;
|
||||
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,83 @@
|
||||
<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>{{
|
||||
$t('MainFooter.Copyright', {
|
||||
year: new Date().getFullYear(),
|
||||
name: 'Code-Create'
|
||||
})
|
||||
}}</span>
|
||||
</div>
|
||||
<div class="right">
|
||||
<div v-for="item in nav" :key="item.path">
|
||||
<router-link class="link" :to="item.path">{{ $t(item.name) }}</router-link>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, onUnmounted, reactive, toRefs } from 'vue'
|
||||
const nav = ref([
|
||||
{
|
||||
name: 'MainFooter.PrivacyPolicy',
|
||||
path: '/privacy-policy'
|
||||
},
|
||||
{
|
||||
name: 'MainFooter.TermsOfUse',
|
||||
path: '/terms-of-use'
|
||||
},
|
||||
{
|
||||
name: 'MainFooter.Disclaimer',
|
||||
path: '/disclaimer'
|
||||
},
|
||||
{
|
||||
name: 'MainFooter.SiteMap',
|
||||
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>
|
||||
@@ -1,55 +1,126 @@
|
||||
<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">
|
||||
{{ item.name }}
|
||||
<div class="nav-item" v-for="item in navList" :key="item.name">
|
||||
<down-menu :title="$t(item.name)" v-if="item.children">
|
||||
<router-link :to="child.path" v-for="child in item.children" :key="child.name">
|
||||
{{ $t(child.name) }}
|
||||
</router-link>
|
||||
</down-menu>
|
||||
<router-link class="link hover-bottom-animation" :to="item.path" v-else>{{
|
||||
$t(item.name)
|
||||
}}</router-link>
|
||||
</div>
|
||||
</div>
|
||||
<div class="right">
|
||||
<span>English</span>
|
||||
<span>My Account</span>
|
||||
<down-menu :title="langList.find((v) => v.value === locale)?.label || 'English'">
|
||||
<router-link
|
||||
class="link"
|
||||
:to="`/${item.value}${path}`"
|
||||
v-for="item in langList"
|
||||
:key="item.value"
|
||||
v-show="item.value !== locale"
|
||||
>
|
||||
{{ item.label }}
|
||||
</router-link>
|
||||
</down-menu>
|
||||
<router-link class="link" to="/my-account">
|
||||
<span class="iconfont icon-tubiao-"></span>
|
||||
<span v-if="token">{{ $t('MainHeader.MyAccount') }}</span>
|
||||
<span v-else>{{ $t('MainHeader.LoginOrSignin') }}</span>
|
||||
</router-link>
|
||||
</div>
|
||||
</header>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, onUnmounted } from "vue";
|
||||
import { useRouter } from "vue-router";
|
||||
const router = useRouter();
|
||||
import { ref, watch, computed } from 'vue'
|
||||
import DownMenu from './down-menu.vue'
|
||||
import { setLang, LangType } from '../lang'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
const { locale } = useI18n()
|
||||
import { useRoute } from 'vue-router'
|
||||
import { useUserInfoStore } from '@/stores/userInfo'
|
||||
const userInfoStore = useUserInfoStore()
|
||||
const token = computed(() => userInfoStore.state.token)
|
||||
const route = useRoute()
|
||||
const lang = computed(() => route.params.lang)
|
||||
const path = computed(() => route.path.replace(new RegExp(`^/${lang.value}/`), '/'))
|
||||
if (lang.value) setLang(lang.value)
|
||||
watch(lang, (newVal) => {
|
||||
if (lang.value) setLang(newVal)
|
||||
})
|
||||
const langList = ref([
|
||||
{
|
||||
label: 'English',
|
||||
value: LangType.en
|
||||
},
|
||||
{
|
||||
label: '简体中文',
|
||||
value: LangType.zhCn
|
||||
},
|
||||
{
|
||||
label: '繁體中文',
|
||||
value: LangType.zhTw
|
||||
}
|
||||
])
|
||||
|
||||
const navList = ref([
|
||||
{
|
||||
name: "Home",
|
||||
path: "/",
|
||||
name: 'MainHeader.Home',
|
||||
path: '/'
|
||||
},
|
||||
{
|
||||
name: "About Us",
|
||||
path: "/about",
|
||||
name: 'MainHeader.AboutUs',
|
||||
path: '/about-us'
|
||||
},
|
||||
{
|
||||
name: "Our Solutions",
|
||||
path: "/solutions",
|
||||
name: 'MainHeader.OurSolutions',
|
||||
path: '',
|
||||
children: [
|
||||
{
|
||||
name: 'MainHeader.AiDA31',
|
||||
path: '/aida'
|
||||
},
|
||||
{
|
||||
name: "Communities",
|
||||
path: "/communities",
|
||||
name: 'MainHeader.Mixi',
|
||||
path: '/mixi'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "Media",
|
||||
path: "/media",
|
||||
name: 'MainHeader.Communities',
|
||||
path: '',
|
||||
children: [
|
||||
{
|
||||
name: 'MainHeader.Events',
|
||||
path: '/events'
|
||||
},
|
||||
{
|
||||
name: "Contact Us",
|
||||
path: "/contact",
|
||||
name: 'MainHeader.UserStories',
|
||||
path: '/user-stories'
|
||||
},
|
||||
]);
|
||||
{
|
||||
name: 'MainHeader.HelpCentre',
|
||||
path: '/help-centre'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'MainHeader.Media',
|
||||
path: '/media'
|
||||
},
|
||||
{
|
||||
name: 'MainHeader.ContactUs',
|
||||
path: '/contact-us'
|
||||
}
|
||||
])
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
.main-header {
|
||||
position: fixed;
|
||||
width: 100%;
|
||||
height: 85px;
|
||||
height: var(--main-header-height, 85px);
|
||||
padding: 15px 30px;
|
||||
box-sizing: border-box;
|
||||
top: 0;
|
||||
@@ -65,16 +136,87 @@
|
||||
> .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;
|
||||
&:deep(> .down-menu > .title),
|
||||
> .link {
|
||||
margin: 0 14px;
|
||||
color: #fff;
|
||||
font-size: 16px;
|
||||
font-size: 14px;
|
||||
text-decoration: none;
|
||||
line-height: 37px;
|
||||
display: inline-block;
|
||||
}
|
||||
> .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;
|
||||
> .link {
|
||||
margin: 0 14px;
|
||||
color: #fff;
|
||||
font-size: 14px;
|
||||
text-decoration: none;
|
||||
line-height: 37px;
|
||||
display: inline-block;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
&:hover {
|
||||
opacity: 0.8;
|
||||
}
|
||||
> .iconfont {
|
||||
margin-right: 5px;
|
||||
font-size: 22px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
74
src/components/video-model.vue
Normal file
@@ -0,0 +1,74 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, onUnmounted, reactive, toRefs } from "vue";
|
||||
import MyEvent from "@/directives/myEvents";
|
||||
//const props = defineProps({
|
||||
//})
|
||||
//const emit = defineEmits([
|
||||
//])
|
||||
const isVideoShow = ref(false)
|
||||
const url = ref('')
|
||||
const poster = ref('')
|
||||
const playVideo = (data)=>{
|
||||
url.value = data.url || ''
|
||||
poster.value = data.poster || ''
|
||||
isVideoShow.value = true
|
||||
}
|
||||
const closeVideoModel = ()=>{
|
||||
url.value = ''
|
||||
poster.value = ''
|
||||
isVideoShow.value = false
|
||||
}
|
||||
onMounted(()=>{
|
||||
MyEvent.add("playVideo",playVideo);
|
||||
})
|
||||
onUnmounted(()=>{
|
||||
})
|
||||
defineExpose({})
|
||||
</script>
|
||||
<template>
|
||||
<div class="vide-model" v-if="isVideoShow">
|
||||
<div class="mask"></div>
|
||||
<video :src="url" autoplay controls preload="metadata" controlslist="nodownload" :poster="poster"></video>
|
||||
<div class="close-btn">
|
||||
<span class="iconfont icon-close" @click="closeVideoModel"></span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<style lang="less" scoped>
|
||||
.vide-model{
|
||||
z-index: 99999;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
> .mask{
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%,-50%);
|
||||
background-color: rgba(0,0,0,.8);
|
||||
}
|
||||
> video{
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
max-height: 80vh;
|
||||
max-width: 80vw;
|
||||
transform: translate(-50%,-50%);
|
||||
}
|
||||
> .close-btn{
|
||||
position: absolute;
|
||||
top: 20px;
|
||||
right: 20px;
|
||||
font-size: 30px;
|
||||
color: #fff;
|
||||
cursor: pointer;
|
||||
span{
|
||||
font-size: 30px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</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);
|
||||
});
|
||||
|
||||
39
src/directives/myEvents.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
|
||||
class MyEvent {
|
||||
private events: Map<string, Array<(data: any) => void>>;
|
||||
constructor() {
|
||||
// 使用 Object 或 Map 存储,实现 O(1) 级别的查找
|
||||
this.events = new Map()
|
||||
}
|
||||
add(name: string, call: (data: any) => void) {
|
||||
if (!this.events.has(name)) {
|
||||
this.events.set(name, [])
|
||||
}
|
||||
this.events.get(name)!.push(call)
|
||||
}
|
||||
remove(name: string, call?: (data: any) => void) {
|
||||
if (!this.events.has(name)) return
|
||||
|
||||
if (!call) {
|
||||
this.events.delete(name)
|
||||
} else {
|
||||
const callbacks = this.events.get(name)
|
||||
const index = callbacks.indexOf(call)
|
||||
if (index !== -1) {
|
||||
callbacks.splice(index, 1)
|
||||
}
|
||||
// 如果该事件没有监听者了,彻底清理 key
|
||||
if (callbacks.length === 0) {
|
||||
this.events.delete(name)
|
||||
}
|
||||
}
|
||||
}
|
||||
emit(name: string, data?: any) {
|
||||
const callbacks = this.events.get(name)
|
||||
if (callbacks) {
|
||||
// 使用 slice() 镜像一份副本,防止在执行回调过程中有 remove 操作导致索引错乱
|
||||
callbacks.slice().forEach((cb) => cb(data))
|
||||
}
|
||||
}
|
||||
}
|
||||
export default new MyEvent()
|
||||
@@ -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)
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
import { gsap, } from 'gsap'
|
||||
import { ScrollTrigger } from 'gsap/ScrollTrigger'
|
||||
export default {
|
||||
name: 'tween-animation',
|
||||
name: 'tween-Y',
|
||||
mounted(el: HTMLElement, binding: any) {
|
||||
const params = binding.value
|
||||
// if(!binding.value.isGsap)return
|
||||
let dom = document.querySelector('body')
|
||||
gsap.registerPlugin(ScrollTrigger);
|
||||
let tl1 = gsap.timeline();
|
||||
tl1.from(el,1, {y:'30px',opacity:0},)
|
||||
tl1.from(el,1, {y:params || '30px',opacity:0},)
|
||||
ScrollTrigger.create({
|
||||
trigger: el, // 触发器元素
|
||||
start: "top 90%", // 滚动触发器的起始滚动位置
|
||||
@@ -15,7 +16,8 @@ export default {
|
||||
markers: false, // 开启标注功能
|
||||
animation:tl1,
|
||||
scroller:dom,//设置指定元素为滚动依据
|
||||
scrub:2,
|
||||
scrub:false,
|
||||
// toggleActions: "play reset play reset",
|
||||
// onUpdate:(v)=>{
|
||||
// if(v.progress < 0.1){
|
||||
// v.trigger?.classList.remove('active')
|
||||
|
||||
24
src/lang/en.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
export default {
|
||||
MainHeader: {
|
||||
Home: 'Home',
|
||||
AboutUs: 'About Us',
|
||||
OurSolutions: 'Our Solutions',
|
||||
AiDA31: 'AiDA 3.1',
|
||||
Mixi: 'Mixi',
|
||||
Communities: 'Communities',
|
||||
Events: 'Events',
|
||||
UserStories: 'User Stories',
|
||||
HelpCentre: 'Help Centre',
|
||||
Media: 'Media',
|
||||
ContactUs: 'Contact Us',
|
||||
MyAccount: 'My Account',
|
||||
LoginOrSignin: 'Log in / Sign in',
|
||||
},
|
||||
MainFooter: {
|
||||
Copyright: '©{year} {name} Limited',
|
||||
PrivacyPolicy: 'Privacy Policy',
|
||||
TermsOfUse: 'Terms of Use',
|
||||
Disclaimer: 'Disclaimer',
|
||||
SiteMap: 'Site Map',
|
||||
}
|
||||
}
|
||||
40
src/lang/index.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
import { createI18n } from 'vue-i18n'
|
||||
|
||||
import enLocale from './en.ts'
|
||||
import zhCnLocale from './zh-cn.ts'
|
||||
import zhTwLocale from './zh-tw.ts'
|
||||
|
||||
export const LangType = {
|
||||
en: "en", // 英文
|
||||
zhTw: "zh-tw", // 繁体中文
|
||||
zhCn: "zh-cn", // 简体中文
|
||||
}
|
||||
export const LANGS = Object.values(LangType)
|
||||
|
||||
// 语言配置整合
|
||||
const messages = {
|
||||
[LangType.en]: enLocale,
|
||||
[LangType.zhCn]: zhCnLocale,
|
||||
[LangType.zhTw]: zhTwLocale,
|
||||
}
|
||||
const localeLang = localStorage.getItem('language')
|
||||
const defaultLocale = checkLocale(localeLang) ? localeLang! : LangType.en
|
||||
// 创建 i18n
|
||||
const i18n = createI18n({
|
||||
legacy: false,
|
||||
globalInjection: true, // 全局模式,可以直接使用 $t
|
||||
locale: defaultLocale,
|
||||
messages: messages
|
||||
})
|
||||
export default i18n
|
||||
// 检查语言是否存在
|
||||
function checkLocale(lang: any) {
|
||||
return LANGS.includes(lang)
|
||||
}
|
||||
export const setLang = (lang: any) => {
|
||||
if (lang === "") lang = LangType.en
|
||||
if (!checkLocale(lang)) return false
|
||||
i18n.global.locale.value = lang
|
||||
localStorage.setItem('language', lang)
|
||||
return true
|
||||
}
|
||||
24
src/lang/zh-cn.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
export default {
|
||||
MainHeader: {
|
||||
Home: '首页',
|
||||
AboutUs: '关于我们',
|
||||
OurSolutions: '我们的产品',
|
||||
AiDA31: 'AiDA 3.1',
|
||||
Mixi: 'Mixi',
|
||||
Communities: '社区',
|
||||
Events: '活动',
|
||||
UserStories: '使用者故事',
|
||||
HelpCentre: '支援中心',
|
||||
Media: '媒体报道',
|
||||
ContactUs: '联系我们',
|
||||
MyAccount: '我的账户',
|
||||
LoginOrSignin: '登入 / 注册',
|
||||
},
|
||||
MainFooter: {
|
||||
Copyright: '©{year} {name} 有限公司',
|
||||
PrivacyPolicy: '隐私政策',
|
||||
TermsOfUse: '使用条款',
|
||||
Disclaimer: '免责声明',
|
||||
SiteMap: '网站地图',
|
||||
}
|
||||
}
|
||||
24
src/lang/zh-tw.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
export default {
|
||||
MainHeader: {
|
||||
Home: '首頁',
|
||||
AboutUs: '關於我們',
|
||||
OurSolutions: '我們的產品',
|
||||
AiDA31: 'AiDA 3.1',
|
||||
Mixi: 'Mixi',
|
||||
Communities: '社區',
|
||||
Events: '活動',
|
||||
UserStories: '使用者故事',
|
||||
HelpCentre: '支援中心',
|
||||
Media: '媒體報導',
|
||||
ContactUs: '聯絡我們',
|
||||
MyAccount: '我的帳號',
|
||||
LoginOrSignin: '登錄 / 登冊',
|
||||
},
|
||||
MainFooter: {
|
||||
Copyright: '©{year} {name} 有限公司',
|
||||
PrivacyPolicy: '私隱政策',
|
||||
TermsOfUse: '使用條款',
|
||||
Disclaimer: '免責聲明',
|
||||
SiteMap: '網站地圖',
|
||||
}
|
||||
}
|
||||
@@ -1,15 +1,20 @@
|
||||
import { ViteSSG } from 'vite-ssg'
|
||||
import App from './App.vue'
|
||||
import { routes } from './routes'
|
||||
import './style.css'
|
||||
// import './style.css'
|
||||
import '@/assets/css/style.less'
|
||||
import directives from './directives/index'
|
||||
import i18n from './lang/index'
|
||||
import store from './stores/index'
|
||||
|
||||
export const createApp = ViteSSG(App, {
|
||||
routes,
|
||||
base: import.meta.env.BASE_URL,
|
||||
},
|
||||
({ app, router, routes, isClient, initialState }) => {
|
||||
({ app }) => {
|
||||
// 注册全局指令
|
||||
app.use(directives)
|
||||
app.use(store)
|
||||
app.use(i18n)
|
||||
}
|
||||
)
|
||||
@@ -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>
|
||||
97
src/pages/about-us/ecosystem.vue
Normal file
@@ -0,0 +1,97 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from "vue";
|
||||
import MyEvent from "@/directives/myEvents";
|
||||
let videoUrl = 'https://code-create.com.hk/wp-content/uploads/2023/05/codec_brand_vid_16x9_ENG.mp4'
|
||||
let videoPosterUrl = 'https://code-create.com.hk/wp-content/uploads/revslider/video-media/codec_brand_vid_16x9_ENG_11_layer.jpeg'
|
||||
const playVideo = ()=>{
|
||||
MyEvent.emit("playVideo",{
|
||||
url: videoUrl,
|
||||
poster: videoPosterUrl,
|
||||
});
|
||||
}
|
||||
|
||||
defineExpose({})
|
||||
</script>
|
||||
<template>
|
||||
<section class="ecosystem">
|
||||
<div class="content">
|
||||
<img src="https://code-create.com.hk/wp-content/uploads/2022/11/home_ai_logo.png" alt="">
|
||||
<h2>REVITALISE THE FASHION ECOSYSTEM</h2>
|
||||
<h4>through Artificial Intelligence (AI)</h4>
|
||||
</div>
|
||||
</section>
|
||||
<section class="ecosystem-video">
|
||||
<div class="content">
|
||||
<img src="https://code-create.com.hk/wp-content/uploads/revslider/video-media/codec_brand_vid_16x9_ENG_11_layer.jpeg" alt="">
|
||||
<span class="iconfont icon-bofang" @click="playVideo"></span>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
<style lang="less" scoped>
|
||||
.ecosystem{
|
||||
width: 100%;
|
||||
background-color: #fff;
|
||||
> .content{
|
||||
margin: 0 auto;
|
||||
max-width: 1200px;
|
||||
padding: 100px 0px 100px 0px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
> img{
|
||||
width: 300px;
|
||||
height: 300px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
> h2{
|
||||
margin-bottom: 20px;
|
||||
font-size: 24px;
|
||||
}
|
||||
> h4{
|
||||
color: #333333;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
.ecosystem-video{
|
||||
width: 100%;
|
||||
background-color: #463a37;
|
||||
> .content{
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 100px 0;
|
||||
position: relative;
|
||||
> img{
|
||||
width: 100%;
|
||||
height: 675px;
|
||||
}
|
||||
> .icon-bofang{
|
||||
font-size: 100px;
|
||||
color: #fff;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%,-50%);
|
||||
animation: identifier 2s ease-in-out infinite;
|
||||
// box-shadow: 0 0 10px #fff;
|
||||
transition: .3s all;
|
||||
cursor: pointer;
|
||||
@keyframes identifier {
|
||||
0% {
|
||||
transform: translate(-50%,-50%) scale(1);
|
||||
filter: drop-shadow(0px 0px 8px rgba(255, 255, 255, 1));
|
||||
}
|
||||
50% {
|
||||
transform: translate(-50%,-50%) scale(.95);
|
||||
filter: drop-shadow(0px 0px 0px rgba(255, 255, 255, 1));
|
||||
}
|
||||
100% {
|
||||
transform: translate(-50%,-50%) scale(1);
|
||||
filter: drop-shadow(0px 0px 8px rgba(255, 255, 255, 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,30 +1,36 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, onUnmounted, reactive, toRefs } from "vue";
|
||||
import Video from './video.vue'
|
||||
import Product from './product.vue'
|
||||
//const props = defineProps({
|
||||
//})
|
||||
//const emit = defineEmits([
|
||||
//])
|
||||
let data = reactive({
|
||||
})
|
||||
onMounted(()=>{
|
||||
})
|
||||
onUnmounted(()=>{
|
||||
})
|
||||
import Ecosystem from './ecosystem.vue'
|
||||
import Title from './title.vue'
|
||||
import Mission from './mission.vue'
|
||||
import OurTeam from './our-team.vue'
|
||||
import StrategicPartners from './strategic-partners.vue'
|
||||
defineExpose({})
|
||||
const {} = toRefs(data);
|
||||
</script>
|
||||
<template>
|
||||
<div class="home">
|
||||
<Video />
|
||||
<Product />
|
||||
<div class="about-us">
|
||||
<div class="bg">
|
||||
<img src="https://code-create.com.hk/wp-content/uploads/2022/12/about_banner-1.jpg" alt="">
|
||||
</div>
|
||||
<Title />
|
||||
<Ecosystem />
|
||||
<Mission />
|
||||
<OurTeam />
|
||||
<StrategicPartners />
|
||||
</div>
|
||||
</template>
|
||||
<style lang="less" scoped>
|
||||
.home{
|
||||
.about-us{
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
> .bg{
|
||||
width: 100%;
|
||||
position: fixed;
|
||||
z-index: -1;
|
||||
top: 0;
|
||||
> img{
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
148
src/pages/about-us/mission.vue
Normal file
@@ -0,0 +1,148 @@
|
||||
<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([
|
||||
//])
|
||||
const imgItem1 = ref(null)
|
||||
const imgItem2 = ref(null)
|
||||
const imgItem3 = ref(null)
|
||||
const imgItem4 = ref(null)
|
||||
const imgBox = ref(null)
|
||||
let data = reactive({
|
||||
})
|
||||
onMounted(()=>{
|
||||
let dom = document.querySelector('body')
|
||||
gsap.registerPlugin(ScrollTrigger);
|
||||
let tl1 = gsap.timeline();
|
||||
tl1.from(imgItem1.value,1, {y:'200px',opacity:0,ease:'power2.out'},0)
|
||||
tl1.from(imgItem2.value,1, {y:'200px',opacity:0,ease:'power2.out'},0.2)
|
||||
tl1.from(imgItem3.value,1, {y:'200px',opacity:0,ease:'power2.out'},0.4)
|
||||
tl1.from(imgItem4.value,1, {y:'200px',opacity:0,ease:'power2.out'},0.6)
|
||||
ScrollTrigger.create({
|
||||
trigger: imgBox.value, // 触发器元素
|
||||
start: "top 90%", // 滚动触发器的起始滚动位置
|
||||
end: '100% 80%', // 滚动触发器的结束滚动位置
|
||||
markers: false, // 开启标注功能
|
||||
animation:tl1,
|
||||
scroller:dom,//设置指定元素为滚动依据
|
||||
// toggleActions: "play reset play reset",
|
||||
scrub:false,
|
||||
});
|
||||
})
|
||||
onUnmounted(()=>{
|
||||
})
|
||||
defineExpose({})
|
||||
const {} = toRefs(data);
|
||||
</script>
|
||||
<template>
|
||||
<section class="mission">
|
||||
<div class="content">
|
||||
<h2>Mission</h2>
|
||||
<p>To be the world leading innovative leader in offering the state-of-the-art AI based solutions and systems to disrupt the fashion industry</p>
|
||||
</div>
|
||||
</section>
|
||||
<section class="mission-focus-area">
|
||||
<div class="content">
|
||||
<h2>Focus Area</h2>
|
||||
<div class="img-box" ref="imgBox">
|
||||
<div class="img-item" ref="imgItem1">
|
||||
<img src="https://code-create.com.hk/wp-content/uploads/2022/11/about_focus_01.png" alt="">
|
||||
<h2>Elevate Operation Efficiency</h2>
|
||||
</div>
|
||||
<div class="img-item" ref="imgItem2">
|
||||
<img src="https://code-create.com.hk/wp-content/uploads/2022/11/about_focus_02-1-600x888.png" alt="">
|
||||
<h2>Vertical Design Platform</h2>
|
||||
</div>
|
||||
<div class="img-item" ref="imgItem3">
|
||||
<img src="https://code-create.com.hk/wp-content/uploads/2022/11/about_focus_03-1-600x887.png" alt="">
|
||||
<h2>Education</h2>
|
||||
</div>
|
||||
<div class="img-item" ref="imgItem4">
|
||||
<img src="https://code-create.com.hk/wp-content/uploads/2022/11/about_focus_04-1-600x888.png" alt="">
|
||||
<h2>Community</h2>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
<style lang="less" scoped>
|
||||
.mission{
|
||||
width: 100%;
|
||||
background-color: #eeeeee;
|
||||
> .content{
|
||||
margin: 0 auto;
|
||||
max-width: 730px;
|
||||
padding: 200px 0px 200px 0px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
> h2{
|
||||
margin-bottom: 30px;
|
||||
color: #000000;
|
||||
font-size: 40px;
|
||||
font-weight: 600;
|
||||
letter-spacing: 1px;
|
||||
font-family: "Poppins", Sans-serif;
|
||||
}
|
||||
> p{
|
||||
color: #555;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
.mission-focus-area{
|
||||
width: 100%;
|
||||
background-color: #f9f9f9;
|
||||
> .content{
|
||||
max-width: 1440px;
|
||||
margin: 0 auto;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
> h2{
|
||||
font-family: "Poppins", Sans-serif;
|
||||
font-size: 40px;
|
||||
font-weight: 600;
|
||||
letter-spacing: 1px;
|
||||
color: #000000;
|
||||
margin: 100px auto;
|
||||
}
|
||||
> .img-box{
|
||||
padding: 10px;
|
||||
margin: 0 auto;
|
||||
margin-bottom: 20px;
|
||||
position: relative;
|
||||
display: flex;
|
||||
> .img-item{
|
||||
margin: 10px;
|
||||
// width: 1120px;
|
||||
width: 340px;
|
||||
height: 503px;
|
||||
position: relative;
|
||||
> h2{
|
||||
font-family: "Poppins", Sans-serif;
|
||||
font-weight: 600;
|
||||
line-height: 1.3em;
|
||||
letter-spacing: 2px;
|
||||
color: #FFFFFF;
|
||||
width: 320px;
|
||||
position: absolute;
|
||||
bottom: 25%;
|
||||
text-align: center;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
> img{
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
204
src/pages/about-us/our-team.vue
Normal file
@@ -0,0 +1,204 @@
|
||||
<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([
|
||||
//])
|
||||
const imgItem1 = ref(null)
|
||||
let data = reactive({
|
||||
})
|
||||
onMounted(()=>{
|
||||
let dom = document.querySelector('body')
|
||||
let ourListDom = document.querySelectorAll('.our-team-item')
|
||||
ourListDom.forEach((item,index)=>{
|
||||
gsap.registerPlugin(ScrollTrigger);
|
||||
let tl1 = gsap.timeline();
|
||||
let img = item.querySelector('.img')
|
||||
let textTop = item.querySelector('.text p')
|
||||
let textBottom = item.querySelector('.text .bottom-text')
|
||||
tl1.from(img,1, {opacity:'0',ease:'power2.out'},0)
|
||||
.from(textTop,1, {y:'20px',opacity:'0',ease:'power2.out'},0)
|
||||
.from(textBottom,1, {y:'-20px',opacity:'0',ease:'power2.out'},0)
|
||||
ScrollTrigger.create({
|
||||
trigger: img, // 触发器元素
|
||||
start: "top 90%", // 滚动触发器的起始滚动位置
|
||||
end: '100% 80%', // 滚动触发器的结束滚动位置
|
||||
markers: false, // 开启标注功能
|
||||
animation:tl1,
|
||||
scroller:dom,//设置指定元素为滚动依据
|
||||
// toggleActions: "play reset play reset",
|
||||
scrub:false,
|
||||
});
|
||||
})
|
||||
|
||||
})
|
||||
onUnmounted(()=>{
|
||||
})
|
||||
defineExpose({})
|
||||
const {} = toRefs(data);
|
||||
</script>
|
||||
<template>
|
||||
<section class="our-team">
|
||||
<div class="content">
|
||||
<h2>Our Team</h2>
|
||||
</div>
|
||||
</section>
|
||||
<section class="our-team-item bg1">
|
||||
<div class="content">
|
||||
<div class="text">
|
||||
<p>
|
||||
Prof Wong is Cheng Yik Hung Professor in Fashion of School of Fashion and Textiles at The Hong Kong Polytechnic University and currently serving as the Chief Executive Officer and Centre Director of
|
||||
<a href="https://www.aidlab.hk/">Laboratory for Artificial Intelligence in Design (AiDLab)</a>.
|
||||
He is one of the inventors of the technologies to be commercialized in the start-up.
|
||||
</p>
|
||||
<div class="bottom-text" ref="bottomText">
|
||||
<h4 class="name">Prof Calvin WONG</h4>
|
||||
<h4 class="position">Co-Founder and Technical Advisor</h4>
|
||||
</div>
|
||||
</div>
|
||||
<div class="img">
|
||||
<img src="https://code-create.com.hk/wp-content/uploads/2022/11/about_team_01-600x887.png" alt="">
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<section class="our-team-item bg2">
|
||||
<div class="content">
|
||||
<div class="img">
|
||||
<img src="https://code-create.com.hk/wp-content/uploads/2022/11/about_team_02-600x887.png" alt="">
|
||||
</div>
|
||||
<div class="text">
|
||||
<p>
|
||||
Ms Wong is an accomplished C-level business executive with extensive experience in the fashion and luxury goods industries, building business across China and APAC and leading operations for renowned fashion houses such as DFS USA, Lane Crawford, Burberry Asia, Brunello Cucinelli and Versace Asia Pacific.
|
||||
</p>
|
||||
<div class="bottom-text" ref="bottomText">
|
||||
<h4 class="name">Ms Kim WONG</h4>
|
||||
<h4 class="position">Co-Founder and Chief Executive Officer</h4>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<section class="our-team-item bg1">
|
||||
<div class="content">
|
||||
<div class="text">
|
||||
<p>
|
||||
Dr Zou is currently serving as Assistant Professor at School of Fashion and Textiles at The Hong Kong Polytechnic University. She is one of the inventors of the technologies to be commercialized in the start-up.
|
||||
</p>
|
||||
<div class="bottom-text" ref="bottomText">
|
||||
<h4 class="name">Dr Aemika ZOU</h4>
|
||||
<h4 class="position">Technical Advisor</h4>
|
||||
</div>
|
||||
</div>
|
||||
<div class="img">
|
||||
<img src="https://code-create.com.hk/wp-content/uploads/2022/11/about_team_03-600x887.png" alt="">
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<section class="our-team-item bg2">
|
||||
<div class="content">
|
||||
<div class="img">
|
||||
<img src="https://code-create.com.hk/wp-content/uploads/2022/11/about_team_04-600x887.png" alt="">
|
||||
</div>
|
||||
<div class="text">
|
||||
<p>
|
||||
Giovanni is currently the Chief Executive Officer Asia of Salvatore Ferragamo.
|
||||
</p>
|
||||
<div class="bottom-text" ref="bottomText">
|
||||
<h4 class="name">Mr Giovanni DI SALVO</h4>
|
||||
<h4 class="position">Advisory Member</h4>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
<style lang="less" scoped>
|
||||
.our-team{
|
||||
width: 100%;
|
||||
background-color: #f9f9f9;
|
||||
> .content{
|
||||
margin: 0 auto;
|
||||
max-width: 730px;
|
||||
padding: 100px 0px 100px 0px;
|
||||
> h2{
|
||||
text-align: center;
|
||||
margin-bottom: 30px;
|
||||
color: #000000;
|
||||
font-size: 40px;
|
||||
font-weight: 600;
|
||||
letter-spacing: 1px;
|
||||
font-family: "Poppins", Sans-serif;
|
||||
}
|
||||
}
|
||||
}
|
||||
.our-team-item{
|
||||
width: 100%;
|
||||
&.bg1{
|
||||
background-color: #eeeeee;
|
||||
.img{
|
||||
text-align: right;
|
||||
}
|
||||
}
|
||||
&.bg2{
|
||||
background-color: #ffffff;
|
||||
.img{
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
> .content{
|
||||
padding: 100px 0;
|
||||
max-width: 960px;
|
||||
margin: 0 auto;
|
||||
display: flex;
|
||||
> div{
|
||||
width: 50%;
|
||||
}
|
||||
> .text{
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
> p{
|
||||
font-family: "Poppins", Sans-serif;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
line-height: 1.6em;
|
||||
color: #000000;
|
||||
margin-bottom: 20px;
|
||||
> a{
|
||||
color: inherit;
|
||||
font-size: inherit;
|
||||
font-family: inherit;
|
||||
line-height: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
> .bottom-text{
|
||||
> .name{
|
||||
font-family: "Poppins", Sans-serif;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #000000;
|
||||
line-height: 1;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
> .position{
|
||||
font-family: "Poppins", Sans-serif;
|
||||
font-size: 16px;
|
||||
font-weight: 400;
|
||||
color: #000000;
|
||||
line-height: 1;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
> .img{
|
||||
> img{
|
||||
width: 100%;
|
||||
height: 492px;
|
||||
object-fit: cover;
|
||||
max-width: 330px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,30 +0,0 @@
|
||||
<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="product">
|
||||
<br v-for="item in 50" />
|
||||
<div class="aa" >1231312</div>
|
||||
<div class="aa" v-tween-animation>1231312</div>
|
||||
<br v-for="item in 10" />
|
||||
</section>
|
||||
</template>
|
||||
<style lang="less" scoped>
|
||||
.product{
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
78
src/pages/about-us/strategic-partners.vue
Normal file
@@ -0,0 +1,78 @@
|
||||
<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([
|
||||
//])
|
||||
const imgItem1 = ref(null)
|
||||
let data = reactive({
|
||||
})
|
||||
onMounted(()=>{
|
||||
|
||||
})
|
||||
onUnmounted(()=>{
|
||||
})
|
||||
defineExpose({})
|
||||
const {} = toRefs(data);
|
||||
</script>
|
||||
<template>
|
||||
<section class="strategic-partners">
|
||||
<div class="content">
|
||||
<h2 v-tween-Y="'30px'">Strategic Partners</h2>
|
||||
<div class="img-box" v-tween-Y="'30px'">
|
||||
<a href="https://www.aidlab.hk/">
|
||||
<img src="https://code-create.com.hk/wp-content/uploads/2022/11/about_partners_01.png" alt="">
|
||||
</a>
|
||||
<a href="https://www.polyu.edu.hk/">
|
||||
<img src="https://code-create.com.hk/wp-content/uploads/2022/11/about_partners_02-1.png" alt="">
|
||||
</a>
|
||||
<a href="https://www.rca.ac.uk/">
|
||||
<img src="https://code-create.com.hk/wp-content/uploads/2022/11/about_partners_03-300x145.png" alt="">
|
||||
</a>
|
||||
<img src="https://code-create.com.hk/wp-content/uploads/2025/05/Eschange-600x130.jpg" alt="">
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
<style lang="less" scoped>
|
||||
.strategic-partners{
|
||||
width: 100%;
|
||||
background-color: #fff;
|
||||
> .content{
|
||||
margin: 0 auto;
|
||||
max-width: 1440px;
|
||||
padding: 200px 0px 200px 0px;
|
||||
> h2{
|
||||
font-family: "Poppins", Sans-serif;
|
||||
font-size: 40px;
|
||||
font-weight: 600;
|
||||
letter-spacing: 1px;
|
||||
color: #000000;
|
||||
margin-bottom: 30px;
|
||||
text-align: center;
|
||||
}
|
||||
> .img-box{
|
||||
width: 1110px;
|
||||
margin: 0 auto;
|
||||
margin-top: 40px;
|
||||
display: flex;
|
||||
height: 100px;
|
||||
> a{
|
||||
width: 25%;
|
||||
> img{
|
||||
object-fit: contain;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
> img{
|
||||
width: 25%;
|
||||
object-fit: contain;
|
||||
padding: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
31
src/pages/about-us/title.vue
Normal file
@@ -0,0 +1,31 @@
|
||||
<script setup lang="ts">
|
||||
defineExpose({})
|
||||
</script>
|
||||
<template>
|
||||
<section class="title-section">
|
||||
<div class="content">
|
||||
<div class="text">
|
||||
<h1>ABOUT US</h1>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
<style lang="less" scoped>
|
||||
.title-section{
|
||||
width: 100%;
|
||||
> .content{
|
||||
max-width: 1400;
|
||||
> .text{
|
||||
padding: 200px 300px;
|
||||
> h1{
|
||||
text-align: center;
|
||||
font-size: 64px;
|
||||
font-weight: 600;
|
||||
line-height: 64px;
|
||||
letter-spacing: 2px;
|
||||
color: #FFFFFF;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,25 +0,0 @@
|
||||
<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>
|
||||
<section class="video">
|
||||
<div class="title">
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
<style lang="less" scoped>
|
||||
.video{
|
||||
}
|
||||
</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>
|
||||
@@ -1,13 +1,53 @@
|
||||
<template>
|
||||
<section class="carousel-container" aria-label="Featured content">
|
||||
<section
|
||||
class="carousel-container"
|
||||
aria-label="Featured content"
|
||||
@mouseenter="pauseAutoplay"
|
||||
@mouseleave="resumeAutoplay"
|
||||
>
|
||||
<KagolCarousel
|
||||
v-model="activePage"
|
||||
class="home-carousel"
|
||||
:autoplay="isAutoplayEnabled"
|
||||
:interval="5000"
|
||||
:autoplay="false"
|
||||
:interval="1000"
|
||||
>
|
||||
<article v-for="slide in slides" :key="slide.id" class="carousel-slide">
|
||||
<img class="carousel-image" :src="slide.image" :alt="slide.alt" />
|
||||
<article
|
||||
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>
|
||||
|
||||
<template #pagination="{ prevPage, nextPage }">
|
||||
@@ -43,58 +83,290 @@
|
||||
<script setup lang="ts">
|
||||
import { Carousel as KagolCarousel } from '@kagol/vue-carousel'
|
||||
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 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 isAutoplayEnabled = shallowRef(false)
|
||||
const slides = [
|
||||
const slideEls = useTemplateRef<HTMLElement[]>('slideEls')
|
||||
const slides: readonly HomeSlide[] = [
|
||||
{
|
||||
id: 'aida',
|
||||
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',
|
||||
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'
|
||||
}
|
||||
]
|
||||
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
|
||||
}
|
||||
] as const
|
||||
|
||||
onMounted(() => {
|
||||
isAutoplayEnabled.value = true
|
||||
resumeAutoplay()
|
||||
queueDescAnimation()
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
if (descAnimationFrame) {
|
||||
window.cancelAnimationFrame(descAnimationFrame)
|
||||
}
|
||||
|
||||
descTimeline?.kill()
|
||||
})
|
||||
|
||||
watch(activePage, queueDescAnimation)
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
<style scoped lang="less">
|
||||
.carousel-container {
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
background: #070b14;
|
||||
}
|
||||
|
||||
.home-carousel {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.carousel-slide {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
aspect-ratio: 16 / 9;
|
||||
min-height: 360px;
|
||||
max-height: 680px;
|
||||
height: 960px;
|
||||
overflow: hidden;
|
||||
background: #070b14;
|
||||
.mask {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: rgba(0, 0, 0, 0.35);
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.carousel-image {
|
||||
.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;
|
||||
}
|
||||
|
||||
.desc-index-group {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
.desc-line {
|
||||
position: absolute;
|
||||
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;
|
||||
@@ -108,11 +380,6 @@
|
||||
transition: transform 0.28s ease;
|
||||
will-change: transform;
|
||||
}
|
||||
|
||||
.carousel-container:hover .carousel-pagination {
|
||||
transform: translateX(0);
|
||||
}
|
||||
|
||||
.carousel-pagination-button {
|
||||
position: relative;
|
||||
display: flex;
|
||||
@@ -159,6 +426,11 @@
|
||||
.carousel-pagination-arrow-next {
|
||||
transform: translate(-50%, -50%) rotate(45deg);
|
||||
}
|
||||
}
|
||||
|
||||
.carousel-container:hover .carousel-pagination {
|
||||
transform: translateX(0);
|
||||
}
|
||||
|
||||
:deep(.xui-carousel) {
|
||||
width: 100%;
|
||||
@@ -173,6 +445,29 @@
|
||||
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 {
|
||||
width: 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,29 +1,33 @@
|
||||
<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 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' },
|
||||
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({
|
||||
@@ -31,65 +35,147 @@ useHead({
|
||||
meta: [
|
||||
{
|
||||
name: 'description',
|
||||
content: 'Code Create home page rendered with Vite SSG.',
|
||||
},
|
||||
],
|
||||
content:
|
||||
'Code Create revitalises the fashion ecosystem through artificial intelligence.'
|
||||
}
|
||||
]
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
<template>
|
||||
<main class="home-page">
|
||||
<HomeCarousel />
|
||||
|
||||
<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>
|
||||
|
||||
<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>
|
||||
|
||||
<ProjectCta />
|
||||
</main>
|
||||
</template>
|
||||
|
||||
<style scoped lang="less">
|
||||
.home-page {
|
||||
/* width: 100%; */
|
||||
/* min-height: 100svh; */
|
||||
/* background: #ffffff; */
|
||||
}
|
||||
|
||||
.home-links {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
gap: 12px;
|
||||
padding: 32px 20px 48px;
|
||||
}
|
||||
|
||||
.home-link {
|
||||
min-width: 112px;
|
||||
padding: 10px 16px;
|
||||
border: 1px solid #d9dde6;
|
||||
border-radius: 6px;
|
||||
color: #1d2430;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
background: #ffffff;
|
||||
font-size: 15px;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.home-content {
|
||||
padding: clamp(116px, 10vw, 172px) 0 clamp(92px, 8vw, 126px);
|
||||
background: #ffffff;
|
||||
}
|
||||
|
||||
.ecosystem-intro {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 0 20px clamp(92px, 10vw, 150px);
|
||||
text-align: center;
|
||||
text-decoration: none;
|
||||
box-sizing: border-box;
|
||||
transition:
|
||||
border-color 0.2s,
|
||||
color 0.2s,
|
||||
box-shadow 0.2s;
|
||||
}
|
||||
|
||||
.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);
|
||||
.ecosystem-logo {
|
||||
display: block;
|
||||
width: clamp(146px, 15vw, 184px);
|
||||
height: auto;
|
||||
margin-bottom: 30px;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.home-link:focus-visible {
|
||||
outline: 2px solid #2f6df6;
|
||||
outline-offset: 2px;
|
||||
.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-links {
|
||||
padding: 24px 16px 40px;
|
||||
.home-content {
|
||||
padding: 72px 0 36px;
|
||||
}
|
||||
|
||||
.home-link {
|
||||
flex: 1 1 calc(50% - 12px);
|
||||
.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>
|
||||
|
||||
115
src/pages/my-account/account-info.vue
Normal file
@@ -0,0 +1,115 @@
|
||||
<template>
|
||||
<div class="account-info">
|
||||
<div class="left">
|
||||
<div class="info">
|
||||
<img alt="" src="" class="avatar" />
|
||||
<div class="name">12312</div>
|
||||
<div class="email">12312@example.com</div>
|
||||
</div>
|
||||
<router-link
|
||||
class="link"
|
||||
v-for="(v, i) in navs"
|
||||
:key="i"
|
||||
:to="v.path"
|
||||
@click="typeof v.onClick === 'function' && v.onClick()"
|
||||
>
|
||||
<span class="iconfont" :class="v.icon"></span>
|
||||
<span class="label">{{ v.label }}</span>
|
||||
</router-link>
|
||||
</div>
|
||||
<div class="content">
|
||||
<router-view></router-view>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { computed, ref } from 'vue'
|
||||
import { useUserInfoStore } from '@/stores/userInfo'
|
||||
const userInfoStore = useUserInfoStore()
|
||||
const navs = ref([
|
||||
{
|
||||
path: '/my-account',
|
||||
label: 'Dashboard',
|
||||
icon: 'icon-dashboard'
|
||||
},
|
||||
{
|
||||
path: '/my-account/orders',
|
||||
label: 'Orders',
|
||||
icon: 'icon-orders'
|
||||
},
|
||||
{
|
||||
path: '/my-account/subscriptions',
|
||||
label: 'Subscriptions',
|
||||
icon: 'icon-subscriptions'
|
||||
},
|
||||
{
|
||||
path: '/my-account/address',
|
||||
label: 'Address',
|
||||
icon: 'icon-address'
|
||||
},
|
||||
{
|
||||
path: '/my-account/methods',
|
||||
label: 'Payment methods',
|
||||
icon: 'icon-duidiaojiaohuanduihuan'
|
||||
},
|
||||
{
|
||||
path: '/my-account/details',
|
||||
label: 'Account details',
|
||||
icon: 'icon-tubiao-'
|
||||
},
|
||||
{
|
||||
path: '/my-account',
|
||||
label: 'Log out',
|
||||
icon: 'icon-tuichu',
|
||||
onClick: () => {
|
||||
console.log('logout')
|
||||
userInfoStore.setToken('')
|
||||
}
|
||||
}
|
||||
])
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.account-info {
|
||||
display: flex;
|
||||
> .left {
|
||||
width: 270px;
|
||||
border-right: 1px solid #e1e1e1;
|
||||
margin-right: 30px;
|
||||
> .info {
|
||||
margin-bottom: 30px;
|
||||
> .avatar {
|
||||
width: 86px;
|
||||
height: 86px;
|
||||
margin-bottom: 15px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
> .name {
|
||||
color: #222;
|
||||
font-size: 16px;
|
||||
}
|
||||
> .email {
|
||||
color: #333;
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
> .link {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
color: #222;
|
||||
font-size: 16px;
|
||||
text-decoration: none;
|
||||
padding: 7px 0;
|
||||
line-height: 1.6;
|
||||
> .iconfont {
|
||||
font-size: 20px;
|
||||
margin-right: 5px;
|
||||
}
|
||||
}
|
||||
}
|
||||
> .content {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
107
src/pages/my-account/css/style.css
Normal file
@@ -0,0 +1,107 @@
|
||||
.login,
|
||||
.register {
|
||||
padding: 35px;
|
||||
border: 1px solid #e1e1e1;
|
||||
background-color: #fff;
|
||||
border-radius: 5px;
|
||||
}
|
||||
.login > h2,
|
||||
.register > h2 {
|
||||
font-weight: 600;
|
||||
color: #222;
|
||||
font-size: 24px;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
.login > form > *,
|
||||
.register > form > * {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.login > form > .form-item,
|
||||
.register > form > .form-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.login > form > .form-item > label,
|
||||
.register > form > .form-item > label {
|
||||
margin-bottom: 5px;
|
||||
font-size: 16px;
|
||||
line-height: 1.6;
|
||||
color: #333;
|
||||
font-weight: 400;
|
||||
}
|
||||
.login > form > .form-item > label::after,
|
||||
.register > form > .form-item > label::after {
|
||||
content: ' *';
|
||||
color: #f00;
|
||||
}
|
||||
.login > form > .form-item > .input-container > input,
|
||||
.register > form > .form-item > .input-container > input,
|
||||
.login > form > .form-item > input,
|
||||
.register > form > .form-item > input {
|
||||
width: 100%;
|
||||
height: 35px;
|
||||
padding: 0 17px;
|
||||
font-size: 16px;
|
||||
border: 1px solid #e1e1e1;
|
||||
outline: none;
|
||||
}
|
||||
.login > form > .form-item > .input-container > input:focus,
|
||||
.register > form > .form-item > .input-container > input:focus,
|
||||
.login > form > .form-item > input:focus,
|
||||
.register > form > .form-item > input:focus {
|
||||
border-color: #000;
|
||||
}
|
||||
.login > form > .form-item > .input-container,
|
||||
.register > form > .form-item > .input-container {
|
||||
position: relative;
|
||||
}
|
||||
.login > form > .form-item > .input-container > input,
|
||||
.register > form > .form-item > .input-container > input {
|
||||
padding-right: 35px;
|
||||
}
|
||||
.login > form > .form-item > .input-container > .iconfont,
|
||||
.register > form > .form-item > .input-container > .iconfont {
|
||||
position: absolute;
|
||||
right: 10px;
|
||||
top: 0;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 18px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.login > form > .remember,
|
||||
.register > form > .remember {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.login > form > .remember > input,
|
||||
.register > form > .remember > input {
|
||||
margin: 4px 8px 0 0;
|
||||
}
|
||||
.login > form > .remember > label,
|
||||
.register > form > .remember > label {
|
||||
color: #333;
|
||||
}
|
||||
.login > form > .remember > .link,
|
||||
.register > form > .remember > .link {
|
||||
margin-left: auto;
|
||||
color: #222;
|
||||
}
|
||||
.login > form > .subscribe,
|
||||
.register > form > .subscribe {
|
||||
font-size: 16px;
|
||||
line-height: 1.6;
|
||||
color: #333;
|
||||
}
|
||||
.login > form > .tip,
|
||||
.register > form > .tip {
|
||||
font-size: 16px;
|
||||
line-height: 1.6;
|
||||
color: #333;
|
||||
}
|
||||
.login > form > .tip > a,
|
||||
.register > form > .tip > a {
|
||||
color: #000;
|
||||
}
|
||||
61
src/pages/my-account/index.vue
Normal file
@@ -0,0 +1,61 @@
|
||||
<template>
|
||||
<div class="my-account">
|
||||
<div class="header">
|
||||
<h1 class="title">
|
||||
<span class="iconfont icon-tubiao-"></span>
|
||||
<span>My account</span>
|
||||
</h1>
|
||||
</div>
|
||||
<div class="content" v-if="!token">
|
||||
<Login />
|
||||
<Register />
|
||||
</div>
|
||||
<div class="content" v-else>
|
||||
<AccountInfo />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import Login from './login.vue'
|
||||
import Register from './register.vue'
|
||||
import AccountInfo from './account-info.vue'
|
||||
import { useUserInfoStore } from '@/stores/userInfo'
|
||||
const userInfoStore = useUserInfoStore()
|
||||
const token = computed(() => userInfoStore.state.token)
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.my-account {
|
||||
background-color: #f9f9f9;
|
||||
> .header {
|
||||
border-top: var(--main-header-height, 85px) solid #000;
|
||||
background-color: #666;
|
||||
padding: 32px 0;
|
||||
color: #fff;
|
||||
margin-bottom: 32px;
|
||||
> h1 {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 80px;
|
||||
> span {
|
||||
font-size: 40px;
|
||||
font-weight: 400;
|
||||
}
|
||||
}
|
||||
}
|
||||
> .content {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding-bottom: 80px;
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 60px;
|
||||
> * {
|
||||
flex: 1;
|
||||
}
|
||||
animation: opacity-in 0.3s linear both;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
107
src/pages/my-account/less/style.less
Normal file
@@ -0,0 +1,107 @@
|
||||
.login,
|
||||
.register {
|
||||
padding: 35px;
|
||||
border: 1px solid #e1e1e1;
|
||||
background-color: #fff;
|
||||
border-radius: 5px;
|
||||
|
||||
>h2 {
|
||||
font-weight: 600;
|
||||
color: #222;
|
||||
font-size: 24px;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
>form {
|
||||
>* {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
>.form-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
>label {
|
||||
margin-bottom: 5px;
|
||||
font-size: 16px;
|
||||
line-height: 1.6;
|
||||
color: #333;
|
||||
font-weight: 400;
|
||||
|
||||
&::after {
|
||||
content: ' *';
|
||||
color: #f00;
|
||||
}
|
||||
}
|
||||
|
||||
>.input-container>input,
|
||||
>input {
|
||||
width: 100%;
|
||||
height: 35px;
|
||||
padding: 0 17px;
|
||||
font-size: 16px;
|
||||
border: 1px solid #e1e1e1;
|
||||
outline: none;
|
||||
|
||||
&:focus {
|
||||
border-color: #000;
|
||||
}
|
||||
}
|
||||
|
||||
>.input-container {
|
||||
position: relative;
|
||||
|
||||
>input {
|
||||
padding-right: 35px;
|
||||
}
|
||||
|
||||
>.iconfont {
|
||||
position: absolute;
|
||||
right: 10px;
|
||||
top: 0;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 18px;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
>.remember {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
>input {
|
||||
margin: 4px 8px 0 0;
|
||||
}
|
||||
|
||||
>label {
|
||||
color: #333;
|
||||
}
|
||||
|
||||
>.link {
|
||||
margin-left: auto;
|
||||
color: #222;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
>.subscribe {
|
||||
font-size: 16px;
|
||||
line-height: 1.6;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
>.tip {
|
||||
font-size: 16px;
|
||||
line-height: 1.6;
|
||||
color: #333;
|
||||
|
||||
>a {
|
||||
color: #000;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
62
src/pages/my-account/login.vue
Normal file
@@ -0,0 +1,62 @@
|
||||
<template>
|
||||
<div class="login">
|
||||
<h2>Login</h2>
|
||||
<form @submit.prevent="handleSubmit">
|
||||
<div class="form-item">
|
||||
<label for="email">Username or email address</label>
|
||||
<input type="email" id="email" name="email" required v-model="fromData.email" />
|
||||
</div>
|
||||
<div class="form-item">
|
||||
<label for="password">Password</label>
|
||||
<div class="input-container">
|
||||
<input
|
||||
id="password"
|
||||
name="password"
|
||||
required
|
||||
:type="passShow ? 'text' : 'password'"
|
||||
v-model="fromData.password"
|
||||
/>
|
||||
<span
|
||||
class="iconfont"
|
||||
:class="[passShow ? 'icon-hide' : 'icon-show']"
|
||||
@click="passShow = !passShow"
|
||||
></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="remember">
|
||||
<input type="checkbox" id="remember" name="remember" v-model="remember" />
|
||||
<label for="remember">Remember me</label>
|
||||
<router-link class="link" to="/my-account/lost-password"
|
||||
>Lost your password?</router-link
|
||||
>
|
||||
</div>
|
||||
<button type="submit" custom>LOG IN</button>
|
||||
</form>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { useGlobalStore } from '@/stores/global'
|
||||
const globalStore = useGlobalStore()
|
||||
import { useUserInfoStore } from '@/stores/userInfo'
|
||||
const userInfoStore = useUserInfoStore()
|
||||
const passShow = ref(false)
|
||||
const remember = ref(false)
|
||||
const fromData = ref({
|
||||
email: '',
|
||||
password: ''
|
||||
})
|
||||
|
||||
const handleSubmit = () => {
|
||||
console.log(fromData.value, remember.value)
|
||||
globalStore.setLoading(true)
|
||||
setTimeout(() => {
|
||||
globalStore.setLoading(false)
|
||||
userInfoStore.setToken('123456')
|
||||
}, 1000)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
@import './less/style.less';
|
||||
</style>
|
||||
11
src/pages/my-account/orders.vue
Normal file
@@ -0,0 +1,11 @@
|
||||
<template>
|
||||
<div class="orders">No order has been made yet.</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { computed, ref } from 'vue'
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.orders {
|
||||
}
|
||||
</style>
|
||||
78
src/pages/my-account/register.vue
Normal file
@@ -0,0 +1,78 @@
|
||||
<template>
|
||||
<div class="register">
|
||||
<h2>Register</h2>
|
||||
<form @submit.prevent="handleSubmit">
|
||||
<div class="form-item">
|
||||
<label for="username">Username</label>
|
||||
<input
|
||||
type="username"
|
||||
id="username"
|
||||
name="username"
|
||||
required
|
||||
v-model="fromData.username"
|
||||
/>
|
||||
</div>
|
||||
<div class="form-item">
|
||||
<label for="email">Email address</label>
|
||||
<input type="email" id="email" name="email" required v-model="fromData.email" />
|
||||
</div>
|
||||
<div class="form-item">
|
||||
<label for="password">Password</label>
|
||||
<div class="input-container">
|
||||
<input
|
||||
id="password"
|
||||
name="password"
|
||||
required
|
||||
:type="passShow ? 'text' : 'password'"
|
||||
v-model="fromData.password"
|
||||
/>
|
||||
<span
|
||||
class="iconfont"
|
||||
:class="[passShow ? 'icon-hide' : 'icon-show']"
|
||||
@click="passShow = !passShow"
|
||||
></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="subscribe">
|
||||
<input
|
||||
type="checkbox"
|
||||
id="subscribe"
|
||||
name="subscribe"
|
||||
v-model="fromData.subscribe"
|
||||
/>
|
||||
<label for="subscribe"
|
||||
>Subscribe to Code Create product/service news and newsletter</label
|
||||
>
|
||||
</div>
|
||||
<div class="tip">
|
||||
Your personal data will be used to support your experience throughout this website,
|
||||
to manage access to your account, and for other purposes described in our
|
||||
<router-link class="link" to="/my-account/privacy-policy"
|
||||
>Privacy Policy</router-link
|
||||
>
|
||||
</div>
|
||||
<button type="submit" custom>REGISTER</button>
|
||||
</form>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { useUserInfoStore } from '@/stores/userInfo'
|
||||
const userInfoStore = useUserInfoStore()
|
||||
const passShow = ref(false)
|
||||
const remember = ref(false)
|
||||
const fromData = ref({
|
||||
username: '',
|
||||
email: '',
|
||||
password: '',
|
||||
subscribe: false
|
||||
})
|
||||
|
||||
const handleSubmit = () => {
|
||||
console.log(fromData.value, remember.value)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
@import './less/style.less';
|
||||
</style>
|
||||
64
src/pages/my-account/welcome.vue
Normal file
@@ -0,0 +1,64 @@
|
||||
<template>
|
||||
<div class="welcome">
|
||||
<h3 class="title">Welcome to your account page</h3>
|
||||
<div class="tip">
|
||||
Hi <b>XXX</b>, today is a great day to check your account page. You can check also:
|
||||
</div>
|
||||
<div class="buttons">
|
||||
<router-link class="link" v-for="(v, i) in navs" :key="i" :to="v.path">
|
||||
<button custom>
|
||||
<span class="iconfont" :class="v.icon"></span>
|
||||
<span class="label">{{ v.label }}</span>
|
||||
</button>
|
||||
</router-link>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { computed, ref } from 'vue'
|
||||
const navs = ref([
|
||||
{
|
||||
path: '/my-account/orders',
|
||||
label: 'RECENT ORDERS',
|
||||
icon: 'icon-orders'
|
||||
},
|
||||
|
||||
{
|
||||
path: '/my-account/address',
|
||||
label: 'ADDRESSSES',
|
||||
icon: 'icon-address'
|
||||
},
|
||||
|
||||
{
|
||||
path: '/my-account/details',
|
||||
label: 'ACCOUNT DETAILS',
|
||||
icon: 'icon-tubiao-'
|
||||
}
|
||||
])
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.welcome {
|
||||
> .title {
|
||||
font-weight: 600;
|
||||
letter-spacing: 2px;
|
||||
color: #222222;
|
||||
font-size: 24px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
> .tip {
|
||||
color: #333;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
> .buttons {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
> .link {
|
||||
margin: 0 4px;
|
||||
flex: 1;
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,33 +1,57 @@
|
||||
import type { RouteRecordRaw } from 'vue-router'
|
||||
import AboutView from './pages/AboutView.vue'
|
||||
import AboutView from './pages/about-us/index.vue'
|
||||
import ContactView from './pages/ContactView.vue'
|
||||
import HomeView from './pages/home/index.vue'
|
||||
import ProductsView from './pages/ProductsView.vue'
|
||||
|
||||
import { LANGS } from './lang'
|
||||
export const routes: RouteRecordRaw[] = [
|
||||
{
|
||||
path: '/:lang?',
|
||||
path: `/:lang(${LANGS.join('|')})?`,
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
component: HomeView,
|
||||
alias: ['/:lang?', '/:lang?/home'],
|
||||
|
||||
},
|
||||
{
|
||||
path: 'about',
|
||||
component: AboutView,
|
||||
path: 'about-us',
|
||||
name: 'about-us',
|
||||
component: AboutView
|
||||
},
|
||||
{
|
||||
path: 'products',
|
||||
name: 'products',
|
||||
component: ProductsView,
|
||||
component: ProductsView
|
||||
},
|
||||
{
|
||||
path: 'contact',
|
||||
path: 'contact-us',
|
||||
name: 'contact',
|
||||
component: ContactView,
|
||||
component: ContactView
|
||||
},
|
||||
],
|
||||
{
|
||||
path: 'aida',
|
||||
name: 'Aida',
|
||||
component: () => import('./pages/aida/index.vue')
|
||||
},
|
||||
{
|
||||
path: 'my-account',
|
||||
name: 'MyAccount',
|
||||
component: () => import('./pages/my-account/index.vue'),
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
component: () => import('./pages/my-account/welcome.vue'),
|
||||
},
|
||||
{
|
||||
path: 'orders',
|
||||
component: () => import('./pages/my-account/orders.vue'),
|
||||
},
|
||||
{
|
||||
path: ':pathMatch(.*)*',
|
||||
component: () => import('./pages/my-account/welcome.vue'),
|
||||
},
|
||||
]
|
||||
},
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
14
src/stores/global.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import { ref, computed } from 'vue'
|
||||
export const useGlobalStore = defineStore('global', () => {
|
||||
const state = ref({
|
||||
loading: false,// 全局loading
|
||||
})
|
||||
|
||||
const setLoading = (v: boolean) => { state.value.loading = v }
|
||||
|
||||
return {
|
||||
state,
|
||||
setLoading,
|
||||
}
|
||||
})
|
||||
9
src/stores/index.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import { createPinia } from 'pinia'
|
||||
import { createPersistedState } from 'pinia-persistedstate-plugin'
|
||||
// 创建store实例
|
||||
const store = createPinia()
|
||||
// 使用持久化插件(全局持久化)
|
||||
store.use(createPersistedState())
|
||||
export default store
|
||||
export * from './global'
|
||||
export * from './userInfo'
|
||||
30
src/stores/userInfo.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
// 每一个存储的模块,命名规则use开头,store结尾
|
||||
import { defineStore } from 'pinia'
|
||||
import { ref, computed } from 'vue'
|
||||
export const useUserInfoStore = defineStore('userInfo', () => {
|
||||
const state = ref({
|
||||
userInfo: {},
|
||||
token: '',
|
||||
|
||||
})
|
||||
|
||||
// actions
|
||||
const setUserInfo = (data: any) => {
|
||||
state.value.userInfo = data
|
||||
}
|
||||
|
||||
const setToken = (data: string) => {
|
||||
state.value.token = data
|
||||
}
|
||||
|
||||
const logOut = async () => {
|
||||
|
||||
}
|
||||
|
||||
return {
|
||||
state,
|
||||
setToken,
|
||||
setUserInfo,
|
||||
logOut,
|
||||
}
|
||||
})
|
||||
@@ -1,11 +1,34 @@
|
||||
html,body{
|
||||
html,
|
||||
body {
|
||||
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
|
||||
}
|
||||
@@ -1,7 +1,26 @@
|
||||
{
|
||||
"files": [],
|
||||
"include": ["env.d.ts", "src/**/*", "src/**/*.vue", "src/types/**/*.d.ts"],
|
||||
"compilerOptions": {
|
||||
"forceConsistentCasingInFileNames": false, // ⚠️ 禁用大小写检查
|
||||
"allowJs": true,
|
||||
"baseUrl": ".",
|
||||
"types": ["node", "unplugin-vue-define-options/macros-global"],
|
||||
"paths": {
|
||||
"@/*": ["./src/*"],
|
||||
"_c/*": ["./src/components/*"]
|
||||
},
|
||||
"skipLibCheck": true,
|
||||
"strict": false,
|
||||
"noEmit": true,
|
||||
"noImplicitAny": false,
|
||||
"noEmitOnError": false
|
||||
},
|
||||
|
||||
"references": [
|
||||
{ "path": "./tsconfig.app.json" },
|
||||
{ "path": "./tsconfig.node.json" }
|
||||
{
|
||||
"path": "./tsconfig.node.json"
|
||||
}
|
||||
]
|
||||
}
|
||||