diff --git a/.claude/launch.json b/.claude/launch.json new file mode 100644 index 0000000..79ae24f --- /dev/null +++ b/.claude/launch.json @@ -0,0 +1,11 @@ +{ + "version": "0.0.1", + "configurations": [ + { + "name": "website", + "runtimeExecutable": "npm", + "runtimeArgs": ["--prefix", "website", "run", "dev", "--", "--port", "5183", "--strictPort"], + "port": 5183 + } + ] +} diff --git a/README.md b/README.md index 8004de2..6c0884d 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,7 @@

+ Official website GitHub release downloads (mirror downloads not counted) GitHub stars Latest release @@ -23,7 +24,7 @@

- 中文 · English + 官网 · Official Website · 中文 · English

--- diff --git a/website/.gitignore b/website/.gitignore new file mode 100644 index 0000000..868d225 --- /dev/null +++ b/website/.gitignore @@ -0,0 +1,5 @@ +node_modules/ +dist/ +assets/raw/ +assets/fonts-src/ +copy-*.json diff --git a/website/README.md b/website/README.md new file mode 100644 index 0000000..f39a45e --- /dev/null +++ b/website/README.md @@ -0,0 +1,41 @@ +# Codex App Manager — 官网 + +中英双语单页官网(滚动叙事 + GSAP ScrollTrigger 管线可视化)。纯静态产物,可直接部署到 +Cloudflare Pages / GitHub Pages(`base: "./"`,任意子路径均可)。 + +## 开发 + +```bash +npm install +npm run dev # Vite dev server +npm run build # 产出 dist/ +``` + +## 资产管线 + +生成素材(git-ignored 的 `assets/raw/`、`assets/fonts-src/`)→ 优化产物(`public/`): + +```bash +npm run fonts # Source Han Serif SC + Fraunces 子集化 → public/fonts/*.woff2 +npm run images # assets/raw/*.png → public/img/*.{avif,webp} 多分辨率 + og.jpg +``` + +- 装饰图像由 gpt-image-2-skill(DuckCoding provider)生成;透明素材经 + `transparent generate` / `transparent extract --method dual` 提取并通过 `--strict` 验收。 +- 修改 `src/locales/*.ts` 或 `index.html` 文案后需重跑 `npm run fonts` + (子集按实际用字收集,缺字会回退到系统字体)。 +- 真实 logo 来自两个仓库的 `assets/logo.png`,缩放为 192px 后自托管。 + +## i18n + +- 默认 `zh-CN`(HTML 静态文案即中文,SEO 友好);`en` 在运行时通过 `data-i18n` 替换。 +- 首次访问按 `navigator.language` 选择,手动切换持久化于 `localStorage("cam-site-lang")`, + 并同步 `` 与 title/description/og:* 元信息。 +- 文案唯一来源:`src/locales/zh.ts` / `src/locales/en.ts`。 + +## 动效 + +- GSAP + ScrollTrigger + MotionPath;桌面端管线段(`#pipeline-scroll`)与管理器三步 + (`#manager-stage`)为 pin + scrub 叙事。 +- `prefers-reduced-motion: reduce` 或窄屏(< 1024px)时,管线自动切换为纵向时间线 + (`.pipeline-rail`),所有状态静态落定。 diff --git a/website/index.html b/website/index.html new file mode 100644 index 0000000..8d59dd4 --- /dev/null +++ b/website/index.html @@ -0,0 +1,905 @@ + + + + + + Codex App Manager — 官方 Codex 桌面应用的安装、更新与卸载管家 + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+
+ + + + + + + +
+ + + + + +
+
+
+

开源项目 · macOS 与 Windows

+

+ 官方 Codex 桌面应用 + 一键装好,持续最新 +

+

+ 一键安装、增量更新、干净卸载官方 Codex 桌面应用,自带国内可达的自更新。 +

+

+ Windows 不用 Microsoft Store,macOS 只下载版本之间的增量。R2 与 IHEP 双镜像,国内直连可达,无需代理。 +

+ +

+ 全部下载方式 +

+
    +
  • Developer ID 签名 + Apple 公证
  • +
  • MIT 开源
  • +
  • Tauri v2
  • +
  • 11 种语言
  • +
+
+ +
+
+
+ + +
+
+ + Codex App Manager + +
+ +
+
+ + + +

有新版本

+

26.602.71036

+

当前 26.602.40724 → 新版 26.602.71036 · 约 12.6 MiB

+
+ + +
+
+ +
+ + + +

正在更新…

+

正在从 镜像 下载

+

0%

+
+
+ +
+ + + +

已是最新

+

当前版本 26.602.71036

+

+ + 官方版本 · 刚刚检查 +

+
+
发布时间今天
+
安装位置/Applications/Codex.app
+
+
+ + +
+
+ +
+ + + +

正在检查…

+

当前版本 26.602.71036

+
+
+
+ + 已托管 + + 更新源:镜像 +
+
+
+ +
+
+
+
+

往下看,它是怎么做到的

+
+ + +
+
+
+
+

为什么

+

装个官方应用,不该这么难

+

+ Codex 桌面应用本身很好。难的是把它顺利装上、按时更新、干净卸掉——尤其当你身在国内。 +

+
+ +
+
+
+

Microsoft Store,指望不上

+

官方 Windows 版只在 Microsoft Store 分发。商店连不上、账号登录不了、设备被策略禁用——任何一条,都让你装不上 Codex。

+
+
+

下载与更新,都压在海外线路上

+

macOS 官方直链和 Sparkle 更新链路都托管在海外。网络一抖,下载重试、更新停在半路,版本越落越远。

+
+
+

手动安装,留下一地痕迹

+

自己下载、覆盖、删除,旧版本残留、状态错乱。出了问题没有回滚,只能从头再来。

+
+
+
+
+ + +
+ +
+
+

Codex App Manager

+

安装、更新、卸载,三步完成

+

+ Manager 不急着改动你的系统。它先检测本地的 Codex 安装状态,再生成一份计划,最后才谨慎执行——破坏性操作之前,逐项核验。 +

+
+ +
+
+
    +
  1. + 1 + 检测 +

    看清本地的一切

    +

    识别本机 Codex 的安装状态、版本与残留。这一步不碰任何文件,只是看清楚。

    +
  2. +
  3. + 2 + 规划 +

    把每一步写在明处

    +

    根据检测结果生成执行计划:装什么、换什么、删什么。先告诉你,再开始。

    +
  4. +
  5. + 3 + 执行 +

    谨慎地完成

    +

    破坏性操作前先验证,执行后确认结果;macOS 上更新校验失败会自动回滚。装好之后,一键启动 Codex。

    +
  6. +
+ + +
+
+ +
+
+ 安装 +

一次点击,从零到可用。装完即可直接拉起 Codex。

+
+
+ 更新 +

macOS 上消费 Sparkle appcast,只下载版本之间的增量,EdDSA 签名逐字节校验。

+
+
+ 回滚 +

更新失败自动回到上一个可用版本,不留半成品在你的系统里。

+
+
+ +
+
+

一键启动

+

安装、更新之后不必去翻启动台。在 Manager 里直接打开 Codex。

+
+
+

Windows 无需 Microsoft Store

+

直接安装官方 MSIX 或便携版,更新分阶段进行,装完自动执行健康检查——商店连不上也照样用。

+
+
+

11 种语言,一套温和的界面

+

OKLCH 暖色材质,深浅两套主题,GSAP 动效,支持包括阿拉伯语 RTL 在内的 11 种语言。

+
+
+
+
+ + +
+ +
+
+

下载

+

下载 Codex App Manager

+

+ 选择适合你的方式。所有直链都是镜像永久链接,始终指向最新版本,国内直连可达。 +

+
+ +
+
+ 为你推荐 + macOS · Apple Silicon +

永久链接,始终最新 · 国内直连可达

+ 下载 .dmg +
+
+ 为你推荐 + macOS · Intel +

永久链接,始终最新 · 国内直连可达

+ 下载 .dmg +
+
+ 为你推荐 + Windows · x64 +

永久链接,始终最新 · 无需 Microsoft Store

+ 下载 .exe +
+
+ +
+
+

Homebrew

+

macOS 推荐

+
+ brew install --cask wangnov/tap/codex-app-manager + +
+
+ +
+
+
+ + +
+
+

Codex App Mirror

+

从官方上游,到你的桌面

+

+ Mirror 负责把官方安装包原样送到离你最近的节点,每一步都可验证;Manager 把这份能力变成桌面上的安装与更新体验。下面是一个安装包的完整链路。 +

+

+ + Codex App Mirror + + + Codex App Manager +

+
+ + +
+
+ + + + +
+ +
+ + +
+ +
+
+ +
+ + +
+
+ MSIX + DMG +

官方上游

+

一切始于 OpenAI 官方发布的安装包。我们不生产字节,只负责送达。

+
+ + + + + +
+ + + +
+
+ EdDSA 校验通过 +

从官方上游到你的 Mac 与 PC,每一个字节都原样、可验证。

+
+
+
+
+ + +
+ + +
+ + MSIX + DMG +

官方上游

+

一切始于 OpenAI 官方发布的安装包。我们不生产字节,只负责送达。

+
+
+ + 15 min +

每 15 分钟看一眼

+

Cloudflare Cron 持续探测官方上游,GitHub Actions 作为后备。上游一有变化,即刻知晓。

+
+
+ + SHA256 +

原样自动发布

+

Windows MSIX 与 macOS DMG(arm64 + x64)逐字节镜像,零修改、零重新打包。每个版本附带 SHA256SUMS 与上游指纹清单。

+
+
+ + R2 + IHEP +

双镜像落地

+

R2 面向全球,IHEP S3 面向中国大陆。同一份字节,落在两个节点。

+
+
+ + CF-IPCountry +

一条短链,就近抵达

+

Cloudflare Worker 按 CF-IPCountry 路由:大陆走 IHEP 预签名链接,其余走 R2。你只需要记住一个地址。

+
+
+ + EdDSA +

增量更新,落到桌面

+

macOS 上,Manager 读取 Sparkle appcast,只取版本之间的差异。官方 EdDSA 签名逐字节校验,失败自动回滚。

+
+ +
+ EdDSA 校验通过 +

从官方上游到你的 Mac 与 PC,每一个字节都原样、可验证。

+
+
+
+ + +
+ +
+
+

可信验证

+

每一环,都可验证

+

+ 不需要相信我们的说法。镜像与官方是否一致,任何人随时都能自己验证。 +

+
+
+
+
+ 校验对比 + 示意 +
+
+ 官方 SHA256 + e3b0 c442 98fc 1c14 9afb f4c8 996f b924 +
+
+ 镜像 SHA256 + e3b0 c442 98fc 1c14 9afb f4c8 996f b924 +
+
+ EdDSA 签名 + 逐字节复制官方签名 +
+
+ 完全一致 +
+
+
+
+

Developer ID 签名与公证

+

macOS 版本由 Apple Developer ID 签名,并通过 Apple 公证。来源可查,系统原生验证。

+
+
+

EdDSA 逐字节校验

+

镜像逐字节复制官方 EdDSA 签名——签名无法伪造,我们也从不伪造。校验不通过,更新就不会安装。

+
+
+

SHA256SUMS 与上游指纹

+

每个镜像版本附带 SHA256SUMS 与上游指纹清单。任何人,任何时候,都能比对镜像与官方是否一致。

+
+
+

开源,可审计

+

Manager 与 Mirror 全部以 MIT 协议开源。每一行代码、每一条流水线,都摆在明处供你审阅。

+
+
+
+
+
+ + + +
+ +
+
+

边界声明

+

做什么,不做什么

+
+
+
+

原样分发,从不改包

+

不修改、不重新打包官方安装程序。EdDSA 签名逐字节复制——它无法被伪造,我们也绝不伪造。

+
+
+

独立项目,无任何隶属

+

本项目是独立的社区工具,与 OpenAI、Microsoft 均无隶属或背书关系。

+
+
+

MIT 开源

+

Manager 与 Mirror 均以 MIT 协议开源,代码与构建流水线公开,可审计。

+
+
+

MIT License — 自由使用、修改与分发。

+
+
+
+ + + + + + + diff --git a/website/package-lock.json b/website/package-lock.json new file mode 100644 index 0000000..2c31825 --- /dev/null +++ b/website/package-lock.json @@ -0,0 +1,1640 @@ +{ + "name": "codex-app-manager-site", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "codex-app-manager-site", + "version": "1.0.0", + "dependencies": { + "gsap": "^3.15.0" + }, + "devDependencies": { + "sharp": "^0.34.5", + "subset-font": "^2.5.0", + "typescript": "^6.0.3", + "vite": "^8.0.16" + } + }, + "node_modules/@emnapi/core": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz", + "integrity": "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.2.1", + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.0.tgz", + "integrity": "sha512-55coeOFKHv1ywEcUXJtWU5f+Jr/W5tZDvZig8DLKSwUN1JpROQ4rk/SNOQiFWmaR/VKF4zuFyW1B8JduOSv6Pg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/wasi-threads": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz", + "integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@img/colour": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.1.0.tgz", + "integrity": "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@img/sharp-darwin-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.5.tgz", + "integrity": "sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-arm64": "1.2.4" + } + }, + "node_modules/@img/sharp-darwin-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.5.tgz", + "integrity": "sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-x64": "1.2.4" + } + }, + "node_modules/@img/sharp-libvips-darwin-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.2.4.tgz", + "integrity": "sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-darwin-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.2.4.tgz", + "integrity": "sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.2.4.tgz", + "integrity": "sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==", + "cpu": [ + "arm" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.2.4.tgz", + "integrity": "sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-ppc64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.2.4.tgz", + "integrity": "sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-riscv64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.2.4.tgz", + "integrity": "sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-s390x": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.2.4.tgz", + "integrity": "sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.2.4.tgz", + "integrity": "sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.2.4.tgz", + "integrity": "sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.2.4.tgz", + "integrity": "sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-linux-arm": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.34.5.tgz", + "integrity": "sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==", + "cpu": [ + "arm" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.34.5.tgz", + "integrity": "sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm64": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-ppc64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.34.5.tgz", + "integrity": "sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-ppc64": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-riscv64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.34.5.tgz", + "integrity": "sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-riscv64": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-s390x": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.34.5.tgz", + "integrity": "sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==", + "cpu": [ + "s390x" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-s390x": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.5.tgz", + "integrity": "sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-x64": "1.2.4" + } + }, + "node_modules/@img/sharp-linuxmusl-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.5.tgz", + "integrity": "sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-arm64": "1.2.4" + } + }, + "node_modules/@img/sharp-linuxmusl-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.5.tgz", + "integrity": "sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-x64": "1.2.4" + } + }, + "node_modules/@img/sharp-wasm32": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.34.5.tgz", + "integrity": "sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", + "optional": true, + "dependencies": { + "@emnapi/runtime": "^1.7.0" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.34.5.tgz", + "integrity": "sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-ia32": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.34.5.tgz", + "integrity": "sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.5.tgz", + "integrity": "sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@napi-rs/wasm-runtime": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.5.tgz", + "integrity": "sha512-AWPoBRJ9tsnVhor4sjO7rkni+7p+2IAEFj6cx06UgP10jkQHqay/36uRV/bFkgrh18D9vb4cr8Q0Pthskgzy+Q==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@tybys/wasm-util": "^0.10.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + }, + "peerDependencies": { + "@emnapi/core": "^1.7.1", + "@emnapi/runtime": "^1.7.1" + } + }, + "node_modules/@oxc-project/types": { + "version": "0.133.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.133.0.tgz", + "integrity": "sha512-KzkdCd6Uxqnf6l3HOw1xfatAlUURA0g14cvBYFyJ5SaNOQbOUvBr9PKArcPcrNIeRsBdgcUzOGrhKveVpvOIGA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/Boshen" + } + }, + "node_modules/@rolldown/binding-android-arm64": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.3.tgz", + "integrity": "sha512-454rs7jHngixp/NMxd5srYD57OnzSlZ/eFTETjORQHLwJG1lRtmNOJcBerZlfu4GjKqeq8aCCIQrMdHyhI51Hw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-arm64": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.3.tgz", + "integrity": "sha512-PcAhP+ynjURNyy8SKGl5DQP94aGuB/7JrXJb/t7P+hanXvQVMWzUvRRhBAcg/lNRadBhoUPqSoP4xw5tR/KBEA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-x64": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.3.tgz", + "integrity": "sha512-9YpfeUvSE2RS7wysJ81uOZkXJz7f7Q55H2Gvp3VEw/EsahqDtrphrZ0EwDLK5vvKOzaCrBsjF8JmnMLcUt78Gg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-freebsd-x64": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.3.tgz", + "integrity": "sha512-yB1IlAsSNHncV6SCTL27/MVGR5htvQsoGxIv5KMGXALp+Ll1wYsn+x98M9MW7qa+NdSbvrrY7ANI4wLJ0n1e6g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm-gnueabihf": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.3.tgz", + "integrity": "sha512-Yi30IVAAfLUCy2MseFjbB1jAMDl1VMCAas5StnYp8da9+CKvMd2H2cbEjWcw5NPaPqzvYkVIaF1nNUG+b7u/sw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-gnu": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.3.tgz", + "integrity": "sha512-jsO7R8To+AdlYgUmN5sHSCZbfhtMBkO0WUx8iORQnPcMMdgr7qM2DQmMwgabs3GhNztdmoKkMKQFHD6DTMCIQw==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-musl": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.3.tgz", + "integrity": "sha512-VWkUHwWriDciit80wleYwKILoR/KMvxh/IdwS/paX+ZgpuRpCrKLUdadJbc0NpBEiyhpYawsJ73j9aCvOH+f7Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-ppc64-gnu": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.3.tgz", + "integrity": "sha512-5f1laC0SlIR0yDbFCd8acUhvJIag6N3zC5P7oUPN6wX0aOma+uKJ0wBDH5aq7I1PVI2ttTlhJwzwRIBnLiSGEg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-s390x-gnu": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.3.tgz", + "integrity": "sha512-Iq4ko0r4XsgbrF/LunNgHtAGLRRVE2kXonAXQ/MV0mC6jQpMOhW1SvtZja2EhC/kd05++bP78dsqBeIQyYJ6Yg==", + "cpu": [ + "s390x" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-gnu": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.3.tgz", + "integrity": "sha512-B8m6tD5+/N5FeNQFbKlLA/2yVq9ycQP1SeedyEYYKWBNR3ZQbkvIUcNnDNM03lO1l5F2roiiFJGgvoLLyZXtSg==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-musl": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.3.tgz", + "integrity": "sha512-pSdpdUJHkuCxun9LE7jvgUB9qsRgaiyNNCX7m/AvHTcq67AiT/Yhoxvw5zPfhrM8k/BfP8ce/hMOpthKDpEUow==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-openharmony-arm64": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.3.tgz", + "integrity": "sha512-OXXS3RKJgX2uLwM+gYyuH5omcH8fL1LJs96pZGgtetVCahON57+d4SJHzTgZiOjxgGkSnpXpOsWuPDGAKAigEg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-wasm32-wasi": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.3.tgz", + "integrity": "sha512-JTtb8BWFynicNSoPrehsCzBtOKjZ6jhMiPFEmOiuXg1Fl8dn2KHQob+GuPSGR0dryQa1PQJbzjF3dqO/whhjLg==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "1.10.0", + "@emnapi/runtime": "1.10.0", + "@napi-rs/wasm-runtime": "^1.1.4" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-wasm32-wasi/node_modules/@emnapi/runtime": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz", + "integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@rolldown/binding-win32-arm64-msvc": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.3.tgz", + "integrity": "sha512-gEdFFEN70A/jxb2svrWsN3aDL7OUtmvlOy+6fa2jxG8K0wQ1ZbdeLGnidov6Yu5/733dI5ySfzFlQ/cb0bSz1g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-x64-msvc": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.3.tgz", + "integrity": "sha512-eXB7CHuaQdqmJcc3koCNtNPmT/bj2gc999kUFgBxG8Ac0NdgXc4rkCHhqrgrhN3zddvvvrgzj1e90SuSfmyIXA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz", + "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tybys/wasm-util": { + "version": "0.10.2", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.2.tgz", + "integrity": "sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/fontverter": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fontverter/-/fontverter-2.0.0.tgz", + "integrity": "sha512-DFVX5hvXuhi1Jven1tbpebYTCT9XYnvx6/Z+HFUPb7ZRMCW+pj2clU9VMhoTPgWKPhAs7JJDSk3CW1jNUvKCZQ==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "wawoff2": "^2.0.0", + "woff2sfnt-sfnt2woff": "^1.0.0" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/gsap": { + "version": "3.15.0", + "resolved": "https://registry.npmjs.org/gsap/-/gsap-3.15.0.tgz", + "integrity": "sha512-dMW4CWBTUK1AEEDeZc1g4xpPGIrSf9fJF960qbTZmN/QwZIWY5wgliS6JWl9/25fpTGJrMRtSjGtOmPnfjZB+A==", + "license": "Standard 'no charge' license: https://gsap.com/standard-license." + }, + "node_modules/harfbuzzjs": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/harfbuzzjs/-/harfbuzzjs-0.10.3.tgz", + "integrity": "sha512-GJnLUrgLMadlMYrBGEXwYEimObbysy3prWT4HyPpFQERvgTU/OZ+ReUlEPOum6w4RBtFXzXiCCmECOr4sz3qwQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/lightningcss": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", + "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", + "dev": true, + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.32.0", + "lightningcss-darwin-arm64": "1.32.0", + "lightningcss-darwin-x64": "1.32.0", + "lightningcss-freebsd-x64": "1.32.0", + "lightningcss-linux-arm-gnueabihf": "1.32.0", + "lightningcss-linux-arm64-gnu": "1.32.0", + "lightningcss-linux-arm64-musl": "1.32.0", + "lightningcss-linux-x64-gnu": "1.32.0", + "lightningcss-linux-x64-musl": "1.32.0", + "lightningcss-win32-arm64-msvc": "1.32.0", + "lightningcss-win32-x64-msvc": "1.32.0" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", + "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", + "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", + "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", + "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", + "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", + "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", + "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", + "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", + "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", + "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", + "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lodash": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", + "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.12", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz", + "integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pako": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", + "dev": true, + "license": "(MIT AND Zlib)" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.15", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz", + "integrity": "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.12", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/rolldown": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.3.tgz", + "integrity": "sha512-i00lAJ2ks1BYr7rjNjKC7BcqAS7nVfiT3QX1SI5aY+AFHblCmaUf9OE9dbdzDvW6dJxbi2ZCZiy9v3CcwOiX3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@oxc-project/types": "=0.133.0", + "@rolldown/pluginutils": "^1.0.0" + }, + "bin": { + "rolldown": "bin/cli.mjs" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "optionalDependencies": { + "@rolldown/binding-android-arm64": "1.0.3", + "@rolldown/binding-darwin-arm64": "1.0.3", + "@rolldown/binding-darwin-x64": "1.0.3", + "@rolldown/binding-freebsd-x64": "1.0.3", + "@rolldown/binding-linux-arm-gnueabihf": "1.0.3", + "@rolldown/binding-linux-arm64-gnu": "1.0.3", + "@rolldown/binding-linux-arm64-musl": "1.0.3", + "@rolldown/binding-linux-ppc64-gnu": "1.0.3", + "@rolldown/binding-linux-s390x-gnu": "1.0.3", + "@rolldown/binding-linux-x64-gnu": "1.0.3", + "@rolldown/binding-linux-x64-musl": "1.0.3", + "@rolldown/binding-openharmony-arm64": "1.0.3", + "@rolldown/binding-wasm32-wasi": "1.0.3", + "@rolldown/binding-win32-arm64-msvc": "1.0.3", + "@rolldown/binding-win32-x64-msvc": "1.0.3" + } + }, + "node_modules/semver": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.4.tgz", + "integrity": "sha512-rUCObTnP32Q08R2uuIrt7r9PlEonuTmtuXYcW6s5kjdlj3xbnwe+21yXptAUYcMAABLkYYTtnmzb3w3EDZfueA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/sharp": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.5.tgz", + "integrity": "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==", + "dev": true, + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "@img/colour": "^1.0.0", + "detect-libc": "^2.1.2", + "semver": "^7.7.3" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-darwin-arm64": "0.34.5", + "@img/sharp-darwin-x64": "0.34.5", + "@img/sharp-libvips-darwin-arm64": "1.2.4", + "@img/sharp-libvips-darwin-x64": "1.2.4", + "@img/sharp-libvips-linux-arm": "1.2.4", + "@img/sharp-libvips-linux-arm64": "1.2.4", + "@img/sharp-libvips-linux-ppc64": "1.2.4", + "@img/sharp-libvips-linux-riscv64": "1.2.4", + "@img/sharp-libvips-linux-s390x": "1.2.4", + "@img/sharp-libvips-linux-x64": "1.2.4", + "@img/sharp-libvips-linuxmusl-arm64": "1.2.4", + "@img/sharp-libvips-linuxmusl-x64": "1.2.4", + "@img/sharp-linux-arm": "0.34.5", + "@img/sharp-linux-arm64": "0.34.5", + "@img/sharp-linux-ppc64": "0.34.5", + "@img/sharp-linux-riscv64": "0.34.5", + "@img/sharp-linux-s390x": "0.34.5", + "@img/sharp-linux-x64": "0.34.5", + "@img/sharp-linuxmusl-arm64": "0.34.5", + "@img/sharp-linuxmusl-x64": "0.34.5", + "@img/sharp-wasm32": "0.34.5", + "@img/sharp-win32-arm64": "0.34.5", + "@img/sharp-win32-ia32": "0.34.5", + "@img/sharp-win32-x64": "0.34.5" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/subset-font": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/subset-font/-/subset-font-2.5.0.tgz", + "integrity": "sha512-Vsa8ngQ/ohhUj0an7on49y9jLZ2rK5U+T1FzPM4/ZQY0xUy5mLis6BfFtPGzecTjFgYXQlvY7FlsJF4t3R/6Ug==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "fontverter": "^2.0.0", + "harfbuzzjs": "^0.10.3", + "lodash": "^4.17.21", + "p-limit": "^3.1.0" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD", + "optional": true + }, + "node_modules/typescript": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz", + "integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/vite": { + "version": "8.0.16", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.16.tgz", + "integrity": "sha512-h9bXPmJichP5fLmVQo3PyaGSDE2n3aPuomeAlVRm0JLmt4rY6zmPKd59HYI4LNW8oTK7tlTsuC7l/m7awx9Jcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "lightningcss": "^1.32.0", + "picomatch": "^4.0.4", + "postcss": "^8.5.15", + "rolldown": "1.0.3", + "tinyglobby": "^0.2.17" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "@vitejs/devtools": "^0.1.18", + "esbuild": "^0.27.0 || ^0.28.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "@vitejs/devtools": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/wawoff2": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/wawoff2/-/wawoff2-2.0.1.tgz", + "integrity": "sha512-r0CEmvpH63r4T15ebFqeOjGqU4+EgTx4I510NtK35EMciSdcTxCw3Byy3JnBonz7iyIFZ0AbVo0bbFpEVuhCYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "woff2_compress.js": "bin/woff2_compress.js", + "woff2_decompress.js": "bin/woff2_decompress.js" + } + }, + "node_modules/woff2sfnt-sfnt2woff": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/woff2sfnt-sfnt2woff/-/woff2sfnt-sfnt2woff-1.0.0.tgz", + "integrity": "sha512-edK4COc1c1EpRfMqCZO1xJOvdUtM5dbVb9iz97rScvnTevqEB3GllnLWCmMVp1MfQBdF1DFg/11I0rSyAdS4qQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "pako": "^1.0.7" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/website/package.json b/website/package.json new file mode 100644 index 0000000..c291f04 --- /dev/null +++ b/website/package.json @@ -0,0 +1,24 @@ +{ + "name": "codex-app-manager-site", + "private": true, + "version": "1.0.0", + "type": "module", + "description": "Official website for Codex App Manager & Codex App Mirror", + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview", + "fonts": "node scripts/subset-fonts.mjs", + "images": "node scripts/optimize-images.mjs", + "assets": "npm run fonts && npm run images" + }, + "dependencies": { + "gsap": "^3.15.0" + }, + "devDependencies": { + "sharp": "^0.34.5", + "subset-font": "^2.5.0", + "typescript": "^6.0.3", + "vite": "^8.0.16" + } +} diff --git a/website/public/fonts/fraunces.woff2 b/website/public/fonts/fraunces.woff2 new file mode 100644 index 0000000..2d69a67 Binary files /dev/null and b/website/public/fonts/fraunces.woff2 differ diff --git a/website/public/fonts/shs-bold.woff2 b/website/public/fonts/shs-bold.woff2 new file mode 100644 index 0000000..6fb92a7 Binary files /dev/null and b/website/public/fonts/shs-bold.woff2 differ diff --git a/website/public/fonts/shs-heavy.woff2 b/website/public/fonts/shs-heavy.woff2 new file mode 100644 index 0000000..a0b4dee Binary files /dev/null and b/website/public/fonts/shs-heavy.woff2 differ diff --git a/website/public/img/bokeh-1600.webp b/website/public/img/bokeh-1600.webp new file mode 100644 index 0000000..13c90f7 Binary files /dev/null and b/website/public/img/bokeh-1600.webp differ diff --git a/website/public/img/bokeh-960.webp b/website/public/img/bokeh-960.webp new file mode 100644 index 0000000..0fda5a9 Binary files /dev/null and b/website/public/img/bokeh-960.webp differ diff --git a/website/public/img/cloud-1200.webp b/website/public/img/cloud-1200.webp new file mode 100644 index 0000000..7d4bb1e Binary files /dev/null and b/website/public/img/cloud-1200.webp differ diff --git a/website/public/img/cloud-640.webp b/website/public/img/cloud-640.webp new file mode 100644 index 0000000..3b14e3d Binary files /dev/null and b/website/public/img/cloud-640.webp differ diff --git a/website/public/img/globe-800.webp b/website/public/img/globe-800.webp new file mode 100644 index 0000000..4077b64 Binary files /dev/null and b/website/public/img/globe-800.webp differ diff --git a/website/public/img/hero-dark-1600.avif b/website/public/img/hero-dark-1600.avif new file mode 100644 index 0000000..5da664b Binary files /dev/null and b/website/public/img/hero-dark-1600.avif differ diff --git a/website/public/img/hero-dark-1600.webp b/website/public/img/hero-dark-1600.webp new file mode 100644 index 0000000..bf66e2c Binary files /dev/null and b/website/public/img/hero-dark-1600.webp differ diff --git a/website/public/img/hero-dark-2560.avif b/website/public/img/hero-dark-2560.avif new file mode 100644 index 0000000..fdde471 Binary files /dev/null and b/website/public/img/hero-dark-2560.avif differ diff --git a/website/public/img/hero-dark-2560.webp b/website/public/img/hero-dark-2560.webp new file mode 100644 index 0000000..bfd18fe Binary files /dev/null and b/website/public/img/hero-dark-2560.webp differ diff --git a/website/public/img/hero-dark-960.avif b/website/public/img/hero-dark-960.avif new file mode 100644 index 0000000..ac3d7d9 Binary files /dev/null and b/website/public/img/hero-dark-960.avif differ diff --git a/website/public/img/hero-dark-960.webp b/website/public/img/hero-dark-960.webp new file mode 100644 index 0000000..38ad5c1 Binary files /dev/null and b/website/public/img/hero-dark-960.webp differ diff --git a/website/public/img/hero-light-1600.avif b/website/public/img/hero-light-1600.avif new file mode 100644 index 0000000..ab1f17c Binary files /dev/null and b/website/public/img/hero-light-1600.avif differ diff --git a/website/public/img/hero-light-1600.webp b/website/public/img/hero-light-1600.webp new file mode 100644 index 0000000..b649ba1 Binary files /dev/null and b/website/public/img/hero-light-1600.webp differ diff --git a/website/public/img/hero-light-2560.avif b/website/public/img/hero-light-2560.avif new file mode 100644 index 0000000..ee20d7c Binary files /dev/null and b/website/public/img/hero-light-2560.avif differ diff --git a/website/public/img/hero-light-2560.webp b/website/public/img/hero-light-2560.webp new file mode 100644 index 0000000..e66f212 Binary files /dev/null and b/website/public/img/hero-light-2560.webp differ diff --git a/website/public/img/hero-light-960.avif b/website/public/img/hero-light-960.avif new file mode 100644 index 0000000..4605e68 Binary files /dev/null and b/website/public/img/hero-light-960.avif differ diff --git a/website/public/img/hero-light-960.webp b/website/public/img/hero-light-960.webp new file mode 100644 index 0000000..435592c Binary files /dev/null and b/website/public/img/hero-light-960.webp differ diff --git a/website/public/img/logo-manager-192.png b/website/public/img/logo-manager-192.png new file mode 100644 index 0000000..f1e4066 Binary files /dev/null and b/website/public/img/logo-manager-192.png differ diff --git a/website/public/img/logo-mirror-192.png b/website/public/img/logo-mirror-192.png new file mode 100644 index 0000000..b854d49 Binary files /dev/null and b/website/public/img/logo-mirror-192.png differ diff --git a/website/public/img/mist-1600.webp b/website/public/img/mist-1600.webp new file mode 100644 index 0000000..8947081 Binary files /dev/null and b/website/public/img/mist-1600.webp differ diff --git a/website/public/img/mist-960.webp b/website/public/img/mist-960.webp new file mode 100644 index 0000000..a9b3ebc Binary files /dev/null and b/website/public/img/mist-960.webp differ diff --git a/website/public/img/og.jpg b/website/public/img/og.jpg new file mode 100644 index 0000000..657c33f Binary files /dev/null and b/website/public/img/og.jpg differ diff --git a/website/public/img/orb-512.webp b/website/public/img/orb-512.webp new file mode 100644 index 0000000..315631d Binary files /dev/null and b/website/public/img/orb-512.webp differ diff --git a/website/public/img/texture-dark-1280.webp b/website/public/img/texture-dark-1280.webp new file mode 100644 index 0000000..fc94707 Binary files /dev/null and b/website/public/img/texture-dark-1280.webp differ diff --git a/website/public/img/texture-light-1280.webp b/website/public/img/texture-light-1280.webp new file mode 100644 index 0000000..ffcbd5a Binary files /dev/null and b/website/public/img/texture-light-1280.webp differ diff --git a/website/scripts/optimize-images.mjs b/website/scripts/optimize-images.mjs new file mode 100644 index 0000000..17b9c32 --- /dev/null +++ b/website/scripts/optimize-images.mjs @@ -0,0 +1,59 @@ +// Converts the raw AI-generated assets (assets/raw, git-ignored) into the +// optimized renditions the site actually ships (public/img). +// +// node scripts/optimize-images.mjs +// +// 4K originals never ship: every output is resized + converted to AVIF/WebP. + +import { mkdir, stat } from "node:fs/promises"; +import { existsSync } from "node:fs"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; +import sharp from "sharp"; + +const root = path.dirname(path.dirname(fileURLToPath(import.meta.url))); +const RAW = path.join(root, "assets/raw"); +const OUT = path.join(root, "public/img"); + +const jobs = [ + // Opaque atmospheres: AVIF (primary) + WebP (fallback), responsive widths. + { src: "hero-bg-dark.png", name: "hero-dark", widths: [2560, 1600, 960], formats: ["avif", "webp"] }, + { src: "hero-bg-light.png", name: "hero-light", widths: [2560, 1600, 960], formats: ["avif", "webp"] }, + { src: "texture-dark.png", name: "texture-dark", widths: [1280], formats: ["webp"], quality: 62 }, + { src: "texture-light.png", name: "texture-light", widths: [1280], formats: ["webp"], quality: 62 }, + + // Transparent layers: WebP keeps alpha at a fraction of PNG size. + { src: "cloud-porcelain.png", name: "cloud", widths: [1200, 640], formats: ["webp"] }, + { src: "mist-band.png", name: "mist", widths: [1600, 960], formats: ["webp"] }, + { src: "bokeh-field.png", name: "bokeh", widths: [1600, 960], formats: ["webp"] }, + { src: "orb-node.png", name: "orb", widths: [512], formats: ["webp"] }, + { src: "arc-globe.png", name: "globe", widths: [800], formats: ["webp"] }, +]; + +await mkdir(OUT, { recursive: true }); + +for (const job of jobs) { + const src = path.join(RAW, job.src); + if (!existsSync(src)) { + console.warn(`SKIP (missing): ${job.src}`); + continue; + } + for (const width of job.widths) { + const base = sharp(src).resize({ width, withoutEnlargement: true }); + for (const fmt of job.formats) { + const file = path.join(OUT, `${job.name}-${width}.${fmt}`); + const pipe = base.clone(); + if (fmt === "avif") await pipe.avif({ quality: 52 }).toFile(file); + else await pipe.webp({ quality: job.quality ?? 76, alphaQuality: 90 }).toFile(file); + const { size } = await stat(file); + console.log(`${path.basename(file)} ${(size / 1024).toFixed(0)} KB`); + } + } +} + +// Social card: 1200x630 center crop of the dark hero. +await sharp(path.join(RAW, "hero-bg-dark.png")) + .resize(1200, 630, { fit: "cover", position: "centre" }) + .jpeg({ quality: 78 }) + .toFile(path.join(OUT, "og.jpg")); +console.log("og.jpg done"); diff --git a/website/scripts/subset-fonts.mjs b/website/scripts/subset-fonts.mjs new file mode 100644 index 0000000..e4da09a --- /dev/null +++ b/website/scripts/subset-fonts.mjs @@ -0,0 +1,57 @@ +// Subsets the self-hosted display fonts to exactly the glyphs the site uses. +// Source Han Serif SC is ~24 MB; the subset ships at a few dozen KB. +// +// node scripts/subset-fonts.mjs +// +// Inputs : assets/fonts-src/*.otf|ttf (downloaded, git-ignored) +// Outputs: public/fonts/*.woff2 + +import { readFile, writeFile, mkdir } from "node:fs/promises"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; +import subsetFont from "subset-font"; + +const root = path.dirname(path.dirname(fileURLToPath(import.meta.url))); +const SRC = path.join(root, "assets/fonts-src"); +const OUT = path.join(root, "public/fonts"); + +// Every file whose text can end up rendered in the display face. +const TEXT_SOURCES = [ + "index.html", + "src/locales/zh.ts", + "src/locales/en.ts", +]; + +const ASCII = Array.from({ length: 95 }, (_, i) => String.fromCharCode(32 + i)).join(""); +// CJK punctuation & typographic marks used by the design even if a locale +// string is edited later. +const SAFETY = "「」『』《》〈〉、。,;:?!·—…··%℃①②③→←↑↓×✓✕“”‘’"; + +async function collectText() { + let chars = new Set((ASCII + SAFETY).split("")); + for (const rel of TEXT_SOURCES) { + const body = await readFile(path.join(root, rel), "utf8"); + for (const ch of body) { + if (ch.charCodeAt(0) > 0x2000) chars.add(ch); + } + } + return Array.from(chars).join(""); +} + +async function subset(srcFile, outFile, text) { + const buf = await readFile(path.join(SRC, srcFile)); + const woff2 = await subsetFont(buf, text, { targetFormat: "woff2" }); + await writeFile(path.join(OUT, outFile), woff2); + console.log( + `${outFile}: ${(buf.length / 1024 / 1024).toFixed(1)} MB -> ${(woff2.length / 1024).toFixed(1)} KB` + ); +} + +await mkdir(OUT, { recursive: true }); +const text = await collectText(); +console.log(`glyph set: ${text.length} chars`); + +await subset("SourceHanSerifSC-Heavy.otf", "shs-heavy.woff2", text); +await subset("SourceHanSerifSC-Bold.otf", "shs-bold.woff2", text); +// Fraunces only ever renders Latin. +await subset("Fraunces-VF.ttf", "fraunces.woff2", ASCII + "“”‘’—–…·"); diff --git a/website/src/i18n.ts b/website/src/i18n.ts new file mode 100644 index 0000000..4b2d44a --- /dev/null +++ b/website/src/i18n.ts @@ -0,0 +1,60 @@ +import zh from "./locales/zh"; +import en from "./locales/en"; + +export type Lang = "zh" | "en"; + +const dicts: Record = { zh, en }; +const STORAGE_KEY = "cam-site-lang"; + +export function initialLang(): Lang { + try { + const stored = localStorage.getItem(STORAGE_KEY); + if (stored === "zh" || stored === "en") return stored; + } catch { + /* storage unavailable */ + } + return navigator.language?.toLowerCase().startsWith("zh") ? "zh" : "en"; +} + +export function t(lang: Lang, path: string): unknown { + return path + .split(".") + .reduce((node, key) => (node == null ? node : node[key]), dicts[lang]); +} + +function setMeta(selector: string, value: string) { + document.querySelector(selector)?.setAttribute("content", value); +} + +export function applyLang(lang: Lang) { + document.documentElement.lang = lang === "zh" ? "zh-CN" : "en"; + document.body.dataset.lang = lang; + try { + localStorage.setItem(STORAGE_KEY, lang); + } catch { + /* storage unavailable */ + } + + document.querySelectorAll("[data-i18n]").forEach((el) => { + const value = t(lang, el.dataset.i18n!); + if (typeof value === "string") el.textContent = value; + }); + document.querySelectorAll("[data-i18n-aria]").forEach((el) => { + const value = t(lang, el.dataset.i18nAria!); + if (typeof value === "string") el.setAttribute("aria-label", value); + }); + document.querySelectorAll("[data-i18n-alt]").forEach((el) => { + const value = t(lang, el.dataset.i18nAlt!); + if (typeof value === "string") el.alt = value; + }); + + document.title = t(lang, "meta.title") as string; + setMeta('meta[name="description"]', t(lang, "meta.description") as string); + setMeta('meta[property="og:title"]', t(lang, "meta.ogTitle") as string); + setMeta('meta[property="og:description"]', t(lang, "meta.ogDescription") as string); + setMeta('meta[property="og:locale"]', lang === "zh" ? "zh_CN" : "en_US"); + setMeta( + 'meta[property="og:locale:alternate"]', + lang === "zh" ? "en_US" : "zh_CN" + ); +} diff --git a/website/src/locales/en.ts b/website/src/locales/en.ts new file mode 100644 index 0000000..a9c74d5 --- /dev/null +++ b/website/src/locales/en.ts @@ -0,0 +1,304 @@ +export default { + meta: { + title: "Codex App Manager — install, update & uninstall the official Codex desktop app", + description: + "One-click install, Sparkle delta updates on macOS, and clean uninstall for the official OpenAI Codex desktop app. Verified byte by byte, mirrored verbatim, and reachable from mainland China without a proxy. Open source, MIT licensed.", + ogTitle: "Codex App Manager — official Codex, installed in one click", + ogDescription: + "Install, update, and uninstall the official Codex desktop app — macOS delta updates with automatic rollback, verbatim mirrors reachable from mainland China. Signed, notarized, open source.", + }, + nav: { + why: "Why", + manager: "The Manager", + pipeline: "The Pipeline", + trust: "Trust", + download: "Download", + scope: "Scope", + cta: "Download", + }, + hero: { + eyebrow: "Open source · macOS & Windows", + titleA: "The official Codex app.", + titleB: "One click. Always current.", + tagline: + "One-click install, delta updates, and clean uninstall for the official OpenAI Codex desktop app — with self-updates that reach mainland China.", + sub: "No Microsoft Store needed on Windows. On macOS, updates download only what changed, verified byte by byte. Dual mirrors — R2 globally, IHEP in mainland China — no proxy required.", + ctaPrimary: "Download now", + ctaSecondary: "View on GitHub", + allOptions: "All download options", + dl: { + macArm: "Download for Apple Silicon", + macIntel: "Download for Intel Mac", + win: "Download for Windows (x64)", + }, + scrollHint: "See how it works", + badges: ["Signed & notarized", "MIT licensed", "Tauri v2", "11 languages"], + }, + demo: { + window: "Codex App Manager", + updTitle: "Update available", + updVer: "26.602.71036", + updFlow: "Now 26.602.40724 → New 26.602.71036 · ~12.6 MiB", + updCta: "Update now", + launch: "Launch Codex", + recheck: "Check again", + checking: "Checking…", + progressTitle: "Updating…", + progressFrom: "Downloading from Mirror", + uptodateTitle: "Up to date", + uptodateSub: "Current version 26.602.71036", + official: "Official build · Just checked", + successFlow: "Updated 26.602.40724 → 26.602.71036", + releaseDate: "Released", + releaseVal: "Today", + loc: "Location", + locVal: "/Applications/Codex.app", + source: "Source: Mirror", + settings: "Settings", + managed: "Managed", + }, + pain: { + kicker: "The problem", + title: "Keeping Codex current shouldn't be a chore", + lead: "The official Codex desktop app is great. Getting it onto your machine — and keeping it there, current and intact — depends on a download chain that wasn't built for everyone.", + cards: [ + { + title: "The Microsoft Store problem", + body: "On Windows, the official Codex ships only through the Microsoft Store. Store unreachable, account won't sign in, Store disabled by policy — any one of these and you can't install Codex at all.", + }, + { + title: "Hosted an ocean away", + body: "The official macOS download and the Sparkle update chain both live on overseas infrastructure. One flaky connection and downloads retry, updates stall, and you quietly fall behind.", + }, + { + title: "Manual installs are a gamble", + body: "Dragging DMGs around by hand means no verification, no rollback, and stray files from versions past. One bad update and you're reinstalling from scratch.", + }, + ], + mock: { + file: "Codex_Installer.dmg", + host: "Source: overseas node", + status: "Network timeout, retrying…", + retry: "Attempt 3", + }, + }, + manager: { + kicker: "The Manager", + title: "Install, update, uninstall — without the mess", + lead: "Codex App Manager treats every operation as three honest steps. It looks before it leaps, and it verifies before doing anything destructive.", + steps: [ + { + name: "Detect", + title: "Know the machine first", + body: "The Manager scans your local Codex install — version, location, state — before it touches anything. No assumptions, no blind writes.", + }, + { + name: "Plan", + title: "Plan the exact change", + body: "From that state it computes a precise plan: what gets downloaded, what gets replaced, what gets removed. Destructive steps are verified before they run.", + }, + { + name: "Execute", + title: "Execute, then verify", + body: "The plan runs and every step is checked. If an update fails verification on macOS, it rolls back automatically — and when it's done, Codex is one click from launching.", + }, + ], + scenarios: [ + { + label: "Install", + desc: "From nothing to a running Codex in one click. No DMG dragging, no setup-wizard archaeology.", + }, + { + label: "Update", + desc: "On macOS, the Manager reads the Sparkle appcast and downloads only the delta between your version and the latest — EdDSA signature verified, byte by byte.", + }, + { + label: "Rollback", + desc: "If a delta fails verification or an update goes sideways, the previous version comes back automatically. You're never stranded on a broken install.", + }, + ], + extras: [ + { + title: "One-click launch", + body: "When the work is done, launch Codex straight from the Manager.", + }, + { + title: "No Microsoft Store needed", + body: "Installs the official MSIX or portable build directly, stages updates, and runs a post-install health check — even when the Store is out of reach.", + }, + { + title: "A UI worth keeping open", + body: "Warm OKLCH material design, dark and light themes, GSAP motion — in 11 languages, including Arabic with full RTL.", + }, + ], + mock: { + window: "Codex App Manager", + detect: { + scan: "Scanning this machine…", + found: "Codex desktop app found", + ver: "Version behind upstream", + leftover: "Old version leftovers detected", + }, + plan: { + title: "Execution plan", + i1: "Download delta package", + i2: "Verify EdDSA signature", + i3: "Replace application files", + i4: "Run health check", + note: "Destructive steps are verified first", + }, + exec: { + doing: "Applying update…", + ok: "Updated · signature verified", + launch: "Launch Codex", + rollback: "Rolls back automatically if verification fails", + }, + }, + }, + pipeline: { + kicker: "The Mirror", + title: "From the official upstream to your desktop", + lead: "Codex App Mirror watches the official upstream and redistributes it verbatim — verifiably, and reachable from mainland China. The Manager turns that pipeline into the install and update experience on your desktop. Here's the full route an installer travels.", + stages: [ + { + title: "It starts upstream", + body: "Every journey begins with an official OpenAI release: Windows MSIX and macOS DMG, arm64 and x64. These are the bytes — exactly as shipped.", + stat: "MSIX + DMG", + }, + { + title: "Probed every 15 minutes", + body: "A Cloudflare Cron checks the upstream every 15 minutes, with GitHub Actions standing by as fallback. The moment something changes, the pipeline wakes up.", + stat: "15 min", + }, + { + title: "Released, untouched", + body: "A new mirror release goes out automatically — zero modification, zero repackaging. Every release ships SHA256SUMS and a fingerprint manifest of the upstream it came from.", + stat: "SHA256", + }, + { + title: "Landed on two nodes", + body: "The bytes settle on two nodes: Cloudflare R2 for the world, IHEP S3 for mainland China. If one path is slow, the other isn't.", + stat: "R2 + IHEP", + }, + { + title: "One link, best node", + body: "A Cloudflare Worker routes by CF-IPCountry: requests from mainland China get IHEP presigned URLs, everyone else gets R2. You never have to choose.", + stat: "1 URL", + }, + { + title: "Applied as a delta", + body: "On macOS, the Manager reads the Sparkle appcast, pulls only the delta between versions, and verifies the official EdDSA signature byte by byte. If anything is off, it rolls back.", + stat: "EdDSA", + }, + ], + nodes: ["Upstream", "15-min probe", "Auto release", "Dual mirrors", "Geo routing", "Your desktop"], + branchGlobal: "Global → R2", + branchCN: "Mainland China → IHEP S3", + verifiedChip: "EdDSA verified", + finale: "From the official upstream to your Mac or PC — every byte arrives intact.", + }, + trust: { + kicker: "Trust", + title: "Verify every hop yourself", + lead: "Nothing here asks for your trust. Whether the mirror matches the official release is something anyone can check, any time.", + mock: { + title: "Checksum comparison", + tag: "Schematic", + official: "Official SHA256", + mirror: "Mirror SHA256", + sig: "EdDSA signature", + sigVal: "Copied byte-for-byte from upstream", + match: "Exact match", + }, + items: [ + { + title: "Developer ID signed, Apple notarized", + body: "Every macOS build of the Manager is signed with a Developer ID certificate and notarized by Apple. Gatekeeper opens it without a fight.", + }, + { + title: "EdDSA, byte for byte", + body: "The mirror copies the official Sparkle EdDSA signatures exactly as published — it doesn't forge them, and it couldn't. On macOS, the Manager verifies each delta against those signatures before applying it.", + }, + { + title: "Checksums and fingerprints", + body: "Every mirror release ships SHA256SUMS plus a manifest fingerprinting the upstream it was taken from. Diff it against the official release yourself — that's the point.", + }, + { + title: "Open source, MIT", + body: "The Manager, the mirror pipeline, the routing worker — all of it is public code under the MIT license. Don't take our word for any of this; read it.", + }, + ], + }, + download: { + kicker: "Download", + title: "Download Codex App Manager", + lead: "Pick your platform. Every link below resolves to the latest release and works from mainland China without a proxy.", + recommended: "Recommended for you", + brew: { + title: "Homebrew", + note: "macOS recommended", + }, + direct: [ + { + platform: "macOS · Apple Silicon", + label: "Download .dmg", + note: "Always-latest permalink · reachable from mainland China", + }, + { + platform: "macOS · Intel", + label: "Download .dmg", + note: "Always-latest permalink · reachable from mainland China", + }, + { + platform: "Windows · x64", + label: "Download .exe", + note: "Always-latest permalink · no Microsoft Store needed", + }, + ], + github: { + title: "On GitHub", + note: "Source, issues, and the full pipeline — audit away.", + managerLabel: "Wangnov/Codex-App-Manager", + mirrorLabel: "Wangnov/codex-app-mirror", + }, + }, + scope: { + kicker: "Scope", + title: "What this project is — and isn't", + items: [ + { + title: "Verbatim or nothing", + body: "The mirror never modifies or repackages official installers, and it never forges signatures. EdDSA signatures are copied byte for byte from upstream — that is the only way they can exist.", + }, + { + title: "No affiliation", + body: "This is an independent, community-built project. It is not affiliated with, nor endorsed by, OpenAI or Microsoft.", + }, + { + title: "Open and auditable", + body: "Both projects are MIT licensed and fully open source. Every claim on this page can be checked against the code.", + }, + ], + mit: "MIT licensed — use it, fork it, audit it.", + }, + footer: { + thanks: + "With thanks to the Institute of High Energy Physics, Chinese Academy of Sciences (IHEP) for hosting the mainland-China mirror node.", + license: "MIT License", + made: "Built with Tauri v2 and an unreasonable concern for bytes.", + backTop: "Back to top", + links: { + manager: "Codex App Manager", + mirror: "Codex App Mirror", + }, + }, + ui: { + langSwitch: "中文", + langAria: "Switch language", + menu: "Menu", + close: "Close", + skip: "Skip to content", + copy: "Copy", + copied: "Copied", + }, +} as const; diff --git a/website/src/locales/zh.ts b/website/src/locales/zh.ts new file mode 100644 index 0000000..35212f1 --- /dev/null +++ b/website/src/locales/zh.ts @@ -0,0 +1,307 @@ +export default { + meta: { + title: "Codex App Manager — 官方 Codex 桌面应用的安装、更新与卸载管家", + description: + "一键安装、增量更新、干净卸载官方 OpenAI Codex 桌面应用。macOS Sparkle 增量更新、EdDSA 逐字节校验、R2 + IHEP 双镜像,国内免代理直连。Tauri v2 构建,MIT 开源。", + ogTitle: "Codex App Manager — 一键装好官方 Codex,自动保持最新", + ogDescription: + "原样镜像官方安装包,送达你的 Mac 与 PC,每一步都可校验。macOS 增量更新、失败自动回滚、国内直连可达。Developer ID 签名 + Apple 公证,开源可审计。", + }, + nav: { + why: "为什么", + manager: "管理器", + pipeline: "镜像链路", + trust: "可信验证", + download: "下载", + scope: "边界声明", + cta: "下载", + }, + hero: { + eyebrow: "开源项目 · macOS 与 Windows", + titleA: "官方 Codex 桌面应用", + titleB: "一键装好,持续最新", + tagline: "一键安装、增量更新、干净卸载官方 Codex 桌面应用,自带国内可达的自更新。", + sub: "Windows 不用 Microsoft Store,macOS 只下载版本之间的增量。R2 与 IHEP 双镜像,国内直连可达,无需代理。", + ctaPrimary: "立即下载", + ctaSecondary: "GitHub 仓库", + allOptions: "全部下载方式", + dl: { + macArm: "下载 macOS 版(Apple Silicon)", + macIntel: "下载 macOS 版(Intel)", + win: "下载 Windows 版(x64)", + }, + scrollHint: "往下看,它是怎么做到的", + badges: [ + "Developer ID 签名 + Apple 公证", + "MIT 开源", + "Tauri v2", + "11 种语言", + ], + }, + demo: { + window: "Codex App Manager", + updTitle: "有新版本", + updVer: "26.602.71036", + updFlow: "当前 26.602.40724 → 新版 26.602.71036 · 约 12.6 MiB", + updCta: "立即更新", + launch: "启动 Codex", + recheck: "重新检查", + checking: "正在检查…", + progressTitle: "正在更新…", + progressFrom: "正在从 镜像 下载", + uptodateTitle: "已是最新", + uptodateSub: "当前版本 26.602.71036", + official: "官方版本 · 刚刚检查", + successFlow: "已更新 26.602.40724 → 26.602.71036", + releaseDate: "发布时间", + releaseVal: "今天", + loc: "安装位置", + locVal: "/Applications/Codex.app", + source: "更新源:镜像", + settings: "设置", + managed: "已托管", + }, + pain: { + kicker: "为什么", + title: "装个官方应用,不该这么难", + lead: "Codex 桌面应用本身很好。难的是把它顺利装上、按时更新、干净卸掉——尤其当你身在国内。", + cards: [ + { + title: "Microsoft Store,指望不上", + body: "官方 Windows 版只在 Microsoft Store 分发。商店连不上、账号登录不了、设备被策略禁用——任何一条,都让你装不上 Codex。", + }, + { + title: "下载与更新,都压在海外线路上", + body: "macOS 官方直链和 Sparkle 更新链路都托管在海外。网络一抖,下载重试、更新停在半路,版本越落越远。", + }, + { + title: "手动安装,留下一地痕迹", + body: "自己下载、覆盖、删除,旧版本残留、状态错乱。出了问题没有回滚,只能从头再来。", + }, + ], + mock: { + file: "Codex_Installer.dmg", + host: "来源:海外节点", + status: "网络超时,正在重试…", + retry: "第 3 次重试", + }, + }, + manager: { + kicker: "Codex App Manager", + title: "安装、更新、卸载,三步完成", + lead: "Manager 不急着改动你的系统。它先检测本地的 Codex 安装状态,再生成一份计划,最后才谨慎执行——破坏性操作之前,逐项核验。", + steps: [ + { + name: "检测", + title: "看清本地的一切", + body: "识别本机 Codex 的安装状态、版本与残留。这一步不碰任何文件,只是看清楚。", + }, + { + name: "规划", + title: "把每一步写在明处", + body: "根据检测结果生成执行计划:装什么、换什么、删什么。先告诉你,再开始。", + }, + { + name: "执行", + title: "谨慎地完成", + body: "破坏性操作前先验证,执行后确认结果;macOS 上更新校验失败会自动回滚。装好之后,一键启动 Codex。", + }, + ], + scenarios: [ + { + label: "安装", + desc: "一次点击,从零到可用。装完即可直接拉起 Codex。", + }, + { + label: "更新", + desc: "macOS 上消费 Sparkle appcast,只下载版本之间的增量,EdDSA 签名逐字节校验。", + }, + { + label: "回滚", + desc: "更新失败自动回到上一个可用版本,不留半成品在你的系统里。", + }, + ], + extras: [ + { + title: "一键启动", + body: "安装、更新之后不必去翻启动台。在 Manager 里直接打开 Codex。", + }, + { + title: "Windows 无需 Microsoft Store", + body: "直接安装官方 MSIX 或便携版,更新分阶段进行,装完自动执行健康检查——商店连不上也照样用。", + }, + { + title: "11 种语言,一套温和的界面", + body: "OKLCH 暖色材质,深浅两套主题,GSAP 动效,支持包括阿拉伯语 RTL 在内的 11 种语言。", + }, + ], + mock: { + window: "Codex App Manager", + detect: { + scan: "正在检测本机环境…", + found: "发现 Codex 桌面应用", + ver: "版本落后于上游", + leftover: "检测到旧版残留", + }, + plan: { + title: "执行计划", + i1: "下载增量更新包", + i2: "校验 EdDSA 签名", + i3: "替换应用文件", + i4: "运行健康检查", + note: "破坏性操作前将逐项核验", + }, + exec: { + doing: "正在应用更新…", + ok: "更新完成 · 签名校验通过", + launch: "启动 Codex", + rollback: "如校验失败,将自动回滚", + }, + }, + }, + pipeline: { + kicker: "Codex App Mirror", + title: "从官方上游,到你的桌面", + lead: "Mirror 负责把官方安装包原样送到离你最近的节点,每一步都可验证;Manager 把这份能力变成桌面上的安装与更新体验。下面是一个安装包的完整链路。", + stages: [ + { + title: "官方上游", + body: "一切始于 OpenAI 官方发布的安装包。我们不生产字节,只负责送达。", + stat: "MSIX + DMG", + }, + { + title: "每 15 分钟看一眼", + body: "Cloudflare Cron 持续探测官方上游,GitHub Actions 作为后备。上游一有变化,即刻知晓。", + stat: "15 min", + }, + { + title: "原样自动发布", + body: "Windows MSIX 与 macOS DMG(arm64 + x64)逐字节镜像,零修改、零重新打包。每个版本附带 SHA256SUMS 与上游指纹清单。", + stat: "SHA256", + }, + { + title: "双镜像落地", + body: "R2 面向全球,IHEP S3 面向中国大陆。同一份字节,落在两个节点。", + stat: "R2 + IHEP", + }, + { + title: "一条短链,就近抵达", + body: "Cloudflare Worker 按 CF-IPCountry 路由:大陆走 IHEP 预签名链接,其余走 R2。你只需要记住一个地址。", + stat: "CF-IPCountry", + }, + { + title: "增量更新,落到桌面", + body: "macOS 上,Manager 读取 Sparkle appcast,只取版本之间的差异。官方 EdDSA 签名逐字节校验,失败自动回滚。", + stat: "EdDSA", + }, + ], + nodes: ["官方上游", "15 分钟探测", "自动发版", "双镜像", "地域分流", "你的桌面"], + branchGlobal: "全球 → R2", + branchCN: "中国大陆 → IHEP S3", + verifiedChip: "EdDSA 校验通过", + finale: "从官方上游到你的 Mac 与 PC,每一个字节都原样、可验证。", + }, + trust: { + kicker: "可信验证", + title: "每一环,都可验证", + lead: "不需要相信我们的说法。镜像与官方是否一致,任何人随时都能自己验证。", + mock: { + title: "校验对比", + tag: "示意", + official: "官方 SHA256", + mirror: "镜像 SHA256", + sig: "EdDSA 签名", + sigVal: "逐字节复制官方签名", + match: "完全一致", + }, + items: [ + { + title: "Developer ID 签名与公证", + body: "macOS 版本由 Apple Developer ID 签名,并通过 Apple 公证。来源可查,系统原生验证。", + }, + { + title: "EdDSA 逐字节校验", + body: "镜像逐字节复制官方 EdDSA 签名——签名无法伪造,我们也从不伪造。校验不通过,更新就不会安装。", + }, + { + title: "SHA256SUMS 与上游指纹", + body: "每个镜像版本附带 SHA256SUMS 与上游指纹清单。任何人,任何时候,都能比对镜像与官方是否一致。", + }, + { + title: "开源,可审计", + body: "Manager 与 Mirror 全部以 MIT 协议开源。每一行代码、每一条流水线,都摆在明处供你审阅。", + }, + ], + }, + download: { + kicker: "下载", + title: "下载 Codex App Manager", + lead: "选择适合你的方式。所有直链都是镜像永久链接,始终指向最新版本,国内直连可达。", + recommended: "为你推荐", + brew: { + title: "Homebrew", + note: "macOS 推荐", + }, + direct: [ + { + platform: "macOS · Apple Silicon", + label: "下载 .dmg", + note: "永久链接,始终最新 · 国内直连可达", + }, + { + platform: "macOS · Intel", + label: "下载 .dmg", + note: "永久链接,始终最新 · 国内直连可达", + }, + { + platform: "Windows · x64", + label: "下载 .exe", + note: "永久链接,始终最新 · 无需 Microsoft Store", + }, + ], + github: { + title: "GitHub", + note: "代码、Issue 与每一次发布记录,都公开在这里。", + managerLabel: "Wangnov/Codex-App-Manager", + mirrorLabel: "Wangnov/codex-app-mirror", + }, + }, + scope: { + kicker: "边界声明", + title: "做什么,不做什么", + items: [ + { + title: "原样分发,从不改包", + body: "不修改、不重新打包官方安装程序。EdDSA 签名逐字节复制——它无法被伪造,我们也绝不伪造。", + }, + { + title: "独立项目,无任何隶属", + body: "本项目是独立的社区工具,与 OpenAI、Microsoft 均无隶属或背书关系。", + }, + { + title: "MIT 开源", + body: "Manager 与 Mirror 均以 MIT 协议开源,代码与构建流水线公开,可审计。", + }, + ], + mit: "MIT License — 自由使用、修改与分发。", + }, + footer: { + thanks: "感谢中国科学院高能物理研究所(IHEP)为中国大陆提供镜像节点。", + license: "MIT License · 开源可审计", + made: "用 Tauri,和一点耐心做成。", + backTop: "回到顶部", + links: { + manager: "Codex App Manager", + mirror: "Codex App Mirror", + }, + }, + ui: { + langSwitch: "EN", + langAria: "切换语言", + menu: "菜单", + close: "关闭", + skip: "跳到主要内容", + copy: "复制命令", + copied: "已复制", + }, +} as const; diff --git a/website/src/main.ts b/website/src/main.ts new file mode 100644 index 0000000..15f1e33 --- /dev/null +++ b/website/src/main.ts @@ -0,0 +1,705 @@ +import "./styles/tokens.css"; +import "./styles/base.css"; +import "./styles/sections.css"; +import "./styles/pipeline.css"; + +import gsap from "gsap"; +import { ScrollTrigger } from "gsap/ScrollTrigger"; +import { MotionPathPlugin } from "gsap/MotionPathPlugin"; +import { applyLang, initialLang, t, type Lang } from "./i18n"; + +gsap.registerPlugin(ScrollTrigger, MotionPathPlugin); + +/* ============================== i18n ==================================== */ + +let lang: Lang = initialLang(); + +function syncLangUI() { + const label = document.getElementById("lang-switch-label"); + if (label) label.textContent = t(lang, "ui.langSwitch") as string; +} + +applyLang(lang); +syncLangUI(); + +/* ====================== loading orchestration =========================== */ + +// The shell fades once the critical pixels are in: hero image + display font, +// capped so a slow connection never stares at the spinner. +const ready: Promise = (() => { + const heroImg = document.querySelector(".hero-bg img"); + const imgReady: Promise = + heroImg && !heroImg.complete + ? new Promise((r) => { + heroImg.addEventListener("load", () => r(), { once: true }); + heroImg.addEventListener("error", () => r(), { once: true }); + }) + : Promise.resolve(); + const fontsReady: Promise = document.fonts?.ready ?? Promise.resolve(); + const cap = new Promise((r) => setTimeout(r, 2200)); + return Promise.race([Promise.all([imgReady, fontsReady]).then(() => undefined), cap]); +})(); + +ready.then(() => { + document.body.classList.add("loaded"); + window.setTimeout(() => document.getElementById("preloader")?.remove(), 600); +}); + +// Every image stays invisible until decoded, then eases in — no half-painted +// decorative layers while the network catches up. +document.querySelectorAll("img").forEach((img) => { + if (img.closest(".preloader")) return; + img.classList.add("fade-in"); + if (img.complete && img.naturalWidth > 0) return; + img.classList.add("fade-pending"); + const done = () => img.classList.remove("fade-pending"); + img.addEventListener("load", done, { once: true }); + img.addEventListener("error", done, { once: true }); +}); + +document.getElementById("lang-switch")?.addEventListener("click", () => { + lang = lang === "zh" ? "en" : "zh"; + applyLang(lang); + syncLangUI(); + // headline lengths differ a lot between languages + requestAnimationFrame(() => ScrollTrigger.refresh()); +}); + +/* ============================== nav ===================================== */ + +const nav = document.getElementById("nav")!; +const onScroll = () => nav.classList.toggle("is-scrolled", window.scrollY > 24); +onScroll(); +addEventListener("scroll", onScroll, { passive: true }); + +const burger = document.getElementById("nav-burger"); +const menu = document.getElementById("nav-menu"); + +function setMenu(open: boolean) { + document.body.classList.toggle("menu-open", open); + burger?.setAttribute("aria-expanded", String(open)); + burger?.setAttribute("aria-label", t(lang, open ? "ui.close" : "ui.menu") as string); + menu?.setAttribute("aria-hidden", String(!open)); +} + +burger?.addEventListener("click", () => + setMenu(!document.body.classList.contains("menu-open")) +); +menu?.querySelectorAll("a").forEach((a) => + a.addEventListener("click", () => setMenu(false)) +); + +/* ===================== decorative hex stream (trust) ==================== */ + +(() => { + const host = document.getElementById("trust-hex"); + if (!host) return; + // deterministic LCG so the texture is stable between loads + let seed = 0x5f3a; + const rnd = () => ((seed = (seed * 48271) % 0x7fffffff) & 0xff) + .toString(16) + .padStart(2, "0"); + const lines: string[] = []; + for (let i = 0; i < 26; i++) { + lines.push(Array.from({ length: 48 }, rnd).join(" ")); + } + host.textContent = lines.join("\n"); +})(); + +/* ====================== platform recommendation ========================= */ + +(() => { + const ua = navigator.userAgent; + let platform: string | null = null; + // iPhone/iPad UAs contain "like Mac OS X", and desktop-mode iPadOS even + // reports "Macintosh" — neither can run a DMG, so bail out before the Mac + // branch (touch points are the reliable tell for masquerading iPads). + const isAppleMobile = + /iPhone|iPad|iPod/i.test(ua) || + (/Macintosh/i.test(ua) && navigator.maxTouchPoints > 1); + if (/Windows/i.test(ua)) platform = "windows"; + else if (!isAppleMobile && /Macintosh/i.test(ua)) { + // Only commit to an architecture on a positive GPU signal — guessing + // wrong would deep-link the hero CTA to an installer that won't run. + try { + const gl = document.createElement("canvas").getContext("webgl"); + const dbg = gl?.getExtension("WEBGL_debug_renderer_info"); + const renderer = dbg + ? String(gl!.getParameter(dbg.UNMASKED_RENDERER_WEBGL)) + : ""; + // Order matters: Chrome's ANGLE-on-Metal renderer string on Intel Macs + // reads "ANGLE (Apple, ... Intel ...)" — the discrete-GPU vendors are + // the discriminating signal, "Apple" alone is not. + if (/(intel|amd|nvidia|radeon)/i.test(renderer)) platform = "mac-intel"; + else if (/apple/i.test(renderer)) platform = "mac-arm"; + } catch { + /* unknown — let the user pick from the download section */ + } + } + if (!platform) return; + document + .querySelector(`.dl-card[data-platform="${platform}"]`) + ?.classList.add("is-recommended"); + + // the hero CTA deep-links straight to the matching installer + const cta = document.getElementById("hero-dl") as HTMLAnchorElement | null; + const targets: Record = { + "mac-arm": { + href: "https://codexapp.agentsmirror.com/manager/latest/CodexAppManager_aarch64.dmg", + key: "hero.dl.macArm", + }, + "mac-intel": { + href: "https://codexapp.agentsmirror.com/manager/latest/CodexAppManager_x86_64.dmg", + key: "hero.dl.macIntel", + }, + windows: { + href: "https://codexapp.agentsmirror.com/manager/latest/CodexAppManager_x64-setup.exe", + key: "hero.dl.win", + }, + }; + const entry = targets[platform]; + if (cta && entry) { + cta.href = entry.href; + cta.dataset.i18n = entry.key; // applyLang keeps the label right after a switch + cta.textContent = t(lang, entry.key) as string; + } +})(); + +/* ===================== interactive product demo ========================= */ + +/* keep the 400x640 replica at the right scale inside the laptop screen */ +(() => { + const screen = document.getElementById("mac-screen"); + const win = document.getElementById("cam-demo"); + if (!screen || !win) return; + const fit = () => { + if (window.innerWidth < 1024) return; // phones show the bare window + const scale = Math.min((screen.clientHeight * 0.92) / 640, (screen.clientWidth * 0.86) / 400); + win.style.setProperty("--app-scale", scale.toFixed(4)); + }; + fit(); + addEventListener("resize", fit, { passive: true }); +})(); + +(() => { + const win = document.getElementById("cam-demo"); + if (!win) return; + const scenes = win.querySelectorAll(".cam-scene"); + const banner = document.getElementById("cam-banner")!; + const pct = document.getElementById("cam-pct")!; + const fill = document.getElementById("cam-bar-fill")!; + const reduced = matchMedia("(prefers-reduced-motion: reduce)").matches; + let bannerTimer = 0; + let busy = false; + + const show = (name: string) => + scenes.forEach((sc) => sc.classList.toggle("is-active", sc.dataset.scene === name)); + + const runUpdate = () => { + if (busy) return; + busy = true; + banner.classList.remove("is-show"); + show("progress"); + const state = { p: 0 }; + gsap.to(state, { + p: 100, + duration: reduced ? 0 : 2.2, + ease: "power1.inOut", + onUpdate() { + pct.textContent = String(Math.round(state.p)); + fill.style.width = `${state.p}%`; + }, + onComplete() { + show("done"); + banner.classList.add("is-show"); + clearTimeout(bannerTimer); + bannerTimer = window.setTimeout(() => banner.classList.remove("is-show"), 4200); + busy = false; + }, + }); + }; + + const runRecheck = () => { + if (busy) return; + busy = true; + show("checking"); + window.setTimeout( + () => { + show("done"); + busy = false; + }, + reduced ? 60 : 1100 + ); + }; + + win.addEventListener("click", (e) => { + const btn = (e.target as HTMLElement).closest("[data-demo]"); + if (!btn) return; + if (btn.dataset.demo === "update") runUpdate(); + else if (btn.dataset.demo === "recheck") runRecheck(); + else gsap.fromTo(btn, { scale: 1 }, { scale: 0.94, yoyo: true, repeat: 1, duration: 0.12 }); + }); +})(); + +/* ============================ copy button =============================== */ + +(() => { + const btn = document.getElementById("copy-brew"); + const cmd = document.getElementById("brew-cmd"); + if (!btn || !cmd) return; + let timer = 0; + btn.addEventListener("click", async () => { + try { + await navigator.clipboard.writeText(cmd.textContent ?? ""); + btn.classList.add("is-copied"); + btn.textContent = t(lang, "ui.copied") as string; + clearTimeout(timer); + timer = window.setTimeout(() => { + btn.classList.remove("is-copied"); + btn.textContent = t(lang, "ui.copy") as string; + }, 1800); + } catch { + /* clipboard unavailable */ + } + }); +})(); + +/* ============================== motion ================================== */ + +const mm = gsap.matchMedia(); + +/* manager-enactment lookups — declared before any mm.add(), because a + matching context (e.g. prefers-reduced-motion) runs its callback + synchronously at registration */ +const steps = gsap.utils.toArray("#manager-steps .step"); +const panels = gsap.utils.toArray(".mock-panel"); + +function setManagerStage(k: number) { + steps.forEach((s, i) => s.classList.toggle("is-active", i === k)); + panels.forEach((p, i) => p.classList.toggle("is-active", i === k)); +} + +mm.add("(prefers-reduced-motion: no-preference)", () => { + /* ---- generic reveals (hero has its own intro) ---- */ + document.querySelectorAll("[data-reveal]").forEach((el) => { + if (el.closest(".hero")) return; + gsap.from(el, { + y: 30, + autoAlpha: 0, + duration: 1, + ease: "power3.out", + scrollTrigger: { trigger: el, start: "top 86%", once: true }, + }); + }); + document.querySelectorAll("[data-reveal-group]").forEach((group) => { + gsap.from(group.children, { + y: 34, + autoAlpha: 0, + duration: 0.9, + ease: "power3.out", + stagger: 0.12, + scrollTrigger: { trigger: group, start: "top 84%", once: true }, + }); + }); + + /* ---- hero intro (waits for the preloader to lift) ---- */ + void ready.then(() => { + gsap.from(".hero-title .line > span", { + yPercent: 118, + duration: 1.25, + ease: "power4.out", + stagger: 0.14, + delay: 0.1, + }); + gsap.from(".hero-copy [data-reveal]", { + y: 26, + autoAlpha: 0, + duration: 1, + ease: "power3.out", + stagger: 0.1, + delay: 0.4, + }); + gsap.from(".hero-cloud", { + y: 60, + autoAlpha: 0, + duration: 1.6, + ease: "power3.out", + delay: 0.25, + }); + }); + gsap.from(".hero-demo", { + y: 60, + autoAlpha: 0, + duration: 1.1, + ease: "power3.out", + scrollTrigger: { trigger: ".hero-demo", start: "top 88%", once: true }, + }); + + /* ---- hero pointer parallax (fine pointers only) ---- */ + let onPointer: ((e: Event) => void) | undefined; + const hero = document.querySelector(".hero"); + if (matchMedia("(pointer: fine)").matches && hero) { + const layers = Array.from( + document.querySelectorAll(".hero [data-depth], .hero img.layer") + ); + const movers = layers.map((el) => ({ + depth: parseFloat(el.dataset.depth ?? "0.15"), + x: gsap.quickTo(el, "x", { duration: 0.9, ease: "power3.out" }), + y: gsap.quickTo(el, "y", { duration: 0.9, ease: "power3.out" }), + })); + onPointer = (e) => { + const { innerWidth: w, innerHeight: h } = window; + const nx = (e as PointerEvent).clientX / w - 0.5; + const ny = (e as PointerEvent).clientY / h - 0.5; + movers.forEach((m) => { + m.x(nx * 70 * m.depth); + m.y(ny * 46 * m.depth); + }); + }; + hero.addEventListener("pointermove", onPointer); + } + + /* ---- checksum card: hashes scramble, settle identical, chip pops ---- */ + const hashes = gsap.utils.toArray("[data-vc-hash]"); + if (hashes.length) { + const finals = hashes.map((h) => h.textContent ?? ""); + ScrollTrigger.create({ + trigger: ".verify-card", + start: "top 78%", + once: true, + onEnter: () => { + const HEX = "0123456789abcdef"; + const state = { p: 0 }; + gsap.to(state, { + p: 1, + duration: 1.1, + ease: "power2.out", + onUpdate() { + hashes.forEach((h, i) => { + const final = finals[i]; + h.textContent = final + .split("") + .map((ch, j) => + ch === " " || j / final.length < state.p + ? ch + : HEX[(Math.random() * 16) | 0] + ) + .join(""); + }); + }, + onComplete() { + hashes.forEach((h, i) => (h.textContent = finals[i])); + }, + }); + gsap.from("#vc-match-chip", { + scale: 0.5, + autoAlpha: 0, + duration: 0.5, + ease: "back.out(2.2)", + delay: 1.05, + }); + }, + }); + } + + return () => { + if (onPointer) hero?.removeEventListener("pointermove", onPointer); + }; +}); + +/* ---- reduced motion: settle everything into its final, visible state ---- */ +mm.add("(prefers-reduced-motion: reduce)", () => { + gsap.set("#rail-fill", { scaleY: 1 }); + document.querySelectorAll(".rail-item").forEach((el) => el.classList.add("is-lit")); + // show the final "execute" panel, with every step lit and the outcome settled + setManagerStage(2); + steps.forEach((s) => s.classList.add("is-active")); + gsap.set("#mock-progress-bar", { width: "100%" }); + gsap.set("#mock-done, #mock-launch", { opacity: 1 }); + return () => {}; +}); + +/* ---- manager enactment: desktop = pinned scrub, mobile = step triggers -- */ + +mm.add( + "(min-width: 1024px) and (prefers-reduced-motion: no-preference)", + () => { + /* hero scroll parallax — desktop only: on phones the hero is taller than + the viewport, so fading the content out would hide the demo mid-read */ + const heroTl = gsap + .timeline({ + scrollTrigger: { + trigger: ".hero", + start: "top top", + end: "bottom top", + scrub: true, + }, + }) + .to(".hero-bg img", { yPercent: 14, scale: 1.06, ease: "none" }, 0) + .to(".hero-bokeh", { yPercent: -16, ease: "none" }, 0) + .to(".hero-mist", { yPercent: -26, ease: "none" }, 0) + .to(".hero-cloud", { yPercent: -52, rotation: 2.5, ease: "none" }, 0); + + /* pinned manager */ + const checks = gsap.utils.toArray(".mock-panel .mock-check"); + const tl = gsap.timeline({ + scrollTrigger: { + trigger: "#manager-stage", + start: "top 12%", + end: "+=2200", + pin: true, + scrub: 0.6, + onUpdate(self) { + const p = self.progress; + const k = p < 0.33 ? 0 : p < 0.66 ? 1 : 2; + setManagerStage(k); + checks.forEach((c, i) => + c.classList.toggle( + "is-done", + p > 0.38 + i * 0.062 // ticks march down the plan list + ) + ); + }, + }, + }); + tl.fromTo( + ".mock-scanline", + { y: -30, opacity: 0.9 }, + { y: 360, opacity: 0.4, duration: 30, ease: "none" }, + 0 + ) + .to("#mock-progress-bar", { width: "100%", duration: 22, ease: "none" }, 70) + .to("#mock-done", { opacity: 1, duration: 4 }, 92) + .to("#mock-launch", { opacity: 1, duration: 4 }, 95); + + /* pinned pipeline */ + const pipeTl = buildPipeline(); + + return () => { + pipeTl?.kill(); + setManagerStage(0); + }; + } +); + +mm.add("(max-width: 1023px) and (prefers-reduced-motion: no-preference)", () => { + /* manager steps activate as they pass; exec panel plays a one-shot fill */ + const triggers = steps.map((step, i) => + ScrollTrigger.create({ + trigger: step, + start: "top 62%", + onEnter: () => { + setManagerStage(i); + if (i === 2) { + gsap.to("#mock-progress-bar", { width: "100%", duration: 1.1, ease: "power1.inOut" }); + gsap.to("#mock-done, #mock-launch", { opacity: 1, delay: 1.0, duration: 0.5 }); + } + }, + onEnterBack: () => setManagerStage(i), + }) + ); + + /* pipeline rail */ + const fill = document.getElementById("rail-fill"); + let railTl: gsap.core.Tween | undefined; + if (fill) { + railTl = gsap.to(fill, { + scaleY: 1, + ease: "none", + scrollTrigger: { + trigger: "#pipeline-rail", + start: "top 64%", + end: "bottom 78%", + scrub: true, + }, + }); + } + const items = gsap.utils.toArray(".rail-item"); + const itemTriggers = items.map((item) => + ScrollTrigger.create({ + trigger: item, + start: "top 68%", + onEnter: () => item.classList.add("is-lit"), + onLeaveBack: () => item.classList.remove("is-lit"), + }) + ); + + return () => { + triggers.forEach((tr) => tr.kill()); + itemTriggers.forEach((tr) => tr.kill()); + railTl?.kill(); + }; +}); + +/* ===================== the pipeline (pinned, scrubbed) =================== */ + +function buildPipeline(): gsap.core.Timeline | undefined { + const stage = document.getElementById("pipeline-stage"); + if (!stage) return; + + const cards = gsap.utils.toArray(".stage-card"); + const nodes = [ + "#node-0", + "#node-1", + "#node-2", + "#node-r2", + "#node-4", + "#node-5", + ]; + const labels = gsap.utils.toArray(".pipe-label"); + const bars = gsap.utils.toArray("#stage-progress i"); + const index = document.getElementById("stage-index")!; + + // prepare lit paths for progressive draw + const litPairs = [ + ["#lit-1", "#lit-glow-1"], + ["#lit-r2", "#lit-glow-r2"], + ["#lit-cn", "#lit-glow-cn"], + ["#lit-tail", "#lit-glow-tail"], + ]; + for (const pair of litPairs) { + for (const sel of pair) { + const el = document.querySelector(sel)!; + const len = el.getTotalLength(); + el.style.strokeDasharray = `${len}`; + el.style.strokeDashoffset = `${len}`; + } + } + const draw = (sel: string[], from: number, to: number, dur: number, pos: number, tl: gsap.core.Timeline) => { + for (const s of sel) { + const el = document.querySelector(s)!; + const len = el.getTotalLength(); + tl.fromTo( + el, + { strokeDashoffset: len * (1 - from) }, + { strokeDashoffset: len * (1 - to), duration: dur, ease: "none" }, + pos + ); + } + }; + + // card k fades in at these timeline positions (of 100) + const STAGE_BOUNDS = [0.116, 0.276, 0.456, 0.636, 0.796]; + const stageFromProgress = (p: number) => + STAGE_BOUNDS.reduce((k, bound) => (p >= bound ? k + 1 : k), 0); + + const tl = gsap.timeline({ + defaults: { ease: "none" }, + scrollTrigger: { + trigger: "#pipeline-scroll", + start: "top top", + end: "+=5200", + pin: "#pipeline-stage", + scrub: 0.7, + anticipatePin: 1, + onUpdate(self) { + const p = self.progress; + const k = stageFromProgress(p); + index.textContent = `0${k + 1}`; + bars.forEach((b, i) => b.classList.toggle("is-on", i <= k)); + const lit = (i: number, on: boolean) => { + document.querySelector(nodes[i])?.classList.toggle("is-lit", on); + labels[i]?.classList.toggle("is-lit", on); + }; + lit(0, p > 0.02); + lit(1, p > 0.2); + lit(2, p > 0.4); + document.querySelector("#node-cn")?.classList.toggle("is-lit", p > 0.56); + lit(3, p > 0.56); + lit(4, p > 0.76); + lit(5, p > 0.92); + }, + }, + }); + + const card = (k: number, pos: number) => { + if (k > 0) tl.to(cards[k - 1], { autoAlpha: 0, y: 18, duration: 2.4 }, pos); + tl.fromTo( + cards[k], + { autoAlpha: 0, y: 24 }, + { autoAlpha: 1, y: 0, duration: 2.6 }, + pos + 1.6 + ); + }; + + // -- timeline body (100 duration units; zero tween pins the total) -- + tl.to("#pipeline-stage", { duration: 0 }, 100); + + // s0: the packet wakes up at the upstream node + tl.fromTo("#packet", { autoAlpha: 0, scale: 0.4 }, { autoAlpha: 1, scale: 1, duration: 3 }, 1); + tl.to("#packet", { + motionPath: { path: "#lit-1", align: "#lit-1", alignOrigin: [0.5, 0.5], start: 0, end: 0.001 }, + duration: 0.01, + }, 0); + + // s1: draw to the probe; pulse rings sweep + card(1, 10); + draw(["#lit-1", "#lit-glow-1"], 0, 0.5, 9, 10, tl); + tl.to("#packet", { + motionPath: { path: "#lit-1", align: "#lit-1", alignOrigin: [0.5, 0.5], start: 0.001, end: 0.5 }, + duration: 9, + }, 10); + tl.fromTo("#pulse-a", { attr: { r: 36 }, opacity: 0.8 }, { attr: { r: 96 }, opacity: 0, duration: 6, immediateRender: false }, 15); + tl.fromTo("#pulse-b", { attr: { r: 36 }, opacity: 0.8 }, { attr: { r: 96 }, opacity: 0, duration: 6, immediateRender: false }, 18.5); + + // s2: continue to the release node; shards join the packet + card(2, 26); + draw(["#lit-1", "#lit-glow-1"], 0.5, 1, 10, 27, tl); + tl.to("#packet", { + motionPath: { path: "#lit-1", align: "#lit-1", alignOrigin: [0.5, 0.5], start: 0.5, end: 1 }, + duration: 10, + }, 27); + tl.fromTo("#packet img:first-child", { scale: 1 }, { scale: 1.3, yoyo: true, repeat: 1, duration: 1.6 }, 36); + + // s3: split onto the two mirror branches + card(3, 44); + draw(["#lit-r2", "#lit-glow-r2"], 0, 0.55, 9, 45, tl); + draw(["#lit-cn", "#lit-glow-cn"], 0, 0.55, 9, 45, tl); + tl.to("#packet", { + motionPath: { path: "#lit-r2", align: "#lit-r2", alignOrigin: [0.5, 0.5], start: 0, end: 0.55 }, + duration: 9, + }, 45); + tl.fromTo("#packet-2", { autoAlpha: 0 }, { autoAlpha: 1, duration: 1.5 }, 45); + tl.to("#packet-2", { + motionPath: { path: "#lit-cn", align: "#lit-cn", alignOrigin: [0.5, 0.5], start: 0, end: 0.55 }, + duration: 9, + }, 45); + + // s4: converge on the router + card(4, 62); + draw(["#lit-r2", "#lit-glow-r2"], 0.55, 1, 8, 63, tl); + draw(["#lit-cn", "#lit-glow-cn"], 0.55, 1, 8, 63, tl); + tl.to("#packet", { + motionPath: { path: "#lit-r2", align: "#lit-r2", alignOrigin: [0.5, 0.5], start: 0.55, end: 1 }, + duration: 8, + }, 63); + tl.to("#packet-2", { + motionPath: { path: "#lit-cn", align: "#lit-cn", alignOrigin: [0.5, 0.5], start: 0.55, end: 1 }, + duration: 8, + }, 63); + tl.to("#packet-2", { autoAlpha: 0, scale: 0.5, duration: 2 }, 71.5); + + // s5: the tail — delta lands on the desktop + card(5, 78); + draw(["#lit-tail", "#lit-glow-tail"], 0, 1, 7, 80, tl); + tl.to("#packet", { + motionPath: { path: "#lit-tail", align: "#lit-tail", alignOrigin: [0.5, 0.5], start: 0, end: 1 }, + duration: 7, + }, 80); + tl.to("#packet", { scale: 0.3, autoAlpha: 0, duration: 2.5 }, 88); + + // finale + tl.fromTo("#pipe-finale", { autoAlpha: 0 }, { autoAlpha: 1, duration: 5 }, 93); + tl.fromTo( + "#pipe-finale .chip", + { scale: 0.6, autoAlpha: 0 }, + { scale: 1, autoAlpha: 1, duration: 3, ease: "back.out(2)" }, + 94 + ); + tl.to("#stage-index", { autoAlpha: 0, duration: 3 }, 93); + + return tl; +} + +/* ---- keep ScrollTrigger honest once webfonts settle ---- */ +document.fonts?.ready.then(() => ScrollTrigger.refresh()); diff --git a/website/src/styles/base.css b/website/src/styles/base.css new file mode 100644 index 0000000..b3fbbd3 --- /dev/null +++ b/website/src/styles/base.css @@ -0,0 +1,263 @@ +*, +*::before, +*::after { + box-sizing: border-box; +} + +html { + scroll-behavior: smooth; + -webkit-text-size-adjust: 100%; +} + +@media (prefers-reduced-motion: reduce) { + html { + scroll-behavior: auto; + } +} + +body { + margin: 0; + background: var(--bg); + color: var(--ink); + font-family: var(--font-body); + font-size: 16px; + line-height: 1.75; + -webkit-font-smoothing: antialiased; + text-rendering: optimizeLegibility; + overflow-x: clip; +} + +img, +svg { + display: block; + max-width: 100%; +} + +/* width/height attributes reserve layout space; CSS decides the real size */ +img { + height: auto; +} + +a { + color: inherit; + text-decoration: none; +} + +button { + font: inherit; + color: inherit; + background: none; + border: 0; + cursor: pointer; + padding: 0; +} + +::selection { + background: var(--accent-glow); +} + +:focus-visible { + outline: 2px solid var(--accent); + outline-offset: 3px; + border-radius: 6px; +} + +.skip-link { + position: fixed; + top: 10px; + left: 10px; + z-index: 100; + padding: 10px 16px; + background: var(--surface); + border: 1px solid var(--line-strong); + border-radius: 999px; + transform: translateY(-220%); + transition: transform 0.2s; +} + +.skip-link:focus-visible { + transform: none; +} + +/* ---- typography ---------------------------------------------------------- */ + +.display { + margin: 0; + font-family: var(--font-display-zh); + font-weight: 900; + line-height: 1.2; + letter-spacing: 0.015em; + text-wrap: balance; +} + +body[data-lang="en"] .display { + font-family: var(--font-display-en); + font-weight: 580; + line-height: 1.08; + letter-spacing: -0.012em; + font-variation-settings: "opsz" 78, "SOFT" 30, "WONK" 0; +} + +.kicker { + margin: 0 0 18px; + font-size: 13px; + font-weight: 600; + letter-spacing: 0.28em; + text-transform: uppercase; + color: var(--amber-deep); +} + +body[data-lang="zh"] .kicker { + letter-spacing: 0.42em; +} + +.lead { + margin: 22px 0 0; + max-width: 34em; + font-size: clamp(16px, 1.35vw, 19px); + color: var(--ink-2); +} + +.section-title { + font-size: clamp(34px, 4.6vw, 58px); + max-width: 18em; +} + +/* ---- layout -------------------------------------------------------------- */ + +.container { + width: min(var(--container), calc(100% - clamp(40px, 8vw, 128px))); + margin-inline: auto; +} + +section { + position: relative; + scroll-margin-top: calc(var(--nav-h) + 12px); +} + +.section-pad { + padding-block: clamp(96px, 14vh, 170px); +} + +.section-head { + position: relative; + z-index: 2; +} + +/* ---- buttons ------------------------------------------------------------- */ + +.btn { + display: inline-flex; + align-items: center; + justify-content: center; + gap: 10px; + padding: 15px 30px; + border-radius: 999px; + font-weight: 600; + font-size: 16px; + line-height: 1.2; + transition: transform 0.35s cubic-bezier(0.22, 1, 0.36, 1), box-shadow 0.35s, background-color 0.35s, border-color 0.35s; + will-change: transform; +} + +.btn:active { + transform: scale(0.97); +} + +.btn-primary { + background: linear-gradient( + 180deg, + color-mix(in oklch, var(--accent) 82%, white), + var(--accent-2) + ); + color: var(--accent-ink); + box-shadow: 0 10px 28px -8px var(--accent-glow), var(--highlight); +} + +.btn-primary:hover { + transform: translateY(-2px); + box-shadow: 0 16px 36px -8px var(--accent-glow), var(--highlight); +} + +.btn-ghost { + border: 1px solid var(--line-strong); + background: color-mix(in oklch, var(--surface) 55%, transparent); + color: var(--ink); + backdrop-filter: blur(6px); +} + +.btn-ghost:hover { + transform: translateY(-2px); + border-color: color-mix(in oklch, var(--ink) 38%, transparent); +} + +/* ---- cards & chips ------------------------------------------------------- */ + +.card { + position: relative; + border-radius: var(--radius-lg); + border: 1px solid var(--line); + background: linear-gradient(180deg, var(--surface-top), var(--surface)); + box-shadow: var(--shadow-card), var(--highlight); + padding: 30px 30px 32px; +} + +.card h3 { + margin: 0 0 10px; + font-size: 19px; + font-weight: 700; + letter-spacing: 0.01em; +} + +.card p { + margin: 0; + color: var(--ink-2); + font-size: 15.5px; +} + +.chip { + display: inline-flex; + align-items: center; + gap: 8px; + padding: 7px 15px; + border-radius: 999px; + border: 1px solid var(--line); + background: color-mix(in oklch, var(--surface-2) 72%, transparent); + font-size: 13px; + font-weight: 500; + color: var(--ink-2); + backdrop-filter: blur(8px); +} + +.chip-amber { + border-color: color-mix(in oklch, var(--amber) 30%, transparent); + background: var(--amber-tint); + color: var(--amber-deep); +} + +.chip-jade { + border-color: color-mix(in oklch, var(--jade) 32%, transparent); + background: var(--jade-tint); + color: var(--jade); +} + +code { + font-family: var(--font-mono); + font-size: 0.92em; +} + +/* decorative layers never intercept input */ +.layer { + position: absolute; + pointer-events: none; + user-select: none; +} + +/* progressive image loading: stay invisible until decoded, then ease in */ +img.fade-in { + transition: opacity 0.7s ease; +} + +img.fade-pending { + opacity: 0 !important; +} diff --git a/website/src/styles/pipeline.css b/website/src/styles/pipeline.css new file mode 100644 index 0000000..a2a2e71 --- /dev/null +++ b/website/src/styles/pipeline.css @@ -0,0 +1,398 @@ +/* =========================================================================== + Pipeline — the scroll-driven "journey of a byte" + Desktop (fine pointers, motion allowed): pinned SVG stage, scrubbed. + Mobile / reduced-motion: vertical rail timeline. +=========================================================================== */ + +.pipeline { + position: relative; + background: + linear-gradient(var(--bg), var(--bg-deep) 30%, var(--bg-deep) 75%, var(--bg)); + overflow: clip; +} + +.pipeline-head { + padding-top: clamp(96px, 14vh, 170px); + padding-bottom: clamp(30px, 6vh, 70px); +} + +.pipeline-duo { + display: inline-flex; + align-items: center; + gap: 12px; + margin-top: 26px; + padding: 10px 18px; + border-radius: 999px; + border: 1px solid var(--line); + background: color-mix(in oklch, var(--surface) 60%, transparent); + font-size: 13px; + color: var(--ink-2); +} + +.pipeline-duo img { + width: 22px; + height: 22px; +} + +.pipeline-duo .arrow { + color: var(--amber-deep); + font-weight: 700; +} + +/* ---- pinned desktop stage ------------------------------------------------ */ + +.pipeline-scroll { + display: none; + position: relative; +} + +.pipeline-stage { + position: relative; + height: 100vh; + overflow: clip; +} + +.pipe-bokeh { + top: 6%; + left: 2%; + width: 44%; + opacity: 0.25; +} + +.pipe-mist { + bottom: -4%; + right: -8%; + width: 70%; + opacity: 0.3; +} + +.pipe-svg-wrap { + position: absolute; + inset: 0; + display: grid; + place-items: center; +} + +.pipe-svg { + width: min(1380px, 96vw); + height: auto; + overflow: visible; +} + +.pipe-track { + fill: none; + stroke: var(--line-strong); + stroke-width: 1.5; + stroke-dasharray: 3 9; + stroke-linecap: round; +} + +.pipe-lit { + fill: none; + stroke: url(#pipeGrad); + stroke-width: 3; + stroke-linecap: round; +} + +.pipe-lit-glow { + fill: none; + stroke: var(--amber); + stroke-width: 9; + stroke-linecap: round; + opacity: 0.22; + filter: blur(6px); +} + +.pipe-node circle.bgc { + fill: var(--surface); + stroke: var(--line-strong); + stroke-width: 1.5; + transition: stroke 0.4s, fill 0.4s; +} + +.pipe-node .icon { + stroke: var(--ink-3); + fill: none; + stroke-width: 1.8; + stroke-linecap: round; + stroke-linejoin: round; + transition: stroke 0.4s; +} + +.pipe-node.is-lit circle.bgc { + stroke: var(--amber); + fill: color-mix(in oklch, var(--amber) 12%, var(--surface)); +} + +.pipe-node.is-lit .icon { + stroke: var(--amber-deep); +} + +.pipe-node.is-jade.is-lit circle.bgc { + stroke: var(--jade); + fill: color-mix(in oklch, var(--jade) 12%, var(--surface)); +} + +.pipe-node.is-jade.is-lit .icon { + stroke: var(--jade); +} + +.pipe-label { + font-family: var(--font-body); + font-size: 13px; + font-weight: 600; + fill: var(--ink-3); + transition: fill 0.4s; +} + +.pipe-node.is-lit ~ g .pipe-label { + fill: var(--ink-3); +} + +.pipe-label.is-lit { + fill: var(--ink); +} + +.pipe-branch-label { + font-family: var(--font-mono); + font-size: 11.5px; + fill: var(--ink-3); + letter-spacing: 0.04em; +} + +.pulse-ring { + fill: none; + stroke: var(--amber); + stroke-width: 1.5; + opacity: 0; +} + +.packet { + position: absolute; + top: 0; + left: 0; + width: 64px; + height: 64px; + margin: -32px 0 0 -32px; + opacity: 0; + will-change: transform; + z-index: 3; +} + +.packet img { + width: 100%; + height: 100%; +} + +/* stage copy card */ + +.stage-card { + position: absolute; + left: clamp(24px, 5vw, 84px); + bottom: clamp(28px, 7vh, 84px); + z-index: 4; + width: min(440px, 38vw); + padding: 28px 30px; + border-radius: var(--radius-lg); + border: 1px solid var(--line); + background: color-mix(in oklch, var(--surface) 82%, transparent); + backdrop-filter: blur(16px) saturate(1.3); + -webkit-backdrop-filter: blur(16px) saturate(1.3); + box-shadow: var(--shadow-soft), var(--highlight); +} + +.stage-card .stat { + position: absolute; + top: -14px; + right: 22px; +} + +.stage-card h3 { + margin: 0 0 8px; + font-size: 21px; +} + +.stage-card p { + margin: 0; + font-size: 14.5px; + color: var(--ink-2); +} + +.stage-index { + position: absolute; + top: clamp(16px, 5vh, 48px); + right: clamp(24px, 5vw, 84px); + z-index: 1; + font-family: var(--font-display-en); + font-variation-settings: "opsz" 80; + font-size: clamp(96px, 14vw, 190px); + font-weight: 500; + line-height: 1; + color: transparent; + -webkit-text-stroke: 1px color-mix(in oklch, var(--ink) 22%, transparent); +} + +.stage-progress { + position: absolute; + right: clamp(28px, 5vw, 88px); + bottom: clamp(28px, 7vh, 84px); + z-index: 4; + display: flex; + gap: 8px; +} + +.stage-progress i { + width: 26px; + height: 3px; + border-radius: 3px; + background: color-mix(in oklch, var(--ink) 16%, transparent); + transition: background-color 0.3s; +} + +.stage-progress i.is-on { + background: var(--amber); +} + +.pipe-finale { + position: absolute; + inset: 0; + z-index: 5; + display: grid; + place-items: center; + text-align: center; + opacity: 0; + visibility: hidden; + background: color-mix(in oklch, var(--bg) 62%, transparent); + backdrop-filter: blur(10px); + -webkit-backdrop-filter: blur(10px); +} + +.pipe-finale .inner { + max-width: 640px; + padding: 0 28px; +} + +.pipe-finale .display { + font-size: clamp(28px, 3.6vw, 44px); +} + +.pipe-finale .chip { + margin-bottom: 26px; +} + +/* ---- vertical rail (mobile + reduced motion) ----------------------------- */ + +.pipeline-rail { + position: relative; + padding-bottom: clamp(80px, 12vh, 140px); +} + +.rail-line { + position: absolute; + top: 10px; + bottom: clamp(80px, 12vh, 140px); + left: 27px; + width: 2px; + background: color-mix(in oklch, var(--ink) 12%, transparent); + overflow: hidden; +} + +.rail-line i { + position: absolute; + inset: 0; + background: linear-gradient(var(--amber-deep), var(--amber)); + transform-origin: top; + transform: scaleY(0); +} + +.rail-item { + position: relative; + padding: 0 0 44px 76px; +} + +.rail-item:last-child { + padding-bottom: 0; +} + +.rail-node { + position: absolute; + left: 0; + top: 0; + width: 56px; + height: 56px; + border-radius: 50%; + border: 1.5px solid var(--line-strong); + background: var(--surface); + display: grid; + place-items: center; + transition: border-color 0.4s, background-color 0.4s; +} + +.rail-node svg { + width: 26px; + height: 26px; + stroke: var(--ink-3); + fill: none; + stroke-width: 1.8; + stroke-linecap: round; + stroke-linejoin: round; + transition: stroke 0.4s; +} + +.rail-item.is-lit .rail-node { + border-color: var(--amber); + background: color-mix(in oklch, var(--amber) 10%, var(--surface)); +} + +.rail-item.is-lit .rail-node svg { + stroke: var(--amber-deep); +} + +.rail-item:last-child.is-lit .rail-node { + border-color: var(--jade); + background: color-mix(in oklch, var(--jade) 10%, var(--surface)); +} + +.rail-item:last-child.is-lit .rail-node svg { + stroke: var(--jade); +} + +.rail-item .stat-chip { + margin-bottom: 10px; +} + +.rail-item h3 { + margin: 0 0 8px; + font-size: 19px; +} + +.rail-item p { + margin: 0; + max-width: 34em; + font-size: 14.5px; + color: var(--ink-2); +} + +.rail-finale { + margin: 8px 0 0 76px; + padding: 26px 28px; + border-radius: var(--radius-lg); + border: 1px solid color-mix(in oklch, var(--jade) 30%, transparent); + background: var(--jade-tint); +} + +.rail-finale .display { + font-size: clamp(20px, 4.6vw, 26px); +} + +/* ---- mode switching ------------------------------------------------------ */ + +@media (min-width: 1024px) and (prefers-reduced-motion: no-preference) { + .pipeline-scroll { + display: block; + } + + .pipeline-rail { + display: none; + } +} diff --git a/website/src/styles/sections.css b/website/src/styles/sections.css new file mode 100644 index 0000000..b266b05 --- /dev/null +++ b/website/src/styles/sections.css @@ -0,0 +1,1816 @@ +/* =========================================================================== + Navigation +=========================================================================== */ + +.nav { + position: fixed; + inset: 0 0 auto; + z-index: 50; + height: var(--nav-h); + transition: background-color 0.4s, box-shadow 0.4s, backdrop-filter 0.4s; +} + +.nav.is-scrolled { + background: var(--nav-bg); + backdrop-filter: blur(18px) saturate(1.4); + -webkit-backdrop-filter: blur(18px) saturate(1.4); + box-shadow: 0 1px 0 var(--line); +} + +.nav-inner { + height: 100%; + display: flex; + align-items: center; + gap: 28px; +} + +.brand { + display: flex; + align-items: center; + gap: 11px; + font-weight: 700; + font-size: 15.5px; + letter-spacing: 0.01em; + white-space: nowrap; +} + +.brand img { + width: 30px; + height: 30px; +} + +.nav-links { + display: flex; + gap: 4px; + margin: 0 auto; + padding: 0; + list-style: none; +} + +.nav-links a { + display: block; + padding: 8px 13px; + border-radius: 999px; + font-size: 14px; + color: var(--ink-2); + transition: color 0.25s, background-color 0.25s; +} + +.nav-links a:hover { + color: var(--ink); + background: color-mix(in oklch, var(--ink) 7%, transparent); +} + +.nav-actions { + display: flex; + align-items: center; + gap: 12px; + margin-left: auto; +} + +.lang-switch { + display: inline-flex; + align-items: center; + gap: 7px; + padding: 8px 14px; + border-radius: 999px; + border: 1px solid var(--line-strong); + font-size: 13px; + font-weight: 600; + color: var(--ink-2); + white-space: nowrap; + transition: color 0.25s, border-color 0.25s, transform 0.25s; +} + +.lang-switch:hover { + color: var(--ink); + border-color: color-mix(in oklch, var(--ink) 36%, transparent); +} + +.lang-switch .globe { + width: 14px; + height: 14px; + opacity: 0.75; +} + +.nav-cta { + padding: 9px 20px; + font-size: 14px; +} + +.nav-burger { + display: none; + width: 42px; + height: 42px; + border-radius: 12px; + position: relative; +} + +.nav-burger span, +.nav-burger span::before, +.nav-burger span::after { + content: ""; + position: absolute; + left: 50%; + width: 18px; + height: 2px; + border-radius: 2px; + background: var(--ink); + transform: translateX(-50%); + transition: transform 0.3s, opacity 0.3s; +} + +.nav-burger span { + top: 50%; +} + +.nav-burger span::before { + top: -6px; +} + +.nav-burger span::after { + top: 6px; +} + +body.menu-open .nav-burger span { + background: transparent; +} + +body.menu-open .nav-burger span::before { + transform: translateX(-50%) translateY(6px) rotate(45deg); +} + +body.menu-open .nav-burger span::after { + transform: translateX(-50%) translateY(-6px) rotate(-45deg); +} + +.nav-menu { + position: fixed; + inset: 0; + z-index: 40; + display: none; + flex-direction: column; + justify-content: center; + padding: 80px clamp(28px, 8vw, 64px) 48px; + background: color-mix(in oklch, var(--bg) 92%, transparent); + backdrop-filter: blur(26px) saturate(1.3); + -webkit-backdrop-filter: blur(26px) saturate(1.3); +} + +body.menu-open .nav-menu { + display: flex; +} + +.nav-menu a { + padding: 14px 0; + font-size: clamp(26px, 7vw, 34px); + font-weight: 700; + border-bottom: 1px solid var(--line); +} + +.nav-menu .menu-cta { + margin-top: 30px; + border: 0; +} + +/* =========================================================================== + Hero +=========================================================================== */ + +.hero { + position: relative; + min-height: 100svh; + display: flex; + flex-direction: column; + justify-content: center; + overflow: clip; +} + +.hero-bg { + position: absolute; + inset: 0; + z-index: -3; +} + +.hero-bg img { + width: 100%; + height: 100%; + object-fit: cover; + object-position: center 68%; +} + +.hero-veil { + position: absolute; + inset: 0; + z-index: -2; + background: + radial-gradient(120% 90% at 30% 12%, transparent 40%, color-mix(in oklch, var(--bg) 55%, transparent) 100%), + linear-gradient(180deg, color-mix(in oklch, var(--bg) 30%, transparent), transparent 30%, transparent 62%, var(--bg) 98%); +} + +.hero-mist { + bottom: -6%; + left: -12%; + width: 80%; + opacity: 0.5; + z-index: -1; +} + +@media (prefers-color-scheme: light) { + .hero-mist { + opacity: 0.14; + } + + .pipe-mist { + opacity: 0.16; + } +} + +.hero-bokeh { + top: 4%; + right: -6%; + width: 58%; + opacity: 0.42; + z-index: -1; +} + +.hero-cloud { + bottom: 2%; + right: clamp(-60px, 1vw, 60px); + width: clamp(180px, 21vw, 300px); + z-index: 0; + opacity: 0.9; + filter: drop-shadow(0 30px 60px oklch(0.05 0.01 274 / 0.45)); +} + +.hero-content { + position: relative; + z-index: 2; + padding-top: calc(var(--nav-h) + 7vh); + padding-bottom: clamp(36px, 6vh, 64px); +} + +.eyebrow { + margin: 0 0 26px; + display: inline-flex; + align-items: center; + gap: 10px; + font-size: 13.5px; + font-weight: 600; + letter-spacing: 0.18em; + color: var(--ink-2); +} + +.eyebrow::before { + content: ""; + width: 26px; + height: 1px; + background: var(--amber); +} + +.hero-title { + font-size: clamp(42px, 6.2vw, 86px); + max-width: 13em; +} + +body[data-lang="en"] .hero-title { + font-size: clamp(42px, 5.8vw, 82px); +} + +@media (max-width: 1023px) { + .hero-title { + font-size: clamp(38px, 8.4vw, 56px); + } + + body[data-lang="en"] .hero-title { + font-size: clamp(38px, 8vw, 54px); + } +} + +.hero-title .line { + display: block; + overflow: hidden; + padding-bottom: 0.08em; + margin-bottom: -0.08em; +} + +.hero-title .line > span { + display: block; +} + +.hero-title .grad > span { + color: transparent; + background: linear-gradient(100deg, var(--amber) 8%, var(--accent-soft) 92%); + -webkit-background-clip: text; + background-clip: text; +} + +.hero-tagline { + margin: 24px 0 0; + max-width: 30em; + font-size: clamp(17px, 1.6vw, 21px); + font-weight: 500; + color: var(--ink); +} + +.hero-sub { + margin: 14px 0 0; + max-width: 36em; + font-size: clamp(14.5px, 1.2vw, 16.5px); + color: var(--ink-2); +} + +.cta-row { + display: flex; + flex-wrap: wrap; + align-items: center; + gap: 16px; + margin-top: 34px; +} + +.hero-badges { + display: flex; + flex-wrap: wrap; + gap: 10px; + margin: 34px 0 0; + padding: 0; + list-style: none; +} + +.hero-grid { + display: flex; + flex-direction: column; + align-items: center; + gap: clamp(44px, 7vh, 72px); +} + +.hero-copy { + display: flex; + flex-direction: column; + align-items: center; + text-align: center; +} + +.hero-alt { + margin: 14px 0 0; + font-size: 13.5px; +} + +.hero-alt a { + color: var(--ink-2); + border-bottom: 1px solid var(--line-strong); + padding-bottom: 2px; + transition: color 0.25s, border-color 0.25s; +} + +.hero-alt a:hover { + color: var(--ink); + border-color: var(--amber); +} + +/* ---- interactive product replica, framed in a MacBook ---- */ + +.hero-demo { + position: relative; + width: min(880px, 100%); + margin-inline: auto; +} + +.mac-laptop { + position: relative; + width: 100%; +} + +.mac-screen { + position: relative; + aspect-ratio: 16 / 10; + border-radius: 18px 18px 0 0; + border: 8px solid oklch(0.13 0.008 274); + border-bottom-width: 26px; + background: oklch(0.13 0.008 274); + overflow: hidden; + box-shadow: var(--shadow-soft); +} + +.mac-screen::before { + /* skeleton shimmer until the wallpaper decodes */ + content: ""; + position: absolute; + inset: 0; + background: linear-gradient( + 110deg, + color-mix(in oklch, var(--surface) 80%, transparent) 30%, + color-mix(in oklch, var(--surface-2-top) 90%, transparent) 45%, + color-mix(in oklch, var(--surface) 80%, transparent) 60% + ); + background-size: 220% 100%; + animation: skel-sweep 1.5s ease-in-out infinite; +} + +@media (prefers-reduced-motion: reduce) { + .mac-screen::before { + animation: none; + } +} + +@keyframes skel-sweep { + 0% { + background-position: 130% 0; + } + 100% { + background-position: -90% 0; + } +} + +.mac-wall { + position: absolute; + inset: 0; + z-index: 1; +} + +.mac-wall img { + width: 100%; + height: 100%; + object-fit: cover; + object-position: center 70%; + /* the panel glows a touch brighter than the page behind it */ + filter: brightness(1.3) saturate(1.08); +} + +.mac-notch { + position: absolute; + top: 0; + left: 50%; + transform: translateX(-50%); + width: 96px; + height: 13px; + border-radius: 0 0 9px 9px; + background: oklch(0.13 0.008 274); + z-index: 4; +} + +.mac-base { + position: relative; + width: 116%; + margin-left: -8%; + height: 15px; + border-radius: 3px 3px 14px 14px; + background: linear-gradient(180deg, oklch(0.78 0.008 274), oklch(0.56 0.012 274) 85%, oklch(0.42 0.012 274)); + box-shadow: 0 18px 36px -16px oklch(0.05 0.01 274 / 0.6); +} + +.mac-base i { + position: absolute; + top: 0; + left: 50%; + transform: translateX(-50%); + width: 92px; + height: 7px; + border-radius: 0 0 8px 8px; + background: oklch(0.5 0.01 274); +} + +/* the app at its real, fixed 400x640 window size — scaled to fit the screen */ +.cam-window { + position: absolute; + left: 50%; + top: 50%; + width: 400px; + height: 640px; + transform: translate(-50%, -50%) scale(var(--app-scale, 0.62)); + display: flex; + flex-direction: column; + z-index: 2; + border-radius: 14px; + border: 1px solid var(--line); + background: linear-gradient(180deg, var(--surface-top), var(--surface)); + box-shadow: 0 30px 70px -18px oklch(0.05 0.01 274 / 0.65), var(--highlight); + overflow: hidden; + font-size: 13.5px; +} + +.cam-titlebar { + display: flex; + align-items: center; + gap: 8px; + padding: 13px 16px; + border-bottom: 1px solid var(--line); +} + +.cam-titlebar i { + width: 11px; + height: 11px; + border-radius: 50%; + background: color-mix(in oklch, var(--ink) 16%, transparent); +} + +.cam-titlebar span { + margin-left: 8px; + font-weight: 600; + font-size: 12.5px; + color: var(--ink-3); + letter-spacing: 0.04em; +} + +.cam-gear { + width: 16px; + height: 16px; + margin-left: auto; + color: var(--ink-3); +} + +.cam-banner { + position: absolute; + top: 56px; + left: 14px; + right: 14px; + z-index: 3; + display: flex; + align-items: center; + gap: 9px; + padding: 10px 14px; + border-radius: 12px; + border: 1px solid color-mix(in oklch, var(--jade) 34%, transparent); + background: color-mix(in oklch, var(--jade) 14%, var(--surface)); + color: var(--jade); + font-weight: 600; + font-size: 12.5px; + opacity: 0; + transform: translateY(-8px); + pointer-events: none; + transition: opacity 0.4s, transform 0.4s; +} + +.cam-banner.is-show { + opacity: 1; + transform: none; +} + +.cam-banner .ok { + font-weight: 800; +} + +.cam-body { + position: relative; + flex: 1; +} + +.cam-scene { + position: absolute; + inset: 0; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + text-align: center; + gap: 7px; + padding: 30px 30px 26px; + opacity: 0; + visibility: hidden; + transform: translateY(8px); + transition: opacity 0.35s, transform 0.35s, visibility 0.35s; +} + +.cam-scene.is-active { + opacity: 1; + visibility: visible; + transform: none; +} + +.cam-ring { + width: 68px; + height: 68px; + border-radius: 50%; + display: grid; + place-items: center; + border: 1.5px solid var(--line-strong); + color: var(--ink-2); + margin-bottom: 8px; +} + +.cam-ring svg { + width: 26px; + height: 26px; +} + +.cam-ring.amber { + border-color: color-mix(in oklch, var(--amber) 55%, transparent); + background: var(--amber-tint); + color: var(--amber-deep); + box-shadow: 0 0 24px -4px color-mix(in oklch, var(--amber) 40%, transparent); +} + +.cam-ring.jade { + border-color: color-mix(in oklch, var(--jade) 50%, transparent); + background: var(--jade-tint); + color: var(--jade); +} + +.cam-ring.spin { + border-color: color-mix(in oklch, var(--accent) 45%, transparent); + background: var(--accent-tint); + color: var(--accent-soft); +} + +.cam-ring.spin svg { + animation: cam-rotate 0.9s linear infinite; +} + +@keyframes cam-rotate { + to { + transform: rotate(360deg); + } +} + +@media (prefers-reduced-motion: reduce) { + .cam-ring.spin svg { + animation: none; + } +} + +.cam-headline { + margin: 0; + font-size: 23px; + font-weight: 800; + letter-spacing: 0.01em; +} + +.cam-ver { + margin: 0; + font-size: 15px; + font-weight: 600; + color: var(--accent-soft); +} + +.cam-flow { + margin: 0; + font-size: 12.5px; + color: var(--ink-3); +} + +.cam-cue { + margin: 2px 0 0; + display: inline-flex; + align-items: center; + gap: 6px; + font-size: 12px; + color: var(--ink-3); +} + +.cam-cue svg { + width: 13px; + height: 13px; +} + +.cam-pct { + margin: 6px 0 0; + font-family: var(--font-display-en); + font-variation-settings: "opsz" 60; + font-size: 44px; + font-weight: 550; + line-height: 1; + color: var(--ink); +} + +.cam-pct .sign { + font-size: 22px; + color: var(--ink-3); +} + +.cam-bar { + width: min(240px, 80%); + height: 7px; + margin-top: 14px; + border-radius: 99px; + background: color-mix(in oklch, var(--ink) 10%, transparent); + overflow: hidden; +} + +.cam-bar i { + display: block; + height: 100%; + width: 0%; + border-radius: 99px; + background: linear-gradient(90deg, var(--accent-2), var(--accent)); +} + +.cam-meta { + width: 100%; + margin-top: 10px; + border-radius: 14px; + border: 1px solid var(--line); + background: color-mix(in oklch, var(--surface-2) 70%, transparent); + padding: 4px 14px; +} + +.cam-row { + display: flex; + justify-content: space-between; + gap: 12px; + padding: 8px 0; + font-size: 12.5px; +} + +.cam-row + .cam-row { + border-top: 1px solid var(--line); +} + +.cam-row span { + color: var(--ink-3); +} + +.cam-row b { + font-weight: 600; + color: var(--ink-2); +} + +.cam-row b.mono { + font-family: var(--font-mono); + font-size: 11.5px; +} + +.cam-actions { + display: flex; + flex-direction: column; + gap: 10px; + width: min(300px, 90%); + margin-top: 18px; +} + +.cam-btn { + padding: 11px 18px; + border-radius: 999px; + font-weight: 600; + font-size: 13.5px; + transition: transform 0.3s cubic-bezier(0.22, 1, 0.36, 1), box-shadow 0.3s; +} + +.cam-btn:active { + transform: scale(0.96); +} + +.cam-btn.primary { + background: linear-gradient(180deg, color-mix(in oklch, var(--accent) 82%, white), var(--accent-2)); + color: var(--accent-ink); + box-shadow: 0 8px 22px -8px var(--accent-glow), var(--highlight); +} + +.cam-btn.primary:hover { + transform: translateY(-1px); +} + +.cam-btn.ghost { + border: 1px solid var(--line-strong); + color: var(--ink-2); +} + +.cam-btn.ghost:hover { + color: var(--ink); + border-color: color-mix(in oklch, var(--ink) 36%, transparent); +} + +.cam-foot { + display: flex; + align-items: center; + gap: 8px; + padding: 11px 18px; + border-top: 1px solid var(--line); + font-size: 11.5px; + color: var(--ink-3); +} + +.cam-foot .dot { + width: 7px; + height: 7px; + border-radius: 50%; + background: var(--jade); +} + +.scroll-hint { + position: relative; + width: fit-content; + margin: 0 auto; + padding-bottom: 30px; + z-index: 2; + display: flex; + flex-direction: column; + align-items: center; + gap: 10px; + font-size: 12.5px; + letter-spacing: 0.14em; + color: var(--ink-3); +} + +.scroll-hint::after { + content: ""; + width: 1px; + height: 44px; + background: linear-gradient(var(--amber), transparent); + animation: hint-drop 2.4s ease-in-out infinite; +} + +@keyframes hint-drop { + 0% { + transform: scaleY(0); + transform-origin: top; + } + 45% { + transform: scaleY(1); + transform-origin: top; + } + 55% { + transform: scaleY(1); + transform-origin: bottom; + } + 100% { + transform: scaleY(0); + transform-origin: bottom; + } +} + +@media (prefers-reduced-motion: reduce) { + .scroll-hint::after { + animation: none; + } +} + +/* =========================================================================== + Pain +=========================================================================== */ + +.pain { + background: linear-gradient(var(--bg), var(--bg-deep) 60%, var(--bg)); +} + +.pain-grid { + display: grid; + grid-template-columns: minmax(0, 1fr) minmax(0, 0.92fr); + gap: clamp(40px, 6vw, 96px); + align-items: center; +} + +.dl-mock { + position: relative; + border-radius: var(--radius-lg); + border: 1px solid var(--line); + background: linear-gradient(180deg, var(--surface-top), var(--surface)); + box-shadow: var(--shadow-soft), var(--highlight); + padding: 26px 28px; + font-size: 14px; + max-width: 460px; + justify-self: end; + width: 100%; +} + +.dl-mock .dl-file { + display: flex; + align-items: center; + gap: 12px; + font-weight: 600; + font-family: var(--font-mono); + font-size: 13.5px; +} + +.dl-mock .dl-file svg { + width: 30px; + height: 30px; + flex: none; + color: var(--ink-3); +} + +.dl-host { + margin: 4px 0 0 42px; + font-size: 12.5px; + color: var(--ink-3); +} + +.dl-bar { + position: relative; + height: 7px; + margin-top: 20px; + border-radius: 99px; + background: color-mix(in oklch, var(--ink) 10%, transparent); + overflow: hidden; +} + +.dl-bar i { + position: absolute; + inset: 0 auto 0 0; + width: 47%; + border-radius: 99px; + background: linear-gradient(90deg, var(--accent-2), var(--accent)); +} + +.dl-bar i::after { + content: ""; + position: absolute; + inset: 0; + background: linear-gradient(90deg, transparent, oklch(1 0 0 / 0.25), transparent); + animation: dl-stall 1.6s ease-in-out infinite; +} + +@keyframes dl-stall { + 0%, + 100% { + transform: translateX(-60%); + } + 50% { + transform: translateX(60%); + } +} + +@media (prefers-reduced-motion: reduce) { + .dl-bar i::after { + animation: none; + } +} + +.dl-status { + display: flex; + justify-content: space-between; + gap: 12px; + margin-top: 12px; + font-size: 12.5px; + color: var(--ink-3); +} + +.dl-status .warn { + color: oklch(0.74 0.13 60); +} + +.pain-cards { + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: 20px; + margin-top: clamp(48px, 7vh, 84px); +} + +.pain-cards .card::before { + content: ""; + position: absolute; + top: 0; + left: 28px; + width: 44px; + height: 2px; + background: linear-gradient(90deg, var(--amber), transparent); + border-radius: 2px; +} + +/* =========================================================================== + Manager — pinned three-step enactment +=========================================================================== */ + +.manager-stage { + position: relative; +} + +.manager-grid { + display: grid; + grid-template-columns: minmax(0, 0.9fr) minmax(0, 1.1fr); + gap: clamp(36px, 6vw, 90px); + align-items: center; + min-height: 78vh; +} + +.steps { + list-style: none; + margin: clamp(30px, 5vh, 54px) 0 0; + padding: 0; + display: grid; + gap: 8px; +} + +.step { + position: relative; + padding: 18px 20px 18px 64px; + border-radius: var(--radius); + border: 1px solid transparent; + transition: background-color 0.4s, border-color 0.4s; +} + +.step.is-active { + background: color-mix(in oklch, var(--surface) 75%, transparent); + border-color: var(--line); +} + +.step-num { + position: absolute; + left: 18px; + top: 20px; + width: 30px; + height: 30px; + display: grid; + place-items: center; + border-radius: 50%; + border: 1px solid var(--line-strong); + font-family: var(--font-display-en); + font-size: 13.5px; + color: var(--ink-3); + transition: all 0.4s; +} + +.step.is-active .step-num { + border-color: var(--amber); + color: var(--amber-deep); + background: var(--amber-tint); +} + +.step-name { + font-size: 12px; + font-weight: 700; + letter-spacing: 0.22em; + text-transform: uppercase; + color: var(--amber-deep); + opacity: 0; + transition: opacity 0.4s; +} + +.step.is-active .step-name { + opacity: 1; +} + +.step h3 { + margin: 2px 0 6px; + font-size: 18px; +} + +.step p { + margin: 0; + font-size: 14.5px; + color: var(--ink-2); + max-width: 30em; +} + +/* the faux app window */ + +.app-mock { + position: relative; + border-radius: 22px; + border: 1px solid var(--line); + background: linear-gradient(180deg, var(--surface-top), var(--surface)); + box-shadow: var(--shadow-soft), var(--highlight); + overflow: hidden; + aspect-ratio: 16 / 11.4; + max-height: 66vh; + margin-inline: auto; + width: 100%; + max-width: 620px; + font-size: 13.5px; +} + +.mock-titlebar { + display: flex; + align-items: center; + gap: 8px; + padding: 13px 16px; + border-bottom: 1px solid var(--line); +} + +.mock-titlebar i { + width: 11px; + height: 11px; + border-radius: 50%; + background: color-mix(in oklch, var(--ink) 16%, transparent); +} + +.mock-titlebar span { + margin-left: 8px; + font-weight: 600; + font-size: 12.5px; + color: var(--ink-3); + letter-spacing: 0.04em; +} + +.mock-body { + position: relative; + height: calc(100% - 47px); +} + +.mock-panel { + position: absolute; + inset: 0; + padding: 24px 26px; + display: flex; + flex-direction: column; + gap: 13px; + opacity: 0; + visibility: hidden; +} + +.mock-panel.is-active { + opacity: 1; + visibility: visible; +} + +.mock-row { + display: flex; + align-items: center; + gap: 12px; + padding: 13px 16px; + border-radius: 14px; + border: 1px solid var(--line); + background: linear-gradient(180deg, var(--surface-2-top), var(--surface-2)); +} + +.mock-row .dot { + width: 8px; + height: 8px; + border-radius: 50%; + background: var(--accent); + flex: none; +} + +.mock-row .dot.amber { + background: var(--amber); +} + +.mock-row .dot.jade { + background: var(--jade); +} + +.mock-scanline { + position: absolute; + inset: 0; + background: linear-gradient(180deg, transparent, var(--accent-tint), transparent); + height: 56px; + opacity: 0; +} + +.mock-check { + display: flex; + align-items: center; + gap: 12px; + padding: 12px 16px; + border-radius: 14px; + border: 1px solid var(--line); + color: var(--ink-2); +} + +.mock-check .box { + width: 19px; + height: 19px; + flex: none; + border-radius: 6px; + border: 1.5px solid var(--line-strong); + display: grid; + place-items: center; + color: var(--accent-ink); + font-size: 11px; +} + +.mock-check.is-done .box { + background: var(--jade); + border-color: var(--jade); +} + +.mock-check.is-done .box::after { + content: "✓"; + font-weight: 800; +} + +.mock-note { + margin-top: auto; + font-size: 12px; + color: var(--ink-3); +} + +.mock-progress { + height: 8px; + border-radius: 99px; + background: color-mix(in oklch, var(--ink) 10%, transparent); + overflow: hidden; +} + +.mock-progress i { + display: block; + height: 100%; + width: 0%; + border-radius: 99px; + background: linear-gradient(90deg, var(--amber-deep), var(--amber)); +} + +.mock-done { + display: flex; + align-items: center; + gap: 12px; + font-weight: 600; + color: var(--jade); +} + +.mock-done .ring { + width: 30px; + height: 30px; + flex: none; + border-radius: 50%; + background: var(--jade-tint); + border: 1px solid color-mix(in oklch, var(--jade) 40%, transparent); + display: grid; + place-items: center; + color: var(--jade); + font-weight: 800; +} + +.mock-launch { + align-self: flex-start; + padding: 10px 22px; + border-radius: 999px; + background: linear-gradient(180deg, color-mix(in oklch, var(--accent) 82%, white), var(--accent-2)); + color: var(--accent-ink); + font-weight: 600; + font-size: 13px; + box-shadow: 0 8px 20px -6px var(--accent-glow); +} + +.scenario-row { + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: 20px; + margin-top: clamp(40px, 6vh, 72px); +} + +.scenario { + padding: 24px 26px; + border-radius: var(--radius-lg); + border: 1px solid var(--line); + background: color-mix(in oklch, var(--surface) 62%, transparent); +} + +.scenario b { + display: inline-block; + margin-bottom: 8px; + font-size: 13px; + font-weight: 700; + letter-spacing: 0.16em; + color: var(--amber-deep); + text-transform: uppercase; +} + +.scenario p { + margin: 0; + font-size: 14.5px; + color: var(--ink-2); +} + +.extras-grid { + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: 20px; + margin-top: 20px; +} + +/* ---- section material textures ------------------------------------------ */ + +.manager, +.scope, +.download { + overflow: clip; +} + +.tex-fill { + inset: 0; + z-index: 0; +} + +.tex-fill img { + width: 100%; + height: 100%; + object-fit: cover; + opacity: 0.5; +} + +.manager > .container, +.scope > .container, +.download > .container { + position: relative; + z-index: 1; +} + +.dl-globe { + top: clamp(-60px, -4vw, -20px); + right: clamp(-120px, -6vw, -40px); + width: clamp(280px, 32vw, 480px); + opacity: 0.45; + z-index: 0; +} + +/* =========================================================================== + Trust +=========================================================================== */ + +.trust { + overflow: clip; + background: linear-gradient(var(--bg), var(--bg-deep) 55%, var(--bg)); +} + +.trust-hex { + position: absolute; + inset: 0; + z-index: 0; + font-family: var(--font-mono); + font-size: 12px; + line-height: 2.6; + letter-spacing: 0.5em; + color: color-mix(in oklch, var(--ink) 6%, transparent); + white-space: nowrap; + overflow: hidden; + mask-image: radial-gradient(80% 70% at 50% 45%, black 30%, transparent 100%); +} + +.trust-wrap { + position: relative; + z-index: 1; + display: grid; + grid-template-columns: minmax(0, 1fr) minmax(0, 1.25fr); + gap: clamp(40px, 6vw, 90px); + align-items: center; +} + +/* checksum-comparison card — the concrete "verify it yourself" visual */ + +.verify-card { + border-radius: var(--radius-lg); + border: 1px solid var(--line); + background: linear-gradient(180deg, var(--surface-top), var(--surface)); + box-shadow: var(--shadow-soft), var(--highlight); + padding: 26px 28px; + max-width: 480px; + width: 100%; + justify-self: center; +} + +.vc-head { + display: flex; + align-items: center; + justify-content: space-between; + font-weight: 700; + font-size: 15px; + margin-bottom: 18px; +} + +.vc-tag { + font-size: 11.5px; + font-weight: 600; + color: var(--ink-3); + border: 1px solid var(--line); + border-radius: 999px; + padding: 3px 10px; +} + +.vc-row { + display: grid; + gap: 5px; + padding: 12px 0; + border-top: 1px solid var(--line); +} + +.vc-label { + font-size: 12px; + font-weight: 600; + letter-spacing: 0.06em; + color: var(--ink-3); +} + +.vc-hash { + font-family: var(--font-mono); + font-size: 13.5px; + letter-spacing: 0.06em; + color: var(--ink); + word-break: break-all; +} + +.vc-val { + font-size: 13.5px; + color: var(--ink-2); +} + +.vc-match { + display: flex; + justify-content: flex-end; + margin-top: 16px; +} + +.trust-grid { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 18px; +} + +.trust-grid .card { + padding: 26px; +} + +.trust-grid .card h3 { + font-size: 16.5px; + display: flex; + gap: 10px; + align-items: baseline; +} + +.trust-grid .card h3::before { + content: "✓"; + flex: none; + color: var(--jade); + font-weight: 800; +} + +.trust-grid .card p { + font-size: 14px; +} + +/* =========================================================================== + Download +=========================================================================== */ + +.dl-cards { + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: 20px; + margin-top: clamp(44px, 6vh, 72px); +} + +.dl-card { + display: flex; + flex-direction: column; + gap: 6px; + padding: 30px 28px 28px; + position: relative; +} + +.dl-card .platform { + font-weight: 700; + font-size: 17px; +} + +.dl-card .note { + font-size: 13px; + color: var(--ink-3); + margin: 0 0 22px; +} + +.dl-card .btn { + margin-top: auto; + width: 100%; +} + +.dl-card .reco { + position: absolute; + top: -13px; + left: 24px; + padding: 5px 13px; + border-radius: 999px; + background: linear-gradient(180deg, color-mix(in oklch, var(--amber) 85%, white), var(--amber-deep)); + color: oklch(0.2 0.03 70); + font-size: 12px; + font-weight: 700; + letter-spacing: 0.04em; + box-shadow: 0 6px 18px -6px color-mix(in oklch, var(--amber) 55%, transparent); + opacity: 0; + transform: translateY(4px); + transition: opacity 0.5s, transform 0.5s; +} + +.dl-card.is-recommended .reco { + opacity: 1; + transform: none; +} + +.dl-card.is-recommended { + border-color: color-mix(in oklch, var(--amber) 38%, transparent); +} + +.dl-alt { + display: grid; + grid-template-columns: minmax(0, 1.35fr) minmax(0, 1fr); + gap: 20px; + margin-top: 20px; +} + +.brew-card .cmd { + display: flex; + align-items: center; + gap: 14px; + margin-top: 18px; + padding: 15px 18px; + border-radius: 14px; + border: 1px solid var(--line); + background: var(--bg-deep); + overflow: hidden; +} + +.brew-card .cmd code { + display: block; + flex: 1 1 auto; + min-width: 0; + overflow-x: auto; + white-space: nowrap; + font-size: 13.5px; + color: var(--ink); + padding-block: 2px; +} + +.brew-card .cmd code::before { + content: "$ "; + color: var(--amber-deep); +} + +.copy-btn { + flex: none; + margin-left: auto; + padding: 7px 14px; + border-radius: 999px; + border: 1px solid var(--line-strong); + font-size: 12.5px; + font-weight: 600; + color: var(--ink-2); + transition: all 0.25s; +} + +.copy-btn:hover { + color: var(--ink); + border-color: color-mix(in oklch, var(--ink) 40%, transparent); +} + +.copy-btn.is-copied { + color: var(--jade); + border-color: color-mix(in oklch, var(--jade) 45%, transparent); + background: var(--jade-tint); +} + +.gh-card .repos { + display: grid; + gap: 12px; + margin-top: 18px; +} + +.repo-link { + display: flex; + align-items: center; + gap: 12px; + padding: 13px 16px; + border-radius: 14px; + border: 1px solid var(--line); + background: color-mix(in oklch, var(--surface-2) 70%, transparent); + font-family: var(--font-mono); + font-size: 13px; + transition: border-color 0.25s, transform 0.25s; +} + +.repo-link:hover { + border-color: color-mix(in oklch, var(--ink) 35%, transparent); + transform: translateX(3px); +} + +.repo-link svg { + width: 18px; + height: 18px; + flex: none; + opacity: 0.8; +} + +.repo-link span { + margin-left: auto; + opacity: 0.5; +} + +/* =========================================================================== + Scope + Footer +=========================================================================== */ + +.scope-list { + display: grid; + gap: 0; + margin-top: clamp(40px, 6vh, 64px); + border-top: 1px solid var(--line); +} + +.scope-item { + display: grid; + grid-template-columns: minmax(0, 0.85fr) minmax(0, 1.4fr); + gap: clamp(20px, 4vw, 64px); + padding: 34px 0; + border-bottom: 1px solid var(--line); +} + +.scope-item h3 { + margin: 0; + font-size: 19px; +} + +.scope-item p { + margin: 0; + color: var(--ink-2); + font-size: 15px; +} + +.scope-mit { + margin-top: 28px; + font-size: 13.5px; + color: var(--ink-3); + font-family: var(--font-mono); +} + +.footer { + border-top: 1px solid var(--line); + background: var(--bg-deep); + padding: clamp(56px, 9vh, 96px) 0 42px; +} + +.footer-grid { + display: flex; + flex-wrap: wrap; + gap: clamp(32px, 5vw, 80px); + justify-content: space-between; +} + +.footer-brand { + max-width: 380px; +} + +.footer-brand .brand { + margin-bottom: 16px; +} + +.footer-thanks { + font-size: 14px; + color: var(--ink-2); + margin: 0; +} + +.footer-col h4 { + margin: 0 0 14px; + font-size: 12.5px; + font-weight: 700; + letter-spacing: 0.18em; + text-transform: uppercase; + color: var(--ink-3); +} + +.footer-col ul { + margin: 0; + padding: 0; + list-style: none; + display: grid; + gap: 9px; +} + +.footer-col a { + font-size: 14.5px; + color: var(--ink-2); + transition: color 0.25s; +} + +.footer-col a:hover { + color: var(--ink); +} + +.footer-base { + display: flex; + flex-wrap: wrap; + align-items: center; + gap: 16px; + justify-content: space-between; + margin-top: clamp(44px, 7vh, 72px); + padding-top: 26px; + border-top: 1px solid var(--line); + font-size: 13px; + color: var(--ink-3); +} + +.footer-base p { + margin: 0; +} + +/* =========================================================================== + Responsive +=========================================================================== */ + +@media (max-width: 1023px) { + .nav-links { + display: none; + } + + .nav-cta { + display: none; + } + + .nav-burger { + display: block; + } + + .manager-grid { + grid-template-columns: 1fr; + min-height: 0; + gap: 40px; + } + + .trust-wrap { + grid-template-columns: 1fr; + } + + .verify-card { + order: -1; + } +} + +@media (max-width: 819px) { + .pain-grid { + grid-template-columns: 1fr; + } + + .dl-mock { + justify-self: stretch; + max-width: none; + } + + .pain-cards, + .scenario-row, + .extras-grid, + .dl-cards { + grid-template-columns: 1fr; + } + + .trust-grid { + grid-template-columns: 1fr; + } + + .dl-alt { + grid-template-columns: 1fr; + } + + .scope-item { + grid-template-columns: 1fr; + gap: 10px; + padding: 28px 0; + } + + .hero-demo { + max-width: 400px; + } + + /* phones: the laptop shell is too small to read — show the bare window */ + .mac-screen { + aspect-ratio: auto; + border: 0; + border-radius: 0; + background: none; + overflow: visible; + box-shadow: none; + } + + .mac-wall, + .mac-notch, + .mac-base { + display: none; + } + + .cam-window { + position: static; + transform: none; + width: 100%; + height: 600px; + margin-inline: auto; + border-radius: 20px; + border-color: var(--line-strong); + background: linear-gradient(180deg, var(--surface-2-top), var(--surface)); + box-shadow: var(--shadow-soft), var(--highlight); + } + + .hero-mist { + opacity: 0.28; + } + + .scroll-hint { + display: none; + } + + .hero-cloud { + width: clamp(140px, 34vw, 210px); + bottom: 1%; + right: -34px; + opacity: 0.55; + } + + .cta-row .btn { + flex: 1 1 100%; + } + + .hero-badges { + gap: 8px; + } + + .hero-badges .chip { + font-size: 12px; + padding: 6px 12px; + } +} + +/* touch targets */ +@media (pointer: coarse) { + .nav-links a, + .lang-switch, + .copy-btn { + min-height: 44px; + display: inline-flex; + align-items: center; + } +} diff --git a/website/src/styles/tokens.css b/website/src/styles/tokens.css new file mode 100644 index 0000000..8f550a6 --- /dev/null +++ b/website/src/styles/tokens.css @@ -0,0 +1,95 @@ +/* --------------------------------------------------------------------------- + Design tokens — inherited from the Manager app's OKLCH material system + (hue 274 porcelain neutrals + amber journey light + jade verification). + Dark is the default; light follows prefers-color-scheme. +--------------------------------------------------------------------------- */ + +:root { + color-scheme: dark; + + --bg: oklch(0.162 0.015 274); + --bg-deep: oklch(0.125 0.013 274); + --surface: oklch(0.212 0.017 274); + --surface-top: oklch(0.245 0.018 274); + --surface-2: oklch(0.255 0.019 274); + --surface-2-top: oklch(0.29 0.02 274); + + --ink: oklch(0.945 0.01 274); + --ink-2: oklch(0.78 0.02 274); + --ink-3: oklch(0.62 0.022 274); + + --accent: oklch(0.72 0.145 276); + --accent-2: oklch(0.62 0.18 271); + --accent-soft: oklch(0.83 0.085 278); + --accent-ink: oklch(0.99 0.01 274); + + --amber: oklch(0.83 0.115 80); + --amber-deep: oklch(0.72 0.13 70); + --jade: oklch(0.8 0.125 168); + + --line: color-mix(in oklch, var(--ink) 13%, transparent); + --line-strong: color-mix(in oklch, var(--ink) 22%, transparent); + --accent-tint: color-mix(in oklch, var(--accent) 16%, transparent); + --accent-glow: color-mix(in oklch, var(--accent) 32%, transparent); + --amber-tint: color-mix(in oklch, var(--amber) 16%, transparent); + --jade-tint: color-mix(in oklch, var(--jade) 15%, transparent); + + --shadow-soft: 0 24px 60px -24px oklch(0.05 0.01 274 / 0.55); + --shadow-card: 0 10px 34px -14px oklch(0.05 0.01 274 / 0.5); + --highlight: inset 0 1px 0 oklch(1 0 0 / 0.07); + + --nav-bg: color-mix(in oklch, var(--bg) 78%, transparent); + + --font-display-zh: "SHS Display", "Source Han Serif SC", "Noto Serif CJK SC", + "Songti SC", "SimSun", serif; + --font-display-en: "Fraunces", "Georgia", "Times New Roman", serif; + --font-body: -apple-system, BlinkMacSystemFont, "SF Pro Text", "PingFang SC", + "Hiragino Sans GB", "HarmonyOS Sans SC", "Microsoft YaHei", "Segoe UI", + Roboto, sans-serif; + --font-mono: ui-monospace, "SF Mono", SFMono-Regular, Menlo, Consolas, + "Liberation Mono", monospace; + + --radius: 18px; + --radius-lg: 26px; + --container: 1180px; + --nav-h: 64px; +} + +@media (prefers-color-scheme: light) { + :root { + color-scheme: light; + + --bg: oklch(0.952 0.008 274); + --bg-deep: oklch(0.925 0.01 274); + --surface: oklch(0.99 0.003 274); + --surface-top: oklch(1 0 0); + --surface-2: oklch(0.973 0.005 274); + --surface-2-top: oklch(0.988 0.004 274); + + --ink: oklch(0.27 0.024 274); + --ink-2: oklch(0.43 0.022 274); + --ink-3: oklch(0.56 0.02 274); + + --accent: oklch(0.54 0.19 273); + --accent-2: oklch(0.49 0.2 270); + --accent-soft: oklch(0.5 0.17 274); + --accent-ink: oklch(0.99 0.01 274); + + --amber: oklch(0.67 0.13 72); + --amber-deep: oklch(0.6 0.13 66); + --jade: oklch(0.56 0.125 165); + + --line: color-mix(in oklch, var(--ink) 13%, transparent); + --line-strong: color-mix(in oklch, var(--ink) 24%, transparent); + --accent-tint: color-mix(in oklch, var(--accent) 11%, transparent); + --accent-glow: color-mix(in oklch, var(--accent) 22%, transparent); + --amber-tint: color-mix(in oklch, var(--amber) 14%, transparent); + --jade-tint: color-mix(in oklch, var(--jade) 13%, transparent); + + --shadow-soft: 0 24px 60px -28px oklch(0.4 0.04 274 / 0.35); + --shadow-card: 0 10px 30px -14px oklch(0.4 0.04 274 / 0.28); + --highlight: inset 0 1px 0 oklch(1 0 0 / 0.85); + + --nav-bg: color-mix(in oklch, var(--surface) 80%, transparent); + } +} diff --git a/website/src/vite-env.d.ts b/website/src/vite-env.d.ts new file mode 100644 index 0000000..11f02fe --- /dev/null +++ b/website/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/website/tsconfig.json b/website/tsconfig.json new file mode 100644 index 0000000..2fe3a65 --- /dev/null +++ b/website/tsconfig.json @@ -0,0 +1,14 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "ESNext", + "moduleResolution": "bundler", + "lib": ["ES2022", "DOM", "DOM.Iterable"], + "strict": true, + "noEmit": true, + "isolatedModules": true, + "skipLibCheck": true, + "useDefineForClassFields": true + }, + "include": ["src"] +} diff --git a/website/vite.config.ts b/website/vite.config.ts new file mode 100644 index 0000000..4b74223 --- /dev/null +++ b/website/vite.config.ts @@ -0,0 +1,11 @@ +import { defineConfig } from "vite"; + +export default defineConfig({ + // Relative base so the bundle works on Cloudflare Pages, GitHub Pages + // (project subpath) or any static file host without configuration. + base: "./", + build: { + target: "es2020", + assetsInlineLimit: 2048, + }, +}); diff --git a/website/wrangler.jsonc b/website/wrangler.jsonc new file mode 100644 index 0000000..ff4f63f --- /dev/null +++ b/website/wrangler.jsonc @@ -0,0 +1,20 @@ +{ + // The official website, served as static assets at the root of + // https://codexapp.agentsmirror.com/. Path routing on the zone: + // /manager/* -> codex-app-manager-download-router (this repo, cloudflare/) + // /latest/* -> codex-app-mirror download-router (mirror repo) + // /* -> this worker (longest-match precedence keeps the two above) + "name": "codex-app-manager-website", + "compatibility_date": "2026-06-04", + "workers_dev": false, + "assets": { + "directory": "./dist", + "not_found_handling": "single-page-application" + }, + "routes": [ + { + "pattern": "codexapp.agentsmirror.com/*", + "zone_id": "a5bae9b722f3c5fe1a24aaebe7a8e663" + } + ] +}