diff --git a/.github/workflows/ci-pipeline.yml b/.github/workflows/ci-pipeline.yml
index b6f13c0..d6eec5a 100644
--- a/.github/workflows/ci-pipeline.yml
+++ b/.github/workflows/ci-pipeline.yml
@@ -64,15 +64,6 @@ jobs:
if: steps.check-node-modules-cache.outputs.cache-hit != 'true'
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:
name: ${{ matrix.name }}
needs: [check_changes, setup]
@@ -102,6 +93,7 @@ jobs:
restore-keys: ${{ runner.os }}-turbo-${{ matrix.check }}-
- name: Restore node_modules cache
+ id: cache
uses: actions/cache/restore@v4
with:
path: |
@@ -121,6 +113,7 @@ jobs:
run_install: false
- name: Install dependencies
+ if: steps.cache.outputs.cache-hit != 'true'
run: pnpm install --frozen-lockfile
- name: Run ${{ matrix.name }}
@@ -128,7 +121,7 @@ jobs:
playwright:
name: Playwright Tests
- needs: [check_changes, setup, quality_checks]
+ needs: [check_changes, setup]
if: ${{ needs.check_changes.outputs.exists == 'true' }}
runs-on: ubuntu-latest
steps:
@@ -144,6 +137,7 @@ jobs:
restore-keys: ${{ runner.os }}-turbo-
- name: Restore node_modules cache
+ id: cache
uses: actions/cache/restore@v4
with:
path: |
@@ -163,6 +157,7 @@ jobs:
run_install: false
- name: Install dependencies
+ if: steps.cache.outputs.cache-hit != 'true'
run: pnpm install --frozen-lockfile
- name: Cache Playwright Browsers
@@ -177,6 +172,7 @@ jobs:
- name: Run Playwright Tests
run: pnpm exec turbo run test:playwright
+ timeout-minutes: 10
verify:
name: Verify
diff --git a/cspell.json b/cspell.json
index 164c581..a507659 100644
--- a/cspell.json
+++ b/cspell.json
@@ -5,21 +5,22 @@
"words": [
"adam",
"AMOLED",
+ "animejs",
"Astronav",
- "Briel",
"brhm",
"Brhm",
+ "Briel",
"bryan",
- "Canoa",
"canoa",
+ "Canoa",
"cfasync",
"createdAsc",
"createdDefault",
"createdDesc",
"daniel",
- "FMPEG",
"ferrocyante",
"flatpaks",
+ "FMPEG",
"Galdámez",
"García",
"Garro",
@@ -33,14 +34,14 @@
"Jokagi",
"junicode",
"Junicode",
- "kristijanribaric",
"Kristijan",
+ "kristijanribaric",
"laggy",
"larvey",
"Larvey",
+ "linaarchsum",
"linuxarmsum",
"linuxsum",
- "linaarchsum",
"mfsa",
"mozilla",
"Nehalem",
@@ -51,12 +52,13 @@
"patreon",
"Pdzly",
"Ribaric",
+ "securtiy",
"taroj",
- "Turborepo",
"testid",
"theming",
- "tuta",
"tsconfigs",
+ "Turborepo",
+ "tuta",
"unfloatable",
"unfocusing",
"unrs",
@@ -66,8 +68,8 @@
"VAAPI",
"wmfcdm",
"workerd",
- "XPCOM",
"xmark",
+ "XPCOM",
"yumemi",
"zsync"
],
diff --git a/package.json b/package.json
index 41f0341..aba27ee 100644
--- a/package.json
+++ b/package.json
@@ -37,23 +37,25 @@
"@fortawesome/free-solid-svg-icons": "6.7.1",
"@types/react": "19.1.6",
"@types/react-dom": "19.1.5",
+ "animejs": "4.0.2",
"astro": "5.7.10",
"astro-navbar": "2.3.7",
- "turbo": "2.5.4",
"autoprefixer": "10.4.14",
"clsx": "2.1.1",
"date-fns": "4.1.0",
"free-astro-components": "1.2.0",
"jiti": "2.4.2",
+ "js-confetti": "0.12.0",
"lefthook": "1.11.13",
- "motion": "12.15.0",
"postcss": "8.5.1",
"react": "19.1.0",
"react-dom": "19.1.0",
"sharp": "0.33.5",
"tailwind-merge": "3.3.0",
"tailwindcss": "3.4.15",
- "typescript": "5.6.3"
+ "turbo": "2.5.4",
+ "typescript": "5.6.3",
+ "ua-parser-js": "2.0.3"
},
"devDependencies": {
"@commitlint/cli": "19.8.1",
@@ -72,6 +74,7 @@
"@playwright/test": "1.52.0",
"@testing-library/jest-dom": "6.6.3",
"@testing-library/user-event": "14.6.1",
+ "@types/animejs": "3.1.13",
"@types/eslint-plugin-jsx-a11y": "6.10.0",
"@types/jsdom": "21.1.7",
"@types/node": "22.15.18",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index fafc27e..36d4eb0 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -44,6 +44,9 @@ importers:
'@types/react-dom':
specifier: 19.1.5
version: 19.1.5(@types/react@19.1.6)
+ animejs:
+ specifier: 4.0.2
+ version: 4.0.2
astro:
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)
@@ -65,12 +68,12 @@ importers:
jiti:
specifier: 2.4.2
version: 2.4.2
+ js-confetti:
+ specifier: 0.12.0
+ version: 0.12.0
lefthook:
specifier: 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:
specifier: 8.5.1
version: 8.5.1
@@ -95,6 +98,9 @@ importers:
typescript:
specifier: 5.6.3
version: 5.6.3
+ ua-parser-js:
+ specifier: 2.0.3
+ version: 2.0.3
devDependencies:
'@commitlint/cli':
specifier: 19.8.1
@@ -144,6 +150,9 @@ importers:
'@testing-library/user-event':
specifier: 14.6.1
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':
specifier: 6.10.0
version: 6.10.0
@@ -1505,6 +1514,9 @@ packages:
'@tybys/wasm-util@0.9.0':
resolution: {integrity: sha512-6+7nlbMVX/PVDCwaIQ8nTOPveOcFLSt8GcXdx8hD0bt39uWxYT88uXzqTd4fTvqta7oeUJqudepapKNt2DYJFw==}
+ '@types/animejs@3.1.13':
+ resolution: {integrity: sha512-yWg9l1z7CAv/TKpty4/vupEh24jDGUZXv4r26StRkpUPQm04ztJaftgpto8vwdFs8SiTq6XfaPKCSI+wjzNMvQ==}
+
'@types/aria-query@5.0.4':
resolution: {integrity: sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==}
@@ -1556,6 +1568,9 @@ packages:
'@types/nlcst@2.0.3':
resolution: {integrity: sha512-vSYNSDe6Ix3q+6Z7ri9lyWqgGhJTmzRjZRqyq15N0Z/1/UnVsno9G/N40NBijoYx2seFDIl0+B2mgAb9mezUCA==}
+ '@types/node-fetch@2.6.12':
+ resolution: {integrity: sha512-8nneRWKCg3rMtF69nLQJnOYUcbafYeFSjqkw3jCRLsqkWFlHaoQrr5mXmofFGOx3DKn7UfmBMyov8ySvLRVldA==}
+
'@types/node@17.0.45':
resolution: {integrity: sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw==}
@@ -1826,6 +1841,9 @@ packages:
ajv@8.17.1:
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:
resolution: {integrity: sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==}
@@ -1942,6 +1960,9 @@ packages:
resolution: {integrity: sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==}
engines: {node: '>= 0.4'}
+ asynckit@0.4.0:
+ resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==}
+
autoprefixer@10.4.14:
resolution: {integrity: sha512-FQzyfOsTlwVzjHxKEqRIAdJx9niO6VCBCoEwax/VLSoQF29ggECcPuBqUMZ+u8jCZOPSy8b8/8KnuFbp0SaFZQ==}
engines: {node: ^10 || ^12 || >=14}
@@ -2125,6 +2146,10 @@ packages:
resolution: {integrity: sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==}
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:
resolution: {integrity: sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==}
@@ -2334,6 +2359,10 @@ packages:
defu@6.1.4:
resolution: {integrity: sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==}
+ delayed-stream@1.0.0:
+ resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==}
+ engines: {node: '>=0.4.0'}
+
dequal@2.0.3:
resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==}
engines: {node: '>=6'}
@@ -2341,6 +2370,9 @@ packages:
destr@2.0.5:
resolution: {integrity: sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==}
+ detect-europe-js@0.1.2:
+ resolution: {integrity: sha512-lgdERlL3u0aUdHocoouzT10d9I89VVhk0qNRmll7mXdGfJT1/wqZ2ZLA4oJAjeACPY5fT1wsbq2AT+GkuInsow==}
+
detect-libc@2.0.4:
resolution: {integrity: sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==}
engines: {node: '>=8'}
@@ -2744,23 +2776,13 @@ packages:
resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==}
engines: {node: '>=14'}
+ form-data@4.0.3:
+ resolution: {integrity: sha512-qsITQPfmvMOSAdeyZ+12I1c+CKSstAFAwu+97zrnWAbIr5u8wfsExUzCesVLC8NgHuRUqNN4Zy6UPWUTRGslcA==}
+ engines: {node: '>= 6'}
+
fraction.js@4.3.7:
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:
resolution: {integrity: sha512-bsT9dWsNlRGDNjqcoIlz6w8NcSCgOpx6oxiwZgYwq9RVbi3JqUImPc6c4Kico2wRJSIXc7HHyr71QgmgXv7nfg==}
@@ -3111,6 +3133,9 @@ packages:
resolution: {integrity: sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==}
engines: {node: '>= 0.4'}
+ is-standalone-pwa@0.1.1:
+ resolution: {integrity: sha512-9Cbovsa52vNQCjdXOzeQq5CnCbAcRk05aU62K20WO372NrTv0NxibLFCK6lQ4/iZEFdEA3p3t2VNOn8AJ53F5g==}
+
is-string@1.1.1:
resolution: {integrity: sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==}
engines: {node: '>= 0.4'}
@@ -3184,6 +3209,9 @@ packages:
resolution: {integrity: sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A==}
hasBin: true
+ js-confetti@0.12.0:
+ resolution: {integrity: sha512-1R0Akxn3Zn82pMqW65N1V2NwKkZJ75bvBN/VAb36Ya0YHwbaSiAJZVRr/19HBxH/O8x2x01UFAbYI18VqlDN6g==}
+
js-tokens@4.0.0:
resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
@@ -3547,6 +3575,14 @@ packages:
resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==}
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:
resolution: {integrity: sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==}
engines: {node: '>=10.0.0'}
@@ -3575,26 +3611,6 @@ packages:
resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==}
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:
resolution: {integrity: sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==}
engines: {node: '>=10'}
@@ -4550,6 +4566,13 @@ packages:
engines: {node: '>=14.17'}
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:
resolution: {integrity: sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA==}
@@ -6316,6 +6339,8 @@ snapshots:
tslib: 2.8.1
optional: true
+ '@types/animejs@3.1.13': {}
+
'@types/aria-query@5.0.4': {}
'@types/babel__core@7.20.5':
@@ -6382,6 +6407,11 @@ snapshots:
dependencies:
'@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@22.15.18':
@@ -6704,6 +6734,8 @@ snapshots:
json-schema-traverse: 1.0.0
require-from-string: 2.0.2
+ animejs@4.0.2: {}
+
ansi-align@3.0.1:
dependencies:
string-width: 4.2.3
@@ -6940,6 +6972,8 @@ snapshots:
async-function@1.0.0: {}
+ asynckit@0.4.0: {}
+
autoprefixer@10.4.14(postcss@8.5.1):
dependencies:
browserslist: 4.25.0
@@ -7129,6 +7163,10 @@ snapshots:
color-convert: 2.0.1
color-string: 1.9.1
+ combined-stream@1.0.8:
+ dependencies:
+ delayed-stream: 1.0.0
+
comma-separated-tokens@2.0.3: {}
commander@14.0.0: {}
@@ -7375,10 +7413,14 @@ snapshots:
defu@6.1.4: {}
+ delayed-stream@1.0.0: {}
+
dequal@2.0.3: {}
destr@2.0.5: {}
+ detect-europe-js@0.1.2: {}
+
detect-libc@2.0.4: {}
deterministic-object-hash@2.0.2:
@@ -7948,16 +7990,15 @@ snapshots:
cross-spawn: 7.0.6
signal-exit: 4.1.0
- fraction.js@4.3.7: {}
-
- framer-motion@12.15.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0):
+ form-data@4.0.3:
dependencies:
- motion-dom: 12.15.0
- motion-utils: 12.12.1
- tslib: 2.8.1
- optionalDependencies:
- react: 19.1.0
- react-dom: 19.1.0(react@19.1.0)
+ asynckit: 0.4.0
+ combined-stream: 1.0.8
+ es-set-tostringtag: 2.1.0
+ hasown: 2.0.2
+ mime-types: 2.1.35
+
+ 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):
dependencies:
@@ -8393,6 +8434,8 @@ snapshots:
dependencies:
call-bound: 1.0.4
+ is-standalone-pwa@0.1.1: {}
+
is-string@1.1.1:
dependencies:
call-bound: 1.0.4
@@ -8481,6 +8524,8 @@ snapshots:
jiti@2.4.2: {}
+ js-confetti@0.12.0: {}
+
js-tokens@4.0.0: {}
js-yaml@4.1.0:
@@ -9004,6 +9049,12 @@ snapshots:
braces: 3.0.3
picomatch: 2.3.1
+ mime-db@1.52.0: {}
+
+ mime-types@2.1.35:
+ dependencies:
+ mime-db: 1.52.0
+
mime@3.0.0: {}
min-indent@1.0.1: {}
@@ -9038,20 +9089,6 @@ snapshots:
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: {}
ms@2.1.3: {}
@@ -10078,6 +10115,18 @@ snapshots:
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: {}
ultrahtml@1.6.0: {}
diff --git a/src/assets/sponsors/blacksmith-logo-dark.svg b/src/assets/sponsors/blacksmith-logo-dark.svg
new file mode 100644
index 0000000..b415853
--- /dev/null
+++ b/src/assets/sponsors/blacksmith-logo-dark.svg
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/assets/sponsors/tutaLogo-dark.svg b/src/assets/sponsors/tutaLogo-dark.svg
new file mode 100644
index 0000000..578a5d0
--- /dev/null
+++ b/src/assets/sponsors/tutaLogo-dark.svg
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/components/Community.astro b/src/components/Community.astro
index 10f9571..9c9277a 100644
--- a/src/components/Community.astro
+++ b/src/components/Community.astro
@@ -1,10 +1,7 @@
---
import Image from 'astro/components/Image.astro'
-import { motion } from 'motion/react'
-import { getTitleAnimation } from '~/animations'
import ComImage from '~/assets/ComImage.png'
import Button from '~/components/Button.astro'
-import Description from '~/components/Description.astro'
import CheckIcon from '~/icons/CheckIcon.astro'
import GitHubIcon from '~/icons/GitHubIcon.astro'
import { getLocale, getUI } from '~/utils/i18n'
@@ -19,50 +16,85 @@ const {
---
+
+
diff --git a/src/components/Features.astro b/src/components/Features.astro
index 76efc5b..b8de3a3 100644
--- a/src/components/Features.astro
+++ b/src/components/Features.astro
@@ -1,9 +1,6 @@
---
-import { motion } from 'motion/react'
-import { getTitleAnimation } from '~/animations'
import { getLocale, getUI } from '~/utils/i18n'
import Description from './Description.astro'
-
import Video from './Video.astro'
const locale = getLocale(Astro)
@@ -19,96 +16,61 @@ interface Props {
}
const { titles } = Astro.props
-
const descriptions = Object.values(features.featureTabs).map(tab => tab.description)
---
-
+
{
(titles || features.titles).map((title, index) =>
title !== '\n' ? (
-
+
{title}
-
+
) : (
)
)
}
-
+
+
{features.description}
-
+
+
-
+
-
- {features.featureTabs.workspaces.title}
-
-
- {features.featureTabs.compactMode.title}
-
-
- {features.featureTabs.glance.title}
-
-
- {features.featureTabs.splitView.title}
-
+ {
+ Object.entries(features.featureTabs).map(([key, tab], i) => (
+
+ {tab.title}
+
+ ))
+ }
-
-
- {features.featureTabs.workspaces.title}
-
-
- {features.featureTabs.workspaces.description}
-
-
-
-
- {features.featureTabs.compactMode.title}
-
-
- {features.featureTabs.compactMode.description}
-
-
-
-
- {features.featureTabs.glance.title}
-
-
- {features.featureTabs.glance.description}
-
-
-
-
- {features.featureTabs.splitView.title}
-
-
- {features.featureTabs.splitView.description}
-
-
+ {
+ Object.entries(features.featureTabs).map(([key, tab], i) => (
+
+ {tab.title}
+ {tab.description}
+
+ ))
+ }
@@ -116,6 +78,7 @@ const descriptions = Object.values(features.featureTabs).map(tab => tab.descript
+
@@ -162,54 +125,58 @@ const descriptions = Object.values(features.featureTabs).map(tab => tab.descript
-
diff --git a/src/i18n/en/translation.json b/src/i18n/en/translation.json
index 580e867..9bc44c2 100644
--- a/src/i18n/en/translation.json
+++ b/src/i18n/en/translation.json
@@ -49,12 +49,16 @@
"tuta": {
"name": "Tuta",
"url": "https://tuta.com/"
+ },
+ "blacksmith": {
+ "name": "BlackSmith",
+ "url": "https://www.blacksmith.sh/"
}
}
},
"community": {
"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": {
"freeAndOpenSource": {
"title": "Free and open-source",
@@ -262,11 +266,13 @@
}
},
"download": {
- "title": "Download Zen",
- "description": "Download Zen for your platform and experience a more mindful internet browsing experience. All downloads include SHA256 checksums for verification.",
+ "title": "Start your journey",
+ "description": "Download Zen for your platform and experience a more mindful internet browsing experience.
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.",
+ "beta": "Zen is currently in ",
+ "otherDownload": "If your device has been wrongly detected,
see other downloads .
Please report any issues you encounter on
GitHub .",
"alertInfo": {
- "description": "
Twilight Mode: 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": {
"title": "Platform Selector",
@@ -290,10 +296,7 @@
"platformNames": {
"mac": "macOS",
"windows": "Windows",
- "linux": "Linux",
- "macDownload": "MacOS Download",
- "windowsDownload": "Windows Download",
- "linuxDownload": "Linux Download"
+ "linux": "Linux"
},
"platformDescriptions": {
"mac": "Works on both new Apple (M-Series) and older Intel Macs.
Requires macOS 11.0 or later.",
@@ -448,7 +451,7 @@
},
"download": {
"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": {
"title": "Privacy Policy - Zen",
@@ -476,6 +479,7 @@
"zenMods": "Zen Mods",
"releaseNotes": "Release Notes",
"getHelp": "Get Help",
+ "securtiy": "Security",
"discord": "Discord",
"uptimeStatus": "Uptime Status",
"reportAnIssue": "Report an Issue",
diff --git a/src/i18n/ja/translation.json b/src/i18n/ja/translation.json
index b66b2ee..2c371c4 100644
--- a/src/i18n/ja/translation.json
+++ b/src/i18n/ja/translation.json
@@ -49,6 +49,10 @@
"tuta": {
"name": "Tuta",
"url": "https://tuta.com/"
+ },
+ "blacksmith": {
+ "name": "BlackSmith",
+ "url": "https://www.blacksmith.sh/"
}
}
},
@@ -262,11 +266,13 @@
}
},
"download": {
- "title": "Zenをダウンロードする",
+ "title": "Ready to experience Zen?",
"description": "お使いのプラットフォーム向けにZenをダウンロード。すべてのダウンロードにはSHA256チェックサムが含まれています",
"twilightInfo": "現在Twilightモードです。最新の実験的機能とアップデートをダウンロードしています。",
+ "beta": "Zen is currently in ",
+ "otherDownload": "If your device got wrongly detected,
see other downloads .
Please report any issues you encounter on
GitHub .",
"alertInfo": {
- "description": "
Twilightモード: 現在Twilightモードで、最新の実験的機能とアップデートをダウンロードしています。"
+ "description": "You're currently in Twilight mode, this means you're downloading the latest experimental features and updates."
},
"platformSelector": {
"title": "プラットフォーム選択",
@@ -290,10 +296,7 @@
"platformNames": {
"mac": "macOS",
"windows": "Windows",
- "linux": "Linux",
- "macDownload": "MacOSダウンロード",
- "windowsDownload": "Windowsダウンロード",
- "linuxDownload": "Linuxダウンロード"
+ "linux": "Linux"
},
"platformDescriptions": {
"mac": "Apple(Mシリーズ)・Intel両対応。
macOS 11.0以降が必要です。",
@@ -481,6 +484,7 @@
"zenMods": "Zen Mods",
"releaseNotes": "リリースノート",
"getHelp": "ヘルプ",
+ "securtiy": "Security",
"discord": "Discord",
"uptimeStatus": "稼働状況",
"reportAnIssue": "問題を報告",
diff --git a/src/pages/[...locale]/download.astro b/src/pages/[...locale]/download.astro
index a442cd2..1ed3fb0 100644
--- a/src/pages/[...locale]/download.astro
+++ b/src/pages/[...locale]/download.astro
@@ -9,8 +9,10 @@ import { getLocale, getUI } from '~/utils/i18n'
import { icon, library } from '@fortawesome/fontawesome-svg-core'
import { faApple, faGithub, faLinux, faWindows } from '@fortawesome/free-brands-svg-icons'
-import ExternalLinkIcon from '~/icons/ExternalLink.astro'
-import LockIcon from '~/icons/LockIcon.astro'
+import Image from 'astro/components/Image.astro'
+
+import AppIconDark from '~/assets/app-icon-dark.png'
+import AppIconLight from '~/assets/app-icon-light.png'
export { getStaticPaths } from '~/utils/i18n'
@@ -24,28 +26,40 @@ library.add(faWindows, faLinux, faApple, faGithub)
const windowsIcon = icon({ prefix: 'fab', iconName: 'windows' })
const linuxIcon = icon({ prefix: 'fab', iconName: 'linux' })
const appleIcon = icon({ prefix: 'fab', iconName: 'apple' })
-const githubIcon = icon({ prefix: 'fab', iconName: 'github' })
const checksums = await getChecksums()
const releases = getReleasesWithChecksums(locale)(checksums)
-const platformNames = download.platformNames
const platformDescriptions = download.platformDescriptions
---
-
-
-
-
{download.title}
-
- {download.description}
+
+
+
+
+
+
+
+
{download.title}
+
+
-
-
-
-
-
-
- {platformNames.mac}
-
-
-
-
-
- {platformNames.windows}
-
-
-
-
-
- {platformNames.linux}
-
-
-
-
-
-
+
-
-
-
- {download.additionalResources.title}
-
-
-
-
+
+ {download.beta}
+
BETA
-
-
-
- {download.additionalResources.sourceCode.title}
-
-
- {download.additionalResources.sourceCode.description}
-
-
-
-
-
-
-
-
-
-
- {download.additionalResources.documentation.title}
-
-
- {download.additionalResources.documentation.description}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {download.securityNotice.title}
-
-
+
+
diff --git a/src/release-notes/twilight.json b/src/release-notes/twilight.json
index 9b790ad..f0ac5cc 100644
--- a/src/release-notes/twilight.json
+++ b/src/release-notes/twilight.json
@@ -1,6 +1,6 @@
{
- "version": "xxx",
- "extra": "",
+ "version": "1.12.11t",
+ "extra": "This update includes some attempts at performance improvements, better workspace management and some bug fixes!",
"fixes": [],
"features": []
}
diff --git a/src/tests/components/PlatformDownload.test.ts b/src/tests/components/PlatformDownload.test.ts
index 6670cf5..90c2e36 100644
--- a/src/tests/components/PlatformDownload.test.ts
+++ b/src/tests/components/PlatformDownload.test.ts
@@ -4,12 +4,6 @@ import { beforeEach, describe, expect, it } from 'vitest'
import PlatformDownload from '~/components/download/PlatformDownload.astro'
const mockIcon = [' ']
-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(' ', () => {
let container: Awaited>
@@ -17,37 +11,6 @@ describe(' ', () => {
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 () => {
const linuxReleases = {
flathub: { all: { label: 'Flathub', link: '/flathub' } },
@@ -63,16 +26,10 @@ describe(' ', () => {
props: {
platform: 'linux',
icon: mockIcon,
- title: 'Linux Title',
- description: 'Linux Desc',
releases: linuxReleases,
},
})
- expect(result).toContain('Linux Title')
- expect(result).toContain('Linux Desc')
expect(result).toContain('Flathub')
- expect(result).toContain('Tarball')
- expect(result).toContain('x86_64')
})
it('renders linux platform with all branches', async () => {
@@ -97,30 +54,11 @@ describe(' ', () => {
props: {
platform: 'linux',
icon: mockIcon,
- title: 'Linux Title',
- description: 'Linux Desc',
releases: linuxReleases,
},
})
- // Test basic content
- expect(result).toContain('Linux Title')
- expect(result).toContain('Linux Desc')
-
// Test Flathub section
- 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')
})
})
diff --git a/src/tests/pages/download.spec.ts b/src/tests/pages/download.spec.ts
index ca8a3ff..f7f1a51 100644
--- a/src/tests/pages/download.spec.ts
+++ b/src/tests/pages/download.spec.ts
@@ -3,38 +3,44 @@ import { expect, test, type BrowserContextOptions, type Page } from '@playwright
import { getReleasesWithChecksums } from '~/components/download/release-data'
import { CONSTANT } from '~/constants'
-// Helper to get the platform section by id
-const getPlatformSection = (page: Page, platform: string) =>
- page.locator(`#${platform}-downloads.platform-section[data-active='true']`)
+const getPlatformSection = (page: Page, platform: string, cpu: string) => {
+ return page.locator(`#${platform}-${cpu}-downloads.platform-section`)
+}
-// Helper to get the platform tab button
-const getPlatformButton = (page: Page, platform: string) =>
- page.locator(`button.platform-selector[data-platform='${platform}']`)
-
-const platformConfigs: { name: string; userAgent: string; platform: string }[] = [
+const platformConfigs: {
+ name: string
+ userAgent: string
+ platform: string
+ expectedCpu: string
+}[] = [
{
name: 'windows',
userAgent:
'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',
+ expectedCpu: 'x86_64',
},
{
name: 'mac',
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',
platform: 'MacIntel',
+ expectedCpu: 'x86_64',
},
{
name: 'linux',
userAgent:
'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',
+ expectedCpu: 'x86_64',
},
]
-test.describe('Download page default tab per platform', () => {
- for (const { name, userAgent, platform } of platformConfigs) {
- test(`shows correct default tab for ${name} platform`, async ({ browser }) => {
+test.describe('Download page shows correct platform section per platform', () => {
+ for (const { name, userAgent, platform, expectedCpu } of platformConfigs) {
+ test(`shows correct platform section for ${name} ${expectedCpu} platform`, async ({
+ browser,
+ }) => {
const context = await browser.newContext({
userAgent,
locale: 'en-US',
@@ -42,66 +48,64 @@ test.describe('Download page default tab per platform', () => {
} as BrowserContextOptions)
const page = await context.newPage()
await page.goto('/download')
- await expect(getPlatformSection(page, name)).toBeVisible()
- await expect(getPlatformButton(page, name)).toHaveAttribute('data-active', 'true')
- // Other platforms should not be active
- for (const other of platformConfigs.filter(p => p.name !== name)) {
- await expect(getPlatformSection(page, other.name)).toBeHidden()
- await expect(getPlatformButton(page, other.name)).not.toHaveAttribute('data-active', 'true')
+
+ await page.waitForLoadState('domcontentloaded')
+
+ await expect(getPlatformSection(page, name, expectedCpu)).toBeVisible()
+
+ 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()
})
}
})
-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', () => {
const releases = getReleasesWithChecksums('en')(CONSTANT.CHECKSUMS)
type Releases = ReturnType>
function getPlatformLinks(releases: Releases) {
return {
- mac: [releases.macos.universal],
- windows: [releases.windows.x86_64, releases.windows.arm64],
- linux: [
- releases.linux.x86_64.tarball,
- releases.linux.aarch64.tarball,
- releases.linux.flathub.all,
- ],
+ 'mac-x86_64': [releases.macos.universal],
+ 'mac-arm64': [releases.macos.universal],
+ 'windows-x86_64': [releases.windows.x86_64],
+ 'windows-arm64': [releases.windows.arm64],
+ 'linux-x86_64': [releases.linux.x86_64.tarball, releases.linux.flathub.all],
+ 'linux-aarch64': [releases.linux.aarch64.tarball, releases.linux.flathub.all],
}
}
test('all platform download links are correct', async ({ page }) => {
- const platforms = ['windows', 'mac', 'linux']
const platformLinkSelectors = getPlatformLinks(releases)
await page.goto('/download')
await page.waitForLoadState('domcontentloaded')
- for (const platform of platforms) {
- await getPlatformButton(page, platform).click()
- for (const { label, link } of platformLinkSelectors[
- platform as keyof typeof platformLinkSelectors
- ]) {
- const downloadLink = page.locator(`#${platform}-downloads .download-link[href="${link}"]`)
- await expect(downloadLink).toContainText(label)
+
+ for (const [platformCpu, links] of Object.entries(platformLinkSelectors)) {
+ const platform = platformCpu.split('-')[0] as 'mac' | 'windows' | 'linux'
+
+ for (const { link } of links) {
+ const downloadLink = page.locator(
+ `#${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)
}
}