mirror of
https://github.com/zen-browser/www.git
synced 2025-07-07 17:05:32 +02:00
feat(animejs): replace motion with animejs for lighter animation (#663)
* feat(animejs): replace motion with animejs for lighter animation * fix(components): adjust video class for consistent styling * refactor(components): remove translate-y-20 class for improved layout consistency * chore: adjust hero animation * refactor(components): use inline style for initial * refactor(components): enhance feature and hero components with consistent inline-block styling * chore(animatejs): update span with bold to make animation work * refactor(components): improve layout and animation consistency in Features and Hero components * refactor(components): adjust spacing in hero component * refactor(features): update autoplay target and enter properties * fix(features): standardize feature keys to use hyphenated format * refactor(components): optimize animation delay and improve class handling
This commit is contained in:
parent
1c1a7088d1
commit
cdc4f1cef9
8 changed files with 323 additions and 443 deletions
|
@ -4,6 +4,7 @@
|
||||||
"files": ["**/*.{ts,js,astro,html,css,json,yml,yaml,md}"],
|
"files": ["**/*.{ts,js,astro,html,css,json,yml,yaml,md}"],
|
||||||
"words": [
|
"words": [
|
||||||
"adam",
|
"adam",
|
||||||
|
"animejs",
|
||||||
"AMOLED",
|
"AMOLED",
|
||||||
"Astronav",
|
"Astronav",
|
||||||
"Briel",
|
"Briel",
|
||||||
|
|
|
@ -37,6 +37,7 @@
|
||||||
"@fortawesome/free-solid-svg-icons": "6.7.1",
|
"@fortawesome/free-solid-svg-icons": "6.7.1",
|
||||||
"@types/react": "^19.1.6",
|
"@types/react": "^19.1.6",
|
||||||
"@types/react-dom": "^19.1.5",
|
"@types/react-dom": "^19.1.5",
|
||||||
|
"animejs": "^4.0.2",
|
||||||
"astro": "5.7.10",
|
"astro": "5.7.10",
|
||||||
"astro-navbar": "2.3.7",
|
"astro-navbar": "2.3.7",
|
||||||
"autoprefixer": "10.4.14",
|
"autoprefixer": "10.4.14",
|
||||||
|
@ -45,7 +46,6 @@
|
||||||
"free-astro-components": "1.2.0",
|
"free-astro-components": "1.2.0",
|
||||||
"jiti": "^2.4.2",
|
"jiti": "^2.4.2",
|
||||||
"lefthook": "^1.11.13",
|
"lefthook": "^1.11.13",
|
||||||
"motion": "^12.15.0",
|
|
||||||
"postcss": "8.5.1",
|
"postcss": "8.5.1",
|
||||||
"react": "^19.1.0",
|
"react": "^19.1.0",
|
||||||
"react-dom": "^19.1.0",
|
"react-dom": "^19.1.0",
|
||||||
|
@ -71,6 +71,7 @@
|
||||||
"@playwright/test": "1.52.0",
|
"@playwright/test": "1.52.0",
|
||||||
"@testing-library/jest-dom": "6.6.3",
|
"@testing-library/jest-dom": "6.6.3",
|
||||||
"@testing-library/user-event": "14.6.1",
|
"@testing-library/user-event": "14.6.1",
|
||||||
|
"@types/animejs": "^3.1.13",
|
||||||
"@types/eslint-plugin-jsx-a11y": "6.10.0",
|
"@types/eslint-plugin-jsx-a11y": "6.10.0",
|
||||||
"@types/jsdom": "21.1.7",
|
"@types/jsdom": "21.1.7",
|
||||||
"@types/node": "22.15.18",
|
"@types/node": "22.15.18",
|
||||||
|
|
84
pnpm-lock.yaml
generated
84
pnpm-lock.yaml
generated
|
@ -44,6 +44,9 @@ importers:
|
||||||
'@types/react-dom':
|
'@types/react-dom':
|
||||||
specifier: ^19.1.5
|
specifier: ^19.1.5
|
||||||
version: 19.1.5(@types/react@19.1.6)
|
version: 19.1.5(@types/react@19.1.6)
|
||||||
|
animejs:
|
||||||
|
specifier: ^4.0.2
|
||||||
|
version: 4.0.2
|
||||||
astro:
|
astro:
|
||||||
specifier: 5.7.10
|
specifier: 5.7.10
|
||||||
version: 5.7.10(@types/node@22.15.18)(jiti@2.4.2)(rollup@4.41.1)(typescript@5.6.3)(yaml@2.8.0)
|
version: 5.7.10(@types/node@22.15.18)(jiti@2.4.2)(rollup@4.41.1)(typescript@5.6.3)(yaml@2.8.0)
|
||||||
|
@ -68,9 +71,6 @@ importers:
|
||||||
lefthook:
|
lefthook:
|
||||||
specifier: ^1.11.13
|
specifier: ^1.11.13
|
||||||
version: 1.11.13
|
version: 1.11.13
|
||||||
motion:
|
|
||||||
specifier: ^12.15.0
|
|
||||||
version: 12.15.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
|
||||||
postcss:
|
postcss:
|
||||||
specifier: 8.5.1
|
specifier: 8.5.1
|
||||||
version: 8.5.1
|
version: 8.5.1
|
||||||
|
@ -141,6 +141,9 @@ importers:
|
||||||
'@testing-library/user-event':
|
'@testing-library/user-event':
|
||||||
specifier: 14.6.1
|
specifier: 14.6.1
|
||||||
version: 14.6.1(@testing-library/dom@10.4.0)
|
version: 14.6.1(@testing-library/dom@10.4.0)
|
||||||
|
'@types/animejs':
|
||||||
|
specifier: ^3.1.13
|
||||||
|
version: 3.1.13
|
||||||
'@types/eslint-plugin-jsx-a11y':
|
'@types/eslint-plugin-jsx-a11y':
|
||||||
specifier: 6.10.0
|
specifier: 6.10.0
|
||||||
version: 6.10.0
|
version: 6.10.0
|
||||||
|
@ -370,8 +373,8 @@ packages:
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
'@babel/core': ^7.0.0-0
|
'@babel/core': ^7.0.0-0
|
||||||
|
|
||||||
'@babel/runtime@7.27.4':
|
'@babel/runtime@7.27.6':
|
||||||
resolution: {integrity: sha512-t3yaEOuGu9NlIZ+hIeGbBjFtZT7j2cb2tg0fuaJKeGotchRjjLfrBA9Kwf8quhpP1EUuxModQg04q/mBwyg8uA==}
|
resolution: {integrity: sha512-vbavdySgbTTrmFE+EsiqUTzlOr5bzlnJtUv9PynGCAKvfQqjIXbvFdumPM/GxMDfyuGMJaJAU6TO4zc1Jf1i8Q==}
|
||||||
engines: {node: '>=6.9.0'}
|
engines: {node: '>=6.9.0'}
|
||||||
|
|
||||||
'@babel/template@7.27.2':
|
'@babel/template@7.27.2':
|
||||||
|
@ -1505,6 +1508,9 @@ packages:
|
||||||
'@tybys/wasm-util@0.9.0':
|
'@tybys/wasm-util@0.9.0':
|
||||||
resolution: {integrity: sha512-6+7nlbMVX/PVDCwaIQ8nTOPveOcFLSt8GcXdx8hD0bt39uWxYT88uXzqTd4fTvqta7oeUJqudepapKNt2DYJFw==}
|
resolution: {integrity: sha512-6+7nlbMVX/PVDCwaIQ8nTOPveOcFLSt8GcXdx8hD0bt39uWxYT88uXzqTd4fTvqta7oeUJqudepapKNt2DYJFw==}
|
||||||
|
|
||||||
|
'@types/animejs@3.1.13':
|
||||||
|
resolution: {integrity: sha512-yWg9l1z7CAv/TKpty4/vupEh24jDGUZXv4r26StRkpUPQm04ztJaftgpto8vwdFs8SiTq6XfaPKCSI+wjzNMvQ==}
|
||||||
|
|
||||||
'@types/aria-query@5.0.4':
|
'@types/aria-query@5.0.4':
|
||||||
resolution: {integrity: sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==}
|
resolution: {integrity: sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==}
|
||||||
|
|
||||||
|
@ -1826,6 +1832,9 @@ packages:
|
||||||
ajv@8.17.1:
|
ajv@8.17.1:
|
||||||
resolution: {integrity: sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==}
|
resolution: {integrity: sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==}
|
||||||
|
|
||||||
|
animejs@4.0.2:
|
||||||
|
resolution: {integrity: sha512-f0L/kSya2RF23iMSF/VO01pMmLwlAFoiQeNAvBXhEyLzIPd2/QTBRatwGUqkVCC6seaAJYzAkGir55N4SL+h3A==}
|
||||||
|
|
||||||
ansi-align@3.0.1:
|
ansi-align@3.0.1:
|
||||||
resolution: {integrity: sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==}
|
resolution: {integrity: sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==}
|
||||||
|
|
||||||
|
@ -2747,20 +2756,6 @@ packages:
|
||||||
fraction.js@4.3.7:
|
fraction.js@4.3.7:
|
||||||
resolution: {integrity: sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==}
|
resolution: {integrity: sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==}
|
||||||
|
|
||||||
framer-motion@12.15.0:
|
|
||||||
resolution: {integrity: sha512-XKg/LnKExdLGugZrDILV7jZjI599785lDIJZLxMiiIFidCsy0a4R2ZEf+Izm67zyOuJgQYTHOmodi7igQsw3vg==}
|
|
||||||
peerDependencies:
|
|
||||||
'@emotion/is-prop-valid': '*'
|
|
||||||
react: ^18.0.0 || ^19.0.0
|
|
||||||
react-dom: ^18.0.0 || ^19.0.0
|
|
||||||
peerDependenciesMeta:
|
|
||||||
'@emotion/is-prop-valid':
|
|
||||||
optional: true
|
|
||||||
react:
|
|
||||||
optional: true
|
|
||||||
react-dom:
|
|
||||||
optional: true
|
|
||||||
|
|
||||||
free-astro-components@1.2.0:
|
free-astro-components@1.2.0:
|
||||||
resolution: {integrity: sha512-bsT9dWsNlRGDNjqcoIlz6w8NcSCgOpx6oxiwZgYwq9RVbi3JqUImPc6c4Kico2wRJSIXc7HHyr71QgmgXv7nfg==}
|
resolution: {integrity: sha512-bsT9dWsNlRGDNjqcoIlz6w8NcSCgOpx6oxiwZgYwq9RVbi3JqUImPc6c4Kico2wRJSIXc7HHyr71QgmgXv7nfg==}
|
||||||
|
|
||||||
|
@ -3575,26 +3570,6 @@ packages:
|
||||||
resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==}
|
resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==}
|
||||||
engines: {node: '>=16 || 14 >=14.17'}
|
engines: {node: '>=16 || 14 >=14.17'}
|
||||||
|
|
||||||
motion-dom@12.15.0:
|
|
||||||
resolution: {integrity: sha512-D2ldJgor+2vdcrDtKJw48k3OddXiZN1dDLLWrS8kiHzQdYVruh0IoTwbJBslrnTXIPgFED7PBN2Zbwl7rNqnhA==}
|
|
||||||
|
|
||||||
motion-utils@12.12.1:
|
|
||||||
resolution: {integrity: sha512-f9qiqUHm7hWSLlNW8gS9pisnsN7CRFRD58vNjptKdsqFLpkVnX00TNeD6Q0d27V9KzT7ySFyK1TZ/DShfVOv6w==}
|
|
||||||
|
|
||||||
motion@12.15.0:
|
|
||||||
resolution: {integrity: sha512-HLouXyIb1uQFiZgJTYGrtEzbatPc6vK+HP+Qt6afLQjaudiGiLLVsoy71CwzD/Stlh06FUd5OpyiXqn6XvqjqQ==}
|
|
||||||
peerDependencies:
|
|
||||||
'@emotion/is-prop-valid': '*'
|
|
||||||
react: ^18.0.0 || ^19.0.0
|
|
||||||
react-dom: ^18.0.0 || ^19.0.0
|
|
||||||
peerDependenciesMeta:
|
|
||||||
'@emotion/is-prop-valid':
|
|
||||||
optional: true
|
|
||||||
react:
|
|
||||||
optional: true
|
|
||||||
react-dom:
|
|
||||||
optional: true
|
|
||||||
|
|
||||||
mrmime@2.0.1:
|
mrmime@2.0.1:
|
||||||
resolution: {integrity: sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==}
|
resolution: {integrity: sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
|
@ -5356,7 +5331,7 @@ snapshots:
|
||||||
'@babel/core': 7.27.4
|
'@babel/core': 7.27.4
|
||||||
'@babel/helper-plugin-utils': 7.27.1
|
'@babel/helper-plugin-utils': 7.27.1
|
||||||
|
|
||||||
'@babel/runtime@7.27.4': {}
|
'@babel/runtime@7.27.6': {}
|
||||||
|
|
||||||
'@babel/template@7.27.2':
|
'@babel/template@7.27.2':
|
||||||
dependencies:
|
dependencies:
|
||||||
|
@ -6289,7 +6264,7 @@ snapshots:
|
||||||
'@testing-library/dom@10.4.0':
|
'@testing-library/dom@10.4.0':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/code-frame': 7.27.1
|
'@babel/code-frame': 7.27.1
|
||||||
'@babel/runtime': 7.27.4
|
'@babel/runtime': 7.27.6
|
||||||
'@types/aria-query': 5.0.4
|
'@types/aria-query': 5.0.4
|
||||||
aria-query: 5.3.0
|
aria-query: 5.3.0
|
||||||
chalk: 4.1.2
|
chalk: 4.1.2
|
||||||
|
@ -6316,6 +6291,8 @@ snapshots:
|
||||||
tslib: 2.8.1
|
tslib: 2.8.1
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
'@types/animejs@3.1.13': {}
|
||||||
|
|
||||||
'@types/aria-query@5.0.4': {}
|
'@types/aria-query@5.0.4': {}
|
||||||
|
|
||||||
'@types/babel__core@7.20.5':
|
'@types/babel__core@7.20.5':
|
||||||
|
@ -6704,6 +6681,8 @@ snapshots:
|
||||||
json-schema-traverse: 1.0.0
|
json-schema-traverse: 1.0.0
|
||||||
require-from-string: 2.0.2
|
require-from-string: 2.0.2
|
||||||
|
|
||||||
|
animejs@4.0.2: {}
|
||||||
|
|
||||||
ansi-align@3.0.1:
|
ansi-align@3.0.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
string-width: 4.2.3
|
string-width: 4.2.3
|
||||||
|
@ -7950,15 +7929,6 @@ snapshots:
|
||||||
|
|
||||||
fraction.js@4.3.7: {}
|
fraction.js@4.3.7: {}
|
||||||
|
|
||||||
framer-motion@12.15.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0):
|
|
||||||
dependencies:
|
|
||||||
motion-dom: 12.15.0
|
|
||||||
motion-utils: 12.12.1
|
|
||||||
tslib: 2.8.1
|
|
||||||
optionalDependencies:
|
|
||||||
react: 19.1.0
|
|
||||||
react-dom: 19.1.0(react@19.1.0)
|
|
||||||
|
|
||||||
free-astro-components@1.2.0(@types/node@22.15.18)(jiti@2.4.2)(prettier-plugin-astro@0.14.1)(prettier@3.5.3)(rollup@4.41.1)(yaml@2.8.0):
|
free-astro-components@1.2.0(@types/node@22.15.18)(jiti@2.4.2)(prettier-plugin-astro@0.14.1)(prettier@3.5.3)(rollup@4.41.1)(yaml@2.8.0):
|
||||||
dependencies:
|
dependencies:
|
||||||
'@astrojs/check': 0.9.4(prettier-plugin-astro@0.14.1)(prettier@3.5.3)(typescript@5.6.3)
|
'@astrojs/check': 0.9.4(prettier-plugin-astro@0.14.1)(prettier@3.5.3)(typescript@5.6.3)
|
||||||
|
@ -9038,20 +9008,6 @@ snapshots:
|
||||||
|
|
||||||
minipass@7.1.2: {}
|
minipass@7.1.2: {}
|
||||||
|
|
||||||
motion-dom@12.15.0:
|
|
||||||
dependencies:
|
|
||||||
motion-utils: 12.12.1
|
|
||||||
|
|
||||||
motion-utils@12.12.1: {}
|
|
||||||
|
|
||||||
motion@12.15.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0):
|
|
||||||
dependencies:
|
|
||||||
framer-motion: 12.15.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
|
||||||
tslib: 2.8.1
|
|
||||||
optionalDependencies:
|
|
||||||
react: 19.1.0
|
|
||||||
react-dom: 19.1.0(react@19.1.0)
|
|
||||||
|
|
||||||
mrmime@2.0.1: {}
|
mrmime@2.0.1: {}
|
||||||
|
|
||||||
ms@2.1.3: {}
|
ms@2.1.3: {}
|
||||||
|
|
|
@ -1,10 +1,7 @@
|
||||||
---
|
---
|
||||||
import Image from 'astro/components/Image.astro'
|
import Image from 'astro/components/Image.astro'
|
||||||
import { motion } from 'motion/react'
|
|
||||||
import { getTitleAnimation } from '~/animations'
|
|
||||||
import ComImage from '~/assets/ComImage.png'
|
import ComImage from '~/assets/ComImage.png'
|
||||||
import Button from '~/components/Button.astro'
|
import Button from '~/components/Button.astro'
|
||||||
import Description from '~/components/Description.astro'
|
|
||||||
import CheckIcon from '~/icons/CheckIcon.astro'
|
import CheckIcon from '~/icons/CheckIcon.astro'
|
||||||
import GitHubIcon from '~/icons/GitHubIcon.astro'
|
import GitHubIcon from '~/icons/GitHubIcon.astro'
|
||||||
import { getLocale, getUI } from '~/utils/i18n'
|
import { getLocale, getUI } from '~/utils/i18n'
|
||||||
|
@ -19,50 +16,85 @@ const {
|
||||||
---
|
---
|
||||||
|
|
||||||
<section
|
<section
|
||||||
id="Community"
|
id="community"
|
||||||
class="relative flex w-full flex-col items-center gap-6 py-12 text-start md:text-center lg:py-36"
|
class="relative flex w-full flex-col items-center gap-6 py-12 text-start md:text-center lg:py-36"
|
||||||
>
|
>
|
||||||
<Description class="mb-2 text-4xl font-bold sm:text-6xl">
|
<h2 class="mb-2 text-4xl font-bold sm:text-6xl">
|
||||||
{
|
{
|
||||||
community.title.map((title, index) =>
|
community.title.map(title =>
|
||||||
title !== '\n' ? (
|
title !== '\n' ? (
|
||||||
<motion.span client:load {...getTitleAnimation(0.2 + index * 0.2)}>
|
<span style="transform: translateY(20px); opacity: 0.001; filter: blur(4px)">
|
||||||
{title}
|
{title}
|
||||||
</motion.span>
|
</span>
|
||||||
) : (
|
) : (
|
||||||
<br class="hidden md:block" />
|
<br class="hidden md:block" />
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
</Description>
|
</h2>
|
||||||
<motion.p client:load {...getTitleAnimation(0.6)} className="lg:w-1/2 lg:px-0">
|
<p
|
||||||
{community.description}
|
class="text-base lg:w-1/2 lg:px-0"
|
||||||
</motion.p>
|
style="transform: translateY(20px); opacity: 0.001; filter: blur(4px)"
|
||||||
|
>
|
||||||
|
<span class="opacity-80">{community.description}</span>
|
||||||
|
</p>
|
||||||
<div class="flex w-full flex-wrap gap-3 sm:gap-10 md:justify-center">
|
<div class="flex w-full flex-wrap gap-3 sm:gap-10 md:justify-center">
|
||||||
<motion.span client:load {...getTitleAnimation(0.8)}>
|
<div
|
||||||
|
class="community__button"
|
||||||
|
style="transform: translateY(20px); opacity: 0.001; filter: blur(4px)"
|
||||||
|
>
|
||||||
<Button class:list={['px-4']} href="https://github.com/zen-browser">
|
<Button class:list={['px-4']} href="https://github.com/zen-browser">
|
||||||
<GitHubIcon class="size-4" />
|
<GitHubIcon class="size-4" />
|
||||||
<span>{community.lists.freeAndOpenSource.title}</span>
|
<span>{community.lists.freeAndOpenSource.title}</span>
|
||||||
</Button>
|
</Button>
|
||||||
</motion.span>
|
</div>
|
||||||
<motion.div client:load {...getTitleAnimation(1)} className="flex items-center gap-4">
|
<div
|
||||||
|
class="community__button flex items-center gap-4"
|
||||||
|
style="transform: translateY(20px); opacity: 0.001; filter: blur(4px)"
|
||||||
|
>
|
||||||
<CheckIcon class="size-4" />
|
<CheckIcon class="size-4" />
|
||||||
<span>{community.lists.simpleYetPowerful.title}</span>
|
<span>{community.lists.simpleYetPowerful.title}</span>
|
||||||
</motion.div>
|
</div>
|
||||||
<motion.div client:load {...getTitleAnimation(1.2)} className="flex items-center gap-4">
|
<div
|
||||||
|
class="community__button flex items-center gap-4"
|
||||||
|
style="transform: translateY(20px); opacity: 0.001; filter: blur(4px)"
|
||||||
|
>
|
||||||
<CheckIcon class="size-4" />
|
<CheckIcon class="size-4" />
|
||||||
<span>{community.lists.privateAndAlwaysUpToDate.title}</span>
|
<span>{community.lists.privateAndAlwaysUpToDate.title}</span>
|
||||||
</motion.div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<motion.span
|
<span class="flex max-w-full lg:max-w-none lg:flex-none">
|
||||||
className="flex max-w-full lg:max-w-none lg:flex-none"
|
|
||||||
client:load
|
|
||||||
{...getTitleAnimation(1.4)}
|
|
||||||
>
|
|
||||||
<Image
|
<Image
|
||||||
src={ComImage}
|
src={ComImage}
|
||||||
alt={community.images.community.alt}
|
alt={community.images.community.alt}
|
||||||
class="rounded-3xl shadow-md lg:mx-auto dark:opacity-80"
|
class="rounded-3xl shadow-md lg:mx-auto dark:opacity-80"
|
||||||
|
style="transform: translateY(20px); opacity: 0.001; filter: blur(4px)"
|
||||||
/>
|
/>
|
||||||
</motion.span>
|
</span>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { animate, onScroll, stagger } from 'animejs'
|
||||||
|
|
||||||
|
function initAnimations() {
|
||||||
|
const debug = false
|
||||||
|
|
||||||
|
const elements = document.querySelectorAll(
|
||||||
|
'#community h2 span, #community p, #community .community__button, #community img'
|
||||||
|
)
|
||||||
|
|
||||||
|
animate(elements, {
|
||||||
|
opacity: { from: 0.001, to: 1 },
|
||||||
|
translateY: { from: 20, to: 0 },
|
||||||
|
filter: { from: 'blur(4px)', to: 'blur(0px)' },
|
||||||
|
duration: 300,
|
||||||
|
delay: stagger(150),
|
||||||
|
ease: 'cubicBezier(0.25, 0.1, 0.25, 1)',
|
||||||
|
autoplay: onScroll({
|
||||||
|
target: '#community',
|
||||||
|
debug,
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
initAnimations()
|
||||||
|
</script>
|
||||||
|
|
|
@ -1,9 +1,5 @@
|
||||||
---
|
---
|
||||||
import { motion } from 'motion/react'
|
|
||||||
import { getTitleAnimation } from '~/animations'
|
|
||||||
import { getLocale, getUI } from '~/utils/i18n'
|
import { getLocale, getUI } from '~/utils/i18n'
|
||||||
import Description from './Description.astro'
|
|
||||||
|
|
||||||
import Video from './Video.astro'
|
import Video from './Video.astro'
|
||||||
|
|
||||||
const locale = getLocale(Astro)
|
const locale = getLocale(Astro)
|
||||||
|
@ -20,290 +16,137 @@ interface Props {
|
||||||
|
|
||||||
const { titles } = Astro.props
|
const { titles } = Astro.props
|
||||||
|
|
||||||
const descriptions = Object.values(features.featureTabs).map(tab => tab.description)
|
const featureList = [
|
||||||
|
{
|
||||||
|
key: 'workspaces',
|
||||||
|
title: features.featureTabs.workspaces.title,
|
||||||
|
description: features.featureTabs.workspaces.description,
|
||||||
|
video: 'workspaces',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'compact-mode',
|
||||||
|
title: features.featureTabs.compactMode.title,
|
||||||
|
description: features.featureTabs.compactMode.description,
|
||||||
|
video: 'compact-mode',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'glance',
|
||||||
|
title: features.featureTabs.glance.title,
|
||||||
|
description: features.featureTabs.glance.description,
|
||||||
|
video: 'glance',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'split-view',
|
||||||
|
title: features.featureTabs.splitView.title,
|
||||||
|
description: features.featureTabs.splitView.description,
|
||||||
|
video: 'split-views',
|
||||||
|
},
|
||||||
|
]
|
||||||
---
|
---
|
||||||
|
|
||||||
<section id="Features" class="relative flex w-full flex-col py-12 text-start lg:py-36">
|
<section
|
||||||
<Description class="mb-2 text-4xl font-bold sm:text-6xl">
|
id="features"
|
||||||
|
class="relative flex w-full flex-col items-center gap-8 py-8 text-center sm:gap-12 sm:py-12 xl:gap-24 xl:py-36"
|
||||||
|
>
|
||||||
|
<!-- Section Header -->
|
||||||
|
<div class="flex w-full flex-col items-center gap-4 sm:gap-6">
|
||||||
|
<h2 class="text-3xl sm:text-4xl md:text-6xl">
|
||||||
|
{
|
||||||
|
(titles || features.titles).map(title => {
|
||||||
|
switch (title) {
|
||||||
|
case '\n':
|
||||||
|
return <br class="hidden md:block" />
|
||||||
|
default:
|
||||||
|
return (
|
||||||
|
<b
|
||||||
|
class="font-bold"
|
||||||
|
style="transform: translateY(20px); opacity: 0.001; filter: blur(4px)"
|
||||||
|
>
|
||||||
|
{title}
|
||||||
|
</b>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</h2>
|
||||||
|
<p
|
||||||
|
class="max-w-4xl text-base sm:text-lg"
|
||||||
|
style="transform: translateY(20px); opacity: 0.001; filter: blur(4px)"
|
||||||
|
>
|
||||||
|
<span class="opacity-80">{features.description}</span>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Features Layout -->
|
||||||
|
<div id="showcase" class="flex w-full flex-col gap-12 sm:gap-16 xl:gap-36">
|
||||||
{
|
{
|
||||||
(titles || features.titles).map((title, index) =>
|
featureList.map((feature, index) => (
|
||||||
title !== '\n' ? (
|
<div class="flex w-full flex-col items-center gap-6 xl:gap-12">
|
||||||
<motion.span client:load {...getTitleAnimation(0.2 + index * 0.2)}>
|
<div class="flex flex-col items-center gap-4">
|
||||||
{title}
|
<h3 class="text-3xl font-bold opacity-0 blur-sm sm:text-4xl xl:text-5xl">
|
||||||
</motion.span>
|
{feature.title}
|
||||||
) : (
|
</h3>
|
||||||
<br class="hidden md:block" />
|
<p
|
||||||
)
|
class="max-w-3xl text-center text-base leading-relaxed sm:text-lg"
|
||||||
)
|
style="transform: translateY(20px); opacity: 0.001; filter: blur(4px)"
|
||||||
}
|
>
|
||||||
</Description>
|
<span class="opacity-80">{feature.description}</span>
|
||||||
<motion.p client:load {...getTitleAnimation(0.6)} className="lg:w-1/2">
|
</p>
|
||||||
{features.description}
|
</div>
|
||||||
</motion.p>
|
|
||||||
<div class="mt-6 flex flex-col gap-6 lg:flex-row lg:justify-between lg:gap-2">
|
|
||||||
<div class="flex w-full flex-col lg:w-1/3">
|
|
||||||
<!-- Mobile tabs -->
|
|
||||||
<div class="flex gap-2 overflow-x-auto overflow-y-clip lg:hidden">
|
|
||||||
<motion.button
|
|
||||||
client:load
|
|
||||||
{...getTitleAnimation()}
|
|
||||||
className="feature-tab whitespace-nowrap"
|
|
||||||
data-active="true"
|
|
||||||
>
|
|
||||||
{features.featureTabs.workspaces.title}
|
|
||||||
</motion.button>
|
|
||||||
<motion.button
|
|
||||||
client:load
|
|
||||||
{...getTitleAnimation(0.2)}
|
|
||||||
className="feature-tab whitespace-nowrap"
|
|
||||||
>
|
|
||||||
{features.featureTabs.compactMode.title}
|
|
||||||
</motion.button>
|
|
||||||
<motion.button
|
|
||||||
client:load
|
|
||||||
{...getTitleAnimation(0.4)}
|
|
||||||
className="feature-tab whitespace-nowrap"
|
|
||||||
>
|
|
||||||
{features.featureTabs.glance.title}
|
|
||||||
</motion.button>
|
|
||||||
<motion.button
|
|
||||||
client:load
|
|
||||||
{...getTitleAnimation(0.6)}
|
|
||||||
className="feature-tab whitespace-nowrap"
|
|
||||||
>
|
|
||||||
{features.featureTabs.splitView.title}
|
|
||||||
</motion.button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Desktop features list -->
|
|
||||||
<div id="features-list" class="hidden lg:flex lg:flex-col lg:gap-3">
|
|
||||||
<motion.div client:load {...getTitleAnimation(0.8)} className="feature" data-active="true">
|
|
||||||
<Description class="text-2xl font-bold">
|
|
||||||
{features.featureTabs.workspaces.title}
|
|
||||||
</Description>
|
|
||||||
<Description>
|
|
||||||
{features.featureTabs.workspaces.description}
|
|
||||||
</Description>
|
|
||||||
</motion.div>
|
|
||||||
<motion.div client:load {...getTitleAnimation(1)} className="feature">
|
|
||||||
<Description class="text-2xl font-bold">
|
|
||||||
{features.featureTabs.compactMode.title}
|
|
||||||
</Description>
|
|
||||||
<Description>
|
|
||||||
{features.featureTabs.compactMode.description}
|
|
||||||
</Description>
|
|
||||||
</motion.div>
|
|
||||||
<motion.div client:load {...getTitleAnimation(1.2)} className="feature">
|
|
||||||
<Description class="text-2xl font-bold">
|
|
||||||
{features.featureTabs.glance.title}
|
|
||||||
</Description>
|
|
||||||
<Description>
|
|
||||||
{features.featureTabs.glance.description}
|
|
||||||
</Description>
|
|
||||||
</motion.div>
|
|
||||||
<motion.div client:load {...getTitleAnimation(1.4)} className="feature">
|
|
||||||
<Description class="text-2xl font-bold">
|
|
||||||
{features.featureTabs.splitView.title}
|
|
||||||
</Description>
|
|
||||||
<Description>
|
|
||||||
{features.featureTabs.splitView.description}
|
|
||||||
</Description>
|
|
||||||
</motion.div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Mobile description -->
|
|
||||||
<div class="feature-description mt-4 lg:hidden" data-descriptions={descriptions.join('|||')}>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="sticky top-6 h-fit w-full lg:w-3/5">
|
|
||||||
<div class="relative w-full">
|
|
||||||
<div class="video-stack relative h-full w-full">
|
|
||||||
<Video
|
<Video
|
||||||
|
class="w-full rounded-xl object-cover shadow-lg"
|
||||||
|
style="transform: translateY(20px); opacity: 0.001; filter: blur(4px)"
|
||||||
|
name={feature.video}
|
||||||
autoplay
|
autoplay
|
||||||
loop
|
loop
|
||||||
muted
|
muted
|
||||||
playsinline
|
playsinline
|
||||||
preload="none"
|
preload="none"
|
||||||
class="feature-video"
|
|
||||||
name="workspaces"
|
|
||||||
/>
|
|
||||||
<Video
|
|
||||||
autoplay
|
|
||||||
loop
|
|
||||||
muted
|
|
||||||
playsinline
|
|
||||||
preload="none"
|
|
||||||
class="feature-video"
|
|
||||||
name="compact-mode"
|
|
||||||
/>
|
|
||||||
<Video
|
|
||||||
autoplay
|
|
||||||
loop
|
|
||||||
muted
|
|
||||||
playsinline
|
|
||||||
preload="none"
|
|
||||||
class="feature-video"
|
|
||||||
name="glance"
|
|
||||||
/>
|
|
||||||
<Video
|
|
||||||
autoplay
|
|
||||||
loop
|
|
||||||
muted
|
|
||||||
playsinline
|
|
||||||
preload="none"
|
|
||||||
class="feature-video"
|
|
||||||
name="split-views"
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
))
|
||||||
</div>
|
}
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
const features = document.querySelectorAll('.feature, .feature-tab') as NodeListOf<HTMLElement>
|
import { animate, onScroll, stagger } from 'animejs'
|
||||||
|
|
||||||
// Set initial description
|
function initAnimations() {
|
||||||
const descriptionEl = document.querySelector('.feature-description') as HTMLDivElement
|
const debug = false
|
||||||
const descriptions = descriptionEl?.dataset.descriptions?.split('|||')
|
|
||||||
if (descriptionEl && descriptions) {
|
|
||||||
descriptionEl.textContent = descriptions[0]
|
|
||||||
}
|
|
||||||
|
|
||||||
function changeToFeature({ target }: MouseEvent | { target: HTMLElement }) {
|
const elements = document.querySelectorAll('#features h2 b, #features > div > p')
|
||||||
let targetEl: HTMLElement | null = target as HTMLElement
|
const showcase = document.getElementById('showcase') as HTMLElement
|
||||||
|
|
||||||
if (target instanceof HTMLElement) {
|
animate(elements, {
|
||||||
targetEl = target.closest('.feature, .feature-tab')
|
opacity: { from: 0.001, to: 1 },
|
||||||
}
|
translateY: { from: 20, to: 0 },
|
||||||
|
filter: { from: 'blur(4px)', to: 'blur(0px)' },
|
||||||
|
duration: 300,
|
||||||
|
delay: stagger(150),
|
||||||
|
ease: 'cubicBezier(0.25, 0.1, 0.25, 1)',
|
||||||
|
autoplay: onScroll({
|
||||||
|
target: '#features',
|
||||||
|
debug,
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
|
||||||
if (!targetEl) {
|
for (const element of showcase.children) {
|
||||||
return
|
const target = element.querySelectorAll('p, h3, video')
|
||||||
}
|
animate(target, {
|
||||||
|
opacity: { from: 0.001, to: 1 },
|
||||||
const index = Array.from(features).indexOf(targetEl) % 4
|
translateY: { from: 20, to: 0 },
|
||||||
|
filter: { from: 'blur(4px)', to: 'blur(0px)' },
|
||||||
if (index === -1) {
|
duration: 300,
|
||||||
return
|
delay: stagger(150),
|
||||||
}
|
ease: 'cubicBezier(0.25, 0.1, 0.25, 1)',
|
||||||
|
autoplay: onScroll({
|
||||||
// Update both mobile and desktop elements
|
enter: { container: 'top+=500' },
|
||||||
for (let i = 0; i < features.length; i += 1) {
|
target: element,
|
||||||
const f = features[i]
|
debug,
|
||||||
|
}),
|
||||||
if (i % 4 === index) {
|
})
|
||||||
f.setAttribute('data-active', 'true')
|
|
||||||
} else {
|
|
||||||
f.removeAttribute('data-active')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update mobile description
|
|
||||||
const descriptionEl = document.querySelector('.feature-description')
|
|
||||||
|
|
||||||
if (descriptionEl && descriptions) {
|
|
||||||
descriptionEl.textContent = descriptions[index]
|
|
||||||
}
|
|
||||||
|
|
||||||
const videos = document.querySelectorAll<HTMLVideoElement>('.feature-video')
|
|
||||||
|
|
||||||
for (let i = 0; i < videos.length; i += 1) {
|
|
||||||
const vid = videos[i]
|
|
||||||
const yOffset = (i - index) * 20
|
|
||||||
const zOffset = i === index ? 0 : -100 - Math.abs(i - index) * 50
|
|
||||||
const scale = i === index ? 1 : 0.95
|
|
||||||
const rotation = (i - index) * 3
|
|
||||||
|
|
||||||
if (i === index) {
|
|
||||||
vid.setAttribute('data-active', 'true')
|
|
||||||
vid.style.opacity = '1'
|
|
||||||
vid.style.transform = `translate3d(-50%, 0, 0) scale(${scale})`
|
|
||||||
vid.style.zIndex = '10'
|
|
||||||
vid.currentTime = 0
|
|
||||||
vid.play()
|
|
||||||
} else {
|
|
||||||
vid.removeAttribute('data-active')
|
|
||||||
vid.style.transform = `translate3d(-50%, ${yOffset}px, ${zOffset}px)
|
|
||||||
rotate3d(1, 0, 0, ${rotation}deg)
|
|
||||||
scale(${scale})`
|
|
||||||
vid.style.zIndex = String(1 - Math.abs(i - index))
|
|
||||||
vid.pause()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
initAnimations()
|
||||||
for (const feature of features) {
|
|
||||||
feature.addEventListener('click', changeToFeature)
|
|
||||||
}
|
|
||||||
|
|
||||||
changeToFeature({ target: features[0] })
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style>
|
|
||||||
.feature {
|
|
||||||
@apply w-full cursor-pointer select-none rounded-lg p-4 opacity-0 hover:bg-subtle;
|
|
||||||
transition: background 0.2s ease-in-out;
|
|
||||||
|
|
||||||
&[data-active='true'] {
|
|
||||||
@apply bg-subtle;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.feature-tab {
|
|
||||||
@apply rounded-lg px-4 py-2 text-lg font-medium opacity-0 hover:bg-subtle;
|
|
||||||
transition: background 0.2s ease-in-out;
|
|
||||||
|
|
||||||
&[data-active='true'] {
|
|
||||||
@apply bg-subtle;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.feature-description {
|
|
||||||
@apply px-4 text-sm;
|
|
||||||
}
|
|
||||||
|
|
||||||
.video-stack {
|
|
||||||
@apply aspect-video;
|
|
||||||
perspective: 2000px;
|
|
||||||
transform-style: preserve-3d;
|
|
||||||
}
|
|
||||||
|
|
||||||
.feature-video {
|
|
||||||
@apply left-1/2 hidden transform rounded-3xl shadow-md lg:absolute lg:mx-auto lg:block lg:w-full dark:opacity-80;
|
|
||||||
max-width: 800px;
|
|
||||||
transition: all 0.6s cubic-bezier(0.4, 0, 0.2, 1);
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
transform-origin: top center;
|
|
||||||
backface-visibility: hidden;
|
|
||||||
will-change: transform, opacity;
|
|
||||||
transform: translate3d(-50%, 0, -100px) scale(0.95);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Don't animate translation on small screens */
|
|
||||||
@media (max-width: 1024px) {
|
|
||||||
.feature-video {
|
|
||||||
position: absolute;
|
|
||||||
inset: 0;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
opacity: 0;
|
|
||||||
transform: none !important;
|
|
||||||
display: none;
|
|
||||||
object-fit: cover;
|
|
||||||
|
|
||||||
&[data-active='true'] {
|
|
||||||
display: block;
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.video-stack {
|
|
||||||
@apply overflow-hidden rounded-xl;
|
|
||||||
position: relative;
|
|
||||||
width: 100%;
|
|
||||||
aspect-ratio: 16/9;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
|
@ -1,24 +1,11 @@
|
||||||
---
|
---
|
||||||
import { motion } from 'motion/react'
|
|
||||||
import { getTitleAnimation } from '~/animations'
|
|
||||||
import Button from '~/components/Button.astro'
|
import Button from '~/components/Button.astro'
|
||||||
import Description from '~/components/Description.astro'
|
|
||||||
import Title from '~/components/Title.astro'
|
import Title from '~/components/Title.astro'
|
||||||
import ArrowRightIcon from '~/icons/ArrowRightIcon.astro'
|
import ArrowRightIcon from '~/icons/ArrowRightIcon.astro'
|
||||||
import { getLocale, getPath, getUI } from '~/utils/i18n'
|
import { getLocale, getPath, getUI } from '~/utils/i18n'
|
||||||
import SocialMediaStrip from './SocialMediaStrip.astro'
|
import SocialMediaStrip from './SocialMediaStrip.astro'
|
||||||
import Video from './Video.astro'
|
import Video from './Video.astro'
|
||||||
|
|
||||||
let titleAnimationCounter = 0
|
|
||||||
function getNewAnimationDelay() {
|
|
||||||
titleAnimationCounter++
|
|
||||||
return titleAnimationCounter * 0.15
|
|
||||||
}
|
|
||||||
|
|
||||||
function getHeroTitleAnimation() {
|
|
||||||
return getTitleAnimation(getNewAnimationDelay())
|
|
||||||
}
|
|
||||||
|
|
||||||
const locale = getLocale(Astro)
|
const locale = getLocale(Astro)
|
||||||
|
|
||||||
const getLocalePath = getPath(locale)
|
const getLocalePath = getPath(locale)
|
||||||
|
@ -32,58 +19,54 @@ const {
|
||||||
|
|
||||||
<header
|
<header
|
||||||
id="header"
|
id="header"
|
||||||
class="flex w-full flex-col items-center gap-[20%] py-32 text-center lg:gap-[25%]"
|
class="flex w-full flex-col items-center gap-6 py-16 text-center lg:gap-12 lg:py-32"
|
||||||
>
|
>
|
||||||
<div class="flex h-full flex-col items-center justify-center">
|
<div class="flex h-full flex-col items-center justify-center gap-6 md:gap-8">
|
||||||
<Title class="relative px-12 text-center font-normal md:text-7xl lg:px-0 lg:text-9xl">
|
<div class="flex flex-col items-center justify-center gap-4 md:gap-8">
|
||||||
{
|
<div>
|
||||||
hero.title.map(title =>
|
<Title class="relative px-12 text-center text-5xl md:text-7xl lg:px-0 lg:text-9xl">
|
||||||
title.text !== '\n' ? (
|
{
|
||||||
<motion.span
|
hero.title.map(title =>
|
||||||
client:load
|
title.text !== '\n' ? (
|
||||||
{...getHeroTitleAnimation()}
|
<b
|
||||||
className={title.highlight ? 'italic text-coral' : ''}
|
class:list={['font-normal', title.highlight && 'italic text-coral']}
|
||||||
>
|
style="transform: translateY(20px); opacity: 0.001; filter: blur(4px)"
|
||||||
{title.text}
|
>
|
||||||
</motion.span>
|
{title.text}
|
||||||
) : (
|
</b>
|
||||||
<br class="hidden md:block" />
|
) : (
|
||||||
)
|
<br class="hidden md:block" />
|
||||||
)
|
)
|
||||||
}
|
)
|
||||||
</Title>
|
}
|
||||||
<motion.span client:load {...getHeroTitleAnimation()}>
|
</Title>
|
||||||
<Description class="px-12 text-center lg:px-0">
|
<p
|
||||||
{hero.description[0]}
|
class="px-12 text-center lg:px-0"
|
||||||
<br class="hidden sm:inline" />
|
style="transform: translateY(20px); opacity: 0.001; filter: blur(4px)"
|
||||||
{hero.description[1]}</Description
|
>
|
||||||
|
{hero.description[0]}
|
||||||
|
<br class="hidden sm:inline" />
|
||||||
|
{hero.description[1]}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="flex w-2/3 flex-col items-center justify-center gap-3 sm:gap-6 md:w-fit md:flex-row"
|
||||||
>
|
>
|
||||||
</motion.span>
|
<div style="transform: translateY(20px); opacity: 0.001; filter: blur(4px)">
|
||||||
<div class="mt-6 flex w-2/3 flex-col gap-3 sm:gap-6 md:w-fit md:flex-row">
|
<Button class="w-fit" href={getLocalePath('/download')} isPrimary>
|
||||||
<motion.span client:load {...getHeroTitleAnimation()}>
|
{hero.buttons.beta}
|
||||||
<Button class="w-full" href={getLocalePath('/download')} isPrimary>
|
<ArrowRightIcon class="size-4" />
|
||||||
{hero.buttons.beta}
|
</Button>
|
||||||
<ArrowRightIcon class="size-4" />
|
</div>
|
||||||
</Button>
|
<div style="transform: translateY(20px); opacity: 0.001; filter: blur(4px)">
|
||||||
</motion.span>
|
<Button class="w-fit" href={getLocalePath('/donate')}>
|
||||||
<motion.span client:load {...getHeroTitleAnimation()}>
|
{hero.buttons.support}
|
||||||
<Button href={getLocalePath('/donate')}>{hero.buttons.support}</Button>
|
</Button>
|
||||||
</motion.span>
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<motion.span
|
<SocialMediaStrip style="transform: translateY(20px); opacity: 0.001; filter: blur(4px)" />
|
||||||
client:load
|
|
||||||
{...getHeroTitleAnimation()}
|
|
||||||
className="mx-auto translate-y-16 !transform"
|
|
||||||
>
|
|
||||||
<SocialMediaStrip />
|
|
||||||
</motion.span>
|
|
||||||
</div>
|
</div>
|
||||||
</header>
|
|
||||||
<motion.span
|
|
||||||
className="flex max-w-full lg:max-w-none lg:flex-none"
|
|
||||||
client:load
|
|
||||||
{...getHeroTitleAnimation()}
|
|
||||||
>
|
|
||||||
<Video
|
<Video
|
||||||
name="hero-video"
|
name="hero-video"
|
||||||
autoplay
|
autoplay
|
||||||
|
@ -91,6 +74,39 @@ const {
|
||||||
muted
|
muted
|
||||||
playsinline
|
playsinline
|
||||||
preload="none"
|
preload="none"
|
||||||
class="mb-24 rounded-3xl shadow-md dark:opacity-80"
|
class="rounded-xl"
|
||||||
|
style="transform: translateY(20px); opacity: 0.001; filter: blur(4px); transform-origin: top;"
|
||||||
/>
|
/>
|
||||||
</motion.span>
|
</header>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { animate, onScroll, stagger } from 'animejs'
|
||||||
|
|
||||||
|
function initAnimations() {
|
||||||
|
const debug = false
|
||||||
|
|
||||||
|
const elements = document.querySelectorAll(
|
||||||
|
'#header h1 b, #header p, #header div:has(a), #header video, #header ul'
|
||||||
|
)
|
||||||
|
|
||||||
|
animate(elements, {
|
||||||
|
// @ts-expect-error - element is HTMLElement
|
||||||
|
opacity: element => {
|
||||||
|
if (element.tagName === 'UL') {
|
||||||
|
return { from: 0.001, to: 0.8 }
|
||||||
|
}
|
||||||
|
return { from: 0.001, to: 1 }
|
||||||
|
},
|
||||||
|
translateY: { from: 20, to: 0 },
|
||||||
|
filter: { from: 'blur(4px)', to: 'blur(0px)' },
|
||||||
|
duration: 300,
|
||||||
|
delay: stagger(150),
|
||||||
|
ease: 'cubicBezier(0.25, 0.1, 0.25, 1)',
|
||||||
|
autoplay: onScroll({
|
||||||
|
target: '#header',
|
||||||
|
debug,
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
initAnimations()
|
||||||
|
</script>
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
---
|
---
|
||||||
const { gap = 4 } = Astro.props
|
const { class: className, ...props } = Astro.props
|
||||||
|
|
||||||
import { icon, library } from '@fortawesome/fontawesome-svg-core'
|
import { icon, library } from '@fortawesome/fontawesome-svg-core'
|
||||||
import {
|
import {
|
||||||
|
@ -9,6 +9,7 @@ import {
|
||||||
faReddit,
|
faReddit,
|
||||||
faXTwitter,
|
faXTwitter,
|
||||||
} from '@fortawesome/free-brands-svg-icons'
|
} from '@fortawesome/free-brands-svg-icons'
|
||||||
|
import { cn } from '~/utils/merge'
|
||||||
|
|
||||||
library.add(faMastodon, faBluesky, faGithub, faXTwitter, faReddit)
|
library.add(faMastodon, faBluesky, faGithub, faXTwitter, faReddit)
|
||||||
const Mastodon = icon({ prefix: 'fab', iconName: 'mastodon' })
|
const Mastodon = icon({ prefix: 'fab', iconName: 'mastodon' })
|
||||||
|
@ -18,7 +19,7 @@ const XTwitter = icon({ prefix: 'fab', iconName: 'x-twitter' })
|
||||||
const Reddit = icon({ prefix: 'fab', iconName: 'reddit' })
|
const Reddit = icon({ prefix: 'fab', iconName: 'reddit' })
|
||||||
---
|
---
|
||||||
|
|
||||||
<ul class={`flex items-center opacity-80 gap-${gap}`}>
|
<ul class={cn('flex items-center gap-4 opacity-80', className)} {...props}>
|
||||||
<li>
|
<li>
|
||||||
<a
|
<a
|
||||||
href="https://github.com/zen-browser"
|
href="https://github.com/zen-browser"
|
||||||
|
|
|
@ -1,7 +1,5 @@
|
||||||
---
|
---
|
||||||
import { motion } from 'motion/react'
|
import h3 from '~/components/Description.astro'
|
||||||
import { getTitleAnimation } from '~/animations'
|
|
||||||
import Description from '~/components/Description.astro'
|
|
||||||
import { getLocale, getUI } from '~/utils/i18n'
|
import { getLocale, getUI } from '~/utils/i18n'
|
||||||
|
|
||||||
const locale = getLocale(Astro)
|
const locale = getLocale(Astro)
|
||||||
|
@ -19,23 +17,55 @@ const {
|
||||||
---
|
---
|
||||||
|
|
||||||
<section id="sponsors" class:list={['py-12', !showSponsors && 'hidden']}>
|
<section id="sponsors" class:list={['py-12', !showSponsors && 'hidden']}>
|
||||||
<div class="mx-auto flex flex-col text-center">
|
<div class="flex flex-col items-center gap-6 text-center">
|
||||||
<motion.span client:load {...getTitleAnimation(0.2)}>
|
<h3
|
||||||
<Description class="mb-2 text-4xl font-bold sm:text-6xl">{sponsors.title}</Description>
|
class="text-4xl font-bold sm:text-6xl"
|
||||||
</motion.span>
|
style="transform: translateY(20px); opacity: 0.001; filter: blur(4px)"
|
||||||
<motion.span client:load {...getTitleAnimation(0.4)}>
|
>
|
||||||
<Description set:html={sponsors.description} />
|
{sponsors.title}
|
||||||
</motion.span>
|
</h3>
|
||||||
<div class="relative mt-8 flex items-center justify-center">
|
<p class="text-base" style="transform: translateY(20px); opacity: 0.001; filter: blur(4px)">
|
||||||
<motion.span client:load {...getTitleAnimation(0.6)}>
|
<span class="opacity-80" set:html={sponsors.description} />
|
||||||
<a href={sponsors.sponsors['tuta'].url} target="_blank" class="w-fit">
|
</p>
|
||||||
<Image
|
<div class="sponsors__sponsor relative mt-8 flex items-center justify-center">
|
||||||
src={tutaLogo}
|
<a
|
||||||
alt={sponsors.sponsors['tuta'].name}
|
href={sponsors.sponsors['tuta'].url}
|
||||||
class="h-16 w-fit object-contain"
|
target="_blank"
|
||||||
/>
|
class="w-fit"
|
||||||
</a>
|
style="transform: translateY(20px); opacity: 0.001; filter: blur(4px)"
|
||||||
</motion.span>
|
>
|
||||||
|
<Image
|
||||||
|
src={tutaLogo}
|
||||||
|
alt={sponsors.sponsors['tuta'].name}
|
||||||
|
class="h-16 w-fit object-contain"
|
||||||
|
/>
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { animate, onScroll, stagger } from 'animejs'
|
||||||
|
|
||||||
|
function initAnimations() {
|
||||||
|
const debug = false
|
||||||
|
|
||||||
|
const elements = document.querySelectorAll(
|
||||||
|
'#sponsors h3, #sponsors p, #sponsors .sponsors__sponsor a'
|
||||||
|
)
|
||||||
|
|
||||||
|
animate(elements, {
|
||||||
|
opacity: { from: 0.001, to: 1 },
|
||||||
|
translateY: { from: 20, to: 0 },
|
||||||
|
filter: { from: 'blur(4px)', to: 'blur(0px)' },
|
||||||
|
duration: 300,
|
||||||
|
delay: stagger(150),
|
||||||
|
ease: 'cubicBezier(0.25, 0.1, 0.25, 1)',
|
||||||
|
autoplay: onScroll({
|
||||||
|
target: '#sponsors',
|
||||||
|
debug,
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
initAnimations()
|
||||||
|
</script>
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue