mirror of
https://github.com/zen-browser/www.git
synced 2025-07-07 17:05:32 +02:00
Replace motion with animejs and new layout redesign (#672)
Co-authored-by: taroj1205 <taroj1205@gmail.com> Co-authored-by: Shintaro Jokagi <61367823+taroj1205@users.noreply.github.com> Co-authored-by: mr. m <91018726+mauro-balades@users.noreply.github.com> Co-authored-by: Canoa <gitgood@thatcanoa.org> Co-authored-by: Bryan Galdámez <josuegalre@gmail.com>
This commit is contained in:
parent
9db42c090f
commit
da96f4844a
20 changed files with 679 additions and 690 deletions
16
.github/workflows/ci-pipeline.yml
vendored
16
.github/workflows/ci-pipeline.yml
vendored
|
@ -64,15 +64,6 @@ jobs:
|
||||||
if: steps.check-node-modules-cache.outputs.cache-hit != 'true'
|
if: steps.check-node-modules-cache.outputs.cache-hit != 'true'
|
||||||
run: pnpm install --frozen-lockfile
|
run: pnpm install --frozen-lockfile
|
||||||
|
|
||||||
- name: Save node_modules cache
|
|
||||||
if: steps.check-node-modules-cache.outputs.cache-hit != 'true'
|
|
||||||
uses: actions/cache/save@v4
|
|
||||||
with:
|
|
||||||
path: |
|
|
||||||
node_modules
|
|
||||||
*/node_modules
|
|
||||||
key: ${{ runner.os }}-node-modules-${{ hashFiles('**/pnpm-lock.yaml') }}
|
|
||||||
|
|
||||||
quality_checks:
|
quality_checks:
|
||||||
name: ${{ matrix.name }}
|
name: ${{ matrix.name }}
|
||||||
needs: [check_changes, setup]
|
needs: [check_changes, setup]
|
||||||
|
@ -102,6 +93,7 @@ jobs:
|
||||||
restore-keys: ${{ runner.os }}-turbo-${{ matrix.check }}-
|
restore-keys: ${{ runner.os }}-turbo-${{ matrix.check }}-
|
||||||
|
|
||||||
- name: Restore node_modules cache
|
- name: Restore node_modules cache
|
||||||
|
id: cache
|
||||||
uses: actions/cache/restore@v4
|
uses: actions/cache/restore@v4
|
||||||
with:
|
with:
|
||||||
path: |
|
path: |
|
||||||
|
@ -121,6 +113,7 @@ jobs:
|
||||||
run_install: false
|
run_install: false
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
|
if: steps.cache.outputs.cache-hit != 'true'
|
||||||
run: pnpm install --frozen-lockfile
|
run: pnpm install --frozen-lockfile
|
||||||
|
|
||||||
- name: Run ${{ matrix.name }}
|
- name: Run ${{ matrix.name }}
|
||||||
|
@ -128,7 +121,7 @@ jobs:
|
||||||
|
|
||||||
playwright:
|
playwright:
|
||||||
name: Playwright Tests
|
name: Playwright Tests
|
||||||
needs: [check_changes, setup, quality_checks]
|
needs: [check_changes, setup]
|
||||||
if: ${{ needs.check_changes.outputs.exists == 'true' }}
|
if: ${{ needs.check_changes.outputs.exists == 'true' }}
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
|
@ -144,6 +137,7 @@ jobs:
|
||||||
restore-keys: ${{ runner.os }}-turbo-
|
restore-keys: ${{ runner.os }}-turbo-
|
||||||
|
|
||||||
- name: Restore node_modules cache
|
- name: Restore node_modules cache
|
||||||
|
id: cache
|
||||||
uses: actions/cache/restore@v4
|
uses: actions/cache/restore@v4
|
||||||
with:
|
with:
|
||||||
path: |
|
path: |
|
||||||
|
@ -163,6 +157,7 @@ jobs:
|
||||||
run_install: false
|
run_install: false
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
|
if: steps.cache.outputs.cache-hit != 'true'
|
||||||
run: pnpm install --frozen-lockfile
|
run: pnpm install --frozen-lockfile
|
||||||
|
|
||||||
- name: Cache Playwright Browsers
|
- name: Cache Playwright Browsers
|
||||||
|
@ -177,6 +172,7 @@ jobs:
|
||||||
|
|
||||||
- name: Run Playwright Tests
|
- name: Run Playwright Tests
|
||||||
run: pnpm exec turbo run test:playwright
|
run: pnpm exec turbo run test:playwright
|
||||||
|
timeout-minutes: 10
|
||||||
|
|
||||||
verify:
|
verify:
|
||||||
name: Verify
|
name: Verify
|
||||||
|
|
18
cspell.json
18
cspell.json
|
@ -5,21 +5,22 @@
|
||||||
"words": [
|
"words": [
|
||||||
"adam",
|
"adam",
|
||||||
"AMOLED",
|
"AMOLED",
|
||||||
|
"animejs",
|
||||||
"Astronav",
|
"Astronav",
|
||||||
"Briel",
|
|
||||||
"brhm",
|
"brhm",
|
||||||
"Brhm",
|
"Brhm",
|
||||||
|
"Briel",
|
||||||
"bryan",
|
"bryan",
|
||||||
"Canoa",
|
|
||||||
"canoa",
|
"canoa",
|
||||||
|
"Canoa",
|
||||||
"cfasync",
|
"cfasync",
|
||||||
"createdAsc",
|
"createdAsc",
|
||||||
"createdDefault",
|
"createdDefault",
|
||||||
"createdDesc",
|
"createdDesc",
|
||||||
"daniel",
|
"daniel",
|
||||||
"FMPEG",
|
|
||||||
"ferrocyante",
|
"ferrocyante",
|
||||||
"flatpaks",
|
"flatpaks",
|
||||||
|
"FMPEG",
|
||||||
"Galdámez",
|
"Galdámez",
|
||||||
"García",
|
"García",
|
||||||
"Garro",
|
"Garro",
|
||||||
|
@ -33,14 +34,14 @@
|
||||||
"Jokagi",
|
"Jokagi",
|
||||||
"junicode",
|
"junicode",
|
||||||
"Junicode",
|
"Junicode",
|
||||||
"kristijanribaric",
|
|
||||||
"Kristijan",
|
"Kristijan",
|
||||||
|
"kristijanribaric",
|
||||||
"laggy",
|
"laggy",
|
||||||
"larvey",
|
"larvey",
|
||||||
"Larvey",
|
"Larvey",
|
||||||
|
"linaarchsum",
|
||||||
"linuxarmsum",
|
"linuxarmsum",
|
||||||
"linuxsum",
|
"linuxsum",
|
||||||
"linaarchsum",
|
|
||||||
"mfsa",
|
"mfsa",
|
||||||
"mozilla",
|
"mozilla",
|
||||||
"Nehalem",
|
"Nehalem",
|
||||||
|
@ -51,12 +52,13 @@
|
||||||
"patreon",
|
"patreon",
|
||||||
"Pdzly",
|
"Pdzly",
|
||||||
"Ribaric",
|
"Ribaric",
|
||||||
|
"securtiy",
|
||||||
"taroj",
|
"taroj",
|
||||||
"Turborepo",
|
|
||||||
"testid",
|
"testid",
|
||||||
"theming",
|
"theming",
|
||||||
"tuta",
|
|
||||||
"tsconfigs",
|
"tsconfigs",
|
||||||
|
"Turborepo",
|
||||||
|
"tuta",
|
||||||
"unfloatable",
|
"unfloatable",
|
||||||
"unfocusing",
|
"unfocusing",
|
||||||
"unrs",
|
"unrs",
|
||||||
|
@ -66,8 +68,8 @@
|
||||||
"VAAPI",
|
"VAAPI",
|
||||||
"wmfcdm",
|
"wmfcdm",
|
||||||
"workerd",
|
"workerd",
|
||||||
"XPCOM",
|
|
||||||
"xmark",
|
"xmark",
|
||||||
|
"XPCOM",
|
||||||
"yumemi",
|
"yumemi",
|
||||||
"zsync"
|
"zsync"
|
||||||
],
|
],
|
||||||
|
|
|
@ -37,23 +37,25 @@
|
||||||
"@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",
|
||||||
"turbo": "2.5.4",
|
|
||||||
"autoprefixer": "10.4.14",
|
"autoprefixer": "10.4.14",
|
||||||
"clsx": "2.1.1",
|
"clsx": "2.1.1",
|
||||||
"date-fns": "4.1.0",
|
"date-fns": "4.1.0",
|
||||||
"free-astro-components": "1.2.0",
|
"free-astro-components": "1.2.0",
|
||||||
"jiti": "2.4.2",
|
"jiti": "2.4.2",
|
||||||
|
"js-confetti": "0.12.0",
|
||||||
"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",
|
||||||
"sharp": "0.33.5",
|
"sharp": "0.33.5",
|
||||||
"tailwind-merge": "3.3.0",
|
"tailwind-merge": "3.3.0",
|
||||||
"tailwindcss": "3.4.15",
|
"tailwindcss": "3.4.15",
|
||||||
"typescript": "5.6.3"
|
"turbo": "2.5.4",
|
||||||
|
"typescript": "5.6.3",
|
||||||
|
"ua-parser-js": "2.0.3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@commitlint/cli": "19.8.1",
|
"@commitlint/cli": "19.8.1",
|
||||||
|
@ -72,6 +74,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",
|
||||||
|
|
169
pnpm-lock.yaml
generated
169
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)
|
||||||
|
@ -65,12 +68,12 @@ importers:
|
||||||
jiti:
|
jiti:
|
||||||
specifier: 2.4.2
|
specifier: 2.4.2
|
||||||
version: 2.4.2
|
version: 2.4.2
|
||||||
|
js-confetti:
|
||||||
|
specifier: 0.12.0
|
||||||
|
version: 0.12.0
|
||||||
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
|
||||||
|
@ -95,6 +98,9 @@ importers:
|
||||||
typescript:
|
typescript:
|
||||||
specifier: 5.6.3
|
specifier: 5.6.3
|
||||||
version: 5.6.3
|
version: 5.6.3
|
||||||
|
ua-parser-js:
|
||||||
|
specifier: 2.0.3
|
||||||
|
version: 2.0.3
|
||||||
devDependencies:
|
devDependencies:
|
||||||
'@commitlint/cli':
|
'@commitlint/cli':
|
||||||
specifier: 19.8.1
|
specifier: 19.8.1
|
||||||
|
@ -144,6 +150,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
|
||||||
|
@ -1505,6 +1514,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==}
|
||||||
|
|
||||||
|
@ -1556,6 +1568,9 @@ packages:
|
||||||
'@types/nlcst@2.0.3':
|
'@types/nlcst@2.0.3':
|
||||||
resolution: {integrity: sha512-vSYNSDe6Ix3q+6Z7ri9lyWqgGhJTmzRjZRqyq15N0Z/1/UnVsno9G/N40NBijoYx2seFDIl0+B2mgAb9mezUCA==}
|
resolution: {integrity: sha512-vSYNSDe6Ix3q+6Z7ri9lyWqgGhJTmzRjZRqyq15N0Z/1/UnVsno9G/N40NBijoYx2seFDIl0+B2mgAb9mezUCA==}
|
||||||
|
|
||||||
|
'@types/node-fetch@2.6.12':
|
||||||
|
resolution: {integrity: sha512-8nneRWKCg3rMtF69nLQJnOYUcbafYeFSjqkw3jCRLsqkWFlHaoQrr5mXmofFGOx3DKn7UfmBMyov8ySvLRVldA==}
|
||||||
|
|
||||||
'@types/node@17.0.45':
|
'@types/node@17.0.45':
|
||||||
resolution: {integrity: sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw==}
|
resolution: {integrity: sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw==}
|
||||||
|
|
||||||
|
@ -1826,6 +1841,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==}
|
||||||
|
|
||||||
|
@ -1942,6 +1960,9 @@ packages:
|
||||||
resolution: {integrity: sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==}
|
resolution: {integrity: sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
|
asynckit@0.4.0:
|
||||||
|
resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==}
|
||||||
|
|
||||||
autoprefixer@10.4.14:
|
autoprefixer@10.4.14:
|
||||||
resolution: {integrity: sha512-FQzyfOsTlwVzjHxKEqRIAdJx9niO6VCBCoEwax/VLSoQF29ggECcPuBqUMZ+u8jCZOPSy8b8/8KnuFbp0SaFZQ==}
|
resolution: {integrity: sha512-FQzyfOsTlwVzjHxKEqRIAdJx9niO6VCBCoEwax/VLSoQF29ggECcPuBqUMZ+u8jCZOPSy8b8/8KnuFbp0SaFZQ==}
|
||||||
engines: {node: ^10 || ^12 || >=14}
|
engines: {node: ^10 || ^12 || >=14}
|
||||||
|
@ -2125,6 +2146,10 @@ packages:
|
||||||
resolution: {integrity: sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==}
|
resolution: {integrity: sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==}
|
||||||
engines: {node: '>=12.5.0'}
|
engines: {node: '>=12.5.0'}
|
||||||
|
|
||||||
|
combined-stream@1.0.8:
|
||||||
|
resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==}
|
||||||
|
engines: {node: '>= 0.8'}
|
||||||
|
|
||||||
comma-separated-tokens@2.0.3:
|
comma-separated-tokens@2.0.3:
|
||||||
resolution: {integrity: sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==}
|
resolution: {integrity: sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==}
|
||||||
|
|
||||||
|
@ -2334,6 +2359,10 @@ packages:
|
||||||
defu@6.1.4:
|
defu@6.1.4:
|
||||||
resolution: {integrity: sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==}
|
resolution: {integrity: sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==}
|
||||||
|
|
||||||
|
delayed-stream@1.0.0:
|
||||||
|
resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==}
|
||||||
|
engines: {node: '>=0.4.0'}
|
||||||
|
|
||||||
dequal@2.0.3:
|
dequal@2.0.3:
|
||||||
resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==}
|
resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==}
|
||||||
engines: {node: '>=6'}
|
engines: {node: '>=6'}
|
||||||
|
@ -2341,6 +2370,9 @@ packages:
|
||||||
destr@2.0.5:
|
destr@2.0.5:
|
||||||
resolution: {integrity: sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==}
|
resolution: {integrity: sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==}
|
||||||
|
|
||||||
|
detect-europe-js@0.1.2:
|
||||||
|
resolution: {integrity: sha512-lgdERlL3u0aUdHocoouzT10d9I89VVhk0qNRmll7mXdGfJT1/wqZ2ZLA4oJAjeACPY5fT1wsbq2AT+GkuInsow==}
|
||||||
|
|
||||||
detect-libc@2.0.4:
|
detect-libc@2.0.4:
|
||||||
resolution: {integrity: sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==}
|
resolution: {integrity: sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==}
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
|
@ -2744,23 +2776,13 @@ packages:
|
||||||
resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==}
|
resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==}
|
||||||
engines: {node: '>=14'}
|
engines: {node: '>=14'}
|
||||||
|
|
||||||
|
form-data@4.0.3:
|
||||||
|
resolution: {integrity: sha512-qsITQPfmvMOSAdeyZ+12I1c+CKSstAFAwu+97zrnWAbIr5u8wfsExUzCesVLC8NgHuRUqNN4Zy6UPWUTRGslcA==}
|
||||||
|
engines: {node: '>= 6'}
|
||||||
|
|
||||||
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==}
|
||||||
|
|
||||||
|
@ -3111,6 +3133,9 @@ packages:
|
||||||
resolution: {integrity: sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==}
|
resolution: {integrity: sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
|
is-standalone-pwa@0.1.1:
|
||||||
|
resolution: {integrity: sha512-9Cbovsa52vNQCjdXOzeQq5CnCbAcRk05aU62K20WO372NrTv0NxibLFCK6lQ4/iZEFdEA3p3t2VNOn8AJ53F5g==}
|
||||||
|
|
||||||
is-string@1.1.1:
|
is-string@1.1.1:
|
||||||
resolution: {integrity: sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==}
|
resolution: {integrity: sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
|
@ -3184,6 +3209,9 @@ packages:
|
||||||
resolution: {integrity: sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A==}
|
resolution: {integrity: sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A==}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
|
js-confetti@0.12.0:
|
||||||
|
resolution: {integrity: sha512-1R0Akxn3Zn82pMqW65N1V2NwKkZJ75bvBN/VAb36Ya0YHwbaSiAJZVRr/19HBxH/O8x2x01UFAbYI18VqlDN6g==}
|
||||||
|
|
||||||
js-tokens@4.0.0:
|
js-tokens@4.0.0:
|
||||||
resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
|
resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
|
||||||
|
|
||||||
|
@ -3547,6 +3575,14 @@ packages:
|
||||||
resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==}
|
resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==}
|
||||||
engines: {node: '>=8.6'}
|
engines: {node: '>=8.6'}
|
||||||
|
|
||||||
|
mime-db@1.52.0:
|
||||||
|
resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==}
|
||||||
|
engines: {node: '>= 0.6'}
|
||||||
|
|
||||||
|
mime-types@2.1.35:
|
||||||
|
resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==}
|
||||||
|
engines: {node: '>= 0.6'}
|
||||||
|
|
||||||
mime@3.0.0:
|
mime@3.0.0:
|
||||||
resolution: {integrity: sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==}
|
resolution: {integrity: sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==}
|
||||||
engines: {node: '>=10.0.0'}
|
engines: {node: '>=10.0.0'}
|
||||||
|
@ -3575,26 +3611,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'}
|
||||||
|
@ -4550,6 +4566,13 @@ packages:
|
||||||
engines: {node: '>=14.17'}
|
engines: {node: '>=14.17'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
|
ua-is-frozen@0.1.2:
|
||||||
|
resolution: {integrity: sha512-RwKDW2p3iyWn4UbaxpP2+VxwqXh0jpvdxsYpZ5j/MLLiQOfbsV5shpgQiw93+KMYQPcteeMQ289MaAFzs3G9pw==}
|
||||||
|
|
||||||
|
ua-parser-js@2.0.3:
|
||||||
|
resolution: {integrity: sha512-LZyXZdNttONW8LjzEH3Z8+6TE7RfrEiJqDKyh0R11p/kxvrV2o9DrT2FGZO+KVNs3k+drcIQ6C3En6wLnzJGpw==}
|
||||||
|
hasBin: true
|
||||||
|
|
||||||
ufo@1.6.1:
|
ufo@1.6.1:
|
||||||
resolution: {integrity: sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA==}
|
resolution: {integrity: sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA==}
|
||||||
|
|
||||||
|
@ -6316,6 +6339,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':
|
||||||
|
@ -6382,6 +6407,11 @@ snapshots:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/unist': 3.0.3
|
'@types/unist': 3.0.3
|
||||||
|
|
||||||
|
'@types/node-fetch@2.6.12':
|
||||||
|
dependencies:
|
||||||
|
'@types/node': 22.15.18
|
||||||
|
form-data: 4.0.3
|
||||||
|
|
||||||
'@types/node@17.0.45': {}
|
'@types/node@17.0.45': {}
|
||||||
|
|
||||||
'@types/node@22.15.18':
|
'@types/node@22.15.18':
|
||||||
|
@ -6704,6 +6734,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
|
||||||
|
@ -6940,6 +6972,8 @@ snapshots:
|
||||||
|
|
||||||
async-function@1.0.0: {}
|
async-function@1.0.0: {}
|
||||||
|
|
||||||
|
asynckit@0.4.0: {}
|
||||||
|
|
||||||
autoprefixer@10.4.14(postcss@8.5.1):
|
autoprefixer@10.4.14(postcss@8.5.1):
|
||||||
dependencies:
|
dependencies:
|
||||||
browserslist: 4.25.0
|
browserslist: 4.25.0
|
||||||
|
@ -7129,6 +7163,10 @@ snapshots:
|
||||||
color-convert: 2.0.1
|
color-convert: 2.0.1
|
||||||
color-string: 1.9.1
|
color-string: 1.9.1
|
||||||
|
|
||||||
|
combined-stream@1.0.8:
|
||||||
|
dependencies:
|
||||||
|
delayed-stream: 1.0.0
|
||||||
|
|
||||||
comma-separated-tokens@2.0.3: {}
|
comma-separated-tokens@2.0.3: {}
|
||||||
|
|
||||||
commander@14.0.0: {}
|
commander@14.0.0: {}
|
||||||
|
@ -7375,10 +7413,14 @@ snapshots:
|
||||||
|
|
||||||
defu@6.1.4: {}
|
defu@6.1.4: {}
|
||||||
|
|
||||||
|
delayed-stream@1.0.0: {}
|
||||||
|
|
||||||
dequal@2.0.3: {}
|
dequal@2.0.3: {}
|
||||||
|
|
||||||
destr@2.0.5: {}
|
destr@2.0.5: {}
|
||||||
|
|
||||||
|
detect-europe-js@0.1.2: {}
|
||||||
|
|
||||||
detect-libc@2.0.4: {}
|
detect-libc@2.0.4: {}
|
||||||
|
|
||||||
deterministic-object-hash@2.0.2:
|
deterministic-object-hash@2.0.2:
|
||||||
|
@ -7948,16 +7990,15 @@ snapshots:
|
||||||
cross-spawn: 7.0.6
|
cross-spawn: 7.0.6
|
||||||
signal-exit: 4.1.0
|
signal-exit: 4.1.0
|
||||||
|
|
||||||
fraction.js@4.3.7: {}
|
form-data@4.0.3:
|
||||||
|
|
||||||
framer-motion@12.15.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0):
|
|
||||||
dependencies:
|
dependencies:
|
||||||
motion-dom: 12.15.0
|
asynckit: 0.4.0
|
||||||
motion-utils: 12.12.1
|
combined-stream: 1.0.8
|
||||||
tslib: 2.8.1
|
es-set-tostringtag: 2.1.0
|
||||||
optionalDependencies:
|
hasown: 2.0.2
|
||||||
react: 19.1.0
|
mime-types: 2.1.35
|
||||||
react-dom: 19.1.0(react@19.1.0)
|
|
||||||
|
fraction.js@4.3.7: {}
|
||||||
|
|
||||||
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:
|
||||||
|
@ -8393,6 +8434,8 @@ snapshots:
|
||||||
dependencies:
|
dependencies:
|
||||||
call-bound: 1.0.4
|
call-bound: 1.0.4
|
||||||
|
|
||||||
|
is-standalone-pwa@0.1.1: {}
|
||||||
|
|
||||||
is-string@1.1.1:
|
is-string@1.1.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
call-bound: 1.0.4
|
call-bound: 1.0.4
|
||||||
|
@ -8481,6 +8524,8 @@ snapshots:
|
||||||
|
|
||||||
jiti@2.4.2: {}
|
jiti@2.4.2: {}
|
||||||
|
|
||||||
|
js-confetti@0.12.0: {}
|
||||||
|
|
||||||
js-tokens@4.0.0: {}
|
js-tokens@4.0.0: {}
|
||||||
|
|
||||||
js-yaml@4.1.0:
|
js-yaml@4.1.0:
|
||||||
|
@ -9004,6 +9049,12 @@ snapshots:
|
||||||
braces: 3.0.3
|
braces: 3.0.3
|
||||||
picomatch: 2.3.1
|
picomatch: 2.3.1
|
||||||
|
|
||||||
|
mime-db@1.52.0: {}
|
||||||
|
|
||||||
|
mime-types@2.1.35:
|
||||||
|
dependencies:
|
||||||
|
mime-db: 1.52.0
|
||||||
|
|
||||||
mime@3.0.0: {}
|
mime@3.0.0: {}
|
||||||
|
|
||||||
min-indent@1.0.1: {}
|
min-indent@1.0.1: {}
|
||||||
|
@ -9038,20 +9089,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: {}
|
||||||
|
@ -10078,6 +10115,18 @@ snapshots:
|
||||||
|
|
||||||
typescript@5.6.3: {}
|
typescript@5.6.3: {}
|
||||||
|
|
||||||
|
ua-is-frozen@0.1.2: {}
|
||||||
|
|
||||||
|
ua-parser-js@2.0.3:
|
||||||
|
dependencies:
|
||||||
|
'@types/node-fetch': 2.6.12
|
||||||
|
detect-europe-js: 0.1.2
|
||||||
|
is-standalone-pwa: 0.1.1
|
||||||
|
node-fetch: 2.7.0
|
||||||
|
ua-is-frozen: 0.1.2
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- encoding
|
||||||
|
|
||||||
ufo@1.6.1: {}
|
ufo@1.6.1: {}
|
||||||
|
|
||||||
ultrahtml@1.6.0: {}
|
ultrahtml@1.6.0: {}
|
||||||
|
|
13
src/assets/sponsors/blacksmith-logo-dark.svg
Normal file
13
src/assets/sponsors/blacksmith-logo-dark.svg
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
<svg width="408" height="96" viewBox="0 0 408 96" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M214.062 57.7319H220.318V64.7607H213.28V58.5127H189.038V70.2275H214.062V76.4756H220.318V89.752H214.062V96H188.256V89.752H181.999V82.7231H189.038V88.9712H213.28V77.2563H188.256V71.0083H181.999V57.7319H188.256V51.4839H214.062V57.7319Z" fill="#2e2e2e"/>
|
||||||
|
<path d="M246.906 57.7319H252.38V51.4839H265.675V57.7319H271.93V96H264.892V58.5127H253.162V96H246.124V58.5127H234.394V96H227.356V57.7319H233.612V51.4839H246.906V57.7319Z" fill="#2e2e2e"/>
|
||||||
|
<path d="M292.263 57.7319H297.737V51.4839H311.031V58.5127H298.519V88.9712H311.031V96H297.737V89.752H292.263V96H278.969V88.9712H291.481V58.5127H278.969V51.4839H292.263V57.7319Z" fill="#2e2e2e"/>
|
||||||
|
<path d="M337.619 57.7319H343.093V51.4839H362.644V58.5127H343.876V96H336.837V58.5127H318.069V51.4839H337.619V57.7319Z" fill="#2e2e2e"/>
|
||||||
|
<path d="M376.719 70.2275H400.962V51.4839H408V96H400.962V77.2563H376.719V96H369.682V51.4839H376.719V70.2275Z" fill="#2e2e2e"/>
|
||||||
|
<path d="M144 95.937H48.0007L47.9841 94.6992C47.327 68.7964 26.0955 48 0 48V0.0629883H144V95.937Z" fill="#2e2e2e"/>
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M214.062 6.24805H220.318V19.5244H214.062V24.9917H220.318V38.2681H214.062V44.5161H181.999V24.9917H188.256V19.5244H181.999V0H214.062V6.24805ZM189.038 37.4873H213.28V25.7725H189.038V37.4873ZM189.038 18.7437H213.28V7.02881H189.038V18.7437Z" fill="#2e2e2e"/>
|
||||||
|
<path d="M237.516 37.4873H262.54V44.5161H230.478V0H237.516V37.4873Z" fill="#2e2e2e"/>
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M304.763 6.24805H311.019V44.5161H303.981V25.7725H279.739V44.5161H272.701V6.24805H278.957V0H304.763V6.24805ZM279.739 18.7437H303.981V7.02881H279.739V18.7437Z" fill="#2e2e2e"/>
|
||||||
|
<path d="M353.241 6.24805H359.498V13.2769H352.46V7.02881H328.217V37.4873H352.46V31.2393H359.498V38.2681H353.241V44.5161H327.435V38.2681H321.179V6.24805H327.435V0H353.241V6.24805Z" fill="#2e2e2e"/>
|
||||||
|
<path d="M376.696 18.7437H394.682V12.4956H400.938V0H407.976V13.2769H401.72V19.5244H395.464V24.9917H401.72V31.2393H407.976V44.5161H400.938V32.0205H394.682V25.7725H376.696V44.5161H369.658V0H376.696V18.7437Z" fill="#2e2e2e"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 2.1 KiB |
13
src/assets/sponsors/tutaLogo-dark.svg
Normal file
13
src/assets/sponsors/tutaLogo-dark.svg
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
<svg width="1407" height="500" viewBox="0 0 1407 500" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<g clip-path="url(#clip0_239_62)">
|
||||||
|
<path d="M1200.9 290.615C1200.9 246.523 1234.56 194.179 1291.91 194.179H1334.26L1319.06 242.181C1301.03 299.309 1273.23 335.14 1239.13 335.14C1214.37 335.14 1200.9 318.196 1200.9 290.615ZM986.088 266.512C965.018 334.06 997.167 380.54 1070.15 380.54C1080.8 380.54 1094.69 379.665 1099.04 378.585C1101.21 377.937 1102.29 376.847 1102.94 374.892L1115.54 332.754C1116.18 330.367 1115.54 328.412 1111.84 328.628C1100.76 329.719 1090.77 330.583 1081.65 330.583C1044.94 330.583 1029.95 311.037 1040.82 276.069L1066.44 193.963H1140.95C1142.9 193.963 1144.64 192.872 1145.5 190.486L1158.75 147.916C1159.4 145.529 1158.11 143.79 1155.28 143.79H1082.08L1091.64 112.732C1092.29 110.561 1091.85 109.255 1090.12 107.743L1051.45 72.7753C1049.28 70.8206 1046.89 71.4686 1046.02 74.298L986.077 266.523L986.088 266.512ZM729.565 268.024C708.064 336.663 742.599 381.62 796.898 381.62C828.172 381.62 852.718 366.631 870.321 342.084L869.889 373.574C869.889 376.62 871.412 377.484 874.014 377.484H908.766C911.379 377.484 912.459 376.393 913.334 374.006L983.269 148.337C984.133 145.518 983.053 143.78 980.008 143.78H930.483C928.085 143.78 926.789 144.87 925.915 147.257L896.152 242.397C877.686 301.263 848.366 334.924 817.74 334.924C787.113 334.924 774.079 310.81 785.591 273.888L824.91 148.348C825.774 145.529 824.694 143.79 821.649 143.79H772.124C769.738 143.79 768.431 144.881 767.556 147.268L729.543 268.034L729.565 268.024ZM560.148 266.512C539.079 334.06 571.228 380.54 644.209 380.54C654.846 380.54 668.755 379.665 673.096 378.585C675.267 377.937 676.358 376.847 677.005 374.892L689.597 332.754C690.245 330.367 689.381 328.412 685.904 328.628C674.824 329.719 664.835 330.583 655.71 330.583C619.003 330.583 604.014 311.037 614.878 276.069L640.504 193.963H715.008C716.962 193.963 718.701 192.872 719.565 190.486L732.815 147.916C733.463 145.529 732.167 143.79 729.338 143.79H656.142L665.699 112.732C666.347 110.561 665.915 109.255 664.176 107.743L625.515 72.7753C623.345 70.8206 620.947 71.4686 620.083 74.298L560.137 266.523L560.148 266.512ZM1220.88 381.631C1249.99 381.631 1274.09 368.596 1292.34 342.095V373.585C1292.34 376.188 1293.86 377.494 1296.25 377.494H1331C1333.39 377.494 1334.69 376.404 1335.56 374.017L1406.15 148.132C1406.8 145.745 1405.5 143.79 1403.11 143.79H1293.42C1186.34 143.79 1144.64 236.317 1144.64 296.263C1144.42 348.607 1177.22 381.62 1220.88 381.62" fill="#2e2e2e"/>
|
||||||
|
<path d="M40.6017 5L126.617 91.4471C128.571 93.4017 130.526 94.0497 133.571 94.0497H500.213C502.826 94.0497 504.338 91.0043 501.736 88.6177L416.79 3.04536C414.835 1.09071 412.88 0 408.755 0H42.5564C39.0791 0 38.6471 3.04536 40.6017 5Z" fill="#2e2e2e"/>
|
||||||
|
<path d="M27.999 496.091C27.5671 498.045 28.431 500 31.0444 500H388.561C392.038 500 393.561 498.477 394.641 495.443L502.815 148.348C503.906 144.87 502.373 143.791 499.338 143.791H140.731C137.686 143.791 136.822 144.881 135.731 147.268L27.999 496.091Z" fill="#2e2e2e"/>
|
||||||
|
<path d="M0.850098 396.177C0.850098 400.086 5.8501 400.086 6.93001 396.177L88.8199 130.54C89.9106 127.494 89.9106 125.54 87.2972 122.937L5.8501 42.5701C3.89545 40.3994 0.850098 41.4794 0.850098 44.0927V396.177Z" fill="#2e2e2e"/>
|
||||||
|
</g>
|
||||||
|
<defs>
|
||||||
|
<clipPath id="clip0_239_62">
|
||||||
|
<rect width="1405.48" height="500" fill="white" transform="translate(0.850098)"/>
|
||||||
|
</clipPath>
|
||||||
|
</defs>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 3.3 KiB |
|
@ -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,6 @@
|
||||||
---
|
---
|
||||||
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 Description from './Description.astro'
|
||||||
|
|
||||||
import Video from './Video.astro'
|
import Video from './Video.astro'
|
||||||
|
|
||||||
const locale = getLocale(Astro)
|
const locale = getLocale(Astro)
|
||||||
|
@ -19,96 +16,61 @@ interface Props {
|
||||||
}
|
}
|
||||||
|
|
||||||
const { titles } = Astro.props
|
const { titles } = Astro.props
|
||||||
|
|
||||||
const descriptions = Object.values(features.featureTabs).map(tab => tab.description)
|
const descriptions = Object.values(features.featureTabs).map(tab => tab.description)
|
||||||
---
|
---
|
||||||
|
|
||||||
<section id="Features" class="relative flex w-full flex-col py-12 text-start lg:py-36">
|
<section id="features" class="relative flex w-full flex-col py-12 text-start lg:py-36">
|
||||||
<Description class="mb-2 text-4xl font-bold sm:text-6xl">
|
<Description class="mb-2 text-4xl font-bold sm:text-6xl">
|
||||||
{
|
{
|
||||||
(titles || features.titles).map((title, index) =>
|
(titles || features.titles).map((title, index) =>
|
||||||
title !== '\n' ? (
|
title !== '\n' ? (
|
||||||
<motion.span client:load {...getTitleAnimation(0.2 + index * 0.2)}>
|
<span class="title-line opacity-0" data-index={index}>
|
||||||
{title}
|
{title}
|
||||||
</motion.span>
|
</span>
|
||||||
) : (
|
) : (
|
||||||
<br class="hidden md:block" />
|
<br class="hidden md:block" />
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
</Description>
|
</Description>
|
||||||
<motion.p client:load {...getTitleAnimation(0.6)} className="lg:w-1/2">
|
|
||||||
|
<p class="feature-description-text opacity-0 lg:w-1/2">
|
||||||
{features.description}
|
{features.description}
|
||||||
</motion.p>
|
</p>
|
||||||
|
|
||||||
<div class="mt-6 flex flex-col gap-6 lg:flex-row lg:justify-between lg:gap-2">
|
<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">
|
<div class="flex w-full flex-col lg:w-1/3">
|
||||||
<!-- Mobile tabs -->
|
<!-- Mobile Tabs -->
|
||||||
<div class="flex gap-2 overflow-x-auto overflow-y-clip lg:hidden">
|
<div class="flex gap-2 overflow-x-auto overflow-y-clip lg:hidden">
|
||||||
<motion.button
|
{
|
||||||
client:load
|
Object.entries(features.featureTabs).map(([key, tab], i) => (
|
||||||
{...getTitleAnimation()}
|
<button
|
||||||
className="feature-tab whitespace-nowrap"
|
class="feature-tab whitespace-nowrap opacity-0"
|
||||||
data-active="true"
|
data-index={i}
|
||||||
>
|
data-feature-key={key}
|
||||||
{features.featureTabs.workspaces.title}
|
data-active={i === 0 ? 'true' : undefined}
|
||||||
</motion.button>
|
>
|
||||||
<motion.button
|
{tab.title}
|
||||||
client:load
|
</button>
|
||||||
{...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>
|
</div>
|
||||||
|
|
||||||
<!-- Desktop features list -->
|
<!-- Desktop features list -->
|
||||||
<div id="features-list" class="hidden lg:flex lg:flex-col lg:gap-3">
|
<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">
|
Object.entries(features.featureTabs).map(([key, tab], i) => (
|
||||||
{features.featureTabs.workspaces.title}
|
<div
|
||||||
</Description>
|
class="feature opacity-0"
|
||||||
<Description>
|
data-index={i}
|
||||||
{features.featureTabs.workspaces.description}
|
data-feature-key={key}
|
||||||
</Description>
|
data-active={i === 0 ? 'true' : undefined}
|
||||||
</motion.div>
|
>
|
||||||
<motion.div client:load {...getTitleAnimation(1)} className="feature">
|
<Description class="text-2xl font-bold">{tab.title}</Description>
|
||||||
<Description class="text-2xl font-bold">
|
<Description>{tab.description}</Description>
|
||||||
{features.featureTabs.compactMode.title}
|
</div>
|
||||||
</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>
|
</div>
|
||||||
|
|
||||||
<!-- Mobile description -->
|
<!-- Mobile description -->
|
||||||
|
@ -116,6 +78,7 @@ const descriptions = Object.values(features.featureTabs).map(tab => tab.descript
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Video Section -->
|
||||||
<div class="sticky top-6 h-fit w-full lg:w-3/5">
|
<div class="sticky top-6 h-fit w-full lg:w-3/5">
|
||||||
<div class="relative w-full">
|
<div class="relative w-full">
|
||||||
<div class="video-stack relative h-full w-full">
|
<div class="video-stack relative h-full w-full">
|
||||||
|
@ -162,54 +125,58 @@ const descriptions = Object.values(features.featureTabs).map(tab => tab.descript
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import { animate, stagger } from 'animejs'
|
||||||
|
|
||||||
|
// Animate Titles
|
||||||
|
animate('.title-line', {
|
||||||
|
opacity: [0, 1],
|
||||||
|
delay: stagger(200, { start: 200 }),
|
||||||
|
duration: 600,
|
||||||
|
easing: 'easeOutQuad',
|
||||||
|
})
|
||||||
|
|
||||||
|
animate('.feature-description-text', {
|
||||||
|
opacity: [0, 1],
|
||||||
|
delay: 600,
|
||||||
|
duration: 600,
|
||||||
|
easing: 'easeOutQuad',
|
||||||
|
})
|
||||||
|
|
||||||
|
animate('.feature-tab, .feature', {
|
||||||
|
opacity: [0, 1],
|
||||||
|
delay: stagger(200, { start: 800 }),
|
||||||
|
duration: 500,
|
||||||
|
easing: 'easeOutQuad',
|
||||||
|
})
|
||||||
|
|
||||||
const features = document.querySelectorAll('.feature, .feature-tab') as NodeListOf<HTMLElement>
|
const features = document.querySelectorAll('.feature, .feature-tab') as NodeListOf<HTMLElement>
|
||||||
|
const descriptionEl = document.querySelector('.feature-description') as HTMLDivElement | null
|
||||||
// Set initial description
|
|
||||||
const descriptionEl = document.querySelector('.feature-description') as HTMLDivElement
|
|
||||||
const descriptions = descriptionEl?.dataset.descriptions?.split('|||')
|
const descriptions = descriptionEl?.dataset.descriptions?.split('|||')
|
||||||
if (descriptionEl && descriptions) {
|
const videos = document.querySelectorAll('.feature-video') as NodeListOf<HTMLVideoElement>
|
||||||
descriptionEl.textContent = descriptions[0]
|
|
||||||
}
|
|
||||||
|
|
||||||
function changeToFeature({ target }: MouseEvent | { target: HTMLElement }) {
|
function changeToFeature({ target }: { target: HTMLElement }) {
|
||||||
let targetEl: HTMLElement | null = target as HTMLElement
|
const targetEl = target?.closest('.feature, .feature-tab') as HTMLElement | null
|
||||||
|
if (!targetEl) return
|
||||||
|
|
||||||
if (target instanceof HTMLElement) {
|
const index = parseInt(targetEl.dataset.index as string, 10)
|
||||||
targetEl = target.closest('.feature, .feature-tab')
|
if (isNaN(index)) return
|
||||||
}
|
|
||||||
|
|
||||||
if (!targetEl) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const index = Array.from(features).indexOf(targetEl) % 4
|
|
||||||
|
|
||||||
if (index === -1) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update both mobile and desktop elements
|
|
||||||
for (let i = 0; i < features.length; i += 1) {
|
|
||||||
const f = features[i]
|
|
||||||
|
|
||||||
|
// Toggle active states
|
||||||
|
features.forEach((el, i) => {
|
||||||
if (i % 4 === index) {
|
if (i % 4 === index) {
|
||||||
f.setAttribute('data-active', 'true')
|
el.setAttribute('data-active', 'true')
|
||||||
} else {
|
} else {
|
||||||
f.removeAttribute('data-active')
|
el.removeAttribute('data-active')
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
|
|
||||||
// Update mobile description
|
// Update mobile description
|
||||||
const descriptionEl = document.querySelector('.feature-description')
|
|
||||||
|
|
||||||
if (descriptionEl && descriptions) {
|
if (descriptionEl && descriptions) {
|
||||||
descriptionEl.textContent = descriptions[index]
|
descriptionEl.textContent = descriptions[index]
|
||||||
}
|
}
|
||||||
|
|
||||||
const videos = document.querySelectorAll<HTMLVideoElement>('.feature-video')
|
// Animate videos
|
||||||
|
videos.forEach((vid, i) => {
|
||||||
for (let i = 0; i < videos.length; i += 1) {
|
|
||||||
const vid = videos[i]
|
|
||||||
const yOffset = (i - index) * 20
|
const yOffset = (i - index) * 20
|
||||||
const zOffset = i === index ? 0 : -100 - Math.abs(i - index) * 50
|
const zOffset = i === index ? 0 : -100 - Math.abs(i - index) * 50
|
||||||
const scale = i === index ? 1 : 0.95
|
const scale = i === index ? 1 : 0.95
|
||||||
|
@ -224,22 +191,17 @@ const descriptions = Object.values(features.featureTabs).map(tab => tab.descript
|
||||||
vid.play()
|
vid.play()
|
||||||
} else {
|
} else {
|
||||||
vid.removeAttribute('data-active')
|
vid.removeAttribute('data-active')
|
||||||
vid.style.transform = `translate3d(-50%, ${yOffset}px, ${zOffset}px)
|
vid.style.transform = `translate3d(-50%, ${yOffset}px, ${zOffset}px) rotate3d(1, 0, 0, ${rotation}deg) scale(${scale})`
|
||||||
rotate3d(1, 0, 0, ${rotation}deg)
|
|
||||||
scale(${scale})`
|
|
||||||
vid.style.zIndex = String(1 - Math.abs(i - index))
|
vid.style.zIndex = String(1 - Math.abs(i - index))
|
||||||
vid.pause()
|
vid.pause()
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
}
|
|
||||||
|
|
||||||
for (const feature of features) {
|
|
||||||
feature.addEventListener('click', changeToFeature)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set up event listeners
|
||||||
|
features.forEach(f => f.addEventListener('click', changeToFeature as unknown as EventListener))
|
||||||
changeToFeature({ target: features[0] })
|
changeToFeature({ target: features[0] })
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.feature {
|
.feature {
|
||||||
@apply w-full cursor-pointer select-none rounded-lg p-4 opacity-0 hover:bg-subtle;
|
@apply w-full cursor-pointer select-none rounded-lg p-4 opacity-0 hover:bg-subtle;
|
||||||
|
|
|
@ -24,9 +24,11 @@ const {
|
||||||
class="flex w-full flex-col gap-4 text-center lg:w-1/2 lg:text-left"
|
class="flex w-full flex-col gap-4 text-center lg:w-1/2 lg:text-left"
|
||||||
aria-labelledby="footer-title"
|
aria-labelledby="footer-title"
|
||||||
>
|
>
|
||||||
<Description id="footer-title" class="text-6xl font-bold !text-paper"
|
<a href="/">
|
||||||
>{footer.title}</Description
|
<Description id="footer-title" class="text-6xl font-bold !text-paper"
|
||||||
>
|
>{footer.title}</Description
|
||||||
|
>
|
||||||
|
</a>
|
||||||
<Description class="mx-auto max-w-xl lg:mx-0">
|
<Description class="mx-auto max-w-xl lg:mx-0">
|
||||||
{footer.description}
|
{footer.description}
|
||||||
</Description>
|
</Description>
|
||||||
|
@ -115,6 +117,11 @@ const {
|
||||||
>{footer.reportAnIssue}</a
|
>{footer.reportAnIssue}</a
|
||||||
>
|
>
|
||||||
</li>
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="https://github.com/zen-browser/desktop/security/policy" class="font-normal"
|
||||||
|
>{footer.securtiy}</a
|
||||||
|
>
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</nav>
|
</nav>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -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,12 +1,11 @@
|
||||||
---
|
---
|
||||||
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)
|
||||||
|
|
||||||
import tutaLogo from '~/assets/sponsors/tutaLogo_monochrome.svg'
|
import tutaLogo from '~/assets/sponsors/tutaLogo-dark.svg'
|
||||||
|
import blacksmithLogo from '~/assets/sponsors/blacksmith-logo-dark.svg'
|
||||||
|
|
||||||
import Image from 'astro/components/Image.astro'
|
import Image from 'astro/components/Image.astro'
|
||||||
const { showSponsors = true } = Astro.props
|
const { showSponsors = true } = Astro.props
|
||||||
|
@ -19,23 +18,64 @@ 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
|
||||||
src={tutaLogo}
|
class="sponsors__sponsor relative mt-4 flex flex-col items-center justify-center gap-8 md:flex-row md:gap-12"
|
||||||
alt={sponsors.sponsors['tuta'].name}
|
>
|
||||||
class="h-16 w-fit object-contain"
|
<a href={sponsors.sponsors['blacksmith'].url} target="_blank" class="w-fit">
|
||||||
/>
|
<Image
|
||||||
</a>
|
src={blacksmithLogo}
|
||||||
</motion.span>
|
alt={sponsors.sponsors['blacksmith'].name}
|
||||||
|
class="h-16 w-fit object-contain dark:invert"
|
||||||
|
/>
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
href={sponsors.sponsors['tuta'].url}
|
||||||
|
target="_blank"
|
||||||
|
class="w-fit"
|
||||||
|
style="transform: translateY(20px); opacity: 0.001; filter: blur(4px)"
|
||||||
|
>
|
||||||
|
<Image
|
||||||
|
src={tutaLogo}
|
||||||
|
alt={sponsors.sponsors['tuta'].name}
|
||||||
|
class="h-16 w-fit object-contain dark:invert"
|
||||||
|
/>
|
||||||
|
</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>
|
||||||
|
|
|
@ -1,43 +1,63 @@
|
||||||
<script>
|
<script>
|
||||||
|
import JSConfetti from 'js-confetti'
|
||||||
|
import { UAParser } from 'ua-parser-js'
|
||||||
|
|
||||||
// Handle platform selection
|
// Handle platform selection
|
||||||
const platformButtons = document.querySelectorAll('.platform-selector')
|
|
||||||
const platformSections = document.querySelectorAll('.platform-section')
|
const platformSections = document.querySelectorAll('.platform-section')
|
||||||
|
|
||||||
// Function to detect OS and select appropriate platform
|
// Function to detect OS and select appropriate platform
|
||||||
function detectOS() {
|
function detectOS() {
|
||||||
const userAgent = window.navigator.userAgent
|
const userAgent = window.navigator.userAgent
|
||||||
|
const { cpu, os } = UAParser(userAgent)
|
||||||
let detectedOS = 'mac' // Default to macOS
|
let detectedOS = 'mac' // Default to macOS
|
||||||
|
|
||||||
if (userAgent.indexOf('Windows') !== -1) {
|
if (os.name === 'Windows') {
|
||||||
detectedOS = 'windows'
|
detectedOS = 'windows'
|
||||||
} else if (userAgent.indexOf('Linux') !== -1) {
|
} else if (os.name === 'Linux' || os.name === 'Ubuntu') {
|
||||||
detectedOS = 'linux'
|
detectedOS = 'linux'
|
||||||
}
|
}
|
||||||
|
|
||||||
return detectedOS
|
let arch = cpu.architecture || 'x86_64' // Default to x86_64
|
||||||
|
if (arch === 'amd64' || arch === 'x86_64') {
|
||||||
|
arch = 'x86_64'
|
||||||
|
} else if (arch === 'arm64' || arch === 'aarch64') {
|
||||||
|
arch = 'aarch64'
|
||||||
|
} else if (arch === 'arm') {
|
||||||
|
arch = 'arm64' // Treat arm as arm64 for simplicity
|
||||||
|
}
|
||||||
|
|
||||||
|
return { os: detectedOS, cpu: arch }
|
||||||
|
}
|
||||||
|
|
||||||
|
function getRandomNumber(min: number, max: number) {
|
||||||
|
return Math.floor(Math.random() * (max - min + 1)) + min
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize platform based on user's OS
|
// Initialize platform based on user's OS
|
||||||
async function initializePlatform() {
|
async function initializePlatform() {
|
||||||
const detectedOS = detectOS()
|
const detectedOS = detectOS()
|
||||||
selectPlatform(detectedOS)
|
selectPlatform(detectedOS.os, detectedOS.cpu)
|
||||||
|
|
||||||
|
for (const button of document.querySelectorAll('.download-button')) {
|
||||||
|
button.addEventListener('click', () => {
|
||||||
|
const jsConfetti = new JSConfetti()
|
||||||
|
jsConfetti.addConfetti({
|
||||||
|
confettiNumber: 100,
|
||||||
|
// If we get a 1 to 10k chance, show only burger confetti
|
||||||
|
...(getRandomNumber(1, 10000) === 1 ? { emojis: ['🍔'] } : {}),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Function to select a platform
|
// Function to select a platform
|
||||||
async function selectPlatform(platform: string) {
|
async function selectPlatform(platform: string, cpu = 'x86_64') {
|
||||||
// Update button styling
|
|
||||||
for (const button of platformButtons) {
|
|
||||||
const buttonPlatform = button.getAttribute('data-platform')
|
|
||||||
if (buttonPlatform === platform) {
|
|
||||||
button.setAttribute('data-active', 'true')
|
|
||||||
} else {
|
|
||||||
button.setAttribute('data-active', 'false')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Show/hide platform sections
|
// Show/hide platform sections
|
||||||
for (const section of platformSections) {
|
for (const section of platformSections) {
|
||||||
if (section.id === `${platform}-downloads`) {
|
if (
|
||||||
|
section.getAttribute('data-platform') === platform &&
|
||||||
|
section.getAttribute('data-cpu') === cpu
|
||||||
|
) {
|
||||||
section.setAttribute('data-active', 'true')
|
section.setAttribute('data-active', 'true')
|
||||||
} else {
|
} else {
|
||||||
section.setAttribute('data-active', 'false')
|
section.setAttribute('data-active', 'false')
|
||||||
|
@ -45,14 +65,6 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle platform button clicks
|
|
||||||
for (const button of platformButtons) {
|
|
||||||
button.addEventListener('click', () => {
|
|
||||||
const platform = button.getAttribute('data-platform') ?? ''
|
|
||||||
selectPlatform(platform)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check for twilight mode
|
// Check for twilight mode
|
||||||
async function checkTwilightMode() {
|
async function checkTwilightMode() {
|
||||||
const urlParams = new URLSearchParams(window.location.search)
|
const urlParams = new URLSearchParams(window.location.search)
|
||||||
|
|
|
@ -1,4 +1,7 @@
|
||||||
---
|
---
|
||||||
|
import ArrowRightIcon from '~/icons/ArrowRightIcon.astro'
|
||||||
|
import Button from '../Button.astro'
|
||||||
|
|
||||||
interface ReleaseInfo {
|
interface ReleaseInfo {
|
||||||
label?: string
|
label?: string
|
||||||
link: string
|
link: string
|
||||||
|
@ -24,130 +27,74 @@ interface PlatformReleases {
|
||||||
interface Props {
|
interface Props {
|
||||||
platform: 'mac' | 'windows' | 'linux'
|
platform: 'mac' | 'windows' | 'linux'
|
||||||
icon: string[]
|
icon: string[]
|
||||||
title: string
|
|
||||||
description: string
|
description: string
|
||||||
releases: PlatformReleases
|
releases: PlatformReleases
|
||||||
|
cpu: 'x86_64' | 'aarch64' | 'arm64'
|
||||||
}
|
}
|
||||||
|
|
||||||
const { platform, icon, title, description, releases } = Astro.props
|
const { platform, icon, releases, cpu } = Astro.props
|
||||||
import { Image } from 'astro:assets'
|
|
||||||
import AppIconDark from '../../assets/app-icon-dark.png'
|
|
||||||
import AppIconLight from '../../assets/app-icon-light.png'
|
|
||||||
import DownloadCard from './ButtonCard.astro'
|
|
||||||
|
|
||||||
function isFlatReleaseInfo(obj: unknown): obj is ReleaseInfo {
|
function isFlatReleaseInfo(obj: unknown): obj is ReleaseInfo {
|
||||||
return !!obj && typeof obj === 'object' && 'link' in obj
|
return !!obj && typeof obj === 'object' && 'link' in obj
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getReleaseLink(releases: PlatformReleases, { os, cpu }: { os: string; cpu?: string }) {
|
||||||
|
if (os === 'mac') {
|
||||||
|
return releases.universal?.link || releases.all?.link || ''
|
||||||
|
} else {
|
||||||
|
if (cpu === 'x86_64') {
|
||||||
|
if (isFlatReleaseInfo(releases.x86_64)) {
|
||||||
|
return releases.flathub?.all.link || releases.x86_64.link || ''
|
||||||
|
}
|
||||||
|
return releases.x86_64?.tarball?.link ? releases.x86_64?.tarball?.link : ''
|
||||||
|
} else if (cpu === 'aarch64') {
|
||||||
|
return releases.aarch64?.tarball?.link || ''
|
||||||
|
} else if (cpu === 'arm64') {
|
||||||
|
return releases.arm64?.link || ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
---
|
---
|
||||||
|
|
||||||
<div
|
<div
|
||||||
id={`${platform}-downloads`}
|
id={`${platform}-${cpu}-downloads`}
|
||||||
data-active={platform === 'mac'}
|
data-active={platform === 'mac'}
|
||||||
class="platform-section data-[active='false']:hidden"
|
class="platform-section data-[active='false']:hidden"
|
||||||
|
data-cpu={cpu}
|
||||||
|
data-platform={platform}
|
||||||
>
|
>
|
||||||
<div class="items-center gap-8 md:flex">
|
<div class="items-center gap-8 md:flex">
|
||||||
<div class="mb-8 md:mb-0 md:w-2/3">
|
<div class="mx-auto flex w-full justify-center gap-4">
|
||||||
<div class="mb-4 flex items-center gap-3">
|
<Button
|
||||||
<div class="download-card__icon">
|
class="download-button w-fit"
|
||||||
<Fragment set:html={icon} />
|
href={getReleaseLink(releases, { os: platform, cpu })}
|
||||||
</div>
|
isPrimary
|
||||||
<h3 class="text-2xl font-medium">{title}</h3>
|
>
|
||||||
</div>
|
<Fragment set:html={icon} />
|
||||||
<p class="text-muted-foreground mb-6" set:html={description} />
|
Download for {
|
||||||
<div class="space-y-6">
|
platform === 'mac' ? 'MacOS' : platform.charAt(0).toUpperCase() + platform.slice(1)
|
||||||
{
|
|
||||||
platform === 'linux' ? (
|
|
||||||
<>
|
|
||||||
{releases.flathub && releases.flathub.all.label && (
|
|
||||||
<article class="flathub-download data-[twilight='true']:hidden">
|
|
||||||
<h4 class="mb-3 text-lg font-medium">Package Managers</h4>
|
|
||||||
<div class="space-y-3">
|
|
||||||
<DownloadCard
|
|
||||||
label={releases.flathub.all.label}
|
|
||||||
href={releases.flathub.all.link}
|
|
||||||
variant="flathub"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</article>
|
|
||||||
)}
|
|
||||||
{releases.x86_64 &&
|
|
||||||
typeof releases.x86_64 === 'object' &&
|
|
||||||
'tarball' in releases.x86_64 &&
|
|
||||||
releases.x86_64.tarball && (
|
|
||||||
<article>
|
|
||||||
<h4 class="mb-3 text-lg font-medium">x86_64</h4>
|
|
||||||
<div class="">
|
|
||||||
{releases.x86_64.tarball && (
|
|
||||||
<DownloadCard
|
|
||||||
label={releases.x86_64.tarball.label ? releases.x86_64.tarball.label : ''}
|
|
||||||
href={releases.x86_64.tarball.link ? releases.x86_64.tarball.link : ''}
|
|
||||||
variant="x86_64"
|
|
||||||
checksum={releases.x86_64.tarball.checksum}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</article>
|
|
||||||
)}
|
|
||||||
{releases.aarch64 &&
|
|
||||||
typeof releases.aarch64 === 'object' &&
|
|
||||||
'tarball' in releases.aarch64 &&
|
|
||||||
releases.aarch64.tarball && (
|
|
||||||
<article>
|
|
||||||
<h4 class="mb-3 text-lg font-medium">ARM64</h4>
|
|
||||||
<div class="gap-3">
|
|
||||||
{releases.aarch64.tarball && (
|
|
||||||
<DownloadCard
|
|
||||||
label={
|
|
||||||
releases.aarch64.tarball.label ? releases.aarch64.tarball.label : ''
|
|
||||||
}
|
|
||||||
href={releases.aarch64.tarball.link ? releases.aarch64.tarball.link : ''}
|
|
||||||
variant="aarch64"
|
|
||||||
checksum={releases.aarch64.tarball.checksum}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</article>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<div class="flex flex-col gap-4">
|
|
||||||
<div class="flex flex-col gap-3">
|
|
||||||
{releases.universal && releases.universal.label && (
|
|
||||||
<DownloadCard
|
|
||||||
label={releases.universal.label}
|
|
||||||
href={releases.universal.link}
|
|
||||||
checksum={releases.universal.checksum}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{releases.x86_64 && isFlatReleaseInfo(releases.x86_64) && releases.x86_64.label && (
|
|
||||||
<DownloadCard
|
|
||||||
label={releases.x86_64.label}
|
|
||||||
href={releases.x86_64.link}
|
|
||||||
checksum={releases.x86_64.checksum}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{releases.arm64 && releases.arm64.label && (
|
|
||||||
<DownloadCard
|
|
||||||
label={releases.arm64.label}
|
|
||||||
href={releases.arm64.link}
|
|
||||||
checksum={releases.arm64.checksum}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
</div>
|
</Button>
|
||||||
</div>
|
{
|
||||||
<div
|
platform === 'linux' && (
|
||||||
class="download-browser-logo flex justify-center text-coral transition-colors data-[twilight='true']:text-zen-blue md:w-1/3"
|
<Button
|
||||||
>
|
class="download-button w-fit"
|
||||||
<Image src={AppIconDark} alt="Zen Browser" class="w-32 translate-y-6 transform dark:hidden" />
|
href={releases.flathub?.all.link || ''}
|
||||||
<Image
|
aria-label="Download from Flathub"
|
||||||
src={AppIconLight}
|
target="_blank"
|
||||||
alt="Zen Browser"
|
rel="noopener noreferrer"
|
||||||
class="hidden w-32 translate-y-6 transform dark:block"
|
>
|
||||||
/>
|
Flathub
|
||||||
|
<ArrowRightIcon class="size-4" />
|
||||||
|
</Button>
|
||||||
|
)
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<style is:global>
|
||||||
|
.download-button .svg-inline--fa {
|
||||||
|
@apply size-5;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
|
@ -49,12 +49,16 @@
|
||||||
"tuta": {
|
"tuta": {
|
||||||
"name": "Tuta",
|
"name": "Tuta",
|
||||||
"url": "https://tuta.com/"
|
"url": "https://tuta.com/"
|
||||||
|
},
|
||||||
|
"blacksmith": {
|
||||||
|
"name": "BlackSmith",
|
||||||
|
"url": "https://www.blacksmith.sh/"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"community": {
|
"community": {
|
||||||
"title": ["Our ", "Core ", "Values"],
|
"title": ["Our ", "Core ", "Values"],
|
||||||
"description": "We make it not only a priority, but a necessity to ensure that Zen always strikes the right balance between beauty, performance, and privacy. We are committed to making Zen the most beautiful, productive, and privacy-respecting browser out there — without compromising on your experience.",
|
"description": "We make it not only a priority, but a necessity to ensure that Zen always strikes the right balance between beauty, performance, and privacy.",
|
||||||
"lists": {
|
"lists": {
|
||||||
"freeAndOpenSource": {
|
"freeAndOpenSource": {
|
||||||
"title": "Free and open-source",
|
"title": "Free and open-source",
|
||||||
|
@ -262,11 +266,13 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"download": {
|
"download": {
|
||||||
"title": "Download Zen",
|
"title": "Start your journey",
|
||||||
"description": "Download Zen for your platform and experience a more mindful internet browsing experience. All downloads include SHA256 checksums for verification.",
|
"description": "Download Zen for your platform and experience a more mindful internet browsing experience.<br/>Zen is available for macOS, Windows, and Linux.",
|
||||||
"twilightInfo": "You're currently in Twilight mode, this means you're downloading the latest experimental features and updates.",
|
"twilightInfo": "You're currently in Twilight mode, this means you're downloading the latest experimental features and updates.",
|
||||||
|
"beta": "Zen is currently in ",
|
||||||
|
"otherDownload": "If your device has been wrongly detected, <a href='https://github.com/zen-browser/desktop/releases/latest/' class='zen-link'>see other downloads</a>.<br />Please report any issues you encounter on <a href='https://github.com/zen-browser/desktop/issues/new/choose' class='zen-link ml-[1px]'>GitHub</a>.",
|
||||||
"alertInfo": {
|
"alertInfo": {
|
||||||
"description": "<strong class='font-medium text-zen-blue'>Twilight Mode:</strong> You're currently in Twilight mode, this means you're downloading the latest experimental features and updates."
|
"description": "You're currently in Twilight mode, this means you're downloading the latest experimental features and updates."
|
||||||
},
|
},
|
||||||
"platformSelector": {
|
"platformSelector": {
|
||||||
"title": "Platform Selector",
|
"title": "Platform Selector",
|
||||||
|
@ -290,10 +296,7 @@
|
||||||
"platformNames": {
|
"platformNames": {
|
||||||
"mac": "macOS",
|
"mac": "macOS",
|
||||||
"windows": "Windows",
|
"windows": "Windows",
|
||||||
"linux": "Linux",
|
"linux": "Linux"
|
||||||
"macDownload": "MacOS Download",
|
|
||||||
"windowsDownload": "Windows Download",
|
|
||||||
"linuxDownload": "Linux Download"
|
|
||||||
},
|
},
|
||||||
"platformDescriptions": {
|
"platformDescriptions": {
|
||||||
"mac": "Works on both new Apple (M-Series) and older Intel Macs.<br />Requires macOS 11.0 or later.",
|
"mac": "Works on both new Apple (M-Series) and older Intel Macs.<br />Requires macOS 11.0 or later.",
|
||||||
|
@ -448,7 +451,7 @@
|
||||||
},
|
},
|
||||||
"download": {
|
"download": {
|
||||||
"title": "Download - Zen",
|
"title": "Download - Zen",
|
||||||
"description": "Download Zen for your platform and experience a more mindful internet browsing experience. All downloads include SHA256 checksums for verification."
|
"description": "Download Zen for your platform and experience a more mindful internet browsing experience."
|
||||||
},
|
},
|
||||||
"privacyPolicy": {
|
"privacyPolicy": {
|
||||||
"title": "Privacy Policy - Zen",
|
"title": "Privacy Policy - Zen",
|
||||||
|
@ -476,6 +479,7 @@
|
||||||
"zenMods": "Zen Mods",
|
"zenMods": "Zen Mods",
|
||||||
"releaseNotes": "Release Notes",
|
"releaseNotes": "Release Notes",
|
||||||
"getHelp": "Get Help",
|
"getHelp": "Get Help",
|
||||||
|
"securtiy": "Security",
|
||||||
"discord": "Discord",
|
"discord": "Discord",
|
||||||
"uptimeStatus": "Uptime Status",
|
"uptimeStatus": "Uptime Status",
|
||||||
"reportAnIssue": "Report an Issue",
|
"reportAnIssue": "Report an Issue",
|
||||||
|
|
|
@ -49,6 +49,10 @@
|
||||||
"tuta": {
|
"tuta": {
|
||||||
"name": "Tuta",
|
"name": "Tuta",
|
||||||
"url": "https://tuta.com/"
|
"url": "https://tuta.com/"
|
||||||
|
},
|
||||||
|
"blacksmith": {
|
||||||
|
"name": "BlackSmith",
|
||||||
|
"url": "https://www.blacksmith.sh/"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -262,11 +266,13 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"download": {
|
"download": {
|
||||||
"title": "Zenをダウンロードする",
|
"title": "Ready to experience Zen?",
|
||||||
"description": "お使いのプラットフォーム向けにZenをダウンロード。すべてのダウンロードにはSHA256チェックサムが含まれています",
|
"description": "お使いのプラットフォーム向けにZenをダウンロード。すべてのダウンロードにはSHA256チェックサムが含まれています",
|
||||||
"twilightInfo": "現在Twilightモードです。最新の実験的機能とアップデートをダウンロードしています。",
|
"twilightInfo": "現在Twilightモードです。最新の実験的機能とアップデートをダウンロードしています。",
|
||||||
|
"beta": "Zen is currently in ",
|
||||||
|
"otherDownload": "If your device got wrongly detected, <a href='https://github.com/zen-browser/desktop/releases/latest/' class='zen-link ml-[1px]'>see other downloads</a>.<br />Please report any issues you encounter on <a href='https://github.com/zen-browser/desktop/issues/new/choose' class='zen-link ml-[1px]'>GitHub</a>.",
|
||||||
"alertInfo": {
|
"alertInfo": {
|
||||||
"description": "<strong class='font-medium text-zen-blue'>Twilightモード:</strong> 現在Twilightモードで、最新の実験的機能とアップデートをダウンロードしています。"
|
"description": "You're currently in Twilight mode, this means you're downloading the latest experimental features and updates."
|
||||||
},
|
},
|
||||||
"platformSelector": {
|
"platformSelector": {
|
||||||
"title": "プラットフォーム選択",
|
"title": "プラットフォーム選択",
|
||||||
|
@ -290,10 +296,7 @@
|
||||||
"platformNames": {
|
"platformNames": {
|
||||||
"mac": "macOS",
|
"mac": "macOS",
|
||||||
"windows": "Windows",
|
"windows": "Windows",
|
||||||
"linux": "Linux",
|
"linux": "Linux"
|
||||||
"macDownload": "MacOSダウンロード",
|
|
||||||
"windowsDownload": "Windowsダウンロード",
|
|
||||||
"linuxDownload": "Linuxダウンロード"
|
|
||||||
},
|
},
|
||||||
"platformDescriptions": {
|
"platformDescriptions": {
|
||||||
"mac": "Apple(Mシリーズ)・Intel両対応。<br />macOS 11.0以降が必要です。",
|
"mac": "Apple(Mシリーズ)・Intel両対応。<br />macOS 11.0以降が必要です。",
|
||||||
|
@ -481,6 +484,7 @@
|
||||||
"zenMods": "Zen Mods",
|
"zenMods": "Zen Mods",
|
||||||
"releaseNotes": "リリースノート",
|
"releaseNotes": "リリースノート",
|
||||||
"getHelp": "ヘルプ",
|
"getHelp": "ヘルプ",
|
||||||
|
"securtiy": "Security",
|
||||||
"discord": "Discord",
|
"discord": "Discord",
|
||||||
"uptimeStatus": "稼働状況",
|
"uptimeStatus": "稼働状況",
|
||||||
"reportAnIssue": "問題を報告",
|
"reportAnIssue": "問題を報告",
|
||||||
|
|
|
@ -9,8 +9,10 @@ import { getLocale, getUI } from '~/utils/i18n'
|
||||||
|
|
||||||
import { icon, library } from '@fortawesome/fontawesome-svg-core'
|
import { icon, library } from '@fortawesome/fontawesome-svg-core'
|
||||||
import { faApple, faGithub, faLinux, faWindows } from '@fortawesome/free-brands-svg-icons'
|
import { faApple, faGithub, faLinux, faWindows } from '@fortawesome/free-brands-svg-icons'
|
||||||
import ExternalLinkIcon from '~/icons/ExternalLink.astro'
|
import Image from 'astro/components/Image.astro'
|
||||||
import LockIcon from '~/icons/LockIcon.astro'
|
|
||||||
|
import AppIconDark from '~/assets/app-icon-dark.png'
|
||||||
|
import AppIconLight from '~/assets/app-icon-light.png'
|
||||||
|
|
||||||
export { getStaticPaths } from '~/utils/i18n'
|
export { getStaticPaths } from '~/utils/i18n'
|
||||||
|
|
||||||
|
@ -24,28 +26,40 @@ library.add(faWindows, faLinux, faApple, faGithub)
|
||||||
const windowsIcon = icon({ prefix: 'fab', iconName: 'windows' })
|
const windowsIcon = icon({ prefix: 'fab', iconName: 'windows' })
|
||||||
const linuxIcon = icon({ prefix: 'fab', iconName: 'linux' })
|
const linuxIcon = icon({ prefix: 'fab', iconName: 'linux' })
|
||||||
const appleIcon = icon({ prefix: 'fab', iconName: 'apple' })
|
const appleIcon = icon({ prefix: 'fab', iconName: 'apple' })
|
||||||
const githubIcon = icon({ prefix: 'fab', iconName: 'github' })
|
|
||||||
|
|
||||||
const checksums = await getChecksums()
|
const checksums = await getChecksums()
|
||||||
const releases = getReleasesWithChecksums(locale)(checksums)
|
const releases = getReleasesWithChecksums(locale)(checksums)
|
||||||
|
|
||||||
const platformNames = download.platformNames
|
|
||||||
const platformDescriptions = download.platformDescriptions
|
const platformDescriptions = download.platformDescriptions
|
||||||
---
|
---
|
||||||
|
|
||||||
<DownloadScript />
|
<DownloadScript />
|
||||||
|
|
||||||
<Layout title={layout.download.title} description={layout.download.description}>
|
<Layout title={layout.download.title} description={layout.download.description}>
|
||||||
<main class="flex min-h-screen flex-col px-6 data-[os='windows']:bg-zen-blue">
|
<main class="mt-48 flex flex-col px-6 data-[os='windows']:bg-zen-blue">
|
||||||
<div class="container relative mx-auto py-12">
|
<div class="container relative mx-auto pb-48">
|
||||||
<div class="mb-6 mt-12 flex flex-col gap-4">
|
<div class="mb-6 flex flex-col gap-4">
|
||||||
<Description id="download-title" class="text-6xl font-bold">{download.title}</Description>
|
<div class="download-browser-logo mx-auto">
|
||||||
<Description class="max-w-xl text-pretty">
|
<Image
|
||||||
{download.description}
|
src={AppIconDark}
|
||||||
|
alt="Zen Browser"
|
||||||
|
class="w-32 translate-y-3 transform dark:hidden"
|
||||||
|
/>
|
||||||
|
<Image
|
||||||
|
src={AppIconLight}
|
||||||
|
alt="Zen Browser"
|
||||||
|
class="hidden w-32 translate-y-3 transform dark:block"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<Description id="download-title" class="mx-auto max-w-3xl text-center text-6xl font-bold"
|
||||||
|
>{download.title}</Description
|
||||||
|
>
|
||||||
|
<Description class="mx-auto text-pretty text-center">
|
||||||
|
<Fragment set:html={download.description} />
|
||||||
</Description>
|
</Description>
|
||||||
<div
|
<div
|
||||||
id="twilight-info"
|
id="twilight-info"
|
||||||
class="hidden max-w-xl items-center justify-center gap-3 text-pretty rounded-xl border border-zen-blue/20 bg-zen-blue/5 p-4 text-left data-[twilight='true']:flex"
|
class="mx-auto hidden max-w-xl items-center justify-center gap-3 text-pretty rounded-xl border border-zen-blue/20 bg-zen-blue/5 p-4 text-left text-center data-[twilight='true']:flex"
|
||||||
>
|
>
|
||||||
<svg
|
<svg
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
@ -65,140 +79,72 @@ const platformDescriptions = download.platformDescriptions
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Platform selector -->
|
|
||||||
<div class="mb-6 flex">
|
|
||||||
<div
|
|
||||||
class="inline-flex rounded-3xl bg-[rgba(255,255,255,0.4)] p-2 shadow-md dark:bg-[rgba(0,0,0,0.3)] dark:shadow-sm"
|
|
||||||
>
|
|
||||||
<button
|
|
||||||
class="platform-selector rounded-2xl px-5 py-2.5 transition-all duration-200 data-[active='true']:bg-subtle"
|
|
||||||
data-platform="mac"
|
|
||||||
>
|
|
||||||
<span class="flex items-center gap-2">
|
|
||||||
<Fragment set:html={appleIcon.html} />
|
|
||||||
<span>{platformNames.mac}</span>
|
|
||||||
</span>
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
class="platform-selector rounded-2xl px-5 py-2.5 transition-all duration-200 data-[active='true']:bg-subtle"
|
|
||||||
data-platform="windows"
|
|
||||||
>
|
|
||||||
<span class="flex items-center gap-2">
|
|
||||||
<Fragment set:html={windowsIcon.html} />
|
|
||||||
<span>{platformNames.windows}</span>
|
|
||||||
</span>
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
class="platform-selector rounded-2xl px-5 py-2.5 transition-all duration-200 data-[active='true']:bg-subtle"
|
|
||||||
data-platform="linux"
|
|
||||||
>
|
|
||||||
<span class="flex items-center gap-2">
|
|
||||||
<Fragment set:html={linuxIcon.html} />
|
|
||||||
<span>{platformNames.linux}</span>
|
|
||||||
</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Download Cards -->
|
<!-- Download Cards -->
|
||||||
<div
|
<div class="relative overflow-hidden">
|
||||||
class="relative mb-16 overflow-hidden rounded-3xl bg-[rgba(255,255,255,0.4)] p-8 shadow-xl md:p-10 dark:bg-[rgba(0,0,0,0.3)]"
|
|
||||||
>
|
|
||||||
<!-- MacOS Download -->
|
<!-- MacOS Download -->
|
||||||
<PlatformDownload
|
<PlatformDownload
|
||||||
platform="mac"
|
platform="mac"
|
||||||
icon={appleIcon.html}
|
icon={appleIcon.html}
|
||||||
title={platformNames.macDownload}
|
|
||||||
description={platformDescriptions.mac}
|
description={platformDescriptions.mac}
|
||||||
releases={releases.macos}
|
releases={releases.macos}
|
||||||
|
cpu="x86_64"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<PlatformDownload
|
||||||
|
platform="mac"
|
||||||
|
icon={appleIcon.html}
|
||||||
|
description={platformDescriptions.mac}
|
||||||
|
releases={releases.macos}
|
||||||
|
cpu="arm64"
|
||||||
|
/>
|
||||||
|
|
||||||
<!-- Windows Download -->
|
<!-- Windows Download -->
|
||||||
<PlatformDownload
|
<PlatformDownload
|
||||||
platform="windows"
|
platform="windows"
|
||||||
icon={windowsIcon.html}
|
icon={windowsIcon.html}
|
||||||
title={platformNames.windowsDownload}
|
|
||||||
description={platformDescriptions.windows}
|
description={platformDescriptions.windows}
|
||||||
releases={releases.windows}
|
releases={releases.windows}
|
||||||
|
cpu="x86_64"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<PlatformDownload
|
||||||
|
platform="windows"
|
||||||
|
icon={windowsIcon.html}
|
||||||
|
description={platformDescriptions.windows}
|
||||||
|
releases={releases.windows}
|
||||||
|
cpu="arm64"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<!-- Linux Download -->
|
<!-- Linux Download -->
|
||||||
<PlatformDownload
|
<PlatformDownload
|
||||||
platform="linux"
|
platform="linux"
|
||||||
icon={linuxIcon.html}
|
icon={linuxIcon.html}
|
||||||
title={platformNames.linuxDownload}
|
|
||||||
description={platformDescriptions.linux}
|
description={platformDescriptions.linux}
|
||||||
releases={releases.linux}
|
releases={releases.linux}
|
||||||
|
cpu="x86_64"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<PlatformDownload
|
||||||
|
platform="linux"
|
||||||
|
icon={linuxIcon.html}
|
||||||
|
description={platformDescriptions.linux}
|
||||||
|
releases={releases.linux}
|
||||||
|
cpu="aarch64"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Additional resources -->
|
<div class="mt-20 flex flex-col items-center justify-center gap-2 text-center">
|
||||||
<section class="mb-16">
|
<div class="flex cursor-default items-center justify-center gap-3 text-sm font-bold">
|
||||||
<h2 class="mb-4 text-3xl font-semibold">
|
{download.beta}
|
||||||
{download.additionalResources.title}
|
<span
|
||||||
</h2>
|
class="leading-20 caption inline-block flex h-6 items-center rounded-md border border-dark px-2 text-xs"
|
||||||
|
>BETA</span
|
||||||
<div class="grid grid-cols-1 gap-6 md:grid-cols-2">
|
|
||||||
<a
|
|
||||||
href="https://github.com/zen-browser"
|
|
||||||
class="group rounded-2xl bg-[rgba(255,255,255,0.4)] p-6 transition-all duration-300 hover:shadow-lg dark:bg-[rgba(0,0,0,0.3)]"
|
|
||||||
>
|
>
|
||||||
<div class="flex items-start justify-between">
|
|
||||||
<div>
|
|
||||||
<h3 class="mb-2 text-xl font-medium">
|
|
||||||
{download.additionalResources.sourceCode.title}
|
|
||||||
</h3>
|
|
||||||
<p class="text-muted-foreground">
|
|
||||||
{download.additionalResources.sourceCode.description}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="grid aspect-square h-12 w-12 place-items-center rounded-xl border border-subtle transition-all duration-100 hover:bg-coral hover:bg-opacity-10 group-hover:border-coral group-hover:border-opacity-10"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="text-muted-foreground transition-all duration-200 group-hover:text-coral"
|
|
||||||
>
|
|
||||||
<Fragment set:html={githubIcon.html} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</a>
|
|
||||||
|
|
||||||
<a
|
|
||||||
href="https://docs.zen-browser.app"
|
|
||||||
class="group rounded-2xl bg-[rgba(255,255,255,0.4)] p-6 transition-all duration-200 hover:shadow-lg dark:bg-[rgba(0,0,0,0.3)]"
|
|
||||||
>
|
|
||||||
<div class="flex items-start justify-between">
|
|
||||||
<div>
|
|
||||||
<h3 class="mb-2 text-xl font-medium">
|
|
||||||
{download.additionalResources.documentation.title}
|
|
||||||
</h3>
|
|
||||||
<p class="text-muted-foreground">
|
|
||||||
{download.additionalResources.documentation.description}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="rounded-xl border border-subtle p-3 transition-all duration-100 hover:bg-coral hover:bg-opacity-10 group-hover:border-coral group-hover:border-opacity-20"
|
|
||||||
>
|
|
||||||
<ExternalLinkIcon
|
|
||||||
class="h-5 w-5 transition-all duration-200 group-hover:text-coral"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</a>
|
|
||||||
</div>
|
</div>
|
||||||
</section>
|
<div
|
||||||
|
class="max-w-[55ch] cursor-default text-balance text-sm font-normal text-black/50 dark:text-white/50"
|
||||||
<!-- Security Notice -->
|
>
|
||||||
<div class="grid grid-cols-[auto,1fr] gap-4 rounded-2xl bg-subtle bg-opacity-10 p-6">
|
<Fragment set:html={download.otherDownload} />
|
||||||
<div class="h-fit rounded-xl bg-subtle p-3">
|
|
||||||
<LockIcon class="h-5 w-5" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<h3 class="mb-2 text-lg font-medium">
|
|
||||||
{download.securityNotice.title}
|
|
||||||
</h3>
|
|
||||||
<p class="text-muted-foreground" set:html={download.securityNotice.description} />
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"version": "xxx",
|
"version": "1.12.11t",
|
||||||
"extra": "",
|
"extra": "This update includes some attempts at performance improvements, better workspace management and some bug fixes!",
|
||||||
"fixes": [],
|
"fixes": [],
|
||||||
"features": []
|
"features": []
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,12 +4,6 @@ import { beforeEach, describe, expect, it } from 'vitest'
|
||||||
import PlatformDownload from '~/components/download/PlatformDownload.astro'
|
import PlatformDownload from '~/components/download/PlatformDownload.astro'
|
||||||
|
|
||||||
const mockIcon = ['<svg></svg>']
|
const mockIcon = ['<svg></svg>']
|
||||||
const mockReleases = {
|
|
||||||
universal: { label: 'Universal', link: '/universal', checksum: 'abc123' },
|
|
||||||
x86_64: { label: 'x86_64', link: '/x86_64', checksum: 'def456' },
|
|
||||||
arm64: { label: 'ARM64', link: '/arm64', checksum: 'ghi789' },
|
|
||||||
flathub: { all: { label: 'Flathub', link: '/flathub' } },
|
|
||||||
}
|
|
||||||
|
|
||||||
describe('<PlatformDownload />', () => {
|
describe('<PlatformDownload />', () => {
|
||||||
let container: Awaited<ReturnType<typeof AstroContainer.create>>
|
let container: Awaited<ReturnType<typeof AstroContainer.create>>
|
||||||
|
@ -17,37 +11,6 @@ describe('<PlatformDownload />', () => {
|
||||||
container = await AstroContainer.create()
|
container = await AstroContainer.create()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('renders mac platform', async () => {
|
|
||||||
const result = await container.renderToString(PlatformDownload, {
|
|
||||||
props: {
|
|
||||||
platform: 'mac',
|
|
||||||
icon: mockIcon,
|
|
||||||
title: 'Mac Title',
|
|
||||||
description: 'Mac Desc',
|
|
||||||
releases: mockReleases,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
expect(result).toContain('Mac Title')
|
|
||||||
expect(result).toContain('Mac Desc')
|
|
||||||
expect(result).toContain('Universal')
|
|
||||||
})
|
|
||||||
|
|
||||||
it('renders windows platform', async () => {
|
|
||||||
const result = await container.renderToString(PlatformDownload, {
|
|
||||||
props: {
|
|
||||||
platform: 'windows',
|
|
||||||
icon: mockIcon,
|
|
||||||
title: 'Win Title',
|
|
||||||
description: 'Win Desc',
|
|
||||||
releases: mockReleases,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
expect(result).toContain('Win Title')
|
|
||||||
expect(result).toContain('Win Desc')
|
|
||||||
expect(result).toContain('x86_64')
|
|
||||||
expect(result).toContain('ARM64')
|
|
||||||
})
|
|
||||||
|
|
||||||
it('renders linux platform with flathub and tarball', async () => {
|
it('renders linux platform with flathub and tarball', async () => {
|
||||||
const linuxReleases = {
|
const linuxReleases = {
|
||||||
flathub: { all: { label: 'Flathub', link: '/flathub' } },
|
flathub: { all: { label: 'Flathub', link: '/flathub' } },
|
||||||
|
@ -63,16 +26,10 @@ describe('<PlatformDownload />', () => {
|
||||||
props: {
|
props: {
|
||||||
platform: 'linux',
|
platform: 'linux',
|
||||||
icon: mockIcon,
|
icon: mockIcon,
|
||||||
title: 'Linux Title',
|
|
||||||
description: 'Linux Desc',
|
|
||||||
releases: linuxReleases,
|
releases: linuxReleases,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
expect(result).toContain('Linux Title')
|
|
||||||
expect(result).toContain('Linux Desc')
|
|
||||||
expect(result).toContain('Flathub')
|
expect(result).toContain('Flathub')
|
||||||
expect(result).toContain('Tarball')
|
|
||||||
expect(result).toContain('x86_64')
|
|
||||||
})
|
})
|
||||||
|
|
||||||
it('renders linux platform with all branches', async () => {
|
it('renders linux platform with all branches', async () => {
|
||||||
|
@ -97,30 +54,11 @@ describe('<PlatformDownload />', () => {
|
||||||
props: {
|
props: {
|
||||||
platform: 'linux',
|
platform: 'linux',
|
||||||
icon: mockIcon,
|
icon: mockIcon,
|
||||||
title: 'Linux Title',
|
|
||||||
description: 'Linux Desc',
|
|
||||||
releases: linuxReleases,
|
releases: linuxReleases,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
// Test basic content
|
|
||||||
expect(result).toContain('Linux Title')
|
|
||||||
expect(result).toContain('Linux Desc')
|
|
||||||
|
|
||||||
// Test Flathub section
|
// Test Flathub section
|
||||||
expect(result).toContain('Flathub')
|
|
||||||
expect(result).toContain('/flathub')
|
expect(result).toContain('/flathub')
|
||||||
|
|
||||||
// Test x86_64 section
|
|
||||||
expect(result).toContain('x86_64')
|
|
||||||
expect(result).toContain('Tarball x86_64')
|
|
||||||
expect(result).toContain('/tarball-x86_64')
|
|
||||||
expect(result).toContain('sha256')
|
|
||||||
|
|
||||||
// Test ARM64 section
|
|
||||||
expect(result).toContain('ARM64')
|
|
||||||
expect(result).toContain('Tarball ARM64')
|
|
||||||
expect(result).toContain('/tarball-arm64')
|
|
||||||
expect(result).toContain('sha256-arm64')
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -3,38 +3,44 @@ import { expect, test, type BrowserContextOptions, type Page } from '@playwright
|
||||||
import { getReleasesWithChecksums } from '~/components/download/release-data'
|
import { getReleasesWithChecksums } from '~/components/download/release-data'
|
||||||
import { CONSTANT } from '~/constants'
|
import { CONSTANT } from '~/constants'
|
||||||
|
|
||||||
// Helper to get the platform section by id
|
const getPlatformSection = (page: Page, platform: string, cpu: string) => {
|
||||||
const getPlatformSection = (page: Page, platform: string) =>
|
return page.locator(`#${platform}-${cpu}-downloads.platform-section`)
|
||||||
page.locator(`#${platform}-downloads.platform-section[data-active='true']`)
|
}
|
||||||
|
|
||||||
// Helper to get the platform tab button
|
const platformConfigs: {
|
||||||
const getPlatformButton = (page: Page, platform: string) =>
|
name: string
|
||||||
page.locator(`button.platform-selector[data-platform='${platform}']`)
|
userAgent: string
|
||||||
|
platform: string
|
||||||
const platformConfigs: { name: string; userAgent: string; platform: string }[] = [
|
expectedCpu: string
|
||||||
|
}[] = [
|
||||||
{
|
{
|
||||||
name: 'windows',
|
name: 'windows',
|
||||||
userAgent:
|
userAgent:
|
||||||
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
|
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
|
||||||
platform: 'Win32',
|
platform: 'Win32',
|
||||||
|
expectedCpu: 'x86_64',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'mac',
|
name: 'mac',
|
||||||
userAgent:
|
userAgent:
|
||||||
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.1 Safari/605.1.15',
|
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.1 Safari/605.1.15',
|
||||||
platform: 'MacIntel',
|
platform: 'MacIntel',
|
||||||
|
expectedCpu: 'x86_64',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'linux',
|
name: 'linux',
|
||||||
userAgent:
|
userAgent:
|
||||||
'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
|
'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
|
||||||
platform: 'Linux x86_64',
|
platform: 'Linux x86_64',
|
||||||
|
expectedCpu: 'x86_64',
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
test.describe('Download page default tab per platform', () => {
|
test.describe('Download page shows correct platform section per platform', () => {
|
||||||
for (const { name, userAgent, platform } of platformConfigs) {
|
for (const { name, userAgent, platform, expectedCpu } of platformConfigs) {
|
||||||
test(`shows correct default tab for ${name} platform`, async ({ browser }) => {
|
test(`shows correct platform section for ${name} ${expectedCpu} platform`, async ({
|
||||||
|
browser,
|
||||||
|
}) => {
|
||||||
const context = await browser.newContext({
|
const context = await browser.newContext({
|
||||||
userAgent,
|
userAgent,
|
||||||
locale: 'en-US',
|
locale: 'en-US',
|
||||||
|
@ -42,66 +48,64 @@ test.describe('Download page default tab per platform', () => {
|
||||||
} as BrowserContextOptions)
|
} as BrowserContextOptions)
|
||||||
const page = await context.newPage()
|
const page = await context.newPage()
|
||||||
await page.goto('/download')
|
await page.goto('/download')
|
||||||
await expect(getPlatformSection(page, name)).toBeVisible()
|
|
||||||
await expect(getPlatformButton(page, name)).toHaveAttribute('data-active', 'true')
|
await page.waitForLoadState('domcontentloaded')
|
||||||
// Other platforms should not be active
|
|
||||||
for (const other of platformConfigs.filter(p => p.name !== name)) {
|
await expect(getPlatformSection(page, name, expectedCpu)).toBeVisible()
|
||||||
await expect(getPlatformSection(page, other.name)).toBeHidden()
|
|
||||||
await expect(getPlatformButton(page, other.name)).not.toHaveAttribute('data-active', 'true')
|
for (const other of platformConfigs.filter(
|
||||||
|
p => !(p.name === name && p.expectedCpu === expectedCpu)
|
||||||
|
)) {
|
||||||
|
const otherSection = page.locator(
|
||||||
|
`#${other.name}-${other.expectedCpu}-downloads.platform-section`
|
||||||
|
)
|
||||||
|
await expect(otherSection).toBeHidden()
|
||||||
}
|
}
|
||||||
await context.close()
|
await context.close()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
test.describe('Download page platform detection and tab switching', () => {
|
|
||||||
test('shows correct platform section and tab when switching platforms', async ({ page }) => {
|
|
||||||
await page.goto('/download')
|
|
||||||
const platforms = ['windows', 'mac', 'linux']
|
|
||||||
for (const platform of platforms) {
|
|
||||||
await getPlatformButton(page, platform).click()
|
|
||||||
await expect(getPlatformSection(page, platform)).toBeVisible()
|
|
||||||
await expect(getPlatformButton(page, platform)).toHaveAttribute('data-active', 'true')
|
|
||||||
// other platform sections should be hidden
|
|
||||||
for (const otherPlatform of platforms.filter(p => p !== platform)) {
|
|
||||||
await expect(getPlatformSection(page, otherPlatform)).toBeHidden()
|
|
||||||
await expect(getPlatformButton(page, otherPlatform)).not.toHaveAttribute(
|
|
||||||
'data-active',
|
|
||||||
'true'
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
test.describe('Download page download links', () => {
|
test.describe('Download page download links', () => {
|
||||||
const releases = getReleasesWithChecksums('en')(CONSTANT.CHECKSUMS)
|
const releases = getReleasesWithChecksums('en')(CONSTANT.CHECKSUMS)
|
||||||
|
|
||||||
type Releases = ReturnType<ReturnType<typeof getReleasesWithChecksums>>
|
type Releases = ReturnType<ReturnType<typeof getReleasesWithChecksums>>
|
||||||
function getPlatformLinks(releases: Releases) {
|
function getPlatformLinks(releases: Releases) {
|
||||||
return {
|
return {
|
||||||
mac: [releases.macos.universal],
|
'mac-x86_64': [releases.macos.universal],
|
||||||
windows: [releases.windows.x86_64, releases.windows.arm64],
|
'mac-arm64': [releases.macos.universal],
|
||||||
linux: [
|
'windows-x86_64': [releases.windows.x86_64],
|
||||||
releases.linux.x86_64.tarball,
|
'windows-arm64': [releases.windows.arm64],
|
||||||
releases.linux.aarch64.tarball,
|
'linux-x86_64': [releases.linux.x86_64.tarball, releases.linux.flathub.all],
|
||||||
releases.linux.flathub.all,
|
'linux-aarch64': [releases.linux.aarch64.tarball, releases.linux.flathub.all],
|
||||||
],
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
test('all platform download links are correct', async ({ page }) => {
|
test('all platform download links are correct', async ({ page }) => {
|
||||||
const platforms = ['windows', 'mac', 'linux']
|
|
||||||
const platformLinkSelectors = getPlatformLinks(releases)
|
const platformLinkSelectors = getPlatformLinks(releases)
|
||||||
await page.goto('/download')
|
await page.goto('/download')
|
||||||
await page.waitForLoadState('domcontentloaded')
|
await page.waitForLoadState('domcontentloaded')
|
||||||
for (const platform of platforms) {
|
|
||||||
await getPlatformButton(page, platform).click()
|
for (const [platformCpu, links] of Object.entries(platformLinkSelectors)) {
|
||||||
for (const { label, link } of platformLinkSelectors[
|
const platform = platformCpu.split('-')[0] as 'mac' | 'windows' | 'linux'
|
||||||
platform as keyof typeof platformLinkSelectors
|
|
||||||
]) {
|
for (const { link } of links) {
|
||||||
const downloadLink = page.locator(`#${platform}-downloads .download-link[href="${link}"]`)
|
const downloadLink = page.locator(
|
||||||
await expect(downloadLink).toContainText(label)
|
`#${platformCpu}-downloads .download-button[href="${link}"]`
|
||||||
|
)
|
||||||
|
|
||||||
|
const isFlathubLink = link.includes('flathub.org')
|
||||||
|
|
||||||
|
if (isFlathubLink) {
|
||||||
|
await expect(downloadLink).toContainText('Flathub')
|
||||||
|
} else {
|
||||||
|
await expect(downloadLink).toContainText(
|
||||||
|
`Download for ${
|
||||||
|
platform === 'mac' ? 'MacOS' : platform.charAt(0).toUpperCase() + platform.slice(1)
|
||||||
|
}`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
await expect(downloadLink).toHaveAttribute('href', link)
|
await expect(downloadLink).toHaveAttribute('href', link)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue